MongoDB: Introdução aos pipelines de agregação

Última atualização: 2026-08-26

Os pipelines de agregação são a ferramenta de análise de dados mais poderosa do MongoDB — dominá-los permite que você use o MongoDB para substituir 90% dos cenários de análise em SQL.

Este curso oferece uma introdução aos conceitos básicos de pipelines de agregação, aos conceitos de estágios e à ordem de execução.

1. O que você vai aprender


2. O que é um pipeline de agregação?

Explicação do conceito: O Aggregation Pipeline é a estrutura de análise de dados mais poderosa do MongoDB. Ele divide o processamento de dados em várias etapas executadas sequencialmente; cada etapa recebe como entrada o resultado da etapa anterior e realiza filtragem, transformação, agrupamento ou cálculos estatísticos para, por fim, produzir o resultado final. Esse modelo de “pipeline” baseia-se no conceito de pipeline do UNIX, tornando a análise complexa de dados modular e fácil de depurar.

Como funciona: O pipeline de agregação é executado etapa por etapa, na ordem da matriz. A primeira etapa coleta todos os documentos da agregação, e cada etapa subsequente transforma o fluxo de documentos (filtrando, projetando, agrupando etc.), passando o resultado para a próxima etapa. O otimizador de consultas do MongoDB tenta colocar $match e $sort no início do pipeline para aproveitar os índices. O limite de memória para uma única etapa é de 100 MB; ultrapassar esse limite requer allowDiskUse: true.

Reordenação automática pelo otimizador de pipeline: O otimizador de consultas do MongoDB reordena automaticamente certas etapas para melhorar o desempenho — 1. Adiantamento de $match: Se $match aparecer após $project/$group, o otimizador tentará adiantá-lo (pois o volume de dados após $match é menor); 2. Fusão de $sort + $match: Operações consecutivas de $sort + $match podem ser fundidas em uma única operação $sort + $match que utilize um índice; 3. Avanço de $project: Se $project afetar apenas campos que não são necessários para as operações subsequentes de $match/$sort, o otimizador poderá mover $project para a frente a fim de reduzir o número de campos. No entanto, o otimizador não altera a semântica definida pelo usuário — os resultados são sempre os mesmos da ordem original.

O impacto prático do limite de memória de 100 MB: Cada etapa de agregação tem um limite de memória de 100 MB — ultrapassar esse limite resulta no erro “Limite de memória excedido”. Esse limite é uma medida de segurança incorporada ao projeto (para evitar que uma única consulta de agregação esgote a memória do servidor). Soluções alternativas: 1. defina allowDiskUse: true (o excesso é gravado em um arquivo temporário; o desempenho diminui, mas nenhum erro é gerado); 2. Use $match antes de $group para reduzir o volume de entrada; 3. Evite usar $push para coletar grandes matrizes em $group (use $sum para contagem); 4. Divida grandes agregações em várias menores (processe em lotes). allowDiskUse deve ser o último recurso — você deve primeiro otimizar a estrutura do pipeline para reduzir os requisitos de memória.

Comparação entre tubos de agregação e agregações SQL: Os tubos de agregação do MongoDB são conceitualmente equivalentes à sintaxe SELECT...GROUP BY...HAVING do SQL, mas diferem na implementação — 1. O SQL combina todas as operações em uma única instrução, enquanto o MongoDB as organiza em uma sequência encadeada de etapas; 2. O WHERE do SQL corresponde a $match, GROUP BY corresponde a $group, HAVING corresponde a $match (após $group), SELECT corresponde a $project e ORDER BY corresponde a $sort; 3. As subconsultas do SQL correspondem a pipelines aninhados ou a $lookup; 4. As funções de janela do SQL correspondem a $setWindowFields. Estratégia-chave para a migração do SQL para pipelines de agregação: dividir as cláusulas SQL em etapas independentes e organizá-las na ordem do fluxo de dados.

Roteiro de Aprendizagem do Pipeline de Agregação: O pipeline de agregação é dividido em três etapas, do nível iniciante ao avançado — 1. Iniciante (este curso): Domine as sete etapas básicas ($match/$group/$project/$sort/$limit/$skip/$count) e o modelo de execução do pipeline; 2. Intermediário (próximo curso): Domine o sistema de expressões ($cond/$switch/$dateOperators/$typeOperators/$stringOperators/$arrayOperators) e as transformações complexas; 3. Avançado (cursos subsequentes): Domine junções entre múltiplas coleções ($lookup/$unwind), pesquisa facetada ($facet/$bucket) e pesquisa de texto/geográfica ($text/$geoNear). Recomenda-se estudar na ordem, completando de 3 a 5 exercícios práticos em cada etapa para reforçar seu aprendizado.

100%
graph LR
    A[Gathering<br/>1000 Documents] --> B[$match<br/>Filter]
    B --> C[$group<br/>Group Aggregation]
    C --> D[$sort<br/>Sort]
    D --> E[$limit<br/>Restrictions]
    E --> F[Results]

    style B fill:#fff3cd
    style C fill:#d4edda
100%
sequenceDiagram
    participant DB as MongoDB
    participant S1 as $match
    participant S2 as $group
    participant S3 as $sort
    participant S4 as $limit

    DB->>S1: 1000 Document
    Note over S1: Filter: category=Electronics
    S1->>S2: 250 Document
    Note over S2: Group by brand<br/>$sum, $avg
    S2->>S3: 15 groups
    Note over S3: Sort by total descending
    S3->>S4: 15 groups
    Note over S4: Take the previous 10
    S4-->>DB: 10 Group Results

Semelhante aos pipes do UNIX:

BASH
# Find products with score > 4, sort by price descending, top 10
cat products.json | jq 'select(.rating > 4)' | jq 'sort_by(-.price)' | head -10

# MongoDB Aggregation
db.products.aggregate([
  { $match: { rating: { $gt: 4 } } },
  { $sort: { price: -1 } },
  { $limit: 10 }
]);

3. Sintaxe básica do aggregate()

Descrição do conceito: db.collection.aggregate(pipeline, options) é o método de entrada para a execução do pipeline de agregação. pipeline é uma matriz de objetos de estágio, e options controla o comportamento da execução (limites de memória, tempos limite, dicas de índice). A função de agregação retorna um cursor que suporta toArray() para recuperar todos os resultados de uma só vez ou forEach() para iterar por eles um por um.

Como funciona: Quando o MongoDB recebe um comando de agregação, o otimizador de consultas primeiro analisa a estrutura do pipeline e tenta avançar o estágio $match (mesclando-o com $sort para utilizar o índice) e, em seguida, executa cada estágio sequencialmente na ordem otimizada. Cada etapa mantém um fluxo de documentos na memória; se o limite de 100 MB for excedido, um erro é gerado (exceto para allowDiskUse: true).

Configuração de produção para opções de agregação: O parâmetro options para aggregate deve ser configurado adequadamente em um ambiente de produção — 1. allowDiskUse: true: Deve ser habilitado para agregações grandes (para evitar erros causados pelo limite de memória de 100 MB), mas priorize a otimização do pipeline para reduzir os requisitos de memória (a E/S de disco é 100 vezes mais lenta que a memória); 2. maxTimeMS: 30000: Defina um tempo limite (30 segundos) para evitar que agregações lentas derrubem toda a instância; 3. hint: {category: 1}: Force o uso de um índice específico (para corrigir manualmente o otimizador caso ele selecione o índice errado); 4. batchSize: 100: Controla o número de documentos retornados por lote (lotes menores reduzem o tempo de primeira resposta, enquanto lotes maiores reduzem as idas e voltas na rede). Agregações em ambientes de produção devem incluir maxTimeMS — agregações sem proteção contra tempo limite são bombas-relógio.

Comportamento do Otimizador do Pipeline de Agregação: O otimizador de agregação do MongoDB realiza automaticamente certas otimizações — 1. $match pushdown: Se $match aparecer após $group/$project, o otimizador tentará movê-lo para um estágio anterior (embora o sucesso não seja garantido); 2. Otimização de $sort + $limit: Operações consecutivas de $sort + $limit são otimizadas no modo Top N (mantendo uma pilha com apenas N elementos em vez de uma ordenação completa); 3. Otimizações não realizadas: o otimizador não reordenará a sequência de estágios definidos pelo usuário (por exemplo, movendo $match da terceira posição para a primeira) nem moverá $match para a frente após $group. Compreender os limites do comportamento do otimizador ajuda você a escrever pipelines mais eficientes manualmente — “Não confie no otimizador; coloque $match bem no início você mesmo.”

Duas maneiras de utilizar cursores agregados: aggregate retorna um cursor, não uma matriz — dois métodos de utilização — 1. cursor.toArray(): Recupera todos os resultados para a memória de uma só vez (adequado para conjuntos de resultados pequenos, < 1.000 linhas); o código é conciso, mas consome muita memória; 2. cursor.forEach() / for await...of: Processa os resultados um por um (adequado para conjuntos de resultados grandes ou processamento em fluxo contínuo); baixo consumo de memória, mas código um pouco mais complexo. Recomenda-se for await...of para ambientes de produção — mesmo que o conjunto de resultados atual seja pequeno, ele não causará um OutOfMemoryError quando os dados crescerem no futuro. O código que usa toArray() encontrará repentinamente um OutOfMemoryError quando o volume de dados aumentar de 100 registros para 100.000 registros.

Parâmetro Tipo Descrição
pipeline Matriz Matriz de objetos de palco; executados em ordem
options.allowDiskUse Booleano Permite gravações temporárias no disco (padrão: falso)
options.maxTimeMS Número Tempo limite (milissegundos)
options.batchSize Número Número de documentos retornados por lote
options.hint String/Objeto Forçar o uso do índice especificado
JAVASCRIPT
// === Basic Usage ===
db.products.aggregate([
  { $match: { category: 'Electronics' } },
  { $group: { _id: '$brand', total: { $sum: 1 } } }
]);

// === Return to Cursor ===
const cursor = db.products.aggregate([...]);
const results = await cursor.toArray();

// === Aggregation Options ===
db.products.aggregate(
  [{ $match: {} }],
  {
    allowDiskUse: true,    // Allow disk usage(Processing Large Datasets)
    maxTimeMS: 30000,      // 30 Timeout in seconds
    batchSize: 100,        // Batch Size
    hint: 'category_1'     // Force Index Usage
  }
);

4. Uma explicação detalhada da fase fundamental

Visão geral do conceito: O pipeline de agregação oferece mais de 30 operadores de etapa; esta seção se concentra nos sete mais básicos: $match (Filtro), $group (Agregação por grupo), $project (Projeção/Cálculo), $sort (Classificação), $limit (Limitar contagem), $skip (Ignorar) e $count (Contagem). Quando usados em combinação, esses operadores cobrem 80% dos cenários analíticos cotidianos.

Como funciona: Cada etapa possui regras específicas de transformação de entrada para saída. $match não altera a estrutura do documento; apenas filtra. $group agrupa vários documentos, gerando um documento por grupo. $project seleciona ou calcula os campos de saída. $sort/$limit/$skip controlam a ordem e o número de documentos. Princípio-chave de otimização: $match Aplique o mais cedo possível para reduzir a carga de processamento nas etapas subsequentes.

100%
graph TB
    A[Foundation Stage] --> B[$match<br/>Filter Documents<br/>Similar to find filter]
    A --> C[$group<br/>Group Aggregation<br/>$sum/$avg/$min/$max]
    A --> D[$project<br/>Field Projection<br/>Select/Calculated Fields]
    A --> E[$sort<br/>Sort<br/>1 ascending -1 descending]
    A --> F[$limit/$skip<br/>Pagination<br/>Restrictions/Skip]
    A --> G[$count<br/>Count<br/>Number of output documents]
    
    B --> H["✅ Put it at the very front<br/>Reducing Data Volume Using Indexes"]
    
    style B fill:#fff3cd
    style C fill:#d4edda
    style H fill:#d4edda

(1) $match: Filtrar documentos

Princípios da pré-otimização do $match: O $match é a única etapa no pipeline de agregação que pode utilizar índices; portanto, colocá-lo logo no início do pipeline é o princípio de otimização mais importante. Motivos: 1. O $match é executado antes do $group, utilizando diretamente os índices para filtragem a fim de reduzir o volume de dados processados nas etapas posteriores; 2. O otimizador de consultas do MongoDB tenta adiantar a execução do $match para que ocorra antes do $lookup, mas não reordena a sequência das etapas definidas pelo usuário; 3. Executar o $match após o $group significa agregar todos os dados primeiro e depois filtrá-los, o que desperdiça uma quantidade significativa de recursos computacionais.

Guia de Seleção: Agregado x Pesquisa: Quando usar aggregate em vez de find? Critérios: 1. Se você precisar de estatísticas agrupadas ($group) → deve usar aggregate; 2. Se você precisar calcular ou renomear campos ($project para calcular campos) → use aggregate; 3. Se forem necessárias transformações em várias etapas (por exemplo, filtrar, depois agrupar e, em seguida, classificar) → use aggregate; 4. Para consultas CRUD simples → find é mais eficiente. O aggregate acarreta maior sobrecarga do que o find (devido à inicialização do pipeline e à transferência de dados entre etapas), portanto, evite usá-lo em excesso para consultas simples.

Diferenças de desempenho entre $match e find: Embora a sintaxe do $match seja a mesma do find, seus contextos de execução diferem — o find é uma consulta independente que o otimizador otimiza totalmente, enquanto o $match faz parte de um pipeline, e suas capacidades de otimização são limitadas pela estrutura do pipeline. Principais diferenças: 1. find pode usar uma consulta coberta (uma consulta indexada que não lê documentos), enquanto $match sempre lê documentos dentro do pipeline; 2. $hint tem efeito direto em find, ao passo que as dicas em $match são passadas por meio de opções de agregação; 3. O cursor retornado por find suporta o controle batchSize, enquanto o cursor de agregação se comporta de maneira semelhante, mas apresenta maior sobrecarga de inicialização.

Melhores práticas para utilizar índices no $match: Condições para que o $match utilize índices — 1. O $match deve ser a primeira etapa de um pipeline (ou a primeira etapa de um subpipeline dentro de um $lookup); caso contrário, ele não poderá utilizar o índice; 2. Os campos nas condições do $match devem estar indexados (seja como índices de campo único ou como prefixos em índices compostos); 3. A combinação $match + $sort pode usar um índice composto para atender tanto à filtragem quanto à classificação; 4. Verifique com explain(): db.orders.aggregate([{$match: {status: 'paid'}}]).explain() para verificar se um IXSCAN é acionado. Se você observar COLLSCAN, isso significa que o $match não está utilizando o índice — é necessário criar um índice ou ajustar a ordem do pipeline. Em ambientes de produção, você deve verificar regularmente o log de consultas lentas para confirmar se o $match no pipeline de agregação está utilizando um índice.

JAVASCRIPT
// === $match Similar to find filter ===
db.products.aggregate([
  { $match: { category: 'Electronics', price: { $gte: 100 } } }
]);
Vantagens do $match Descrição
Filtragem na fase inicial do fluxo de trabalho Reduz o volume de dados processados nas etapas subsequentes
É possível usar índices Alto desempenho (semelhante ao find)

(2) Agregação de grupos $group

$group – Princípio de agrupamento: $group é a etapa mais crítica no pipeline de agregação — ele agrupa o fluxo de documentos pelo campo _id, realiza cálculos de acumulador de forma independente para cada grupo e gera um único resultado de agregação. O valor de _id determina a granularidade do agrupamento: uma referência de campo (‘_id: $category’) agrupa por um único campo, uma expressão de objeto (‘_id: {year, month}’) agrupa por uma combinação de campos, e o valor nulo indica que não há agrupamento — as estatísticas são calculadas para toda a coleção. Compreender a transformação “um para muitos → um para um” do $group é fundamental para dominar o pipeline de agregação.

Explicação detalhada do acumulador $group: $group oferece vários acumuladores — $sum (soma/contagem), $avg (média), $min/$max (mínimo/máximo), $first/$last (primeiro/último valor) e $push/$addToSet (coletar em um array/criar um array único). Principais diferenças: $sum: 1 conta (incrementando em 1 para cada documento), $sum: '$field' soma (acumulando valores de campo); $push mantém valores duplicados, enquanto $addToSet remove automaticamente as duplicatas; os valores de $first/$last dependem da ordem de classificação da entrada (imprevisível quando $sort não é usado). O número de documentos diminui drasticamente após o $group (N documentos → M grupos), resultando em uma carga de processamento menor nas etapas subsequentes.

Padrão de projeto do _id do $group: O _id do $group determina a granularidade do agrupamento e é a decisão de projeto mais crítica para os resultados da agregação — 1. Agrupamento por campo único (_id: '$category'): Agrega por categoria, gerando uma linha para cada categoria; 2. Agrupamento por combinação de vários campos (_id: {category: '$category', status: '$status'}): tabulação cruzada por categoria e status, gerando uma linha para cada combinação; 3. Agrupamento por data (_id: {year: {$year: '$createdAt'}, month: {$month: '$createdAt'}}): análise de tendências por ano e mês; 4. Estatísticas globais (_id: null): sem agrupamento; agrega toda a coleção. Quanto mais granular for o _id, mais grupos haverá; os resultados são mais detalhados, mas o efeito de agregação é mais fraco. Quanto mais grosseiro for o _id, menos grupos haverá; o efeito de agregação é mais forte, mas mais informações são perdidas.

Combinação de acumuladores: Vários acumuladores no $group podem ser usados simultaneamente para gerar resultados estatísticos abrangentes — 1. Calcule contagem, média, mínimo e máximo simultaneamente (as quatro estatísticas básicas que atendem às necessidades da estatística descritiva); 2. Use $push para coletar todos os valores dentro de um grupo (por exemplo, coletar todos os nomes de produtos em cada categoria), mas observe que $push pode gerar matrizes grandes (use $slice para extrair um subconjunto ou $size para contar); 3. Use $addToSet para coletar valores únicos (por exemplo, contar quantas marcas diferentes existem em cada categoria), mas $addToSet é mais lento do que outros acumuladores (pois requer verificações de duplicatas); 4. $sum + $cond é usado para realizar contagem condicional (por exemplo, contar o número de produtos em cada categoria em que o preço > 1000: $sum: {$cond: [{$gt: ['$price', 1000]}, 1, 0]}).

Armadilhas comuns e solução de problemas para $group: Existem três armadilhas comuns relacionadas a $group — 1. O campo _id não pode ser omitido: $group deve especificar o campo _id (mesmo que seja nulo); caso contrário, ocorrerá um erro; 2. Os campos são perdidos após o $group: o $group retém apenas os campos _id e o acumulador; todos os campos originais são perdidos (por exemplo, para acessar o campo name após o $group, é necessário usar $push: '$name' no acumulador ou usar $project antes do $group para preservar os campos necessários e, em seguida, usar $lookup após o $group para recuperá-los); 3. $group altera a estrutura do documento: os documentos após $group são resultados de agregação inteiramente novos, com uma estrutura completamente diferente dos documentos originais — as etapas subsequentes só podem fazer referência a _id e aos campos gerados pelo agregador. Dica para solução de problemas: execute a agregação etapa por etapa (omitindo as etapas subsequentes) e observe a estrutura de saída em cada etapa.

JAVASCRIPT
// === Group by Field ===
db.products.aggregate([
  { $group: {
      _id: '$category',         // Grouping Field
      count: { $sum: 1 },       // Count
      avgPrice: { $avg: '$price' },
      maxPrice: { $max: '$price' },
      minPrice: { $min: '$price' }
  }}
]);
// [
//   { _id: 'Electronics', count: 250, avgPrice: 599, maxPrice: 1999, minPrice: 99 },
//   { _id: 'Books', count: 200, avgPrice: 29, maxPrice: 79, minPrice: 9 },
//   ...
// ]

// === Multi-Field Grouping ===
db.orders.aggregate([
  { $group: {
      _id: { year: { $year: '$createdAt' }, month: { $month: '$createdAt' } },
      total: { $sum: '$total' },
      count: { $sum: 1 }
  }}
]);

// === Grouping the Entire Set ===
db.products.aggregate([
  { $group: {
      _id: null,                // No groups,Statistics for the Entire Set
      totalProducts: { $sum: 1 },
      avgPrice: { $avg: '$price' }
  }}
]);

(3) Projetando o campo $project

Duas funções do $project: O $project tem duas finalidades — 1. Seleção de campos (semelhante ao SELECT do SQL), em que 1 ou 0 controla se um campo é exibido; 2. Cálculo de campos (semelhante ao AS do SQL), usando expressões para criar novos campos. Observação: Na regra 1/0 para $project, _id é exibido por padrão (deve ser explicitamente definido como 0 para ocultá-lo). Para outros campos, se qualquer um for definido como 1, os demais assumem o valor padrão 0 (modo lista de permissões); se qualquer um for definido como 0, os demais assumem o valor padrão 1 (modo lista de restrições). Misturar esses modos pode levar a um comportamento inesperado.

JAVASCRIPT
// === Select Output Fields ===
db.products.aggregate([
  { $project: {
      sku: 1,
      title: 1,
      price: 1,
      discountedPrice: { $multiply: ['$price', 0.9] }  // Calculate a New Field
  }}
]);

// === Rename Field ===
db.products.aggregate([
  { $project: {
      productName: '$title',     // Rename title → productName
      price: 1,
      category: 1
  }}
]);

(4) $sort Ordenação

Limites de memória e otimização para $sort: $sort realiza a classificação na memória e possui um limite padrão de 100 MB — ultrapassar esse limite resultará em um erro (a menos que allowDiskUse: true esteja definido). Estratégias de otimização: 1. Use $match antes de $sort para reduzir o volume de dados (classificar 1.000 registros é muito mais rápido do que classificar 100.000); 2. Crie um índice no campo de classificação (como o índice já está classificado, o MongoDB pode retornar os resultados diretamente na ordem do índice, sem precisar classificar na memória); 3. Ao combinar $sort com $limit, o MongoDB mantém apenas o heap Top N (em vez de classificar todo o conjunto de dados), reduzindo o uso de memória de O(N) para O(limit); 4. Um índice composto {category: 1, price: -1} pode satisfazer tanto $match quanto $sort, permitindo uma “consulta abrangente + classificação”.

JAVASCRIPT
// === 1 ascending, -1 descending ===
db.products.aggregate([
  { $sort: { price: -1 } }
]);

// === Sorting by Multiple Fields ===
db.products.aggregate([
  { $sort: { category: 1, price: -1 } }
]);

(5) Paginação com $limit / $skip

Armadilhas de desempenho do $skip: Quanto maior o valor de $skip, pior é o desempenho — $skip(10000) exige que o MongoDB faça a varredura e descarte os primeiros 10.000 documentos; embora esses documentos não sejam retornados ao cliente, a sobrecarga da varredura e da classificação ainda existe. Otimização avançada da paginação: 1. Paginação baseada em cursor (use _id: {$gt: lastId} em vez de skip; o desempenho permanece constante); 2. Limite o número máximo de páginas (por exemplo, permita a navegação apenas até a página 50; se esse limite for excedido, solicite ao usuário que restrinja o escopo da pesquisa); 3. Modo “Top N” usando $sort + $limit (sem $skip; recupera apenas os primeiros N registros). A paginação com deslocamento só é aceitável quando o volume de dados é pequeno (< 1.000 páginas).

Detalhes da implementação da paginação baseada em cursor: A paginação baseada em cursor utiliza o valor de ordenação do último registro da página anterior como um “cursor” — 1. Primeira página: consulta normal $sort + $limit(N); 2. Páginas subsequentes: $match({createdAt: {$lt: lastCursor}, _id: {$lt: lastId}}) + $sort + $limit(N). O uso de dois campos (campo de ordenação + _id) garante a exclusividade do cursor (o campo de ordenação pode ter duplicatas, mas o _id não); 3. Transmissão para o front-end: Os valores createdAt e _id do último registro da página anterior são usados como parâmetros do cursor para a próxima página; 4. Vantagens: Independentemente do número da página, a complexidade da consulta permanece constante em O(N) (apenas N registros são examinados; sem pulos); 5. Limitações: Não é possível saltar para números de página arbitrários (apenas a “próxima página” é suportada); não é adequado para cenários que exijam navegação por número de página. Listas de produtos de comércio eletrônico utilizam classificação baseada em cursor, enquanto back-ends administrativos utilizam classificação baseada em deslocamento.

JAVASCRIPT
// === $limit Limit the number of results returned ===
db.products.aggregate([
  { $sort: { price: -1 } },
  { $limit: 10 }
]);

// === $skip Skip ===
db.products.aggregate([
  { $sort: { price: -1 } },
  { $skip: 20 },
  { $limit: 10 }  // Items 21-30
]);

(6) $count Contagem

Casos de uso para $count: $count é a etapa de agregação mais simples — recebe N documentos como entrada e gera um único documento contendo um valor de contagem. É equivalente a $group({ _id: null, total: { $sum: 1 } }), mas com uma sintaxe mais concisa. Usos comuns: 1. Contar o número de documentos filtrados ($match + $count); 2. Como um subpipeline em $facet para obter a contagem total (para paginação); 3. Combinado com $match para realizar contagem condicional (por exemplo, “Quantos produtos de eletrônicos existem?”).

JAVASCRIPT
// === Simple Counting ===
db.products.aggregate([
  { $match: { category: 'Electronics' } },
  { $count: 'totalElectronics' }
]);
// [ { totalElectronics: 250 } ]

// === equivalent to countDocuments ===
db.products.countDocuments({ category: 'Electronics' });

5. Acumulador

Explicação do conceito: Os acumuladores são funções de agregação utilizadas na fase $group que realizam cálculos em cada grupo de documentos e geram um único resultado. $sum Soma, $avg Média, $min/$max Valores extremos, $first/$last Primeiro e último valores, $push/$addToSet Acumulação de matriz. Os acumuladores são ferramentas essenciais para a análise estatística.

Como funciona: A fase $group agrupa documentos com base no campo _id e realiza cálculos de acumulação de forma independente para cada grupo. $sum: 1 conta, $sum: '$field' soma, $avg: '$field' calcula a média, $push: '$field' reúne todos os valores em uma matriz e $addToSet: '$field' reúne os valores deduplicados. _id: null indica que não há agrupamento; as estatísticas são calculadas para todo o conjunto.

100%
graph LR
    A[Grouping Field _id] --> B[Group 1]
    A --> C[Group 2]
    A --> D[Group 3]
    
    B --> E["$sum: {$sum:1}<br/>$avg: {$avg:'$price'}<br/>$push: {$push:'$name'}"]
    C --> F["$sum: {$sum:1}<br/>$avg: {$avg:'$price'}<br/>$push: {$push:'$name'}"]
    D --> G["$sum: {$sum:1}<br/>$avg: {$avg:'$price'}<br/>$push: {$push:'$name'}"]
    
    E --> H[Output one line per group<br/>Aggregated Results]
    F --> H
    G --> H
    
    style H fill:#d4edda

(1) Lista completa de acumuladores

Acumulador Significado Exemplo
$sum Soma { $sum: '$price' }
$avg Média { $avg: '$price' }
$min valor mínimo { $min: '$price' }
$max Valor máximo { $max: '$price' }
$first Primeiro { $first: '$name' }
$last Último { $last: '$name' }
$push Acumulação de matriz { $push: '$name' }
$addToSet Remoção de duplicatas e acumulação de valores em um array { $addToSet: '$name' }
$count Contagem (para o nível superior) { $count: 'total' }

▶ Exemplo 1: O acumulador em ação

Uso combinado de acumuladores: Em cenários empresariais reais, vários acumuladores são normalmente combinados em um único $group — por exemplo, as estatísticas de categorias de comércio eletrônico exigem o cálculo simultâneo de contagem ($sum: 1), receita total ($sum: '$price'), preço médio ($avg: '$price') e preço máximo ($max: '$price'). Todas as quatro métricas são calculadas em uma única operação $group, eliminando a necessidade de múltiplas consultas. O estágio $group é a fase de “compressão de dados” — ele recebe N documentos como entrada e gera M grupos (M << N), reduzindo drasticamente a carga de processamento nos estágios subsequentes.

Escolhendo entre $push e $addToSet: $push mantém valores duplicados (por exemplo, ['Eletrônicos', 'Eletrônicos', 'Livros']), enquanto $addToSet remove automaticamente as duplicatas (por exemplo, ['Eletrônicos', 'Livros']). Critérios de seleção: 1. É necessária uma lista completa de elementos (incluindo duplicatas) → $push (por exemplo, “Lista de itens de todos os pedidos”); 2. São necessários apenas elementos únicos → $addToSet (por exemplo, “Categorias de itens comprados”). Observação: $push pode resultar em matrizes muito grandes (por exemplo, categorias populares com 10.000 nomes de produtos); use $slice para limitar o comprimento da matriz ou $unwind + $group para reestruturar a matriz.

JAVASCRIPT
// === Count the number of items in each category and summarize the prices ===
db.products.aggregate([
  {
    $group: {
      _id: '$category',
      count: { $sum: 1 },
      totalStock: { $sum: '$stock' },
      avgPrice: { $avg: '$price' },
      maxPrice: { $max: '$price' },
      minPrice: { $min: '$price' },
      topProducts: { $push: '$title' }  // All Product Names
    }
  }
]);

// === $addToSet Cumulative deduplication ===
db.users.aggregate([
  {
    $group: {
      _id: '$city',
      uniqueRoles: { $addToSet: '$role' }  // Set of Roles for Each City
    }
  }
]);

6. Otimização da ordem de execução

Explicação do conceito: O desempenho dos pipelines de agregação depende fortemente da ordem das etapas. Princípios fundamentais de otimização: $match deve ser colocado o mais cedo possível; $project deve reduzir o número de campos após $group; e $sort e $match devem estar adjacentes para aproveitar os índices. O otimizador de consultas do MongoDB realiza automaticamente algumas otimizações (como colocar $match antes de $lookup), mas não altera a ordem das fases definida pelo usuário.

Como funciona: O otimizador tenta dois tipos de otimização: (1) Avançar $match e mesclá-lo com $sort em uma varredura de índice; (2) Empurrar $match para baixo no pipeline de $lookup. No entanto, o otimizador não reordena a sequência das fases definidas pelo usuário — se $match vier depois de $group, ele não poderá ser movido automaticamente para a frente.

100%
graph LR
    subgraph "✅ Optimization Order"
        A1[$match<br/>Filter first] --> B1[$sort<br/>Sort] --> C1[$limit<br/>Restrictions]
    end
    
    subgraph "⚠️ Anti-pattern"
        A2[$sort<br/>Full Sort] --> B2[$limit<br/>Restrictions] --> C2[$match<br/>Post-filtration]
    end
    
    style A1 fill:#d4edda
    style C2 fill:#f8d7da
Estratégia de otimização Efeito Condições de aplicação
$match Colocar no início Reduzir a quantidade de dados subsequentes Filtrar campos com índices
$match + $sort são adjacentes Combinados em uma varredura de índice O campo de classificação possui um índice composto
$project Otimizar campos Reduzir o uso de memória Usar após $group
hint() Índice forçado Como evitar varreduras completas da tabela Quando o otimizador seleciona o índice errado
JAVASCRIPT
// ✅ Optimization:$match Put it at the very front
db.products.aggregate([
  { $match: { category: 'Electronics' } },  // Filter first(Using an Index)
  { $sort: { price: -1 } },
  { $limit: 10 }
]);

// ⚠️ Counterexample:$match Put it in the back
db.products.aggregate([
  { $sort: { price: -1 } },
  { $limit: 10 },
  { $match: { category: 'Electronics' } }   // Filter only after processing,Waste of resources
]);

7. Treinamento prático abrangente

Princípios de execução do pipeline e gerenciamento de memória: Cada estágio do pipeline de agregação mantém um fluxo de documentos na memória, com um limite de memória de 100 MB por estágio (um erro é relatado se esse limite for excedido, a menos que allowDiskUse: true esteja definido). Isso significa que: 1. O número de chaves de grupo na etapa $group não deve ser excessivo (milhões de chaves de grupo causarão estouro de memória); 2. O acumulador $push coleta todos os valores em um array, o que pode facilmente exceder o limite ao lidar com grandes volumes de dados; 3. $sort realiza a classificação na memória; para conjuntos de dados grandes, é necessário um índice para evitar estouro. Estratégias de otimização: pré-processe os dados antes do $match para reduzir o volume, use $project para simplificar os campos e substitua a classificação completa por $sort + $limit.

(1) Estatísticas de vendas do comércio eletrônico

JAVASCRIPT
// === Monthly Sales Statistics ===
db.orders.aggregate([
  { $match: { status: 'paid', createdAt: { $gte: new Date('2026-01-01') } } },
  {
    $group: {
      _id: {
        year: { $year: '$createdAt' },
        month: { $month: '$createdAt' }
      },
      totalSales: { $sum: '$total' },
      orderCount: { $sum: 1 },
      avgOrderValue: { $avg: '$total' }
    }
  },
  { $sort: { '_id.year': 1, '_id.month': 1 } }
]);

(2) Análise das categorias de produtos

JAVASCRIPT
// === Product Categories + Price Range Analysis ===
db.products.aggregate([
  {
    $bucket: {
      groupBy: '$price',
      boundaries: [0, 100, 500, 1000, 5000, 10000],
      default: 'Other',
      output: {
        count: { $sum: 1 },
        products: { $push: '$title' }
      }
    }
  }
]);

▶ Exemplo: E-commerce Sales Analysis Pipeline (Difficulty ⭐⭐)

JAVASCRIPT
// Scene: ShopHub monthly sales dashboard - total revenue, orders, and top products
db.orders.insertMany([
  { orderNumber: 'ORD-001', userId: 'user_001', items: [{ sku: 'PHONE-001', qty: 2, price: 599 }], total: 1198, status: 'paid', createdAt: new Date('2026-07-01') },
  { orderNumber: 'ORD-002', userId: 'user_002', items: [{ sku: 'LAPTOP-001', qty: 1, price: 1299 }], total: 1299, status: 'paid', createdAt: new Date('2026-07-05') },
  { orderNumber: 'ORD-003', userId: 'user_001', items: [{ sku: 'PHONE-001', qty: 1, price: 599 }, { sku: 'BOOK-001', qty: 2, price: 29 }], total: 657, status: 'paid', createdAt: new Date('2026-07-10') }
]);

// Complete pipeline: filter → group → sort → limit
const salesReport = db.orders.aggregate([
  // Stage 1: Filter only paid orders in July 2026
  { $match: {
    status: 'paid',
    createdAt: {
      $gte: new Date('2026-07-01'),
      $lt: new Date('2026-08-01')
    }
  }},
  // Stage 2: Group by user and calculate totals
  { $group: {
    _id: '$userId',
    totalSpent: { $sum: '$total' },
    orderCount: { $sum: 1 },
    avgOrderValue: { $avg: '$total' },
    orders: { $push: '$orderNumber' }
  }},
  // Stage 3: Calculate customer tier based on spending
  { $addFields: {
    tier: {
      $switch: {
        branches: [
          { case: { $gte: ['$totalSpent', 2000] }, then: 'Platinum' },
          { case: { $gte: ['$totalSpent', 1000] }, then: 'Gold' },
          { case: { $gte: ['$totalSpent', 500] }, then: 'Silver' }
        ],
        default: 'Bronze'
      }
    }
  }},
  // Stage 4: Sort by total spent descending
  { $sort: { totalSpent: -1 } },
  // Stage 5: Limit to top 10 customers
  { $limit: 10 }
]);

// Output results
salesReport.forEach(r => {
  console.log(`User: ${r._id}, Spent: $${r.totalSpent}, Orders: ${r.orderCount}, Tier: ${r.tier}`);
});

Saída:

TEXT 📖 Somente leitura
User: user_001, Spent: $1855, Orders: 2, Tier: Gold
User: user_002, Spent: $1299, Orders: 1, Tier: Silver

▶ Exemplo 2: Pipeline de análise de categorias de comércio eletrônico do ShopHub

JAVASCRIPT
// Scene:ShopHub The operations team needs to analyze product data from multiple perspectives.
// Preparing the Data
db.products.insertMany([
  { sku: 'PHONE-001', title: 'Smartphone X', category: 'Electronics', brand: 'TechCorp', price: 599, stock: 120, rating: 4.5 },
  { sku: 'PHONE-002', title: 'Smartphone Y', category: 'Electronics', brand: 'DataFlow', price: 399, stock: 80, rating: 4.0 },
  { sku: 'LAPTOP-001', title: 'Laptop Pro', category: 'Electronics', brand: 'TechCorp', price: 1299, stock: 50, rating: 4.8 },
  { sku: 'BOOK-001', title: 'MongoDB Guide', category: 'Books', brand: 'AppVenture', price: 29, stock: 500, rating: 4.2 },
  { sku: 'BOOK-002', title: 'Node.js Mastery', category: 'Books', brand: 'AppVenture', price: 39, stock: 300, rating: 4.6 }
]);

// 1. Statistics by Category:Number of Products、Average Price、Highest Price、Brand List
db.products.aggregate([
  {
    $group: {
      _id: '$category',
      count: { $sum: 1 },
      avgPrice: { $avg: '$price' },
      maxPrice: { $max: '$price' },
      minPrice: { $min: '$price' },
      totalStock: { $sum: '$stock' },
      brands: { $addToSet: '$brand' }
    }
  },
  { $sort: { count: -1 } }
]);

// 2. Price Range Distribution($bucket)
db.products.aggregate([
  {
    $bucket: {
      groupBy: '$price',
      boundaries: [0, 100, 500, 1000, 5000],
      default: '5000+',
      output: { count: { $sum: 1 }, titles: { $push: '$title' } }
    }
  }
]);

// 3. Highly Rated Products(rating >= 4.5)
db.products.aggregate([
  { $match: { rating: { $gte: 4.5 } } },
  { $project: { sku: 1, title: 1, price: 1, rating: 1, _id: 0 } },
  { $sort: { rating: -1, price: 1 } }
]);

Resultado: 1) Estatísticas categorizadas (Eletrônicos: 3 itens com preço médio de 765,67; Livros: 2 itens com preço médio de 34); 2) Faixas de preço (0–100: 2; 500–1.000: 1; 1.000–5.000: 1); 3) Lista de itens com avaliações positivas.

Padrão de Design do _id do $group: O _id do $group determina a granularidade do agrupamento — 1. Agrupamento por campo único: _id: '$category' (agrupado por categoria); 2. Agrupamento por múltiplos campos: _id: {category: '$category', brand: '$brand'} (agrupamento cruzado por categoria e marca, gerando uma chave composta); 3. Agrupamento baseado em data: _id: {year: {$year: '$createdAt'}, month: {$month: '$createdAt'}} (agrupado por ano e mês; comumente usado para relatórios mensais); 4. Agrupamento por expressão: _id: {$cond: [{$gte: ['$price', 100]}, 'premium', 'budget']} (agrupamento por condições, categorização dinâmica); 5. Agrupamento nulo: _id: null (sem agrupamento; usado para calcular estatísticas globais, como o número total de pedidos e o total de vendas). O design do _id determina a dimensão estatística — um _id com vários campos produzirá um produto cartesiano (uma linha para cada combinação), e quanto mais campos houver, mais linhas de saída serão geradas.

Combinação de acumuladores: Os acumuladores em $group podem ser combinados — 1. $sum + $avg: Calcula o número de itens e o preço médio para cada categoria (count: {$sum: 1}, avgPrice: {$avg: '$price'}); 2. $min + $max: Calcule a faixa de preço (minPrice: {$min: '$price'}, maxPrice: {$max: '$price'}); 3. $push + $addToSet: Colete valores dentro de um grupo (products: {$push: '$title'} para manter duplicatas, brands: {$addToSet: '$brand'} para remover duplicatas); 4. $first + $last: Recupere o primeiro e o último valores dentro de um grupo (usado em conjunto com $sort para recuperar os registros mais antigos e mais recentes). Observação: $push e $addToSet podem gerar matrizes grandes (coletando todos os valores dentro de cada grupo); para conjuntos de dados grandes, use-os em conjunto com $limit ou $slice.

❓ Perguntas Frequentes

P: Em que medida o desempenho dos pipelines agregados é inferior ao do find? R: Os pipelines agregados são mais potentes, mas apresentam uma sobrecarga maior. Use o find para consultas simples e o aggregate para análises complexas.

P: Como se usa índices com o pipeline Aggregate? R: É possível usar índices com $match, $sort e $group. A opção hint() força o uso de um índice específico.

P: Qual é o limite de memória para o pipeline de agregação? R: O limite de memória para uma única etapa é de 100 MB. Se esse limite for excedido, os dados deverão ser gravados temporariamente no disco usando allowDiskUse: true.

P: Como faço para percorrer o cursor retornado por aggregate? R: Use cursor.hasNext() e cursor.next(), ou percorra diretamente em mongosh.


📖 Resumo


📝 Exercícios

  1. Problema básico (⭐): Use $group para contar o número de itens em cada categoria e calcular o preço médio.
  2. Questão básica (⭐): Use $match + $sort + $limit para consultar os 10 principais produtos da categoria Eletrônicos, ordenados por preço.
  3. Problema avançado (⭐⭐): Calcule o número total de pedidos e o total de vendas por mês ($ano/$mês + $grupo).
  4. Problema avançado (⭐⭐): Use $project para calcular o preço com desconto (preço × 0,9).
  5. Desafio (⭐⭐⭐): Concluir o painel de vendas (análise de vendas por mês, categoria e cliente).
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%