0

I have a firestore database that stores some documents by their ID in multiple locations. I want to write a function that can access all instances of this document by ID and update/delete them without knowing exactly where they are.

For instance, in a shopping example with multiple users each having a subcollection with a cart, wishlist, and owned. The document ID may or may not exist in each one of the user doc's subcollections. I'm trying to write a function that iterates through these lists and performs the action upon matching the docID, yet have been unsuccessful in finding an example of this.

I understand how to add all references to a batch but not how to find them without a crazy amount of read/write operations over the entire user collection. Is this possible?

My initial thought is to:

    let batch = admin.firestore().batch();
    let allUsers = db.collection('users').get()
      .then(snapshot => {
        snapshot.forEach(doc => {
          batch.delete(db.collection('users').doc(doc.id).collection('cart').doc(ID_TO_MATCH));
          batch.delete(db.collection('users').doc(doc.id).collection('wishlist').doc(ID_TO_MATCH));
          batch.delete(db.collection('users').doc(doc.id).collection('owned').doc(ID_TO_MATCH));
        });
      })
      .catch(err => {
        console.log('Error getting documents', err);
      });
      return batch.commit().then(function () {
          // should delete all the direct referenes
      });

This would work, as firestore returns a success for deletion of documents that don't exits. The obvious downside is A LOT of document reads as the number of users grow. Is there anyway to use .where() queries to make this more efficient?

Mr.Drew
  • 939
  • 2
  • 9
  • 30

1 Answers1

1

You could use a where() query based on the documentId sentinel, as follows:

...collection('cart').where(firebase.firestore.FieldPath.documentId(), '==', ID_TO_MATCH)

but I don't think it will change something in terms of pricing or performance. As a matter of fact, "there is a minimum charge of one document read for each query that you perform, even if the query returns no results", as explained in the doc. So reading the doc by doing

collection('cart').doc(ID_TO_MATCH)

or by doing

collection('cart').where(firebase.firestore.FieldPath.documentId(), '==', ID_TO_MATCH)

does not make any difference IMO.


Another possibility would be to use a collectionGroup query. The problem, in this specific case, is that when you query a collection group by FieldPath.documentId(), the value provided must result in a valid document path. See this SO post for more details. So this will not work with ID_TO_MATCH...

But, there is a workaround: you could save in your documents a field which has the same value than the document ID and then a collectionGroup query would work.

So, with a field docId in your document, that holds the value of the Document ID you can write the following query:

const cartsQuery = db.collectionGroup('cart').where('docId', '==', ID_TO_MATCH);
Renaud Tarnec
  • 79,263
  • 10
  • 95
  • 121
  • Okay, so where doesn't help. Is there anyway to do this action more efficiently in terms of price or performance? At the very least, how can this operation be guaranteed to fun until finished without stopping? I believe the batch limits are 10MB and a document can be up to 1MB. Keep a counter and run the batch every 10 docs in the iteration over users? – Mr.Drew Apr 10 '20 at 08:54
  • What about using the new `collectionGroup()` query? That should only read documents that match potentially scaling down the size. `let cartSheets = db.collectionGroup('cart').where(firebase.firestore.FieldPath.documentId(), '==', ID_TO_MATCH);` I think think should work unless there is some limitation on this new query I don't understand. – Mr.Drew Apr 10 '20 at 09:39
  • 1
    Unfortunately a collectionGroup query in your case will not work because when querying a collection group by `FieldPath.documentId()`, the value provided must result in **a valid document path**, and ID_TO_MATCH is not a full document path. See https://stackoverflow.com/questions/56149601/firestore-collection-group-query-on-documentid for more details. – Renaud Tarnec Apr 10 '20 at 09:53
  • 1
    **However**, you could save in your documents a field which has the same value than the docID and then a collectionGroup would work. – Renaud Tarnec Apr 10 '20 at 09:55
  • That's very doable in my situation. So, if I add a document field that is it's docId, then `let cartSheets = db.collectionGroup('cart').where('docFieldName', '==', ID_TO_MATCH);` would theoretically do the trick. Thanks so much! – Mr.Drew Apr 10 '20 at 12:20