Explore o novo chatbot do Developer Center! O MongoDB AI chatbot pode ser acessado na parte superior da sua navegação para responder a todas as suas perguntas sobre o MongoDB .

Junte-se a nós no Amazon Web Services re:Invent 2024! Saiba como usar o MongoDB para casos de uso de AI .
Desenvolvedor do MongoDB
Central de desenvolvedor do MongoDBchevron-right
Produtoschevron-right
MongoDBchevron-right

Como usar expressões de agregação personalizadas no MongoDB 4.4

Ado Kukic11 min read • Published Jan 28, 2022 • Updated Sep 23, 2022
MongoDBFramework de agregação
Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
A próxima versão do MongoDB 4.4 torna mais fácil do que nunca trabalhar, transformar, acessar e dar sentido aos seus dados. Esta versão, o beta da qual você pode experimentar agora mesmo, vem com alguns novos operadores que tornam possível escrever funções personalizadas para estender a linguagem de query do MongoDB. Esse recurso, chamado Expressões de agregação personalizadas, permite escrever funções JavaScript que são executadas como parte de um estágio do pipeline de agregação. Eles são úteis quando você precisa implementar um comportamento que não é suportado pela linguagem de query do MongoDB por padrão.
A linguagem de query do MongoDB tem muitos operadores, ou funções, que permitem manipular e transformar seus dados para se adequar ao caso de uso do seu aplicativo. Operadores como $avg , $concate $filter facilitam para os desenvolvedores consultar, manipular e transformar seu conjunto de dados diretamente no nível do banco de dados, versus ter que escrever código adicional e transformar os dados em outro lugar. Embora haja operadores para quase tudo que você possa pensar, há alguns casos extremos em que um operador fornecido ou uma série de operadores não serão suficientes, e é aqui que entram as expressões de agregação customizadas.
Nesta publicação no blog, aprenderemos como podemos estender a linguagem de query do MongoDB para atender às nossas necessidades, escrevendo nossas próprias expressões de agregação personalizadas usando os novos operadores$function e $accumulator . Vamos mergulhar de cabeça!

Pré-requisitos

Para este tutorial, você precisará de:

Expressões de agregação personalizadas

MongoDB 4.4 vem com dois novos operadores: $function e $accumulator . Esses dois operadores nos permitem escrever funções JavaScript personalizadas que podem ser usadas em um aggregation pipeline do MongoDB. Veremos exemplos de como usar ambos implementando nossas próprias expressões de agregação personalizadas.
Para obter o máximo valor desta postagem do blog, presumirei que você já esteja familiarizado com a estrutura de agregaçãodo MongoDB. Caso contrário, sugere-se verificar os Docs e seguir um tutorial ou dois para se familiarizar com como esse recurso funciona antes de abordar este tópico mais avançado.
Antes de entrarmos no código, quero falar brevemente sobre por que você se importaria com esse recurso em primeiro lugar. O primeiro motivo é oferecer maior desempenho aos seus usuários. Se você puder obter os dados exatos de que precisa diretamente do banco de dados em uma viagem, sem precisar fazer processamento e manipulação adicionais, poderá atender e atender às solicitações mais rapidamente. Em segundo lugar, as expressões de agregação personalizadas permitem que você cuide de casos extremos diretamente no estágio do pipeline de agregação. Se você já trabalhou com o pipeline de agregação no passado, se sentirá em casa e será produtivo rapidamente. Se você não estiver familiarizado com o pipeline de agregação, precisará aprendê-lo apenas uma vez. No momento em que você se encontrar com um caso de uso para os operadores$function ou $accumulator , todo o seu conhecimento anterior será transferido. Acho que esses são dois motivos sólidos para se preocupar com expressões de agregação personalizadas: melhor desempenho para seus usuários e maior produtividade do desenvolvedor.
A única ressalva ao uso liberal dos operadores$function e $accumulator é o desempenho. A execução do JavaScript dentro de uma expressão de agregação consome muitos recursos e pode reduzir o desempenho. Você deve sempre optar por usar primeiro os operadores existentes e altamente otimizados, especialmente se eles puderem realizar o trabalho para o seu caso de uso. Somente considere o uso de $function e $accumulator se um operador existente não puder atender às necessidades de seu aplicativo.

$function Operador

O primeiro operador que vamos dar uma olhada é chamado $function. Como o nome indica, esse operador permite que você implemente uma função JavaScript personalizada para implementar qualquer tipo de comportamento. A sintaxe deste operador é:
1{
2 $function: {
3 body: <code>,
4 args: <array expression>,
5 lang: "js"
6 }
7}
O operador $functiontem três propriedades. Obody , que será nossa função JavaScript, uma arrayargs contendo os argumentos que queremos passar para nossa função e uma propriedadelang especificando a linguagem do nosso $function, que a partir do MongoDB 4.4 é compatível apenas com JavaScript.
A propriedadebody mantém nossa função JavaScript como um tipo de código BSON ou string. Em nossos exemplos nesta postagem do blog, escreveremos nosso código como uma string. Nossa função JavaScript terá uma assinatura parecida com esta:
1function(arg){
2 return arg
3}
À primeira vista, parece uma função JavaScript padrão. Você pode passar em n argumentos e a função retorna um resultado. Os argumentos dentro da propriedadebody serão mapeados para os argumentos fornecidos na propriedade de matrizargs, então você precisará certifique-se de passar e capturar todos os argumentos fornecidos.

Implementando o operador $function

Agora que sabemos as propriedades do operador$function, vamos usá-lo em um aggregation pipeline. Para começar, vamos escolher um conjunto de dados para trabalhar. Usaremos um dos conjuntos de dados de amostrado MongoDB fornecidos que você pode encontrar no MongoDB Atlas. Se você ainda não tiver um cluster configurado, pode fazê-lo criando uma conta gratuita do MongoDB Atlas. Carregar os conjuntos de dados de amostra é tão simples quanto clicar no botão "..." em seu cluster e selecionar a opção "Carregar conjunto de dados de amostra".
Carregar conjunto de dados de amostra do MongoDB Atlas
Depois de carregar o conjunto de dados de amostra, vamos Go conectar ao nosso cluster MongoDB. Sempre que aprendo algo novo, prefiro usar uma abordagem visual, então, para este tutorial, vou contar com o MongoDB Compass. Se você já tiver o MongoDB Compass instalado, conecte-se ao cluster que tem o conjunto de dados de amostra carregado. Caso contrário, baixe a versão mais recente aqui e conecte-se.
Se você estiver usando o MongoDB Compass ou se conectando por meio do mongo shell, poderá encontrar sua connection string do MongoDB Atlas clicando no botão "Conectar" em seu cluster, escolhendo o tipo de aplicativo que usará para se conectar e copiando a string que será assim: mongodb+srv://mongodb:<password>@cluster0-tdm0q.mongodb.net/test.
Quando você estiver conectado, o conjunto de dados com o qual trabalharemos se chamará sample_mflix e a coleção movies. Go em frente, conecte-se a essa coleção e navegue até a guia " Agregações ". Para garantir que tudo funcione bem, vamos escrever um pipeline de agregação muito simples usando o novo $function operador . No menu suspenso, selecione o $addFields operador e adicione o seguinte código como sua implementação :
1{
2 fromFunction: {$function: {body: "function(){return 'hello'}", args: [], lang: 'js'}}
3}
Se você estiver usando o mongo shell para executar essas queries, o código ficará assim:
1db.movies.aggregate([
2 {
3 $addFields: {
4 fromFunction: {
5 $function: {
6 body: "function(){return 'hello'}",
7 args: [],
8 lang: 'js'
9 }
10 }
11 }
12 }
13])
Se você examinar a saída no MongoDB Compass e rolar até a parte inferior de cada documento retornado, verá que cada documento agora tem um campo chamado fromFunction com o texto hello como seu valor. Poderíamos ter simplesmente passado a string "olá" em vez de usar o operador$function, mas o motivo pelo qual eu queria fazer isso foi garantir que sua versão do MongoDB Compass suportasse o operador$function e essa é uma maneira mínima de testar ele.
Exemplo básico do operador $function
Em seguida, vamos implementar uma função personalizada que realmente faz algum trabalho. Vamos adicionar um novo campo a cada filme que tenha a pontuação de crítica de Ado, ou talvez a sua?
Vou nomear meu campo como adoScore. Agora, meu sistema de classificação é único. Dependendo do dia e do meu humor, posso gostar mais ou menos de um filme, portanto, começaremos a calcular a pontuação de Ado em um filme atribuindo-lhe aleatoriamente um valor entre 0 e 5. Assim, teremos uma base parecida com esta: let base = Math.floor(Math.random() * 6);.
Em seguida, se os críticos gostariam do filme, eu também, então vamos dizer que, se um filme tiver uma pontuação no IMDB de mais de 8, daremos +1 à pontuação de Ado. Caso contrário, deixaremos como está. Para isso, passaremos o campoimdb.rating para nossa função.
Finalmente, os filmes que ganharam prêmios também recebem um impulso no sistema de pontuação do Ado. Então, para cada nomeação de prêmios que um filme recebe, a pontuação total do Ado aumentará em 0.25, e para cada prêmios ganhos, a pontuação aumentará em 0.5. Para calcular isso, teremos que fornecer o campoawards em nossa função também.
Como nada é perfeito, adicionaremos uma regra personalizada à nossa função: se a pontuação total exceder 10, apenas produziremos a pontuação final a ser 9.9. Vamos ver como é toda essa função:
1{
2 adoScore: {$function: {
3 body: "function(imdb, awards){let base = Math.floor(Math.random() * 6) \n let imdbBonus = 0 \n if(imdb > 8){ imdbBonus = 1} \n let nominations = (awards.nominations * 0.25) \n let wins = (awards.wins * 0.5) \n let final = base + imdbBonus + nominations + wins \n if(final > 10){final = 9.9} \n return final}",
4 args: ["$imdb.rating", "$awards"],
5 lang: 'js'}}
6}
Para facilitar a leitura da função JavaScript, aqui está ela em formato não string:
1function(imdb, awards){
2 let base = Math.floor(Math.random() * 6)
3 let imdbBonus = 0
4
5 if(imdb > 8){ imdbBonus = 1}
6
7 let nominations = awards.nominations * 0.25
8 let wins = awards.wins * 0.5
9
10 let final = base + imdbBonus + nominations + wins
11 if(final > 10){final = 9.9}
12
13 return final
14}
E, novamente, se você estiver usando o mongo shell, o código será parecido:
1db.movies.aggregate([
2 {
3 $addFields: {
4 adoScore: {
5 $function: {
6 body: "function(imdb, awards){let base = Math.floor(Math.random() * 6) \n let imdbBonus = 0 \n if(imdb > 8){ imdbBonus = 1} \n let nominations = (awards.nominations * 0.25) \n let wins = (awards.wins * 0.5) \n let final = base + imdbBonus + nominations + wins \n if(final > 10){final = 9.9} \n return final}",
7 args: ["$imdb.rating", "$awards"],
8 lang: 'js'
9 }
10 }
11 }
12 }
13])
A execução da agregação$addFieldsacima , que usa o operador$function, produzirá um resultado que adiciona um novo campoadoScore ao final de cada documento. Este campo conterá um valor numérico que varia de 0 a 9.9. No operador$function, passamos nossa função JavaScript personalizada para a propriedadebody. À medida que iterávamos em nossos documentos, os campos$imdb.rating e $awards de cada documento foram passados para nossa função personalizada.
Usando a notação de ponto, vimos como especificar qualquer subdocumento que você queira usar em uma agregação. Também aprendemos a usar um campo inteiro e seus subcampos em uma agregação, como vimos com o parâmetro$awardsem nosso exemplo anterior. Nosso resultado final tem a seguinte aparência:
Pontuação da avaliação do Ado usando $function
Isso é apenas uma amostra do que podemos fazer com o operador $function . Em nosso exemplo acima, emparelhá-lo com o operador$addFields, mas também podemos usar $function como alternativa ao operador$where ou também com outros operadores. Confira o $function docs para obter mais informações.

Operador $accumulator

O próximo operador que examinaremos, que também nos permite escrever funções JavaScript personalizadas, é chamado de operador$accumulator e é um pouco mais complexo. Este operador nos permite definir uma função de acumulador personalizada com JavaScript. Os acumuladores são operadores que mantêm seu estado à medida que os documentos progridem no pipeline. Grande parte das mesmas regras se aplicam ao operador $accumulatore$function. Começaremos dando uma olhada na sintaxe do operador$accumulator :
1{
2 $accumulator: {
3 init: <code>,
4 initArgs: <array expression>, // Optional
5 accumulate: <code>,
6 accumulateArgs: <array expression>,
7 merge: <code>,
8 finalize: <code>, // Optional
9 lang: <string>
10 }
11}
Temos alguns campos adicionais para discutir. Em vez de apenas um campobody que contém uma função JavaScript, o operador$accumulator nos dá quatro locais adicionais para escrever JavaScript:
  • O campoinit que inicializa o estado do acumulador.
  • O campoaccumulateque acumula os documentos que passam pelo pipeline.
  • O campomerge usado para mesclar vários estados.
  • O finalize campo que é usado para atualizar o resultado do acúmulo.
Para argumentos, temos dois locais para fornecê-los: o initArgs que é passado para nossa funçãoinit e o accumulateArgs que é passado para nossa funçãoaccumulate. O processo para definir e passar os argumentos é o mesmo aqui e para o operador $function . É importante observar que, para a funçãoaccumulate, o primeiro argumento é state e não o primeiro item na arrayaccumulateArgs .
Finalmente, temos que especificar o campolang. Como antes, será js, pois esse é o único idioma suportado a partir do MongoDB 4.4 liberação.

Implementando o operador $accumulator

Para ver um exemplo concreto do operador$accumulatorem ação, continuaremos a usar nosso conjunto de dadossample_mflix. Também criaremos em cima dos adoScore que adicionamos com o operador$function. Emparelharemos nosso $accumulator com um operador de$group e retornaremos o número de filmes lançados a cada ano de nosso conjunto de dados, bem como quantos filmes são considerados assistíveis pelo sistema de pontuação do Ado (o que significa que eles têm uma pontuação maior que 8). Nossa função$accumulator ficará assim:
1{
2 _id: "$year",
3 consensus: {
4 $accumulator: {
5 init: "function(){return {total:0, worthWatching: 0}}",
6 accumulate: "function(state, adoScore){let worthIt = 0; if(adoScore > 8){worthIt = 1}; return {total:state.total + 1, worthWatching: state.worthWatching + worthIt }}",
7 accumulateArgs:["$adoScore"],
8 merge: "function(state1, state2){return {total: state1.total + state2.total, worthWatching: state1.worthWatching + state2.worthWatching}}",
9 }
10 }
11}
E apenas para exibir as funções JavaScript em formato não string para legibilidade:
1// Init
2function(){
3 return { total:0, worthWatching: 0 }
4}
5
6// Accumulate
7function(state, adoScore){
8 let worthIt = 0;
9 if(adoScore > 8){ worthIt = 1};
10 return {
11 total: state.total + 1,
12 worthWatching: state.worthWatching + worthIt }
13}
14
15// Merge
16function(state1, state2){
17 return {
18 total: state1.total + state2.total,
19 worthWatching: state1.worthWatching + state2.worthWatching
20 }
21}
Se você estiver executando a agregação acima usando o mongo shell, a consulta será parecida com a seguinte:
1db.movies.aggregate([
2 {
3 $group: {
4 _id: "$year",
5 consensus: {
6 $accumulator: {
7 init: "function(){return {total:0, worthWatching: 0}}",
8 accumulate: "function(state, adoScore){let worthIt = 0; if(adoScore > 8){worthIt = 1}; return {total:state.total + 1, worthWatching: state.worthWatching + worthIt }}",
9 accumulateArgs:["$adoScore"],
10 merge: "function(state1, state2){return {total: state1.total + state2.total, worthWatching: state1.worthWatching + state2.worthWatching}}",
11 }
12 }
13 }
14 }
15 ])
O resultado da execução desta query no sample_mflix banco de dados será parecido com isso:
Função $accumulator.
Observação: como a funçãoadoScore depende de Math.random() para parte de seus cálculos, você pode obter resultados variáveis sempre que executar a agregação.
Assim como o operador $function, escrever um acumulador personalizado e usar o operador$accumulator só deve ser feito quando os operadores existentes não puderem atender ao caso de uso do seu aplicativo. Da mesma forma, também estamos apenas arranhando a superfície do que é possível fazer ao escrever seu próprio acumulador. Consulte a Docs para saber mais.
Antes de encerrarmos esta postagem do blog, vamos dar uma olhada em como será o nosso pipeline de agregação completo, combinando os operadores$function e $accumulator. Se estiver usando o conjunto de dadossample_mflix, você poderá executar os dois exemplos com o seguinte código de pipeline de agregação:
1db.movies.aggregate([
2 {
3 '$addFields': {
4 'adoScore': {
5 '$function': {
6 'body': 'function(imdb, awards){let base = Math.floor(Math.random() * 6) \n let imdbBonus = 0 \n if(imdb > 8){ imdbBonus = 1} \n let nominations = (awards.nominations * 0.25) \n let wins = (awards.wins * 0.5) \n let final = base + imdbBonus + nominations + wins \n if(final > 10){final = 9.9} \n return final}',
7 'args': [
8 '$imdb.rating', '$awards'
9 ],
10 'lang': 'js'
11 }
12 }
13 }
14 }, {
15 '$group': {
16 '_id': '$year',
17 'consensus': {
18 '$accumulator': {
19 'init': 'function(){return {total:0, worthWatching: 0}}',
20 'accumulate': 'function(state, adoScore){let worthIt = 0; if(adoScore > 8){worthIt = 1}; return {total:state.total + 1, worthWatching: state.worthWatching + worthIt }}',
21 'accumulateArgs': [
22 '$adoScore'
23 ],
24 'merge': 'function(state1, state2){return {total: state1.total + state2.total, worthWatching: state1.worthWatching + state2.worthWatching}}'
25 }
26 }
27 }
28 }
29])

Conclusão

Os novos operadores de $functione $accumulator lançados no MongoDB 4.4 melhorar a produtividade do desenvolvedor e permitir que o MongoDB lide com muito mais casos extremos prontos para uso. Lembre-se de que esses novos operadores, embora poderosos, só devem ser usados se os operadores existentes não puderem fazer o trabalho, pois podem prejudicar o desempenho!
Se você estiver tentando usar a nova funcionalidade com esses operadores, ajustando seu cluster MongoDB para obter um melhor desempenho, ou apenas tentando fazer mais com menos, o MongoDB 4.4 seguramente fornecerá algumas coisas novas e úteis para você. Você pode experimentar todas essas funcionalidades hoje mesmo implantando um MongoDB 4.4 cluster beta no MongoDB Atlas gratuitamente.
Se você tiver alguma dúvida sobre esses novos operadores ou sobre esta publicação,acesseos fóruns da MongoDB Community e nos encontraremos lá.
Boas experiências!
Declaração de Porto Seguro
O desenvolvimento, lançamento e cronograma de quaisquer recursos ou funcionalidades descritos para produtos MongoDB permanecem a critério exclusivo da MongoDB. Esta informação destina-se apenas a delinear a direção geral do nosso produto e não deve ser invocada na tomada de uma decisão de compra nem é um compromisso, promessa ou obrigação legal de entregar qualquer material, código ou funcionalidade. Exceto conforme exigido por lei, não assumimos nenhuma obrigação de atualizar quaisquer declarações prospectivas para refletir eventos ou circunstâncias após a data de tais declarações.

Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Relacionado
Artigo

Driver Java: migrando do 4.11 a 5.0


Mar 01, 2024 | 3 min read
Tutorial

Primeiros passos no MongoDB e C++


Aug 14, 2024 | 7 min read
Tutorial

Orquestrando o MongoDB e BigQuery para excelência em aprendizado de máquina com as bibliotecas PyMongoArrow e BigQuery Pandas


Feb 08, 2024 | 4 min read
Tutorial

Use o MongoDB como o armazenamento de dados para seu CMS Strapi Headless


Sep 23, 2022 | 8 min read
Sumário