Menu Docs
Página inicial do Docs
/
Manual do MongoDB
/ / /

$accumulator (agregação)

Nesta página

  • Definição
  • Sintaxe
  • Comportamento
  • Exemplos
$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:

  • $bucket

  • $bucketAuto

  • $group

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.

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 init recebe seus argumentos da expressão de array initArgs. Você pode especificar a definição da função como tipos de BSON ou String do tipo BSON.

A função init tem a seguinte forma:

function (<initArg1>, <initArg2>, ...) {
...
return <initialState>
}
Array

Opcional. Argumentos passados para a função init.

initArgs tem o seguinte formato:

[ <initArg1>, <initArg2>, ... ]

IMPORTANTE: quando usado em um estágio $bucketAuto , initArgs não pode fazer referência à chave de grupo (ou seja, você não pode usar a sintaxe $<fieldName> ). Em vez disso, em um estágio do $bucketAuto , você pode somente especificar valores constantes no initArgs.

String ou código

Função usada para acumular documentos. A função accumulate recebe seus argumentos do estado atual e da expressão da array accumulateArgs. O resultado da função accumulate torna-se o novo estado. Você pode especificar a definição da função como Code ou String do tipo BSON.

A função accumulate tem a seguinte forma:

function(state, <accumArg1>, <accumArg2>, ...) {
...
return <newState>
}
Array

Argumentos passados para a função accumulate . Você pode usar accumulateArgs para especificar quais valores de campo passar para a função accumulate .

accumulateArgs tem o seguinte formato:

[ <accumArg1>, <accumArg2>, ... ]
String ou código

Função usada para mesclar dois estados internos. merge deve ser do tipo String ou Code de BSON. merge retorna o resultado combinado dos dois estados mesclados. Para obter informações sobre quando a função de mesclagem é chamada, consulte Mesclar dois estados com $merge.

A função merge tem a seguinte forma:

function (<state1>, <state2>) {
<logic to merge state1 and state2>
return <newState>
}
String ou código

Opcional. Função usada para atualizar o resultado do acúmulo.

A função finalize tem a seguinte forma:

function (state) {
...
return <finalState>
}
String

A linguagem usada no código $accumulator.

IMPORTANTE: atualmente, o único valor suportado para lang é js.

As etapas a seguir descrevem como o operador $accumulator processa documentos:

  1. O operador começa em um estado inicial, definido pela função init.

  2. 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.

  3. 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.

  4. 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.

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ção allowDiskUse, 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 .

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.

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:

Consulte também ➤ Executar o MongoDB com opções de configuração seguras.

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"
}
}
}
}
])

Esta operação retorna o seguinte resultado:

{ "_id" : "Dante", "avgCopies" : 1.6666666666666667 }
{ "_id" : "Homer", "avgCopies" : 10 }

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 e

  • Adicionando os valores do campo copies do documento ao sum. A função accumulate pode acessar o campo copies 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.

Esta operação é equivalente à seguinte pipeline, que utiliza o operador $avg:

db.books.aggregate([
{
$group : {
_id : "$author",
avgCopies: { $avg: "$copies" }
}
}
])

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

Para executar este exemplo em mongosh, substitua <userProfileCity> no initArgs por uma string contendo um valor de cidade real, como Bettles.

1db.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])

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" ] } }

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.

Voltar

$abs