如何使用Filter在Firestore中实现多字段搜索?
我正在为我的Android应用实现搜索功能,但在此过程中遇到了瓶颈。
该应用使用Firebase Firestore作为数据库,拥有一个名为questions的集合,其中的文档包含字符串字段choice_a和choice_b。
我希望查询所有文档,只要用户的查询内容包含或以choice_a或choice_b字段中的任意单词开头即可。
单字段查询可行代码
仅查询单个字段时,以下代码可以正常运行:
firebaseFirestore.collection("questions") .where(Filter.or( Filter.and( Filter.greaterThanOrEqualTo("choice_a", textQuery), Filter.lessThanOrEqualTo("choice_a", textQuery + '\uf8ff') ), Filter.and( Filter.greaterThanOrEqualTo("choice_a", textQuery.toLowerCase()), Filter.lessThanOrEqualTo("choice_a", textQuery.toLowerCase() + '\uf8ff') ), Filter.and( Filter.greaterThanOrEqualTo("choice_a", textQuery.substring(0, 1).toUpperCase() + textQuery.substring(1)), Filter.lessThanOrEqualTo("choice_a", textQuery.substring(0, 1).toUpperCase() + textQuery.substring(1) + '\uf8ff') ) ) ) .get();
双字段查询报错情况
扩展到同时查询两个字段时,以下代码无法运行:
firebaseFirestore.collection("questions") .where(Filter.or( Filter.and( Filter.greaterThanOrEqualTo("choice_a", textQuery), Filter.lessThanOrEqualTo("choice_a", textQuery + '\uf8ff') ), Filter.and( Filter.greaterThanOrEqualTo("choice_a", textQuery.toLowerCase()), Filter.lessThanOrEqualTo("choice_a", textQuery.toLowerCase() + '\uf8ff') ), Filter.and( Filter.greaterThanOrEqualTo("choice_a", textQuery.substring(0, 1).toUpperCase() + textQuery.substring(1)), Filter.lessThanOrEqualTo("choice_a", textQuery.substring(0, 1).toUpperCase() + textQuery.substring(1) + '\uf8ff') ), Filter.and( Filter.greaterThanOrEqualTo("choice_b", textQuery), Filter.lessThanOrEqualTo("choice_b", textQuery + '\uf8ff') ), Filter.and( Filter.greaterThanOrEqualTo("choice_b", textQuery.toLowerCase()), Filter.lessThanOrEqualTo("choice_b", textQuery.toLowerCase() + '\uf8ff') ), Filter.and( Filter.greaterThanOrEqualTo("choice_b", textQuery.substring(0, 1).toUpperCase() + textQuery.substring(1)), Filter.lessThanOrEqualTo("choice_b", textQuery.substring(0, 1).toUpperCase() + textQuery.substring(1) + '\uf8ff') ) ) ) .get();
错误信息
所有包含不等式(notEqualTo、notIn、lessThan、lessThanOrEqualTo、greaterThan、greaterThanOrEqualTo)的where过滤器必须作用于同一字段,但您同时对'choice_a'和'choice_b'设置了过滤器
Firestore的查询规则限制了跨多个字段使用不等式过滤,针对你的需求,有以下几种可行的实现方案:
方案1:拆分查询+客户端合并去重
分别对choice_a和choice_b执行两个独立的单字段查询,之后在客户端合并结果并去重,避免重复文档:
// 发起choice_a的查询任务 Task<QuerySnapshot> queryATask = firebaseFirestore.collection("questions") .where(Filter.or( Filter.and( Filter.greaterThanOrEqualTo("choice_a", textQuery), Filter.lessThanOrEqualTo("choice_a", textQuery + '\uf8ff') ), Filter.and( Filter.greaterThanOrEqualTo("choice_a", textQuery.toLowerCase()), Filter.lessThanOrEqualTo("choice_a", textQuery.toLowerCase() + '\uf8ff') ), Filter.and( Filter.greaterThanOrEqualTo("choice_a", textQuery.substring(0, 1).toUpperCase() + textQuery.substring(1)), Filter.lessThanOrEqualTo("choice_a", textQuery.substring(0, 1).toUpperCase() + textQuery.substring(1) + '\uf8ff') ) ) ) .get(); // 发起choice_b的查询任务 Task<QuerySnapshot> queryBTask = firebaseFirestore.collection("questions") .where(Filter.or( Filter.and( Filter.greaterThanOrEqualTo("choice_b", textQuery), Filter.lessThanOrEqualTo("choice_b", textQuery + '\uf8ff') ), Filter.and( Filter.greaterThanOrEqualTo("choice_b", textQuery.toLowerCase()), Filter.lessThanOrEqualTo("choice_b", textQuery.toLowerCase() + '\uf8ff') ), Filter.and( Filter.greaterThanOrEqualTo("choice_b", textQuery.substring(0, 1).toUpperCase() + textQuery.substring(1)), Filter.lessThanOrEqualTo("choice_b", textQuery.substring(0, 1).toUpperCase() + textQuery.substring(1) + '\uf8ff') ) ) ) .get(); // 合并两个查询结果并去重 Tasks.whenAllSuccess(queryATask, queryBTask) .addOnSuccessListener(results -> { Set<DocumentSnapshot> uniqueDocs = new HashSet<>(); // 添加第一个查询的结果 for (DocumentSnapshot doc : (QuerySnapshot) results.get(0)) { uniqueDocs.add(doc); } // 添加第二个查询的结果,HashSet自动去重 for (DocumentSnapshot doc : (QuerySnapshot) results.get(1)) { uniqueDocs.add(doc); } // 转换为列表后处理最终结果 List<DocumentSnapshot> finalResults = new ArrayList<>(uniqueDocs); // TODO: 在这里使用查询到的结果 }) .addOnFailureListener(e -> { // 处理查询失败的情况 });
方案2:预存搜索关键词数组
在每个questions文档中新增一个数组字段(比如searchKeywords),将choice_a和choice_b的所有单词、以及它们的大小写变体(全小写、首字母大写)统一存入这个数组,之后直接针对该数组字段做查询:
步骤1:更新文档结构
写入文档时自动生成searchKeywords字段,示例结构如下:
{ "choice_a": "Apple Pie", "choice_b": "Banana Bread", "searchKeywords": ["Apple Pie", "apple pie", "Apple pie", "Banana Bread", "banana bread", "Banana bread", "Apple", "apple", "Pie", "pie", "Banana", "banana", "Bread", "bread"] }
步骤2:简化查询代码
// 生成需要匹配的关键词变体 String original = textQuery; String lowerCase = textQuery.toLowerCase(); String capitalized = textQuery.substring(0,1).toUpperCase() + textQuery.substring(1); // 针对预存的数组字段查询 firebaseFirestore.collection("questions") .where(Filter.or( Filter.and(Filter.greaterThanOrEqualTo("searchKeywords", original), Filter.lessThanOrEqualTo("searchKeywords", original + '\uf8ff')), Filter.and(Filter.greaterThanOrEqualTo("searchKeywords", lowerCase), Filter.lessThanOrEqualTo("searchKeywords", lowerCase + '\uf8ff')), Filter.and(Filter.greaterThanOrEqualTo("searchKeywords", capitalized), Filter.lessThanOrEqualTo("searchKeywords", capitalized + '\uf8ff')) ) ) .get() .addOnSuccessListener(querySnapshot -> { // 处理查询结果 }) .addOnFailureListener(e -> { // 处理查询失败 });
方案3:集成专业搜索服务
如果需要更强大的搜索能力(比如模糊匹配、多词组合搜索),可以使用Firebase官方的Firestore Search with Algolia扩展,它会自动同步Firestore数据到Algolia索引,支持跨字段的复杂搜索需求。
内容的提问来源于stack exchange,提问作者Miha M

