$accumulator (agregação)
Nesta página
Definição
$accumulator
Define um operador acumulador personalizado. Acumuladores são operadores que mantêm seu estado (por exemplo totais, máximos, mínimos e dados relacionados) à medida que os documentos avançam no pipeline. Use o operador
$accumulator
para executar suas próprias funções JavaScript para implementar um comportamento não suportado pela linguagem de query MongoDB. Consulte também$function
.$accumulator
está disponível nestes estágios:Importante
Executar JavaScript dentro de um operador de agregação pode diminuir o desempenho. Use o operador
$accumulator
somente se os operadores de pipeline fornecidos não puderem atender às necessidades do seu aplicativo.
Sintaxe
O operador $accumulator
tem esta sintaxe:
{ $accumulator: { init: <code>, initArgs: <array expression>, // Optional accumulate: <code>, accumulateArgs: <array expression>, merge: <code>, finalize: <code>, // Optional lang: <string> } }
Campo | Tipo | Descrição | ||||
---|---|---|---|---|---|---|
String ou código | Função usada para inicializar o estado. A função A função
| |||||
Array | Opcional. Argumentos passados para a função
IMPORTANTE: quando usado em um estágio | |||||
String ou código | Função usada para acumular documentos. A função A função
| |||||
Array | Argumentos passados para a função
| |||||
String ou código | Função usada para mesclar dois estados internos. A função
| |||||
String ou código | Opcional. Função usada para atualizar o resultado do acúmulo. A função
| |||||
String | A linguagem usada no código IMPORTANTE: atualmente, o único valor suportado para |
Comportamento
As etapas a seguir descrevem como o operador $accumulator
processa documentos:
O operador começa em um estado inicial, definido pela função init.
Para cada documento, o operador atualiza o estado com base na função accumulate . O primeiro argumento da função accumulate é o estado atual, e os argumentos adicionais são especificados na array accumulateArgs.
Quando o operador precisa mesclar vários estados intermediários, ele executa a função de mesclagem . Para obter mais informações sobre quando a função de mesclagem é chamada, consulte Mesclar dois estados com
$merge
.Se uma função finalizar tiver sido definida, uma vez que todos os documentos tenham sido processados e o estado tenha sido atualizado de acordo, finalizar converte o estado em uma saída final.
Mesclar dois estados com $merge
Como parte de suas operações internas, o operador $accumulator
pode precisar mesclar dois estados intermediários separados. A função merge especifica como o operador deve mesclar dois estados.
A função de mesclagem sempre mescla dois estados de cada vez. No evento de mais de dois estados precisarem ser mesclados, a fusão resultante de dois estados é mesclada com um único estado. Esse processo se repete até que todos os estados sejam mesclados.
Por exemplo, $accumulator
pode precisar combinar dois estados nos seguintes cenários:
$accumulator
é executado em um cluster fragmentado. O operador precisa mesclar os resultados de cada fragmento para obter o resultado final.Uma única operação
$accumulator
excede seu limite de memória especificado. Se você especificar a opçãoallowDiskUse
, o operador armazenará a operação em andamento no disco e finalizará a operação na memória. Quando a operação for concluída, os resultados do disco e da memória serão mesclados usando a função de mesclagem .
Ordem de processamento de documentos
A ordem em que o MongoDB processa documentos para as funções init()
, accumulate()
e merge()
pode variar e ser diferente da ordem em que esses documentos são especificados para a função $accumulator
.
Por exemplo, considere uma série de documentos em que os campos _id
são as letras do alfabeto:
{ _id: 'a' }, { _id: 'b' }, { _id: 'c' } ... { _id: 'z' }
Em seguida, considere um pipeline de agregação que classifica os documentos pelo campo _id
e usa uma função $accumulator
para concatenar os valores do 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" } } } } ]
O MongoDB não garante que os documentos sejam processados na ordem de classificação, o que significa que o campo alphabet
não é necessariamente definido como abc...z
.
Devido a esse comportamento, garanta que sua função $accumulator
não precise processar e retornar documentos em uma ordem específica.
Javascript ativado
Para usar $accumulator
, os scripts do lado do servidor devem estar habilitados.
Se você não usar $accumulator
(ou $function
, $where
ou mapReduce
), desative o script do lado do servidor:
Para uma instância
mongod
, consulte opção de configuraçãosecurity.javascriptEnabled
ou opção de linha de comando--noscripting
.Para instâncias do
mongos
, consulte a opção de configuraçãosecurity.javascriptEnabled
ou a opção de linha de comando--noscripting
.In earlier versions, MongoDB does not allow JavaScript execution onmongos
instances.
Consulte também ➤ Executar o MongoDB com opções de configuração seguras.
Funções de array e string não suportadas
O MongoDB 6.0 atualiza o mecanismo JavaScript interno usado para expressões JavaScript do lado do servidor, $accumulator
, $function
e $where
e de MozJS-60 para MozJS-91. Várias funções de array e string obsoletas e não padrão que existiam no MozJS-60 foram removidas no MozJS-91.
Para obter a lista completa das funções de array e string removidas, consulte as notas de compatibilidade da versão 6.0.
Exemplos
Usar $accumulator
para implementar o $avg
operador
Observação
Este exemplo mostra como usar o operador $accumulator
para implementar o operador $avg
, que já é suportado pelo MongoDB. O objetivo deste exemplo não é implementar novas funcionalidades, mas ilustrar o comportamento e sintaxe do operador $accumulator
com uma lógica familiar.
No mongosh
, crie uma coleção de amostra denominada books
com os seguintes 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 } ])
A operação seguinte groups
os documentos por author
e usa $accumulator
para calcular o número médio de cópias entre livros para 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 operação retorna o seguinte resultado:
{ "_id" : "Dante", "avgCopies" : 1.6666666666666667 } { "_id" : "Homer", "avgCopies" : 10 }
Comportamento
O $accumulator
define um estado inicial onde count
e sum
estão ambos configurados para 0
. Para cada documento que o $accumulator
processa, ele atualiza o estado da seguinte forma:
Incrementando o
count
por 1 eAdicionando os valores do campo
copies
do documento aosum
. A função accumulate pode acessar o campocopies
porque ele é passado no campo AccumulateArgs.
Com cada documento processado, a função de acumulação retorna o estado atualizado.
Depois que todos os documentos tiverem sido processados, a função finalizar divide o sum
das cópias pelo count
dos documentos para obter a média. Isso remove a necessidade de manter uma média computada em execução, pois a função finalizar recebe o sum
cumulativo e o count
de todos os documentos.
Comparação com $avg
Esta operação é equivalente à seguinte pipeline, que utiliza o operador $avg
:
db.books.aggregate([ { $group : { _id : "$author", avgCopies: { $avg: "$copies" } } } ])
Usar initArgs
para variar o estado inicial por grupo
Você pode utilizar a opção initArgs para variar o estado inicial de $accumulator
. Isso pode ser útil se você quiser, por exemplo:
Usar o valor de um campo que não está em seu estado para afetar seu estado, ou
Definir o estado inicial para um valor diferente com base no grupo que está sendo processado.
No mongosh
, crie uma coleção de amostra denominada restaurants
com os seguintes 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" } ])
Supor que um aplicativo permita que os usuários consultem esses dados para encontrar restaurantes. Pode ser útil mostrar mais resultados para a cidade onde o usuário vive. Para este exemplo, assumimos que a cidade do usuário é chamada em uma variável chamada userProfileCity
.
O pipeline de agregação a seguir groups
os documentos por city
. A operação utiliza o $accumulator
para exibir um número diferente de resultados de cada cidade, dependendo se a cidade do restaurante corresponde à cidade no perfil do usuário ou não:
Observação
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
Se o valor de userProfileCity
for Bettles
, esta operação retornará o seguinte 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" ] } }
Se o valor de userProfileCity
for Onida
, esta operação retornará o seguinte 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" ] } }
Se o valor de userProfileCity
for Pyote
, esta operação retornará o seguinte resultado:
{ "_id" : { "city" : "Bettles" }, "restaurants" : { "restaurants" : [ "Food Fury" ] } } { "_id" : { "city" : "Onida" }, "restaurants" : { "restaurants" : [ "The Wrap" ] } } { "_id" : { "city" : "Pyote" }, "restaurants" : { "restaurants" : [ "Crave", "The Gala" ] } }
Se o valor de userProfileCity
for qualquer outro valor, esta operação retornará o seguinte resultado:
{ "_id" : { "city" : "Bettles" }, "restaurants" : { "restaurants" : [ "Food Fury" ] } } { "_id" : { "city" : "Onida" }, "restaurants" : { "restaurants" : [ "The Wrap" ] } } { "_id" : { "city" : "Pyote" }, "restaurants" : { "restaurants" : [ "Crave" ] } }
Comportamento
A função init define um estado inicial contendo os campos max
e restaurants
. O campo max
define o número máximo de restaurantes desse grupo em particular. Se o campo city
do documento corresponder a userProfileCity
, esse grupo conterá um máximo de 3 restaurantes. Caso contrário, se o documento _id
não corresponder a userProfileCity
, o grupo conterá no máximo um único restaurante. A função init recebe os argumentos city
userProfileCity
da array initArgs.
Para cada documento que o $accumulator
processa, ele passa o name
do restaurante para a array restaurants
, desde que esse nome não coloque o comprimento de restaurants
acima do valor de max
. Com cada documento processado, a função de acumulação retorna o estado atualizado.
A função de mesclagem define como mesclar dois estados. A função concatena as restaurant
arrays de cada estado, e o comprimento da array resultante é limitado usando a fatia () para garantir que não exceda o max
valor .
Depois que todos os documentos forem processados, a função finalize modifica o estado resultante para retornar apenas os nomes dos restaurantes. Sem essa função, o campo max
também seria incluído na saída, o que não atende a nenhuma das necessidades do aplicativo.