4

Like the question states, Firebase only allows one array-contains-any per query (https://firebase.google.com/docs/firestore/query-data/queries).

What happens when someone needs a complex query? I need about 10 .where() with 4 array-contains-any clauses on my query. Is there a work around or do I have to take the results from the first array-contains-any and then callout again and again? Seems taxing and unnecessary.

var usersMatchesCollection = config.db.collection("Users");
var currentUserMatchesAllData = usersMatchesCollection.where('preferences.lookingFor', 'array-contains-any', docDataPreferences.lookingFor)
.where('info.identifyAs', '==', docDataPreferences.prefIdentifyAs)
.where('info.languages', 'array-contains-any', docDataPreferences.prefLanguages) 
.where('age', 'array-contains-any', docDataPreferences.ageRange) 
.where('profession', 'array-contains-any', docDataPreferences.prefProfession) 
.where('education', '==', docDataPreferences.prefEducation) 
.where('kids', '==', docDataPreferences.prefKids) 
.where('married', '==', docDataPreferences.prefMarried) 
.where('drinking', '==', docDataPreferences.prefDrinking) 
.where('smokingCig', '==', docDataPreferences.prefSmokingCig) 
await currentUserMatchesAllData.get().then( function (matchesQuerySnapshot) {
  matchesQuerySnapshot.forEach(function(doc) {
      console.log('doc data: ' + JSON.stringify(doc.data().id));
  })
})
2
  • 1
    It looks like you might be asking a bit much of the querying capabilities of Firestore. If you're unable to model your data in such a way that it can be queried given the limitations, then you're probably going to have to do multiple queries as you've pointed out. Commented Feb 6, 2020 at 23:27
  • I was able to get it to work with this comment if you want to post it as the answer. It seems unfortunate that there isn't a less taxing way of querying, hopefully someone will see this and let us know of another way! Commented Feb 7, 2020 at 1:32

1 Answer 1

6

What your asking for is a query Cloud Firestore cannot serve, as we cannot do it in a generic way that's scales regardless of the data. There are some tricks you could employ that reduce the queries complexity, which means reducing the number of queries you'd need to break this into.

preferences.lookingFor

For the where('preferences.lookingFor', 'array-contains-any', docDataPreferences.lookingFor) condition, assuming possible different values is limited and the total number is know, you could create a mapping of possibilities and reduce it to a single equality. For example,

Let's assume 3 possible values A, B, and C, and that you must have at least 1. This means there is only 7 different query variations, which could be encoded as a series of Boolean fields:

preferences.lookingForCheck = {
    A: <if A>,
    B: <If B>,
    C: <If C>,
    AB: <if A OR B>,
    AC: <If A OR C>,
    BC: <If B OR C>,
    ABC: <if A OR B OR C>,
} 

These are fairly simple at write-time to calculate, and mean you can easily calculate which field to query against: where('preferences.lookingForCheck.BC', '==', true)

info.languages

This one is probably to extension to want to explode as combinations, so let's keep this one.

age

I'll over simplify, and assume some simple buckets, so: 21-30, 31-44, 45-64, 65+. You could make this explicit fields like above (A, B, C, D), which would be 15 fields. If you can assume the range will be continuous (e.g if 21-30 & '45-64' is selected, then 31-30 must be selected to), you can optimize even more by instead saying there 2 lists of values from which users must select one from each and the value in the second must be equal or greater than the second: lower_age: [21, 31, 45, 65+] upper_age: [30, 44, 64, 65+]

How you only need 10 combinations with a true/false value: 21-30, 21-44, 21-64, 21-65+, 30-44, 30-64, 30-65+, 45-65, 45-65+, 65+-65+

profession

This is another hard one. Since we've already committed info.languages, we can't have this as an array-contains query. We do have some options to consider.

  1. Issue 1 query per profession type, and merge the results client side. This is more code, but straight forward.
  2. Don't query this field at all, but rather filter out any documents that don't match on the client side. Assumes number of false matches is low, otherwise you'll be read a lot of documents you end up discarding.
  3. A hybrid approach, whereby you bucket professions (e.g. [white, blue, pink] collar) and the same approach discussed for the other fields, while then filter on the client side by the specific profession.

Final Query

Following the above, you'll end up with something like:

preferences = `preferences.lookingForCheck.${prefCombo}`;
ageRange = `ageRange.${lowerRange}to${upperRange}`;

var usersMatchesCollection = config.db.collection("Users");
var currentUserMatchesAllData = usersMatchesCollection.where(preferences, '==', docDataPreferences.lookingForMatch)
.where('info.identifyAs', '==', docDataPreferences.prefIdentifyAs)
.where('info.languages', 'array-contains-any', docDataPreferences.prefLanguages) 
.where(ageRange, '==', docDataPreferences.ageRange) 
.where('profession', '==', docDataPreferences.prefProfessionCollarCheck) 
.where('education', '==', docDataPreferences.prefEducation) 
.where('kids', '==', docDataPreferences.prefKids) 
.where('married', '==', docDataPreferences.prefMarried) 
.where('drinking', '==', docDataPreferences.prefDrinking) 
.where('smokingCig', '==', docDataPreferences.prefSmokingCig) 
await currentUserMatchesAllData.get().then( function (matchesQuerySnapshot) {
  matchesQuerySnapshot.forEach(function(doc) {
      // Filter by profession here
      console.log('doc data: ' + JSON.stringify(doc.data().id));
  })
})
Sign up to request clarification or add additional context in comments.

1 Comment

This is excellent! Good way to find inclusion and filter data. I really liked the depth this answer covers, starting with the changes one needs to do while writing object. Thanks!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.