Definición
$accumulatorDefine un operador acumulador personalizado. Los acumuladores son operadores que mantienen su estado (por ejemplo, totales, máximos, mínimos y datos relacionados) a medida que los documentos avanzan por el flujo de trabajo. Utilice el operador
$accumulatorpara ejecutar sus propias funciones JavaScript e implementar comportamientos no compatibles con el lenguaje de consulta de MongoDB. Consulte también$function.$accumulatorestá disponible en estas etapas:Importante
La ejecución de JavaScript dentro de un operador de agregación puede disminuir el rendimiento. Utilice el operador
$accumulatorúnicamente si los operadores de canalización disponibles no satisfacen las necesidades de su aplicación.
Sintaxis
El operador $accumulator tiene esta sintaxis:
{ $accumulator: { init: <code>, initArgs: <array expression>, // Optional accumulate: <code>, accumulateArgs: <array expression>, merge: <code>, finalize: <code>, // Optional lang: <string> } }
Campo | Tipo | Descripción | ||||
|---|---|---|---|---|---|---|
string o código | Función utilizada para inicializar el estado. La función La función Derramar en el disco o ejecutar una query en un clúster fragmentado puede hacer que el acumulador se calcule como una fusión de varias subacumulaciones, cada una de las cuales comienza llamando a | |||||
Arreglo | opcional. Argumentos pasados a la función
IMPORTANTE: Cuando se use en una | |||||
string o código | Función utilizada para acumular documentos. La función La función | |||||
Arreglo | Argumentos pasados a la función
| |||||
string o código | Función utilizada para fusionar dos estados internos. La función | |||||
string o código | opcional. Función utilizada para actualizar el resultado del acumulador. La función | |||||
String | El lenguaje utilizado en el código IMPORTANTE: Actualmente, el único valor admitido para |
Comportamiento
Los siguientes pasos describen cómo el operador $accumulator procesa los documentos:
Para cada documento, el operador actualiza el estado según la función accumulate. El primer argumento de la función accumulate es el estado actual, y se pueden especificar argumentos adicionales en el arreglo accumulateArgs.
Cuando el operador necesita fusionar varios estados intermedios, ejecuta la función de fusión. Para obtener más información sobre cuándo se llama a la función de fusión, consulte Fusionar dos estados con
$merge.
Combinar dos estados con $merge
Como parte de sus operaciones internas, el operador $accumulator puede necesitar fusionar dos estados intermedios separados. La función de fusión especifica cómo debe fusionar el operador dos estados.
La función de fusión siempre combina dos estados a la vez. Si se deben combinar más de dos estados, la combinación resultante se realiza con un solo estado. Este proceso se repite hasta que todos los estados estén fusionados.
Por ejemplo, $accumulator puede necesitar combinar dos estados en los siguientes escenarios:
$accumulatorSe ejecuta en un clúster fragmentado. El operador necesita fusionar los resultados de cada partición para obtener el resultado final.Una sola operación
$accumulatorsupera el límite de memoria especificado. Si se especifica la opciónallowDiskUse, el operador guarda la operación en curso en el disco y la finaliza en la memoria. Una vez finalizada la operación, los resultados del disco y la memoria se combinan mediante la función de fusión.
Orden de Procesamiento de Documentos
El orden con el que MongoDB procesa documentos para las funciones init(), accumulate() y merge() puede variar y podría diferir del orden en que esos documentos se especifican para la función $accumulator.
Por ejemplo, considera una serie de documentos donde los campos _id son las letras del alfabeto:
{ _id: 'a' }, { _id: 'b' }, { _id: 'c' } ... { _id: 'z' }
A continuación, considere un pipeline de agregación que ordena los documentos por el campo _id y luego utiliza una función $accumulator para concatenar los valores del campo _id:
[ { $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 no garantiza que los documentos se procesen en orden clasificado, lo que significa que el campo alphabet no necesariamente se establecerá en abc...z.
Debido a este comportamiento, asegúrate de que tu función $accumulator no necesite procesar y retornar documentos en un orden específico.
Javascript habilitado
Para usar $accumulator, debe tener habilitado el scripting del lado del servidor.
Si no utiliza $accumulator (o $function, $where o mapReduce), desactive la ejecución de scripts del lado del servidor:
Para una instancia de
mongod, consulta la opción de configuraciónsecurity.javascriptEnabledo la opción de línea de comandos--noscripting.Para una instancia
mongos, consulta la opción de configuraciónsecurity.javascriptEnabledo la opción de línea de comandos--noscripting.En versiones anteriores, MongoDB no permite la ejecución de JavaScript en instancias de
mongos.
Consulta también ➤ Ejecutar MongoDB con opciones de configuración seguras.
Funciones de string y arreglo no compatibles
MongoDB 6.0 actualiza el motor JavaScript interno utilizado para JavaScript del lado del servidor, $accumulator, $function y $where expresiones, y pasa de MozJS-60 a MozJS-91. Varias funciones de matriz y cadena obsoletas y no estándar que existían en MozJS-60 se eliminan en MozJS-91.
Ejemplos
Utilice $accumulator para implementar el operador $avg.
Nota
Este ejemplo muestra cómo usar el operador $accumulator para implementar el operador $avg, que ya es compatible con MongoDB. El objetivo de este ejemplo no es implementar una nueva funcionalidad, sino ilustrar el comportamiento y la sintaxis del operador $accumulator con una lógica familiar.
En mongosh, cree una colección de ejemplo llamada books con los siguientes documentos:
db.books.insertMany([ { _id: 8751, title: "The Banquet", author: "Dante", copies: 2 }, { _id: 8752, title: "Divine Comedy", author: "Dante", copies: 1 }, { _id: 8645, title: "Eclogues", author: "Dante", copies: 2 }, { _id: 7000, title: "The Odyssey", author: "Homer", copies: 10 }, { _id: 7020, title: "Iliad", author: "Homer", copies: 10 } ])
La siguiente operación groups procesa los documentos mediante author y utiliza $accumulator para calcular el número promedio de copias en todos los libros de cada autor:
db.books.aggregate([ { $group : { _id : "$author", avgCopies: { $accumulator: { init: function() { // Set the initial state return { count: 0, sum: 0 } }, accumulate: function(state, numCopies) { // Define how to update the state return { count: state.count + 1, sum: state.sum + numCopies } }, accumulateArgs: ["$copies"], // 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" } } } } ])
Resultado
Esta operación devuelve el siguiente resultado:
{ _id: "Dante", avgCopies: 1.6666666666666667 } { _id: "Homer", avgCopies: 10 }
Comportamiento
El $accumulator define un estado inicial donde count y sum están ambos establecidos en 0. Para cada documento que procesa el $accumulator, actualiza el estado de la siguiente manera:
Incrementando el
countpor 1 ySe suman los valores del campo
copiesdel documento al camposum. La función accumulate puede acceder al campocopiesporque se pasa en el campo accumulateArgs.
Con cada documento que se procesa, la función acumular devuelve el estado actualizado.
Una vez procesados todos los documentos, la función de finalización divide el sum de las copias entre el count de los documentos para obtener el promedio. Esto elimina la necesidad de mantener un promedio acumulado, ya que la función de finalización recibe el sum y el count acumulados de todos los documentos.
Comparación con $avg
Esta operación es equivalente al siguiente pipeline, que utiliza el operador $avg:
db.books.aggregate([ { $group : { _id : "$author", avgCopies: { $avg: "$copies" } } } ])
Utilice initArgs para variar el estado inicial por grupo.
Puedes usar la opción initArgs para variar el estado inicial de $accumulator. Esto puede ser útil si quieres, por ejemplo:
Utilice el valor de un campo que no esté en su estado para afectar su estado, o
Establece el estado inicial a un valor diferente según el grupo que se procese.
En mongosh, cree una colección de ejemplo llamada restaurants con los siguientes documentos:
db.restaurants.insertMany( [ { _id: 1, name: "Food Fury", city: "Bettles", cuisine: "American" }, { _id: 2, name: "Meal Macro", city: "Bettles", cuisine: "Chinese" }, { _id: 3, name: "Big Crisp", city: "Bettles", cuisine: "Latin" }, { _id: 4, name: "The Wrap", city: "Onida", cuisine: "American" }, { _id: 5, name: "Spice Attack", city: "Onida", cuisine: "Latin" }, { _id: 6, name: "Soup City", city: "Onida", cuisine: "Chinese" }, { _id: 7, name: "Crave", city: "Pyote", cuisine: "American" }, { _id: 8, name: "The Gala", city: "Pyote", cuisine: "Chinese" } ] )
Suponga que una aplicación permite a los usuarios query estos datos para encontrar restaurantes. Puede ser útil mostrar más resultados para la ciudad donde vive el usuario. Para este ejemplo, asumimos que la ciudad del usuario se almacena en una variable llamada userProfileCity.
La siguiente canalización de agregación groups procesa los documentos mediante city. La operación utiliza $accumulator para mostrar un número diferente de resultados de cada ciudad, dependiendo de si la ciudad del restaurante coincide con la ciudad del perfil del usuario:
Nota
Para ejecutar este ejemplo en mongosh, reemplace <userProfileCity> en initArgs con una cadena que contenga un valor de ciudad real, como Bettles.
1 db.restaurants.aggregate([ 2 { 3 $group : 4 { 5 _id : { city: "$city" }, 6 restaurants: 7 { 8 $accumulator: 9 { 10 init: function(city, userProfileCity) { // Set the initial state 11 return { 12 max: city === userProfileCity ? 3 : 1, // If the group matches the user's city, return 3 restaurants 13 restaurants: [] // else, return 1 restaurant 14 } 15 }, 16 17 initArgs: ["$city", <userProfileCity>], // Argument to pass to the init function 18 19 accumulate: function(state, restaurantName) { // Define how to update the state 20 if (state.restaurants.length < state.max) { 21 state.restaurants.push(restaurantName); 22 } 23 return state; 24 }, 25 26 accumulateArgs: ["$name"], // Argument required by the accumulate function 27 28 merge: function(state1, state2) { 29 return { 30 max: state1.max, 31 restaurants: state1.restaurants.concat(state2.restaurants).slice(0, state1.max) 32 } 33 }, 34 35 finalize: function(state) { // Adjust the state to only return field we need 36 return state.restaurants 37 } 38 39 lang: "js" 40 } 41 } 42 } 43 } 44 ])
Resultados
Si el valor de userProfileCity es Bettles, esta operación arroja el siguiente resultado:
{ _id: { city: "Bettles" }, restaurants: { restaurants: [ "Food Fury", "Meal Macro", "Big Crisp" ] } } { _id: { city: "Onida" }, restaurants: { restaurants: [ "The Wrap" ] } } { _id: { city: "Pyote" }, restaurants: { restaurants: [ "Crave" ] } }
Si el valor de userProfileCity es Onida, esta operación arroja el siguiente resultado:
{ _id: { city: "Bettles" }, restaurants: { restaurants: [ "Food Fury" ] } } { _id: { city: "Onida" }, restaurants: { restaurants: [ "The Wrap", "Spice Attack", "Soup City" ] } } { _id: { city: "Pyote" }, restaurants: { restaurants: [ "Crave" ] } }
Si el valor de userProfileCity es Pyote, esta operación arroja el siguiente resultado:
{ _id: { city: "Bettles" }, restaurants: { restaurants: [ "Food Fury" ] } } { _id: { city: "Onida" }, restaurants: { restaurants: [ "The Wrap" ] } } { _id: { city: "Pyote" }, restaurants: { restaurants: [ "Crave", "The Gala" ] } }
Si el valor de userProfileCity es cualquier otro valor, esta operación devuelve el siguiente resultado:
{ _id: { city: "Bettles" }, restaurants: { restaurants: [ "Food Fury" ] } } { _id: { city: "Onida" }, restaurants: { restaurants: [ "The Wrap" ] } } { _id: { city: "Pyote" }, restaurants: { restaurants: [ "Crave" ] } }
Comportamiento
La función init define un estado inicial que contiene los campos max y restaurants. El campo max establece el número máximo de restaurantes para ese grupo en particular. Si el campo city del documento coincide con userProfileCity, ese grupo contiene un máximo de 3 restaurantes. De lo contrario, si el documento _id no coincide con userProfileCity, el grupo contiene como máximo un solo restaurante. La función init recibe los argumentos city y userProfileCity del array initArgs.
Para cada documento que $accumulator procesa, se inserta el name del restaurante en el arreglo restaurants, siempre que ese nombre no exceda la longitud de restaurants respecto al valor max. Con cada documento que se procesa, la función acumular devuelve el estado actualizado.
La función merge define cómo fusionar dos estados. La función concatena los arreglos restaurant de cada estado, y la longitud del arreglo resultante se limita mediante el método slice() para garantizar que no exceda el valor max.
Una vez procesados todos los documentos, la función de finalización modifica el estado resultante para que solo devuelva los nombres de los restaurantes. Sin esta función, el campo max también se incluiría en la salida, lo cual no satisface las necesidades de la aplicación.