【问题标题】:How to perform compound queries with logical OR in Cloud Firestore?如何在 Cloud Firestore 中使用逻辑 OR 执行复合查询?
【发布时间】:2019-08-30 20:09:26
【问题描述】:

来自the docs:

您还可以链接多个 where() 方法来创建更具体的查询(逻辑与)。

如何执行OR 查询? 示例:

  1. 把status字段为open或upcoming的所有文件给我
  2. 把status == open或createdAt <= <somedatetime>字段所在的所有文件给我

【问题讨论】:

  • 我有一个类似的问题,现在我正在编辑我的数据架构,以便这些值是具有有意范围的数字(例如:无访问权限:0,读取访问权限:1,编辑访问权限:2,所有者访问权限 3。然后我可以查询访问字段 isGreaterThan,例如 1)。本质上,我正在考虑在数字范围内利用隐式 OR。

标签: database firebase google-cloud-platform google-cloud-firestore


【解决方案1】:

我不喜欢每个人都说不可能。

如果您在模型中创建另一个“hacky”字段来构建复合...

例如,为每个包含所有逻辑或元素的文档创建一个数组

然后查询 .where("field", arrayContains: [...]

【讨论】:

    【解决方案2】:

    如果您的字段数量有限,请务必像上面的示例一样创建具有 true 和 false 的新字段。但是,如果您直到运行时才知道字段是什么,那么您只需组合查询即可。

    这是一个标签或示例...

    // the ids of students in class
    const students = [studentID1, studentID2,...];
    
    // get all docs where student.studentID1 = true
    const results = this.afs.collection('classes',
      ref => ref.where(`students.${students[0]}`, '==', true)
    ).valueChanges({ idField: 'id' }).pipe(
      switchMap((r: any) => {
    
        // get all docs where student.studentID2...studentIDX = true
        const docs = students.slice(1).map(
          (student: any) => this.afs.collection('classes',
            ref => ref.where(`students.${student}`, '==', true)
          ).valueChanges({ idField: 'id' })
        );
        return combineLatest(docs).pipe(
    
          // combine results by reducing array
          map((a: any[]) => {
            const g: [] = a.reduce(
              (acc: any[], cur: any) => acc.concat(cur)
            ).concat(r);
    
            // filter out duplicates by 'id' field
            return g.filter(
              (b: any, n: number, a: any[]) => a.findIndex(
                (v: any) => v.id === b.id) === n
            );
          }),
        );
      })
    );
    

    不幸的是,没有其他方法可以组合超过 10 个项目(如果

    也没有其他方法可以避免重复读取,因为您不知道搜索将匹配的 ID 字段。幸运的是,Firebase 具有良好的缓存。

    对于那些喜欢承诺的人...

    const p = await results.pipe(take(1)).toPromise();
    

    有关这方面的更多信息,请参阅我写的 this article。

    J

    【讨论】:

      【解决方案3】:

      这并不能解决所有情况,但是对于“枚举”字段,您可以通过为每个枚举值创建一个单独的布尔字段来模拟“或”查询,然后为每个值添加一个where("enum_<value>", "==", false) 不是你想要的“OR”子句的一部分。

      例如,考虑您想要的第一个查询:

      1. 给我所有字段状态为 open 或即将到来的文档

      您可以通过将status: string 字段拆分为多个布尔字段来完成此操作,每个字段对应一个枚举值:

      status_open: bool
      status_upcoming: bool
      status_suspended: bool
      status_closed: bool
      

      要执行“状态为打开或即将到来的位置”查询,请执行以下操作:

      where("status_suspended", "==", false).where("status_closed", "==", false)
      

      这是如何工作的?好吧,因为它是一个枚举,所以您知道其中一个值必须分配有true。因此,如果您可以确定所有 other 值都不匹配给定条目,那么通过推断,它必须与您最初寻找的值之一匹配。

      另见

      in/not-in/array-contains-in:https://firebase.google.com/docs/firestore/query-data/queries#in_and_array-contains-any

      !=:https://firebase.googleblog.com/2020/09/cloud-firestore-not-equal-queries.html

      【讨论】:

        【解决方案4】:

        实际上我发现@Dan McGrath 的答案在这里工作是对他的答案的重写:

         private void query() {
                FirebaseFirestore db = FirebaseFirestore.getInstance();
                db.collection("STATUS")
                        .whereIn("status", Arrays.asList("open", "upcoming")) // you can add up to 10 different values like : Arrays.asList("open", "upcoming", "Pending", "In Progress", ...)
                        .addSnapshotListener(new EventListener<QuerySnapshot>() {
                            @Override
                            public void onEvent(@Nullable QuerySnapshot queryDocumentSnapshots, @Nullable FirebaseFirestoreException e) {
        
                                for (DocumentSnapshot documentSnapshot : queryDocumentSnapshots) {
        // I assume you have a  model class called MyStatus
        
                                    MyStatus status= documentSnapshot.toObject(MyStatus.class);
                                    if (status!= null) {
                                       //do somthing...!
                                    }
                                }
                            }
                        });
        
            }
        

        【讨论】:

          【解决方案5】:

          对于 Flutter dart 语言使用这个:

          db.collection("projects").where("status", whereIn: ["public", "unlisted", "secret"]);
          

          【讨论】:

          • 如果您提供解释为什么这是首选解决方案并解释它是如何工作的,它会更有帮助。我们想要教育,而不仅仅是提供代码。
          【解决方案6】:

          OR 不受支持,因为服务器很难对其进行扩展(需要保持状态以进行去重)。解决方法是发出 2 个查询,每个条件一个,并在客户端上进行重复数据删除。


          编辑(2019 年 11 月):

          Cloud Firestore 现在支持IN 查询,这是OR 查询的有限类型。

          对于上面的例子,你可以这样做:

          // Get all documents in 'foo' where status is open or upcmoming
          db.collection('foo').where('status','in',['open','upcoming']).get()
          

          但是,仍然无法执行涉及多个字段的一般 OR 条件。

          【讨论】:

          • 我理解答案,但如果我想获取字段status 为a 或b 或c 或... 的所有文档(比如说50 或)。我需要执行 50 个查询并加入它们吗?它看起来不是超级优化?我错了吗?有没有更好的方法来解决这个问题?谢谢。
          • @nerotulip 成功了吗?
          • 我还想知道是否有比运行 N 个查询并在客户端加入更好的解决方案。看起来效率很低。
          • @GeekGuy 您认为 Google 希望减少客户的付款吗?
          • “服务器很难扩展”的说法只不过是谷歌缺乏技能的一个蹩脚的借口。仅仅因为您的名字是“Google”并不能授予您在该领域的自动专业知识。其他索引服务器确实支持 OR 运算符。
          【解决方案7】:

          借助recent addition of IN queries,Firestore 支持“在同一字段上最多包含 10 个具有逻辑 OR 的相等子句”

          (1)的一个可能的解决方案是:

          documents.where('status', 'in', ['open', 'upcoming']);
          
          

          见Firebase Guides: Query Operators | in and array-contains-any

          【讨论】:

            【解决方案8】:

            不支持 OR

            但如果你需要,你可以在你的代码中做到这一点

            Ex : 如果我想查询产品 (Size Equal Xl OR XXL : AND Gender is Male)

            productsCollectionRef
                            //1* first get query where can firestore handle it
                            .whereEqualTo("gender", "Male")
                            .addSnapshotListener((queryDocumentSnapshots, e) -> {
            
                                if (queryDocumentSnapshots == null)
                                    return;
            
                                List<Product> productList = new ArrayList<>();
                                for (DocumentSnapshot snapshot : queryDocumentSnapshots.getDocuments()) {
                                    Product product = snapshot.toObject(Product.class);
            
                                    //2* then check your query OR Condition because firestore just support AND Condition
                                        if (product.getSize().equals("XL") || product.getSize().equals("XXL"))
                                            productList.add(product);
                                }
            
                                liveData.setValue(productList);
            
                            });
            

            【讨论】:

              【解决方案9】:

              我们刚才遇到了同样的问题,幸运的是我们唯一可能的值是 A,B,C,D (4) 所以我们必须查询像 A||B, A||C, A|| 这样的东西B||C、D等


              就像几个月前一样,firebase 支持一个新查询 array-contains,所以我们要做的是创建一个数组,然后我们将 OR 值预处理到数组中

              if (a) {
              array addObject:@"a"
              }
              if (b) {
              array addObject:@"b"
              }
              if (a||b) {
              array addObject:@"a||b"
              }
              etc
              

              我们对所有4! 值或有多少组合都执行此操作。

              那么我们可以简单地检查查询[document arrayContains:@"a||c"] 或我们需要的任何类型的条件。

              因此,如果某些内容仅符合我们的 4 个条件(A、B、C、D)中的条件 A,那么它的数组将包含以下文字字符串:@["A", "A||B", "A||C", "A||D", "A||B||C", "A||B||D", "A||C||D", "A||B||C||D"]

              那么对于这些​​OR 组合中的任何一个,我们可以搜索array-contains 我们可能想要的任何内容(例如“A||C”)


              注意:如果您有几个可能的值要与 OR 进行比较,这只是一种合理的方法。

              更多关于 Array-contains here 的信息,因为它对 firebase 文档来说是新的

              【讨论】:

                【解决方案10】:

                我不会有“状态”字段,而是状态相关的字段,根据请求将它们更新为 true 或 false,例如

                { name: "a", status_open: true, status_upcoming: false, status_closed: false}
                

                但是,请检查 Firebase Cloud Functions。你可以有一个监听状态变化的函数,更新状态相关的属性,比如

                { name: "a", status: "open", status_open: true, status_upcoming: false, status_closed: false}
                

                一个或另一个,你的查询可能只是

                ...where('status_open','==',true)...
                

                希望对你有帮助。

                【讨论】:

                  【解决方案11】:

                  建议也为地位赋予价值。
                  例如。

                  { name: "a", statusValue = 10, status = 'open' }
                  
                  { name: "b", statusValue = 20, status = 'upcoming'}
                  
                  { name: "c", statusValue = 30, status = 'close'}
                  

                  您可以通过ref.where('statusValue', '&lt;=', 20)查询,然后'a'和'b'都会找到。

                  这可以节省您的查询成本和性能。

                  顺便说一句,它不能解决所有情况。

                  【讨论】:

                    【解决方案12】:

                    您可以使用 rxjs 合并运算符绑定两个 Observable。 这里有一个例子。

                    import { Observable } from 'rxjs/Observable';
                    import 'rxjs/add/observable/merge';
                    
                    ...
                    
                    getCombinatedStatus(): Observable<any> {
                       return Observable.merge(this.db.collection('foo', ref => ref.where('status','==','open')).valueChanges(),
                                               this.db.collection('foo', ref => ref.where('status','==','upcoming')).valueChanges());
                    }
                    

                    然后就可以使用上面的方法订阅新的 Observable 更新了:

                    getCombinatedStatus.subscribe(results => console.log(results);
                    

                    希望这对你有帮助,来自智利的问候!!

                    【讨论】:

                    • 这仍然可以处理 5000 个之后必须连接在一起的查询吗?
                    猜你喜欢
                    • 2020-05-06
                    • 2021-12-10
                    • 1970-01-01
                    • 1970-01-01
                    • 2018-03-20
                    相关资源
                    最近更新 更多