0

There is a firestore collection that stores recipes with a list of ingredients. We need to find recipes that contain at least one of the ingredients. How to implement it? This is possible with the firestore?

--- recipe1 
          |
          --- ingredients: ["salt", "pepper", "sucar"]
--- recipe2 
          |
          ---  ingredients: ["pepper"]
--- recipe3 
          |
          ---  ingredients: ["salt", "pepper"] 

How to choose a recipe in which there is either "pepper" OR "salt"?

Alexander
  • 91
  • 1
  • 5
  • Have a look at https://firebase.google.com/docs/firestore/query-data/queries#array_membership. However with the the `array_contains` operator you can only check for one item. – Renaud Tarnec Nov 14 '18 at 15:32
  • That's right! This does not work for me! "FirebaseError: [code=invalid-argument]: Invalid query. Queries only support a single array-contains filter." – Alexander Nov 14 '18 at 16:36

1 Answers1

0

This is possible with the firestore?

Yes it is.

We need to find recipes that contain at least one of the ingredients. How to implement it?

In order to implement this feature, you should use arrays. Please see below a database schema, that can help you achieve this:

Firestore-root
    |
    --- recipes (collection)
         |
         --- recipeId (document)
               |
               --- ingredients: ["salt", "pepper"] (array)
               |
               --- //other decipe details

In order to find all recipes that contain one of the ingredients, you should use a query that looks like this:

FirebaseFirestore rootRef = FirebaseFirestore.getInstance();
Query query = rootRef.collection("recipes").whereArrayContains("ingredients", "salt");

This is for Android but in the same way you can achieve this for other programming languages. See here more details.

But be aware of one thing, you cannot find all recipes that contain more than one of the ingredients, without making significant changes in your database. Firestore does not allow chained whereArrayContains() calls. So you can get all recipes that contain only one of the ingredients.

Edit: According to your comment:

How to choose a recipe in which there is either "pepper" OR "salt"?

You need to know that there is no OR clause in Firestore. According to my answer from this post, you should create two separate queries and merge the result cliend side.

Edit2:

100,000 recipes to combine on the client.

If this is the use-case of your app then you should query this way. Firestore scales massively.

But this is a fairly simple query!

It is in terms of SQL databases but this is how NoSQL databases work.

If you need to exclude recipes with the ingredient "sugar"?

According to the official documentation, beside the fact that there is no OR clause, there is also another query limitation:

Queries with a != clause. In this case, you should split the query into a greater-than query and a less-than query.

So this is the way in which you can solve this exclude or not equal situation.

Your link also does not solve the problem.

My link cannot solve a problem that cannot be solved in the way you want, it just indicates the constraints that Firestore official documentation provides. In that link, I also provide a workaround in the way the docs recommend. In this case, you should make your own attempt given the information in my answer and that post and ask another question if something else comes up.

The result - recipes with salt AND pepper. But not salt OR pepper!

No, if you create two separate queries and merge the result cliend side, you'll have the desired result. I've test it and it works pretty fine.

I know that there is no "OR". I therefore ask - is there a solution?

Yes it is, the solution I have provided you above, which is the simplest one. If you are not happy with that, you might also consider change your entire database structure so it can be organized according to a reverse look up. Your structure should look like this:

Firestore-root
   |
   --- salt_peper (collection)
   |     |
   |     --- recipeId (document)
   |     |     |
   |     |     --- //recipe with salt
   |     |
   |     --- recipeId (document)
   |           |
   |           --- //recipe with peper
   |
   --- salt_sugar (collection)
         |
         --- recipeId (document)
               |
               --- //recipe details

As you can see, this is another schema for your database. The first collection will provide you all recipes with salt OR peper.

This practice is called denormalization and is a common practice when it comes to Firebase. For a better understanding, I recommend you see this video, Denormalization is normal with the Firebase Database. It is for Firebase realtime database but same principle apply to Cloud Firestore.

Also, when you are duplicating data, there is one thing that need to keep in mind. In the same way you are adding data, you need to maintain it. With other words, if you want to update/detele an item, you need to do it in every place that it exists.

I also recommend you take a look at my answer from this post to see pro and cons regarding the tehnique above.

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
  • This does not work! It only works for one ingredient (for example, "pepper") - recipe1: {ingredients: ["salt", "pepper", "sucar"]} - recipe2: { ingredients: ["pepper"]} - recipe3: {ingredients: ["salt", "pepper"]} How to choose a recipe in which there is either "pepper" **OR** "salt"? `let query = docRef.where("ingredients", "array-contains", "salt").OR("ingredients", "array-contains", "pepper").or(...` – Alexander Nov 14 '18 at 16:57
  • I know that there is no "OR". I therefore ask - is there a solution? 100,000 recipes to combine on the client ?! Why then do we need a database with queries ?! But this is a fairly simple query! If you need to exclude recipes with the ingredient "sugar"? Your link also does not solve the problem. The result - recipes with salt AND pepper. But not salt OR pepper! – Alexander Nov 14 '18 at 17:40
  • Please see again my updated answer, where I've tried to responde all your new questions. Hope I've been clear enough this time. – Alex Mamo Nov 14 '18 at 18:17
  • sugar_black-pepper_red-pepper_salt_vinegar_egg_flour_butter... sugar_red-pepper_salt_vinegar_egg_flour_butter... sugar_salt_vinegar_egg_flour_butter... ? Unacceptable! How many options needed for one recipe with twenty ingredients?! – Alexander Nov 14 '18 at 18:29
  • This was only an alternative structure. If you have so many ingredients, you should consider use the first solution. I'm sorry to see you aren't happy with the way NoSQL databases work but if you'll use the solution I gave you, I'm sure you'll solve the problem. – Alex Mamo Nov 14 '18 at 18:35
  • The problem is not in noSQL. Here is the same querie in Mongo: `db.recipes.find({ingredients: {$ in: ['salt', 'pepper']}});` Sorry, but your answer does not solve the problem. I explained above. 100000 recipes to combine on the client - not a very good solution as well as denormalization with a combination of a large number of ingredients in the recipe. – Alexander Nov 14 '18 at 19:04
  • When I said NoSQL, I was referring in fact to Firestore, this is what we are talking about. Unfortunately, we haven't a simlar method in here as it is in Mongo, at least for the moment. Maybe in the future, Firebase creators will develop one. You're right, my answer is meant to solve your problem, but don't forget, is also meant to explain you, if your problem can be or cannot be solved in the first place. Unfortunately it cannot be in the way you want. – Alex Mamo Nov 14 '18 at 19:43
  • The fact that the things do not work in the way you expected, doesn't mean I didn't provide you all the options. I gave you an explanation regarding what cannot be done and what your options are. So it's up to you to decide if my explanations were good enough or not. – Alex Mamo Nov 14 '18 at 19:43
  • Anyway, thanks for your replies. [Firestore OR operator in WHERE query](https://github.com/firebase/firebase-js-sdk/issues/321) – Alexander Nov 14 '18 at 19:58
  • You're welcome. That's aslo what that post said. So hope that this `OR` clause will also be available asap in Cloud Firestore. In meanwhile, the only option that you have, is to split the query in two parts. So hope you'll reconsider my answer. – Alex Mamo Nov 14 '18 at 20:05