If I have a simple collection such as:
Fruits:
Banana:
title: \"Banana\"
vitamins: [\"potassium\",\"B6\",\"C\"]
Apple:
title: \"Apple\"
vitam
It has been commented upon, but the accepted answer isn't feasible if your query requires you to create a composite index, because it'll likely lead to your count of composite indexes quickly reaching the limit of 200. It's hacky, but the best solution I can think of is to store - in some kind of guaranteed order, e.g. alphabetically - an array of every combination of vitamin for a given fruit, each combination being concatenated as a string. For example, as bananas contain B6, C, and potassium, an array of vitamin combinations for bananas would be:
B6
B6||C
B6||C||potassium
B6||potassium
C
C||potassium
potassium
Then, if your user was searching for every fruit that contains BOTH B6 AND potassium, your query would be:
db.collection("Fruits").where("vitamins", "array-contains", "B6||potassium")
There's no way to do multiple array-contains queries in Firestore. You can, however, combine as many where
conditions as practically possible. Therefore, consider this:
[Fruits]
<Banana>
title: "Banana"
vitamins:
potassium: true
b6: true
c: true
Firestore.firestore().collection("Fruits").whereField("vitamins.potassium", isEqualTo: true).whereField("vitamins.b6", isEqualTo: true)
However, this still doesn't allow a clean OR
search. To query for fruits with vitamin-x or vitamin-y, you'd have to get more creative. Furthermore, this approach should not be used if there are a lot of possible values and they need to be combined with compound queries. That's because Firestore does not permit more than 200 composite indices and each composite index would need to specify the exact vitamin. Which means you'd have to create an individual composite index for vitamins.b6
, vitamins.potassium
, etc. and you only have 200 total.
There is no way in Firestore to query a Firestore database with more than one arrayContains
. If you will use more than one, an error like this might occur:
Invalid Query. Queries only support having a single array-contains filter.
If you need to filter on more than one vitamins, you'll need to change the logic of structuring your database by creating a property for each individual vitamin that you have and then chain .whereField()
function calls. I know it sounds a little weird but this is how Cloud Firestore works.
Your schema should like this:
vitamins: {
"potassium": true,
"B6": true,
"C": true
}
To find all the fruits with potassium
, B6
and C
vitamins, you should use a query that looks like this:
let fruitsRef = db.collection("Fruits")
.whereField("vitamins.potassium", isEqualTo: true)
.whereField("vitamins.B6", isEqualTo: true)
.whereField("vitamins.C", isEqualTo: true)
It would be tempting to to look for documents that match multiple conditions, by chaining the conditions in the query:
db.collection("Fruits")
.whereField("vitamins", arrayContains: "B6")
.whereField("vitamins", arrayContains: "C")
But the documentation on compound queries suggests that you cannot currently have multiple arrayContains conditions in a single query.
So, it is not possible with what you have today. Consider instead using a map structure instead of an array:
Fruits:
Banana:
title: "Banana"
vitamins: {
"potassium": true,
"B6": true,
"C": true
}
Then you could query like this:
db.collection("Fruits")
.whereField("vitamins.B6", isEqualTo: true)
.whereField("vitamins.C", isEqualTo: true)
Firestore introduced the whereIn, arrayContains and arrayContainsAny methods in late 2019. These methods perform logical 'OR' queries, but have a limit of 10 clauses you can pass in (so max 10 vitamins you can search for).
Some solutions already mentioned restructuring your data. Another solution leveraging the restructuring approach is to not include the Vitamins into the Fruit document, but the other way around. This way you get all the documents 'Banana' is part of.
let vitaminsRef = db.collection('Vitamins').where('fruits', arrayContains: 'banana');
This solution allows you to circumvent the limit of 10 clauses. It gets all the 'Vitamin' documents that have 'Banana' in their 'Fruits' array in one read operation (think about pagination, if too many).