76

Say I have this kind of structure

    A (collection): { 
       a (doc): {
           name:'Tim',
           B (collection):{
               b (doc): {
                      color:'blue'
               }
             }
          }
    }

where A and B are collections while a and b are documents.
Is there a way to get everything contained in a root document with one query?
If I query like this

db.collection("A").doc("a").get()

I just gets name:'Tim' field. What I want is to also get all B's documents.
I basically wish my query returns

         {
           user:'Tim',
           B (collection):{
               b (doc): {
                      color:'blue'
               }
             }
          }

Is it possibly or do I really need to make multiple queries one for each collection :/ ?

Say I have a really deep nested tree of collections representing the user profile, my costs will raise like hell since each time I load a user profile I have a multiplier of read requests 1 x N where N is the depth of my tree :/.

r4id4
  • 5,877
  • 8
  • 46
  • 76

3 Answers3

40

If you are concerned about costs of each pull, you will need to structure your data according to your common view / pull needs, rather than what you might prefer for a perfect structure. If you need to pull these things together every time, Consider using "maps" for things that do not actually need to be sub-collections with documents.

In this example, "preferences" is a map.

{
  user: "Tim",
  preferences: {
    color: "blue",
    nickname: "Timster"
  }
}

Each document is also limited in size to 1MB - so if you need to store something for this user that will scale and continue to grow, like log records, then it would make sense to break logs into a sub-collection that only gets pulled when you want it, making each log entry a separate document... And whether all logs for all users are stored in a separate parent collection, or a sub-collection of each user really depends on how you will be pulling logs and what will result in fast speeds, balanced against costs of pulls. If you're showing this user their last 10 searches, then a search-log would make good sense as a sub-collection. If you're pulling all search data for all users for analysis, then a separate parent level collection would make sense because you can pull all logs in 1 pull, to prevent the need to pull logs from each user separately.

You can also nest your pulls and promises together for convenience purposes.

// Get reference to all of the documents
console.log('Retrieving list of documents in collection');
let documents = collectionRef
  .limit(1)
  .get()
  .then((snapshot) => {
    snapshot.forEach((doc) => {
      console.log('Parent Document ID: ', doc.id);

      let subCollectionDocs = collectionRef
        .doc(doc.id)
        .collection('subCollection')
        .get()
        .then((snapshot) => {
          snapshot.forEach((doc) => {
            console.log('Sub Document ID: ', doc.id);
          });
        })
        .catch((err) => {
          console.log('Error getting sub-collection documents', err);
        });
    });
  })
  .catch((err) => {
    console.log('Error getting documents', err);
  });
Andrew Allen
  • 6,512
  • 5
  • 30
  • 73
Matthew Rideout
  • 7,330
  • 2
  • 42
  • 61
  • For me, the outer forEach completes before it processes any of the inner items, so the inner items are not associated with the correct parent doc. Output: https://www.dropbox.com/s/2u831pue7edym2w/Screenshot%202017-12-21%2020.33.28.png?dl=0 – AdamG Dec 22 '17 at 01:34
  • This depends on what you need to do with the data. The goal here was to retrieve all of the data. If you need it to print out or be processed synchronously, you may need to use a counter / recursive function with a callback to do one at a time. Keep in mind that will significantly increase your read / write costs. If you just need all the data, then you have it all and can do as you please with it. – Matthew Rideout Dec 23 '17 at 18:10
34

As we know querying in Cloud Firestore is shallow by default. This type of query isn't supported, although it is something Google may consider in the future.

J Prakash
  • 1,323
  • 8
  • 13
  • 14
    Now supported: https://firebase.googleblog.com/2019/06/understanding-collection-group-queries.html – AshClarke Oct 27 '19 at 01:49
  • 2
    @AshClarke I went through the article but didn't understand how to do it, can you please post it as an answer and mention me as well? or guide me to the right direction? – touhid udoy Oct 30 '19 at 16:32
  • 11
    @AshClarke, collectionGroup queries will query all subcollections of a particular name across documents but the query is still shallow and does not return the entire document tree. Also, once you have he query results there is no method to get the 'owning' document (by default). You could store the parent docID in the subcollection document and use that value to retrieve the parent document but it's not automatic. – Kenneth Argo Jun 19 '20 at 17:38
11

Adding to Matt R answer, if you're using babel or you can use async/await, you can get the same result with less code(no catch/then):

// Get reference to all of the documents
console.log("Retrieving list of documents in collection");
let documents = await collectionRef.get();
documents.forEach(async doc => {
    console.log("Parent Document ID: ", doc.id);
    let subCollectionDocs = await collectionRef.doc(doc.id).collection("subCollection").get()
    subCollectionDocs.forEach(subCollectionDoc => {
        subCollectionDoc.forEach(doc => {
            console.log("Sub Document ID: ", doc.id);
        })
    });
});
Sanyam Khurana
  • 1,336
  • 1
  • 14
  • 27
Or Duan
  • 13,142
  • 6
  • 60
  • 65