Definition
$accumulatorDefines a custom accumulator operator. Accumulators are operators that maintain their state (e.g. totals, maximums, minimums, and related data) as documents progress through the pipeline. Use the
$accumulatoroperator to execute your own JavaScript functions to implement behavior not supported by the MongoDB Query Language. See also$function.$accumulatoris available in these stages:Important
Executing JavaScript inside of an aggregation operator may decrease performance. Only use the
$accumulatoroperator if the provided pipeline operators cannot fulfill your application's needs.
Syntax
The $accumulator operator has this syntax:
{ $accumulator: { init: <code>, initArgs: <array expression>, // Optional accumulate: <code>, accumulateArgs: <array expression>, merge: <code>, finalize: <code>, // Optional lang: <string> } }
Field | Type | Description | ||||
|---|---|---|---|---|---|---|
String or Code | Function used to initialize the state. The The Spilling to disk or running a query on a sharded cluster can cause the accumulator to be computed as a merge of multiple sub-accumulations, each of which begins by calling | |||||
Array | Optional. Arguments passed to the
IMPORTANT: When used in a | |||||
String or Code | Function used to accumulate documents. The The | |||||
Array | Arguments passed to the
| |||||
String or Code | Function used to merge two internal states. The | |||||
String or Code | Optional. Function used to update the result of the accumulation. The | |||||
String | The language used in the IMPORTANT: Currently, the only supported value for |
Behavior
The following steps outline how the $accumulator operator processes documents:
The operator begins at an initial state, defined by the init function.
For each document, the operator updates the state based on the accumulate function. The accumulate function's first argument is the current state, and additional arguments are be specified in the accumulateArgs array.
When the operator needs to merge multiple intermediate states, it executes the merge function. For more information on when the merge function is called, see Merge Two States with
$merge.
Merge Two States with $merge
As part of its internal operations, the $accumulator operator may need to merge two separate, intermediate states. The merge function specifies how the operator should merge two states.
The merge function always merges two states at a time. If more than two states must be merged, the resulting merge of two states is merged with a single state. This process repeats until all states are merged.
For example, $accumulator may need to combine two states in the following scenarios:
$accumulatoris run on a sharded cluster. The operator needs to merge the results from each shard to obtain the final result.A single
$accumulatoroperation exceeds its specified memory limit. If you specify theallowDiskUseoption, the operator stores the in-progress operation on disk and finishes the operation in memory. Once the operation finishes, the results from disk and memory are merged together using the merge function.
Document Processing Order
The order that MongoDB processes documents for the init(), accumulate(), and merge() functions can vary, and might differ from the order that those documents are specified to the $accumulator function.
For example, consider a series of documents where the _id fields are the letters of the alphabet:
{ _id: 'a' }, { _id: 'b' }, { _id: 'c' } ... { _id: 'z' }
Next, consider an aggregation pipeline that sorts the documents by the _id field and then uses an $accumulator function to concatenate the _id field values:
[ { $sort: { _id: 1 } }, { $group: { _id: null, alphabet: { $accumulator: { init: function() { return "" }, accumulate: function(state, letter) { return(state + letter) }, accumulateArgs: [ "$_id" ], merge: function(state1, state2) { return(state1 + state2) }, lang: "js" } } } } ]
MongoDB does not guarantee that the documents are processed in the sorted order, meaning the alphabet field does not necessarily get set to abc...z.
Due to this behavior, ensure that your $accumulator function does not need to process and return documents in a specific order.
Javascript Enabled
To use $accumulator, you must have server-side scripting enabled.
If you do not use $accumulator (or $function, $where, or mapReduce), disable server-side scripting:
For a
mongodinstance, seesecurity.javascriptEnabledconfiguration option or--noscriptingcommand-line option.For a
mongosinstance, seesecurity.javascriptEnabledconfiguration option or the--noscriptingcommand-line option.In earlier versions, MongoDB does not allow JavaScript execution on
mongosinstances.
Unsupported Array and String Functions
MongoDB 6.0 upgrades the internal JavaScript engine used for server-side JavaScript, $accumulator, $function, and $where expressions and from MozJS-60 to MozJS-91. Several deprecated, non-standard array and string functions that existed in MozJS-60 are removed in MozJS-91.
Examples
The examples on this page use data from the sample_mflix dataset. For details on how to load this dataset into your self-managed MongoDB deployment, see Load the Sample Dataset. If you made any modifications to the sample databases, you may need to drop and recreate the databases to run the examples on this page.
Use $accumulator to Implement the $avg Operator
Note
This example walks through using the $accumulator operator to implement the $avg operator, which is already supported by MongoDB. The goal of this example is not to implement new functionality, but to illustrate the behavior and syntax of the $accumulator operator with familiar logic.
The following operation groups the documents by rated, and uses $accumulator to compute the average imdb.rating for each content rating. The $match stage restricts the input to the G, PG, PG-13, and R content ratings, and excludes documents where imdb.rating is not a double, such as an empty string:
db.movies.aggregate( [ { $match: { rated: { $in: [ "G", "PG", "PG-13", "R" ] }, "imdb.rating": { $type: "double" } } }, { $group: { _id: "$rated", avgRating: { $accumulator: { init: function() { // Set the initial state return { count: 0, sum: 0 } }, accumulate: function(state, rating) { // Define how to update the state return { count: state.count + 1, sum: state.sum + rating } }, accumulateArgs: [ "$imdb.rating" ], // Argument required by the accumulate function merge: function(state1, state2) { // When the operator performs a merge, return { // add the fields from the two states count: state1.count + state2.count, sum: state1.sum + state2.sum } }, finalize: function(state) { // After collecting the results from all documents, return (state.sum / state.count) // calculate the average }, lang: "js" } } } }, { $sort: { _id: 1 } } ] )
[ { _id: 'G', avgRating: 6.628092243186578 }, { _id: 'PG', avgRating: 6.43525917926564 }, { _id: 'PG-13', avgRating: 6.404911676001717 }, { _id: 'R', avgRating: 6.456657633242984 } ]
Behavior
The $accumulator defines an initial state where count and sum are both set to 0. For each document that the $accumulator processes, it updates the state by:
Incrementing the
countby 1 andAdding the values of the document's
imdb.ratingfield to thesum. The accumulate function can access theimdb.ratingfield because it is passed in the accumulateArgs field.
With each document that is processed, the accumulate function returns the updated state.
Once all documents have been processed, the finalize function divides the sum of the ratings by the count of documents to obtain the average. This removes the need to keep a running computed average, since the finalize function receives the cumulative sum and count of all documents.
Comparison with $avg
This operation is equivalent to the following pipeline, which uses the $avg operator:
db.movies.aggregate( [ { $match: { rated: { $in: [ "G", "PG", "PG-13", "R" ] }, "imdb.rating": { $type: "double" } } }, { $group: { _id: "$rated", avgRating: { $avg: "$imdb.rating" } } }, { $sort: { _id: 1 } } ] )
[ { _id: 'G', avgRating: 6.6280922431865825 }, { _id: 'PG', avgRating: 6.435259179265659 }, { _id: 'PG-13', avgRating: 6.404911676001723 }, { _id: 'R', avgRating: 6.456657633242999 } ]
Use initArgs to Vary the Initial State by Group
You can use the initArgs option to vary the initial state of $accumulator. This can be useful if you want to, for example:
Use the value of a field which is not in your state to affect your state, or
Set the initial state to a different value based on the group being processed.
Suppose an application allows users to query this data to find movies. It may be useful to show more results for the content rating that the user prefers. For this example, the user's preferred content rating is PG-13, which is passed to the init function as userProfileRated.
The following aggregation pipeline groups the documents by rated. The operation uses the $accumulator to display a different number of movies from each content rating depending on whether the movie's rating matches the rating in the user's profile:
Note
To run this example with a different preferred content rating, replace "PG-13" in the initArgs array with another value, such as "R".
db.movies.aggregate( [ { $match: { rated: { $in: [ "G", "PG", "PG-13", "R" ] }, "imdb.rating": { $type: "double" } } }, { $sort: { "imdb.rating": -1, title: 1 } }, { $group: { _id: { rated: "$rated" }, movies: { $accumulator: { init: function(rated, userProfileRated) { // Set the initial state return { max: rated === userProfileRated ? 3 : 1, // If the group matches the user's rating, movies: [] // return 3 movies. Otherwise, return 1. } }, initArgs: [ "$rated", "PG-13" ], // Argument to pass to the init function accumulate: function(state, movieTitle) { // Define how to update the state if (state.movies.length < state.max) { state.movies.push(movieTitle); } return state; }, accumulateArgs: [ "$title" ], // Argument required by the accumulate function merge: function(state1, state2) { return { max: state1.max, movies: state1.movies.concat(state2.movies).slice(0, state1.max) } }, finalize: function(state) { // Adjust the state to only return the field we need return state.movies }, lang: "js" } } } }, { $sort: { "_id.rated": 1 } } ] )
[ { _id: { rated: 'G' }, movies: [ 'Modern Times' ] }, { _id: { rated: 'PG' }, movies: [ 'Life' ] }, { _id: { rated: 'PG-13' }, movies: [ 'A Brave Heart: The Lizzie Velasquez Story', 'The Dark Knight', 'The Lord of the Rings: The Return of the King' ] }, { _id: { rated: 'R' }, movies: [ 'The Shawshank Redemption' ] } ]
Behavior
The init function defines an initial state containing max and movies fields. The max field sets the maximum number of movies for that particular group. If the document's rated field matches userProfileRated, that group contains a maximum of 3 movies. Otherwise, if the document's rated field does not match userProfileRated, the group contains at most a single movie. The init function receives both the rated and userProfileRated arguments from the initArgs array.
For each document that the $accumulator processes, it pushes the title of the movie to the movies array, if that title would not put the length of movies over the max value. With each document that is processed, the accumulate function returns the updated state.
The merge function defines how to merge two states. The function concatenates the movies arrays from each state together, and the length of the resulting array is limited using the slice() method to ensure that it does not exceed the max value.
Once all documents have been processed, the finalize function modifies the resulting state to only return the titles of the movies. Without this function, the max field would also be included in the output, which does not fulfill any needs for the application.