29

What's the best way to delete many (not all) documents from Cloud Firestore?

The official documentation contains information regarding deleting one document and all documents: https://firebase.google.com/docs/firestore/manage-data/delete-data.

Michał Wolny
  • 383
  • 1
  • 3
  • 9

7 Answers7

25

To delete multiple documents, you can do a single batched write. The WriteBatch class has a delete() method for this purpose.

The performance to between a single BatchedWrite and multiple DocumentReference.delete calls is similar though, see here. As in: I expect both of them to be plenty enough efficient for a case where the user selects documents to be deleted. If you find out that this is not the case, share the code that reproduces the performance problem.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • Hi Frank, thanks for the answer, but I've optimized my code and deleting documents individually is sufficient. – Michał Wolny Jan 14 '18 at 13:12
  • Deleting them in a batch allows you to implement error handling with a single handler though. It also makes easy to perform actions after all selected documents have been deleted. – Paul Smith Mar 24 '22 at 13:57
13

As this is the first result when you search for "how to delete multiple documents on Firestore", I wanted to share a working code snippet that not only does what OP is asking, but also splits your batches into chunks to avoid reaching the limit of 500 commits per batch.

const deleteEmptyMessages = async () => {
  
  const snapshot = await firestore.collection('messages').where('text', '==', '').get();
  const MAX_WRITES_PER_BATCH = 500; /** https://cloud.google.com/firestore/quotas#writes_and_transactions */

  /**
   * `chunk` function splits the array into chunks up to the provided length.
   * You can get it from either:
   * - [Underscore.js](https://underscorejs.org/#chunk)
   * - [lodash](https://lodash.com/docs/4.17.15#chunk)
   * - Or one of [these answers](https://stackoverflow.com/questions/8495687/split-array-into-chunks#comment84212474_8495740)
   */
  const batches = chunk(snapshot.docs, MAX_WRITES_PER_BATCH);
  const commitBatchPromises = [];

  batches.forEach(batch => {
    const writeBatch = firestore.batch();
    batch.forEach(doc => writeBatch.delete(doc.ref));
    commitBatchPromises.push(writeBatch.commit());
  });

  await Promise.all(commitBatchPromises);
};
Fappaz
  • 3,033
  • 3
  • 28
  • 39
  • Since you read the data before deleting. Why don't you use transaction instead of batchWrite ? – Thanh Nhật Nov 26 '22 at 20:33
  • 1
    @ThanhNhật Unlike transactions, [batched writes](https://firebase.google.com/docs/firestore/manage-data/transactions#batched-writes) don't read documents during a write operation. – Fappaz Nov 29 '22 at 05:17
11
FirebaseFirestore db = FirebaseFirestore.getInstance();

WriteBatch writeBatch = db.batch();

for (int i=0;i<cartList.size();i++){
    DocumentReference documentReference = db.collection("Users").document(cartList.get(i).getProductId());
    writeBatch.delete(documentReference);
}

writeBatch.commit().addOnSuccessListener(new OnSuccessListener<Void>() {
    @Override
    public void onSuccess(Void aVoid) {
        // Do anything here
    }
});
Ruli
  • 2,592
  • 12
  • 30
  • 40
Aniket Vishal
  • 213
  • 2
  • 10
3

I used batched writes for this. For example I deleted all documents, where the field "text" is empty:

const emptyMessages = await firestore.collection('messages').where("text", "==", "").get()
const batch = firestore.batch();
emptyMessages.forEach(doc => {
    batch.delete(doc.ref);
});
await batch.commit();
Ruli
  • 2,592
  • 12
  • 30
  • 40
Svyatoslav
  • 31
  • 4
0
btnDeleteSomeTasks.setOnClickListener{
    for (ids in 0 until mRecyclerView.size){
        val currentID = entryAdapter.snapshots.getSnapshot(ids).id
        val reference = firestore.collection("allTasks")
            .document(user.uid)
            .collection("userTasks")
            .document(currentID)
        reference.delete()
        notifyItemRemoved(ids)
    }
}
Ruli
  • 2,592
  • 12
  • 30
  • 40
Mathew
  • 1
  • 1
  • 1
0

The question didn't provide a specific use case. But...

One common scenario where the user may want to delete multiple documents, would be for example: Removing all of the items from a user's shopping cart. (But not from other user's carts.)

To do this efficiently, you must use a batched write. This allows you to perform multiple delete operations simultaneously.

For example, to delete three documents at once:

WriteBatch batch = db.batch();
batch.delete(db.collection("cartItems").document("item1"));
batch.delete(db.collection("cartItems").document("item2"));
batch.delete(db.collection("cartItems").document("item3"));
batch.commit()
     .addOnSuccessListener((result) -> { 
       Log.i(LOG_TAG, "Selected items have been deleted."); 
     })
     .addOnFailureListener((error) -> { 
       Log.e(LOG_TAG, "Failed to delete selected items.", error); 
     });

So if you know the document Ids in advance, this is fairly simple:

public void removeSelectedItemsFromShoppingCart(List<String> itemIds) {
  WriteBatch batch = db.batch();
  CollectionReference collection = db.collection("cartItems");
  for (String itemId : itemIds) {
    batch.delete(collection.document(itemId));
  }

  batch.commit()
       .addOnSuccessListener((result) -> {
          Log.i(LOG_TAG, "Selected items have been removed.");
        })
        .addOnFailureListener((error) -> {
           Log.e(LOG_TAG, "Failed to remove selected items.", error);
        });
}

But if you don't know the document Ids in advance, you may have to query the database first:

public void removeAllItemsFromShoppingCart(String userId) {
  db.collection("cartItems")
    .whereEqualTo("userId", userId)
    .get()
    .addOnSuccessListener((querySnapshot) -> {
      WriteBatch batch = db.batch();
      for (DocumentSnapshot doc : querySnapshot) {
        batch.delete(doc.getReference());
      }

      batch.commit()
           .addOnSuccessListener((result) -> {
              Log.i(LOG_TAG, "All items have been removed.");
            })
            .addOnFailureListener((error) -> {
               Log.e(LOG_TAG, "Failed to remove all items.", error);
            });
    })
    .addOnFailureListener((error) -> {
       Log.e(LOG_TAG, "Failed to get your cart items.", error);
    });
}

Note that the code above uses a one-time read, instead of a real-time read. Which is critical here, so it doesn't continue deleting documents after the operation is done.

Paul Smith
  • 166
  • 3
  • 13
0

With the march 2023 firebase update here is the method to delete all objects that match multiple conditions:

const q = query(collection(db, 'collectionid'),
                             and(where('fieldname', '==', surveyObject.uid),
                               where('array-of-objecst', 'array-contains', object)
                             )
                           );

                           const batch = writeBatch(db);
                           const objectsToDelete = await getDocs(q);
                           questionsToDelete.forEach(doc => {
                             batch.delete(doc.ref);
                           });

                           await batch.commit();
Uros Randelovic
  • 425
  • 5
  • 6