如何在 Cloud Firestore 中使用逻辑或执行复合查询?

How to perform compound queries with logical OR in Cloud Firestore?

来自the docs

You can also chain multiple where() methods to create more specific queries (logical AND).

如何执行 OR 查询? 示例:

  1. 给我字段 statusopenupcoming
  2. 的所有文档
  3. 给我所有文档,其中字段 status == opencreatedAt <= <somedatetime>

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 条件。

您可以使用 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);

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

建议也给状态赋值。
例如。

{ name: "a", statusValue = 10, status = 'open' }

{ name: "b", statusValue = 20, status = 'upcoming'}

{ name: "c", statusValue = 30, status = 'close'}

你可以通过ref.where('statusValue', '<=', 20)查询,然后'a''b'都会找到。

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

顺便说一句,它并不能解决所有的问题。

我没有 "status" 字段,但有状态相关字段,根据请求将它们更新为 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)...

希望对您有所帮助。

我们刚才遇到了同样的问题,幸运的是我们唯一可能的值是 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 时,这才是合理的方法。

关于 的更多信息,因为它是 firebase 文档的新手

或者不支持

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

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);

                });

借助 recent addition of IN queries,Firestore 支持 "up to 10 equality clauses on the same field with a logical OR"

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

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

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

对于 Flutter dart 语言,使用这个:

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

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

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

  1. Give me all documents where the field status is open OR upcoming

您可以通过将 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

实际上我发现@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...!
                            }
                        }
                    }
                });

    }

如果您的字段数量有限,请务必像上面的示例一样使用 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 个项目(如果 < 10 个项目,请使用 array-contains-any)。

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

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

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

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

J

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

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

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

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