55

From the docs:

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

How can I perform an OR query? Example:

  1. Give me all documents where the field status is open OR upcoming
  2. Give me all documents where the field status == open OR createdAt <= <somedatetime>
Dan McGrath
  • 41,220
  • 11
  • 99
  • 130
ProblemsOfSumit
  • 19,543
  • 9
  • 50
  • 61
  • 1
    I had a similar question and now I'm editing my data schema so that the values are numbers with an intentional range (example: no access: 0, read access: 1, edit access: 2, owner access 3. Then I could query for an access field isGreaterThan, say, 1). Essentially, I'm thinking of leveraging the implicit OR in number ranges. – jwehrle Jun 21 '19 at 03:32

13 Answers13

41

OR isn't supported as it's hard for the server to scale it (requires keeping state to dedup). The work around is to issue 2 queries, one for each condition, and dedup on the client.


Edit (Nov 2019):

Cloud Firestore now supports IN queries which are a limited type of OR query.

For the example above you could do:

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

However it's still not possible to do a general OR condition involving multiple fields.

Sam Stern
  • 24,624
  • 13
  • 93
  • 124
Dan McGrath
  • 41,220
  • 11
  • 99
  • 130
  • 32
    I understand the answer, but If I want to get all documents where the field `status` is `a` OR `b` OR `c` OR `...` (let's say 50 OR). Do I need to perform 50 queries and join them? It doesn't appear super-optimal? Am I wrong? Is there a better way to approach this? Thx. – charnould Oct 18 '17 at 08:44
  • 1
    @nerotulip any success on this ? – abh Dec 14 '17 at 21:43
  • 1
    I'm also wondering if there's a better solution than running N queries and joining on the client. Seems very inefficient. – bvaughn Jan 01 '18 at 02:17
  • This query should be availed to minimize firestore costs for developers – Geek Guy Aug 26 '18 at 20:23
  • 11
    @GeekGuy Do you think Google wants less payments from their customers? – rendom Sep 23 '18 at 11:20
  • any news on this? – Patricio Vargas Feb 28 '19 at 06:37
  • Dan, would you like to update this with the latest? :-) – Doug Stevenson Dec 16 '19 at 18:11
  • @DougStevenson what is the latest ? is there a way to do so today ? – hkrlys Dec 20 '19 at 08:48
  • @hkrlys Read kariem 's Answer below. – Ankit Jan 04 '20 at 09:10
  • I may be biased, but I have a problem with the last edit: This answer was great for the time it was posted. However, there had been an addition to Firestore which I addressed in my answer on Dec 6 2019. I do not think this answer should have been edited. In addition, the edit happened in January, not in November as indicated in the header. Please see the discussion here: https://meta.stackexchange.com/questions/11474/what-is-the-etiquette-for-modifying-posts – Kariem Jan 24 '20 at 09:05
  • @Kariem, Sam is a colleague of mine working directly on Firestore. I've been a tad busy and have had less time to update information around the web than I use to. Think of this as an extension of a self edit, which is fine to update to amend new and changing information. I've also upvoted your answer. – Dan McGrath Feb 05 '20 at 06:26
  • 8
    The statement "it's hard for the server to scale" is nothing but a lame excuse for a lack of skills on Google's part. Just because your name is "Google" doesn't grant you automatic expertise in this field. Other indexing servers DO support the OR operator. – Johann Sep 07 '20 at 08:15
14

With the recent addition of IN queries, Firestore supports "up to 30 equality clauses on the same field with a logical OR"

A possible solution to (1) would be:

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

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

Support for general OR queries was announced at Google I/O 2023, so based on the documentation a solution to (2) would be:

query(collection(db, '{collection-name}'), or(
  where('status', '==', 'open'),   
  where('createdAt', '<=', somedatetime),   
));
Kariem
  • 4,398
  • 3
  • 44
  • 73
3

you can bind two Observables using the rxjs merge operator. Here you have an example.

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

Then you can subscribe to the new Observable updates using the above method:

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

I hope this can help you, greetings from Chile!!

3

suggest to give value for status as well.
ex.

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

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

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

you can query by ref.where('statusValue', '<=', 20) then both 'a' and 'b' will found.

this can save your query cost and performance.

btw, it is not fix all case.

3

I would have no "status" field, but status related fields, updating them to true or false based on request, like

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

However, check Firebase Cloud Functions. You could have a function listening status changes, updating status related properties like

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

one or the other, your query could be just

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

Hope it helps.

norgematos
  • 610
  • 8
  • 18
3

This doesn't solve all cases, but for "enum" fields, you can emulate an "OR" query by making a separate boolean field for each enum-value, then adding a where("enum_<value>", "==", false) for every value that isn't part of the "OR" clause you want.

For example, consider your first desired query:

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

You can accomplish this by splitting the status: string field into multiple boolean fields, one for each enum-value:

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

To perform your "where status is open or upcoming" query, you then do this:

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

How does this work? Well, because it's an enum, you know one of the values must have true assigned. So if you can determine that all of the other values don't match for a given entry, then by deduction it must match one of the values you originally were looking for.

See also

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

Venryx
  • 15,624
  • 10
  • 70
  • 96
3

I don't like everyone saying it's not possible.

it is if you create another "hacky" field in the model to build a composite...

for instance, create an array for each document that has all logical or elements

then query for .where("field", arrayContains: [...]

Arjun Patel
  • 79
  • 1
  • 5
2

We have the same problem just now, luckily the only possible values for ours are A,B,C,D (4) so we have to query for things like A||B, A||C, A||B||C, D, etc


As of like a few months ago firebase supports a new query array-contains so what we do is make an array and we pre-process the OR values to the array

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

And we do this for all 4! values or however many combos there are.

THEN we can simply check the query [document arrayContains:@"a||c"] or whatever type of condition we need.

So if something only qualified for conditional A of our 4 conditionals (A,B,C,D) then its array would contain the following literal strings: @["A", "A||B", "A||C", "A||D", "A||B||C", "A||B||D", "A||C||D", "A||B||C||D"]

Then for any of those OR combinations we can just search array-contains on whatever we may want (e.g. "A||C")


Note: This is only a reasonable approach if you have a few number of possible values to compare OR with.

More info on Array-contains here, since it's newish to firebase docs

Albert Renshaw
  • 17,282
  • 18
  • 107
  • 195
1

If you have a limited number of fields, definitely create new fields with true and false like in the example above. However, if you don't know what the fields are until runtime, you have to just combine queries.

Here is a tags OR example...

// 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
        );
      }),
    );
  })
);

Unfortunately there is no other way to combine more than 10 items (use array-contains-any if < 10 items).

There is also no other way to avoid duplicate reads, as you don't know the ID fields that will be matched by the search. Luckily, Firebase has good caching.

For those of you that like promises...

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

For more info on this, see this article I wrote.

J

Jonathan
  • 3,893
  • 5
  • 46
  • 77
1

Firebase introduced the OR clause in 2023.

Modular web example:

const q = query(
  collection(db, "collection_name"), 
  or(
    where('status', '==', 'open'),
    where('createdAt', '<=', Timestamp.fromDate(new Date()))
  )
);

It is possible to combine AND with OR clauses:

const q = query(
  collection(db, "cities"), 
  and(
    where('state', '==', 'CA'),   
    or(
      where('capital', '==', true),
      where('population', '>=', 1000000)
  )
));
Artur A
  • 7,115
  • 57
  • 60
0

OR isn't supported

But if you need that you can do It in your code

Ex : if i want query products where (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);

                });
Mahmoud Abu Alheja
  • 3,343
  • 2
  • 24
  • 41
0

For Flutter dart language use this:

db.collection("projects").where("status", whereIn: ["public", "unlisted", "secret"]);
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
  • It helps more if you supply an explanation why this is the preferred solution and explain how it works. We want to educate, not just provide code. – the Tin Man May 24 '20 at 17:55
0

actually I found @Dan McGrath answer working here is a rewriting of his answer:

 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...!
                            }
                        }
                    }
                });

    }
Joseph Ali
  • 345
  • 1
  • 5
  • 11