0

I am writing a function to query a Firestore database collection for documents that have only certain properties. The filters is defined as an array of "key, value" pairs. eg:

[
  ["Colour", "green"],
  ["Colour", "blue"],
  ["Greeting", "hello"]
]

This array can be of any length, and I am trying to get every document in the database that does not have values listed in the filter array.

I can do this by using:

await db.collection("database")
  .where("Colour", "!=", "blue")
  .where("Colour", "!=", "green")
  .where("Greeting", "!=", "hello")
  .get()

My issue is that the filter can be any length, so I cannot write the query to have a set number of .where() methods. Is there any way in JavaScript that I can dynamically add methods to a query like shown above (not knowing how many methods I need to add)?

My workaround right now is just to query the entire database, then sort it using Javascript filter functions, but I would like to only have to query the database for the values needed.

Alternatively, are there any other Firestore queries that could complete this filter? I was looking at the docs, but the way my filter is set up with key/value pairs that could be repeated or undefined, it did not seem that any of the complex query methods would work.

Dharmaraj
  • 47,845
  • 8
  • 52
  • 84
Matthew Keller
  • 283
  • 2
  • 8
  • Have you also checked [Firestore conditional where clause using Modular SDK v9](https://stackoverflow.com/q/69036031/13130697)? – Dharmaraj Jul 12 '22 at 16:53
  • 2
    Have you realized that your query will never work due to Firestore query limitations? https://firebase.google.com/docs/firestore/query-data/queries#query_limitations, "In a compound query, range (<, <=, >, >=) and not equals (!=, not-in) comparisons must all filter on the same field." – l1b3rty Jul 12 '22 at 17:18
  • Oh yes, I did not realize that != us a range operator. I guess I will have to change how my filter works. Thanks! – Matthew Keller Jul 13 '22 at 00:19

2 Answers2

3

Assuming that you are building an array full of only excluded key-value pairs and the values you are excluding are properly indexed, we can start off defining some constants:

const collectionRef = db.collection("database");

const excludedKeyValuePairs = [
  ["Colour", "green"],
  ["Colour", "blue"],
  ["Greeting", "hello"],
]

Now we have those, we can build the query using Array#reduce.

const query = excludedKeyValuePairs
  .reduce(
    (query, [key, value]) => query.where(key, "!=", value), // appends the new constraint, returning the new query object
    collectionRef
  );

const querySnapshot = await query.get();

However, if you can use the newer modular Firestore SDK, you can also achieve the same result using:

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

const db = getFirestore();
const collectionRef = collection(db, "database");
const constraints = [
  where("Colour", "!=", "green"),
  where("Colour", "!=", "blue"),
  where("Greeting", "!=", "Hello")
  // elements can also be added or removed using standard array methods as needed.
]
// OR const constraints = excludedKeyValuePairs.map(([key, value]) => where(key, "!=", value))

const querySnapshot = await getDocs(query(collectionRef, ...constraints));
samthecodingman
  • 23,122
  • 4
  • 30
  • 54
  • I was trying to chain multiple where(key, "!=", val) methods onto the query, but the function kept timing out because pointed out by @l1b3rty, it doesn't fit the [firestore query limitations](https://firebase.google.com/docs/firestore/query-data/queries#query_limitations) Thanks for the suggestions though. This should work if I change my filter array to be "==" comparisons – Matthew Keller Jul 13 '22 at 00:23
2

You can map from your array of conditions to where clauses with:

const exclusions = [
  ["Colour", "green"],
  ["Colour", "blue"],
  ["Greeting", "hello"],
]

let collectionRef = db.collection("database");
const conditions = exclusions.map((e) => where(e[0], "!=", e[1]));
let query = query(collectRef, conditions);

const querySnapshot = await getDocs(query);
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807