'Can Firestore update multiple documents matching a condition, using one query?

In other words, I'm trying to figure out what is the Firestore equivalent to this in SQL:

UPDATE table SET field = 'foo' WHERE <condition>`

Yes, I am asking how to update multiple documents, at once, but unlike the linked questions, I'm specifically asking how to do this in one shot, without reading anything into memory, because there's no need to do so when all you want is to set a flag on all documents matching a condition.

db.collection('table')
  .where(...condition...)
  .update({
    field: 'foo',
  });

is what I expected to work, CollectionReference doesn't have an .update method.

The Transactions and Batched Writes documentation mentions transactions and batched writes. Transactions are out because "A transaction consists of any number of get() operations followed by any number of write operations" Batched writes are also not a solution because they work document-by-document.

With MongoDB, this would be

db.table.update(
  { /* where clause */ },
  { $set: { field: 'foo' } }
)

So, can Firestore update multiple documents with one query, the way SQL database or MongoDB work, i.e. without requiring a round-trip to the client for each document? If not, how can this be done efficiently?



Solution 1:[1]

Updating a document in Cloud Firestore requires knowings its ID. Cloud Firestore does not support the equivalent of SQL's update queries.

You will always have to do this in two steps:

  1. Run a query with your conditions to determine the document IDs
  2. Update the documents with individual updates, or with one or more batched writes.

Note that you only need the document ID from step 1. So you could run a query that only returns the IDs. This is not possible in the client-side SDKs, but can be done through the REST API and Admin SDKs as shown here: How to get a list of document IDs in a collection Cloud Firestore?

Solution 2:[2]

Frank's answer is actually a great one and does solve the issue.

But for those in a hurry maybe this snippet might help you:

const updateAllFromCollection = async (collectionName) => {
    const firebase = require('firebase-admin')

    const collection = firebase.firestore().collection(collectionName)

    const newDocumentBody = {
        message: 'hello world'
    }

    collection.where('message', '==', 'goodbye world').get().then(response => {
        let batch = firebase.firestore().batch()
        response.docs.forEach((doc) => {
            const docRef = firebase.firestore().collection(collectionName).doc(doc.id)
            batch.update(docRef, newDocumentBody)
        })
        batch.commit().then(() => {
            console.log(`updated all documents inside ${collectionName}`)
        })
    })
}

Just change what's inside the where function that queries the data and the newDocumentBody which is what's getting changed on every document.

Also don't forget to call the function with the collection's name.

Solution 3:[3]

The simplest approach is this

const ORDER_ITEMS = firebase.firestore().collection('OrderItems')

ORDER_ITEMS.where('order', '==', 2)
  .get()
  .then(snapshots => {
    if (snapshots.size > 0) {
      snapshots.forEach(orderItem => {
        ORDER_ITEMS.doc(orderItem.id).update({ status: 1 })
      })
    }
  })

Solution 4:[4]

For Dart / Flutter user (editted from Renato Trombini Neto)

// CollectionReference collection = FirebaseFirestore.instance.collection('something');
// This collection can be a subcollection.

_updateAllFromCollection(CollectionReference collection) async {
  var newDocumentBody = {"username": ''};
  User firebaseUser = FirebaseAuth.instance.currentUser;
  DocumentReference docRef;

  var response = await collection.where('uid', isEqualTo: firebaseUser.uid).get();
  var batch = FirebaseFirestore.instance.batch();
  response.docs.forEach((doc) {
    docRef = collection.doc(doc.id);
    batch.update(docRef, newDocumentBody);
  });
  batch.commit().then((a) {
    print('updated all documents inside Collection');
  });
}

Solution 5:[5]

If anyone's looking for a Java solution:

public boolean bulkUpdate() {
  try {
    // see https://firebase.google.com/docs/firestore/quotas#writes_and_transactions
    int writeBatchLimit = 500;
    int totalUpdates = 0;

    while (totalUpdates % writeBatchLimit == 0) {
      WriteBatch writeBatch = this.firestoreDB.batch();
      // the query goes here
      List<QueryDocumentSnapshot> documentsInBatch =
          this.firestoreDB.collection("student")
              .whereEqualTo("graduated", false)
              .limit(writeBatchLimit)
              .get()
              .get()
              .getDocuments();

      if (documentsInBatch.isEmpty()) {
        break;
      }
      // what I want to change goes here
      documentsInBatch.forEach(
          document -> writeBatch.update(document.getReference(), "graduated", true));

      writeBatch.commit().get();

      totalUpdates += documentsInBatch.size();
    }

    System.out.println("Number of updates: " + totalUpdates);

  } catch (Exception e) {
    return false;
  }
  return true;
}

Solution 6:[6]

Combining the answers from Renato and David, plus async/await syntax for batch part. Also enclosing them a try/catch in case any promise fails:

    const updateAllFromCollection = async (collectionName) => {

        const firebase = require('firebase-admin');
        const collection = firebase.firestore().collection(collectionName);
        const newDocumentBody = { message: 'hello world' };

        try {
           const response = await collection.where('message', '==', 'goodbye world').get();
           const batch = firebase.firestore().batch();
           response.docs.forEach((doc) => {
              batch.update(doc.ref, newDocumentBody);
           });
           await batch.commit();  //Done
           console.log(`updated all documents inside ${collectionName}`);

       } catch (err) {
           console.error(err);
       }
       return;
    }

Solution 7:[7]

I like some of the answers but I feel this is cleaner:

import * as admin from "firebase-admin";
const db = admin.firestore();
const updates = { status: "pending" }
await db
  .collection("COLLECTION_NAME")
  .where("status", "==", "open")
  .get()
  .then((snap) => {
    let batch = db.batch();
    snap.docs.forEach((doc) => {
      const ref = doc.ref;
      batch.update(ref, updates);
    });
    return batch.commit();
  });

It uses batched updates and the "ref" from the doc.

Solution 8:[8]

If you have already gathered uids for updating collections, simply do these steps.

if(uids.length) {
    for(let i = 0; i < uids.length; i++) {    
    await (db.collection("collectionName")
             .doc(uids[i]))
             .update({"fieldName": false});
             };
        };

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Nick Parsons
Solution 3 allegutta
Solution 4 lupin
Solution 5
Solution 6 pref
Solution 7 Glacier One
Solution 8 Karen Boyakhchyan