63
votes

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 :/.

3
What type is B? There is a reference type you could try.JonZarate
Reference type ends up with the same problem, the data is not fetched.Van Du Tran
@AshClarke no. Both are different things.Sagar V

3 Answers

29
votes

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.

34
votes

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);
  });
7
votes

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);
    })
});