43

I have 2 collections "photos" and "users" and each document in "users" has one or more photo IDs with an array.

photos > 5528c46b > name: "Photo1"
         a1e820eb > name: "Photo2"
         32d410a7 > name: "Photo3"
users > acd02b1d > name: "John", photos: ["5528c46b"]
        67f60ad3 > name: "Tom", photos: ["5528c46b", "32d410a7"]
        7332ec75 > name: "Sara", photos: ["a1e820eb"]
        9f4edcc1 > name: "Anna", photos: ["32d410a7"]

I want to get all users who have one or more specific photo IDs.

Are there any ways to do that?

Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129
Jaap Weijland
  • 3,146
  • 5
  • 23
  • 31

5 Answers5

34

See Henry's answer, as we've no made Array Contains queries available.

Unfortunately not yet, although it's on our roadmap.

In the meantime, you'll need to use a map instead, in the form of:

photos: {
    id1: true
    id2: true
}

Now you can find all users with id1 by filtering by photos.id1 == true.

Read more about querying such sets in the Firebase documentation.

Dan McGrath
  • 41,220
  • 11
  • 99
  • 130
  • 8
    Is this roadmap published somewhere? Just curious. – Jaap Weijland Oct 21 '17 at 21:31
  • It isn't. We generally don't talk about future features since things can unexpected crop up that change priorities (or even feasibility). – Dan McGrath Oct 21 '17 at 21:50
  • 2
    @DanMcGrath Is there any update to this Feature / Roadmap? – Gal Bracha Nov 05 '17 at 13:23
  • 8
    Firestore won't let me query data this way without indexing every single possible id. Is there some way to not require an index, or to have a dynamic index? – Kevin Beal Dec 24 '17 at 21:10
  • @KevinBeal have i am also having same question – Er. Khatri Jan 02 '18 at 13:10
  • @Er.Khatri if your query is simple shouldn't have problem. If your query is compound i.e, adding other params other than a simple where() then you need to index. Firebase already indexes for simple queries. – TheBen Feb 13 '18 at 19:29
  • @KevinBeal see my comment above – TheBen Feb 13 '18 at 19:29
  • @TheeBen I don't understand, but if you have an answer, then post it and you'll have my upvote. – Kevin Beal Feb 13 '18 at 20:18
  • 1
    I'm thinking Dan is saying don't use an array field to store the id's, [use an object, or a "map"](https://firebase.google.com/docs/firestore/data-model#documents). Seems like no big deal/headache to me. Is there any downside to using the map/object vs array? I can't think of any... just my 2 cents. – Ronnie Royston Mar 10 '18 at 22:23
  • How would you update the array field/object field once created? I am using update() or set() and both seem to overwrite an item in the nested arry/object. How can we prevent this? Any code example? @Frank van Puffelen – Otieno Rowland May 07 '18 at 20:38
  • 2
    The biggest issue as mentioned by @KevinBeal is that you can index the keys if you need a compound query. This is a pretty big deal breaker for any advanced query functionality. – aglassman May 22 '18 at 16:50
  • @Man Personson Even worse. If you want to query by multiple ids then you need to create indexes for all the possible combinations. – wube Oct 30 '18 at 21:01
  • @wube -> This is no longer the case as Cloud Firestore supports queries on arrays now – Dan McGrath Oct 31 '18 at 05:04
  • 3
    @Dan McGrath It does, but only on a single parameter. You can do `.where('photos', 'array-contains', 'id1')` but you **can't** do `.where('photos', 'array-contains', ['id1', 'id2'])` – wube Oct 31 '18 at 07:30
30

Added 'array-contains' query operator for use with .where() to find documents where an array field contains a specific element.

https://firebase.google.com/support/release-notes/js 5.3.0

Update: also available in @google-cloud/firestore: https://github.com/googleapis/nodejs-firestore/releases/tag/v0.16.0

Update 2 https://firebase.googleblog.com/2018/08/better-arrays-in-cloud-firestore.html

Update 3 now available in Admin Node.js SDK v6.0.0 https://github.com/firebase/firebase-admin-node/releases

Henry
  • 32,689
  • 19
  • 120
  • 221
  • 1
    Welp that took a while :D I was working on a project where I needed this and I'm glad that I lagged back enough for the firebase team to get in the lead :p – madprogramer Aug 03 '18 at 19:34
  • 3
    Unfortunately, there are no ways to make a query with few 'array-contains' values. – Bohdan Didukh Aug 14 '18 at 12:05
  • @BohdanDidukh have you tried chaining them? .where(..).where(...)? – Henry Aug 20 '18 at 16:51
  • 4
    @Henry, yes, I tried it. Firebase server responds me with error: FirebaseError: Invalid query. Queries only support a single array-contains filter. – Bohdan Didukh Aug 22 '18 at 11:03
  • is it possible to query array of object using value of object's key like {arrayItems:[{id:123, ....}, {id: 234, ...}]...} and to do something like .where('arrayItems', 'array-contains', 'id=123')? That syntax doesn't seem to work, but I still hope there is a way to do something like that. Please advice – vir us Feb 27 '19 at 19:13
5

Here is a bit of expansion on the answer as some seem to be confused about having to make indexes for each key, Firestore already indexes your data for simple queries thus you can do a simple query like

documentReference.where('param','==','value').onSnapshot(...)

but you can not do a compound query unless you index your data for those parameters. So you would need indexes to be able to do something like this:

 documentReference.where('param','==','value').where(..otherparams...).onSnapshot(...)

So as long as you need the photos for an id you can save them as

usersCollection :                        (a collection)
    uidA:                                (a document)
         photoField:                     (a field value that is a map or object)
            fieldID1 : true              (a property of the photoField)
            fieldID2 : true              (a property of the photoField)
            etc ...  

and you can simply query user(s) that have, let's say, fieldID1 in their photoField without needing to form any index and like query below.

firestore.doc('usersCollection/uidA').where('photoField.fieldID1','==',true).onSnapshot(...)
TheBen
  • 3,410
  • 3
  • 26
  • 51
1

Firestore has now added an 'in' query as of November 2019. According to the announcement article:

With the in query, you can query a specific field for multiple values (up to 10) in a single query. You do this by passing a list containing all the values you want to search for, and Cloud Firestore will match any document whose field equals one of those values.

ThinkDigital
  • 3,189
  • 4
  • 26
  • 34
1

With Firebase Version 9 (Dec, 2021 Update):

You can use "array-contains" with one single photo document ID in the "while()" to get all users who have it:

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

// Here
const q = query(
  collection(db, "users"),
  where("photos", "array-contains", "5528c46b")
);
// Here

const usersDocsSnap = await getDocs(q);

usersDocsSnap .forEach((doc) => {
  console.log(doc.data()); // "John's doc", "Tom's doc"
});

You can alse use "array-contains-any" with one or more photo document IDs with an array in the "while()" to get more corresponding users:

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

// Here
const q = query(
  collection(db, "users"),
  where("photos", "array-contains-any", ["5528c46b", "a1e820eb"])
);
// Here

const usersDocsSnap = await getDocs(q);

usersDocsSnap .forEach((doc) => {
  console.log(doc.data()); // "John's doc", "Tom's doc", "Sara's doc"
});
Henry Ecker
  • 34,399
  • 18
  • 41
  • 57
Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129