61

For example I have dynamic filter for my list of books where I can set specific color, authors and categories. This filter can set multiple colors at once and multiple categories.

   Book > Red, Blue > Adventure, Detective.

How can I add "where" conditionally?

  firebase
    .firestore()
    .collection("book")
    .where("category", "==", )
    .where("color", "==", )
    .where("author", "==", )

    .orderBy("date")
    .get()
    .then(querySnapshot => {...
Kai - Kazuya Ito
  • 6,454
  • 5
  • 44
  • 50
rendom
  • 2,836
  • 6
  • 33
  • 46

8 Answers8

133

As you can see in the API docs, the collection() method returns a CollectionReference. CollectionReference extends Query, and Query objects are immutable. Query.where() and Query.orderBy() return new Query objects that add operations on top of the original Query (which remains unmodified). You will have to write code to remember these new Query objects so you can continue to chain calls with them. So, you can rewrite your code like this:

var query = firebase.firestore().collection("book")
query = query.where(...)
query = query.where(...)
query = query.where(...)
query = query.orderBy(...)
query.get().then(...)

Now you can put in conditionals to figure out which filters you want to apply at each stage. Just reassign query with each newly added filter.

if (some_condition) {
    query = query.where(...)
}
Doug Stevenson
  • 268,359
  • 30
  • 341
  • 380
  • 4
    Edit: It works, when you reassign `query =`, I missed that... – _This doesn't work for me. All of the where calls are simply ignored. Does this still work for you?_ – Thomas Ebert Jul 20 '18 at 12:52
  • Is this answer still true? Doing `this.afs.collection('events').doc(eventID).collection('activities').doc(activityID).collection('streams')` does not return a class that extends query. – Jimmy Kane Dec 11 '18 at 16:51
  • 1
    @JimmyKane What is `afs`? Are you using AngularFire? If so, all your objects are different. AF wraps everything into its own types. – Doug Stevenson Dec 11 '18 at 16:54
  • yes its angular fire . Dang I need to check their docu then – Jimmy Kane Dec 11 '18 at 16:55
  • 19
    It should be noted that while you can query by multiple where with different properties, you _still_ cannot query multiple where of the same property. Using your question as an example, there is still **no** solution to query `.where("category", "==", "adventure").where("category", "==", "detective")` – blaytenshi Mar 27 '19 at 23:48
  • I do this solution using as @ThomasEbert said redefining `query =` but the problem is that I am using `typescript` any ideas to avoid `@ts-ignore`? – pikilon Jan 18 '20 at 08:07
  • 1
    @pikilon This is just opinionated syntax. Typescript should allow you to redefine variables, using `let` to define them. I know that this is not recommended, because it can cause unexpected behavior due to scoping issues. If you just want to avoid the warning, the `@ts-ignore` will do just fine. Another thing that comes to mind is pushing each new result into an array: `const queries = [firebase.firestore().collection("book")]; queries.push(queries[queries.length - 1].where(...)); queries[queries.length - 1].get().then(...)`. Or you can write a wrapper class... Pick your poison ;-) – Thomas Ebert Jan 18 '20 at 17:34
  • 3
    You can now with IN operator: https://firebase.google.com/docs/firestore/query-data/queries – Elyx0 Jan 25 '20 at 18:00
  • Note! "You can perform range (, >=) or not equals (!=) comparisons only on a single field, and you can include at most one array-contains or array-contains-any clause in a compound query" https://firebase.google.com/docs/firestore/query-data/queries#compound_queries – genericUser Dec 21 '20 at 17:25
  • 2
    @DougStevenson How would I do this with Web v9? – Giovanni Di Toro Sep 17 '21 at 13:45
  • @blaytenshi you actually CAN do multiple where queries for the same field: `.where('category', 'in', ['adventure', 'detective'])` - https://firebase.google.com/docs/firestore/query-data/queries#in_not-in_and_array-contains-any – inorganik Dec 17 '21 at 18:15
  • @inorganik I stand corrected, I guess firestore's finally added the capability! Will have to give firestore a try again some time :) – blaytenshi Apr 19 '22 at 09:02
14

Firebase Version 9

The docs do not cover this but here is how to add conditional where clauses to a query

import { collection, query, where } from 'firebase/firestore'

const queryConstraints = []
if (group != null) queryConstraints.push(where('group', '==', group))
if (pro != null) queryConstraints.push(where('pro', '==', pro))
const q = query(collection(db, 'videos'), ...queryConstraints)

The source of this answer is a bit of intuitive guesswork and help from my best friend J-E^S^-U-S

danday74
  • 45,909
  • 39
  • 198
  • 245
  • Ha! That's what I needed! Thanks! – mgPePe Mar 19 '22 at 21:15
  • basically you don't use any extra firestore feature, you use the JS syntax to store the arguments in an array first, then use the spread syntax to spread the array into a sequence of arguments, within the query() argument list. well done – Antoine Weber Apr 22 '22 at 13:17
11

With Firebase Version 9 (Jan, 2022 Update):

You can filter data with multiple where clauses:

import { query, collection, where, getDocs } from "firebase/firestore";

const q = query(
  collection(db, "products"),
  where("category", "==", "Computer"),
  where("types", "array-contains", ['Laptop', 'Lenovo', 'Intel']),
  where("price", "<=", 1000),
);

const docsSnap = await getDocs(q);
    
docsSnap.forEach((doc) => {
  console.log(doc.data());
});
Kai - Kazuya Ito
  • 6,454
  • 5
  • 44
  • 50
  • 1
    Is there a link to the documentation for this? – m.spyratos Jan 14 '22 at 16:13
  • 2
    @m.spyratos here is the documentation link https://firebase.google.com/docs/firestore/query-data/queries#compound_queries – adkstar Jan 17 '22 at 09:04
  • Why google does that? based on the above signature of the `query` if you want to create a wrapper so that you can replace it with some other no sql, you simple can't. Why have such signatures? For each where condition repeating the same thing. – 8bitIcon Apr 19 '22 at 02:32
8

In addition to @Doug Stevenson answer. When you have more than one where it is necessary to make it more dynamic as in my case.

function readDocuments(collection, options = {}) {
    let {where, orderBy, limit} = options;
    let query = firebase.firestore().collection(collection);

    if (where) {
        if (where[0] instanceof Array) {
            // It's an array of array
            for (let w of where) {
                query = query.where(...w);
            }
        } else {
            query = query.where(...where);
        }

    }

    if (orderBy) {
        query = query.orderBy(...orderBy);
    }

    if (limit) {
        query = query.limit(limit);
    }

    return query
            .get()
            .then()
            .catch()
    }

// Usage
// Multiple where
let options = {where: [["category", "==", "someCategory"], ["color", "==", "red"], ["author", "==", "Sam"]], orderBy: ["date", "desc"]};

//OR
// A single where
let options = {where: ["category", "==", "someCategory"]};

let documents = readDocuments("books", options);
Abk
  • 1,989
  • 1
  • 21
  • 31
2

If you're using angular fire, you can just use reduce like so:

const students = [studentID, studentID2,...];

this.afs.collection('classes',
  (ref: any) => students.reduce(
    (r: any, student: any) => r.where(`students.${student}`, '==', true)
    , ref)
).valueChanges({ idField: 'id' });

This is an example of multiple tags...

You could easily change this for any non-angular framework.

For OR queries (which can't be done with multiple where clauses), see here.

Jonathan
  • 2,697
  • 2
  • 32
  • 64
1

For example, there's an array look like this

const conditionList = [
  {
    key: 'anyField',
    operator: '==',
    value: 'any value',
  },
  {
    key: 'anyField',
    operator: '>',
    value: 'any value',
  },
  {
    key: 'anyField',
    operator: '<',
    value: 'any value',
  },
  {
    key: 'anyField',
    operator: '==',
    value: 'any value',
  },
  {
    key: 'anyField',
    operator: '==',
    value: 'any value',
  },
]

Then you can just put the collection which one you want to set query's conditions into this funcion.

function* multipleWhere(
  collection,
  conditions = [{ field: '[doc].[field name]', operator: '==', value: '[any value]' }],
) {
  const pop = conditions.pop()
  if (pop) {
    yield* multipleWhere(
      collection.where(pop.key, pop.operator, pop.value),
      conditions,
    )
  }
  yield collection
}

You will get the collection set query's conditions.

吳約南
  • 178
  • 1
  • 7
1
async yourFunction(){
    const Ref0 = firebase.firestore().collection("your_collection").doc(doc.id)

    const Ref1 = appointmentsRef.where('val1', '==',condition1).get();
    const Ref2 = appointmentsRef.where("val2", "!=", condition2).get()

    const [snapshot_val1, snapshot_val2] = await Promise.all([Ref1, Ref2]);

    
    const val1_Array = snapshot_val1.docs;
    const val2_Array = snapshot_val2.docs;

    const globale_val_Array = val1_Array .concat(val2_Array );

    return globale_val_Array ;
  }



/*Call you function*/
this.checkCurrentAppointment().then(docSnapshot=> {
      docSnapshot.forEach(doc=> {
          console.log("Your data with multiple code query:", doc.data());
      });
    });
1

Note that a multiple WHERE clause is inherently an AND operation.

Adrian Bartholomew
  • 2,181
  • 6
  • 27
  • 35