46

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?

Dan Dascalescu
  • 143,271
  • 52
  • 317
  • 404

8 Answers8

40

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?

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • 2
    Is this still true today? If so, does the roadmap include a feature to emulate SQL-functionality for `UPDATE table SET field = 'foo' WHERE `? the documentation implies it's still true but is not explicit about this. thanks for your help! – Crashalot Mar 12 '21 at 19:49
  • 2
    Yes, this is still true today. – Frank van Puffelen Mar 12 '21 at 20:07
25

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.

Nick Parsons
  • 45,728
  • 6
  • 46
  • 64
  • 2
    You could reuse your `collection` variable in the initialization of docRef, right? `const docRef = collection.doc(doc.id)` – draperunner Mar 04 '20 at 14:35
  • 4
    `firebase.firestore().collection(collectionName).doc(doc.id)` should be replaced with `doc.ref` – David Jun 25 '20 at 18:17
  • 1
    BEWARE! You can't do that if you have more than 500 documents. Transactions and batch limits: https://firebase.google.com/docs/firestore/manage-data/transactions – maganap Feb 04 '21 at 16:39
8

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 })
      })
    }
  })
allegutta
  • 5,626
  • 10
  • 38
  • 56
Mpwanyi Samuel
  • 178
  • 3
  • 7
  • thank u so much it helped me a lot with my project! – MatkoMilic Feb 04 '21 at 19:16
  • This is the best answer simple and understandable – Brightcode Mar 25 '21 at 22:44
  • 1
    Hello, this approach works until something happens (network crash, closed browser etc.) and stops the update. So you will have some document with the updated value and the others with the old value. That's why batch writes is more recommended. It avoid partial updates. – Steffi Jul 25 '22 at 10:19
3

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');
  });
}
lupin
  • 138
  • 1
  • 8
1

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;
}
  • 1
    This will create an infinite loop as you will read the same documents out of firestore over and over again. It does not start to read at the position where you left to read the previous iteration if there are more documents than the batchlimit. But `break;` is also never called when document count is smaller than the batchlimit. – progNewbie Nov 15 '20 at 13:56
  • 1
    Thanks for noticing! That was actually a modified version of my actual query. I edited my answer to be explicit that I search for A = x, and then bulk update A = y, so it won't loop forever anymore. – Jordi Reinsma Nov 16 '20 at 15:53
1

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;
    }
pref
  • 1,651
  • 14
  • 24
0

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.

0

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