Stay organized with collectionsSave and categorize content based on your preferences.
Queries inCloud Firestorelet you find documents
in large collections. To gain insight into properties of the
collection as a whole, you can aggregate data over a collection.
You can aggregate data either at read-time or at write time:
Read-time aggregationscalculate a result at the time of the request.Cloud Firestoresupports thecount(),sum(), andaverage()aggregation queries at read-time. Read-time aggregation queries are are easier
to add to your app than write-time aggregations. For more on
aggregation queries, seeSummarize data with aggregation queries.
Write-time aggregationscalculate a result each time the app performs
a relevant write operation. Write-time aggregations are more work to implement,
but you might use them instead of read-time aggregations for one of the
following reasons:
You want to listen to the aggregation result for real-time updates.
Thecount(),sum(), andaverage()aggregation queries do not support
real-time updates.
You want to store the aggregation result in a client-side cache.
Thecount(),sum(), andaverage()aggregation queries do not support
caching.
You are aggregating data from tens of thousands of documents for each
of your users and consider costs. At a lower number of documents, read-time
aggregations cost less. For a large number of documents in an aggregations,
write-time aggregations might cost less.
You can implement a write-time aggregation using either a client-side
transaction or withCloud Functions. The following sections describe how to implement
write-time aggregations.
Solution: Write-time aggregation with a client-side transaction
Consider a local recommendations app that helps users find great restaurants.
The following query retrieves all the ratings for a given restaurant:
dataclassRestaurant(// default values required for use with "toObject"internalvarname:String="",internalvaravgRating:Double=0.0,internalvarnumRatings:Int=0,)
In order to keep these aggregations consistent, they must be updated each time
a new rating is added to the subcollection. One way to achieve consistency
is to perform the add and the update in a single transaction:
Web
functionaddRating(restaurantRef,rating){// Create a reference for a new rating, for use inside the transactionvarratingRef=restaurantRef.collection('ratings').doc();// In a transaction, add the new rating and update the aggregate totalsreturndb.runTransaction((transaction)=>{returntransaction.get(restaurantRef).then((res)=>{if(!res.exists){throw"Document does not exist!";}// Compute new number of ratingsvarnewNumRatings=res.data().numRatings+1;// Compute new average ratingvaroldRatingTotal=res.data().avgRating*res.data().numRatings;varnewAvgRating=(oldRatingTotal+rating)/newNumRatings;// Commit to Firestoretransaction.update(restaurantRef,{numRatings:newNumRatings,avgRating:newAvgRating});transaction.set(ratingRef,{rating:rating});});});}
Note:This product is not available on watchOS and App Clip targets.
funcaddRatingTransaction(restaurantRef:DocumentReference,rating:Float)async{letratingRef:DocumentReference=restaurantRef.collection("ratings").document()do{let_=tryawaitdb.runTransaction({(transaction,errorPointer)->Any?indo{letrestaurantDocument=trytransaction.getDocument(restaurantRef).data()guardvarrestaurantData=restaurantDocumentelse{returnnil}// Compute new number of ratingsletnumRatings=restaurantData["numRatings"]as!IntletnewNumRatings=numRatings+1// Compute new average ratingletavgRating=restaurantData["avgRating"]as!FloatletoldRatingTotal=avgRating*Float(numRatings)letnewAvgRating=(oldRatingTotal+rating)/Float(newNumRatings)// Set new restaurant inforestaurantData["numRatings"]=newNumRatingsrestaurantData["avgRating"]=newAvgRating// Commit to Firestoretransaction.setData(restaurantData,forDocument:restaurantRef)transaction.setData(["rating":rating],forDocument:ratingRef)}catch{// Error getting restaurant data// ...}returnnil})}catch{// ...}}
Note:This product is not available on watchOS and App Clip targets.
-(void)addRatingTransactionWithRestaurantReference:(FIRDocumentReference*)restaurantrating:(float)rating{FIRDocumentReference*ratingReference=[[restaurantcollectionWithPath:@"ratings"]documentWithAutoID];[self.dbrunTransactionWithBlock:^id(FIRTransaction*transaction,NSError**errorPointer){FIRDocumentSnapshot*restaurantSnapshot=[transactiongetDocument:restauranterror:errorPointer];if(restaurantSnapshot==nil){returnnil;}NSMutableDictionary*restaurantData=[restaurantSnapshot.datamutableCopy];if(restaurantData==nil){returnnil;}// Compute new number of ratingsNSIntegerratingCount=[restaurantData[@"numRatings"]integerValue];NSIntegernewRatingCount=ratingCount+1;// Compute new average ratingfloataverageRating=[restaurantData[@"avgRating"]floatValue];floatnewAverageRating=(averageRating*ratingCount+rating)/newRatingCount;// Set new restaurant inforestaurantData[@"numRatings"]=@(newRatingCount);restaurantData[@"avgRating"]=@(newAverageRating);// Commit to Firestore[transactionsetData:restaurantDataforDocument:restaurant];[transactionsetData:@{@"rating":@(rating)}forDocument:ratingReference];returnnil;}completion:^(id_Nullableresult,NSError*_Nullableerror){// ...}];}
privatefunaddRating(restaurantRef:DocumentReference,rating:Float):Task<Void>{// Create reference for new rating, for use inside the transactionvalratingRef=restaurantRef.collection("ratings").document()// In a transaction, add the new rating and update the aggregate totalsreturndb.runTransaction{transaction->valrestaurant=transaction.get(restaurantRef).toObject<Restaurant>()!!// Compute new number of ratingsvalnewNumRatings=restaurant.numRatings+1// Compute new average ratingvaloldRatingTotal=restaurant.avgRating*restaurant.numRatingsvalnewAvgRating=(oldRatingTotal+rating)/newNumRatings// Set new restaurant inforestaurant.numRatings=newNumRatingsrestaurant.avgRating=newAvgRating// Update restauranttransaction.set(restaurantRef,restaurant)// Update ratingvaldata=hashMapOf<String,Any>("rating"torating,)transaction.set(ratingRef,data,SetOptions.merge())null}}
privateTask<Void>addRating(finalDocumentReferencerestaurantRef,finalfloatrating){// Create reference for new rating, for use inside the transactionfinalDocumentReferenceratingRef=restaurantRef.collection("ratings").document();// In a transaction, add the new rating and update the aggregate totalsreturndb.runTransaction(newTransaction.Function<Void>(){@OverridepublicVoidapply(@NonNullTransactiontransaction)throwsFirebaseFirestoreException{Restaurantrestaurant=transaction.get(restaurantRef).toObject(Restaurant.class);// Compute new number of ratingsintnewNumRatings=restaurant.numRatings+1;// Compute new average ratingdoubleoldRatingTotal=restaurant.avgRating*restaurant.numRatings;doublenewAvgRating=(oldRatingTotal+rating)/newNumRatings;// Set new restaurant inforestaurant.numRatings=newNumRatings;restaurant.avgRating=newAvgRating;// Update restauranttransaction.set(restaurantRef,restaurant);// Update ratingMap<String,Object>data=newHashMap<>();data.put("rating",rating);transaction.set(ratingRef,data,SetOptions.merge());returnnull;}});}
Using a transaction keeps your aggregate data consistent with the underlying
collection. To read more about transactions inCloud Firestore,
seeTransactions and Batched Writes.
Limitations
The solution shown above demonstrates aggregating data using theCloud Firestoreclient library, but you should be aware of the following
limitations:
Security- Client-side transactions require giving clients permission
to update the aggregate data in your database. While you can reduce the
risks of this approach by writing advanced security rules, this may not
be appropriate in all situations.
Offline support- Client-side transactions will fail when the user's device
is offline, which means you need to handle this case in your app and retry
at the appropriate time.
Performance- If your transaction contains multiple read, write, and
update operations, it could require multiple requests to theCloud Firestorebackend. On a mobile device, this could take
significant time.
Write rates- this solution may not work for frequently updated
aggregations because Cloud Firestore documents can only be updated at most
once per second. Additionally, If a transaction reads a document that was
modified outside of the transaction, itretries a finite number of timesand then fails. Check outdistributed countersfor a relevant workaround for aggregations which need more frequent updates.
Solution: Write-time aggregation with Cloud Functions
If client-side transactions are not suitable for your application, you can use
aCloud Functionto update the aggregate information
each time a new rating is added to a restaurant:
Node.js
exports.aggregateRatings=functions.firestore.document('restaurants/{restId}/ratings/{ratingId}').onWrite(async(change,context)=>{// Get value of the newly added ratingconstratingVal=change.after.data().rating;// Get a reference to the restaurantconstrestRef=db.collection('restaurants').doc(context.params.restId);// Update aggregations in a transactionawaitdb.runTransaction(async(transaction)=>{constrestDoc=awaittransaction.get(restRef);// Compute new number of ratingsconstnewNumRatings=restDoc.data().numRatings+1;// Compute new average ratingconstoldRatingTotal=restDoc.data().avgRating*restDoc.data().numRatings;constnewAvgRating=(oldRatingTotal+ratingVal)/newNumRatings;// Update restaurant infotransaction.update(restRef,{avgRating:newAvgRating,numRatings:newNumRatings});});});
This solution offloads the work from the client to a hosted function, which
means your mobile app can add ratings without waiting for a transaction to
complete. Code executed in a Cloud Function is not bound by security rules,
which means you no longer need to give clients write access to the aggregate
data.
Limitations
Using a Cloud Function for aggregations avoids some of the issues with
client-side transactions, but comes with a different set of limitations:
Cost- Each rating added will cause a Cloud Function invocation, which
may increase your costs. For more information, see the Cloud Functionspricing page.
Latency- By offloading the aggregation work to a Cloud Function, your
app will not see updated data until the Cloud Function has finished
executing and the client has been notified of the new data. Depending on
the speed of your Cloud Function, this could take longer than executing the
transaction locally.
Write rates- this solution may not work for frequently updated
aggregations because Cloud Firestore documents can only be updated at most
once per second. Additionally, If a transaction reads a document that was
modified outside of the transaction, itretries a finite number of timesand then fails. Check outdistributed countersfor a relevant workaround for aggregations which need more frequent updates.
[[["Easy to understand","easyToUnderstand","thumb-up"],["Solved my problem","solvedMyProblem","thumb-up"],["Other","otherUp","thumb-up"]],[["Missing the information I need","missingTheInformationINeed","thumb-down"],["Too complicated / too many steps","tooComplicatedTooManySteps","thumb-down"],["Out of date","outOfDate","thumb-down"],["Samples / code issue","samplesCodeIssue","thumb-down"],["Other","otherDown","thumb-down"]],["Last updated 2025-09-04 UTC."],[],[],null,[]]