Dia 08 de 15

Framework de Agregação — Parte 2

Olá, dev! Seja bem-vindo ao Dia 08 do seu plano de estudos para a certificação MongoDB Associate Developer. Hoje aprofundaremos nas operações mais avançadas da pipeline de agregação: a manipulação de estruturas embutidas com $unwind, relacionamentos e junções entre coleções com $lookup, e agregações paralelas com $facet.

1. Desconstruindo Arrays com `$unwind`

O estágio $unwind desconstrói um campo do tipo array em um documento de entrada, gerando um documento individual para cada elemento contido no array.

// Sintaxe Simples
{ $unwind: "$itens" }

// Sintaxe Avançada com Opções
{
  $unwind: {
    path: "$itens",
    includeArrayIndex: "posicaoItem", // Cria um campo com o índice original (0, 1, 2...)
    preserveNullAndEmptyArrays: true  // Mantém o documento mesmo se o array for nulo/vazio
  }
}

2. Junções de Coleções com `$lookup`

O MongoDB realiza junções do tipo Left Outer Join entre duas coleções no mesmo banco de dados através do estágio $lookup.

A. Sintaxe de Correspondência de Igualdade (Equality Match)

db.pedidos.aggregate([
  {
    $lookup: {
      from: "clientes",          // Coleção de destino
      localField: "clienteId",   // Campo na coleção atual (pedidos)
      foreignField: "_id",       // Campo na coleção de destino (clientes)
      as: "dadosCliente"         // Campo que receberá o array de correspondências
    }
  }
])

B. Subconsultas e Junções Complexas (`let` e `pipeline`)

Quando a junção exige condições além da igualdade simples (ex: filtros adicionais, ordenação ou limitação no lado estrangeiro), utiliza-se a sintaxe estendida com variáveis let e sub-pipeline:

db.clientes.aggregate([
  {
    $lookup: {
      from: "pedidos",
      let: { id_cliente: "$_id" }, // Define variável local com o cifrão simples
      pipeline: [
        {
          $match: {
            $expr: {
              $and: [
                { $eq: ["$clienteId", "$$id_cliente"] }, // Variável do 'let' usa cifrão duplo $$
                { $eq: ["$status", "ENTREGUE"] }
              ]
            }
          }
        },
        { $sort: { dataPedido: -1 } },
        { $limit: 3 }
      ],
      as: "ultimosPedidosEntregues"
    }
  }
])

3. Agregações Multi-Faceta com `$facet`

O estágio $facet permite executar múltiplas pipelines de agregação independentes e paralelas sobre o mesmo conjunto de dados de entrada em uma única etapa.

É a solução ideal para construir dashboards ou APIs de busca que precisam retornar resultados paginados e contadores por categoria em uma única requisição.

db.produtos.aggregate([
  { $match: { ativo: true } },
  {
    $facet: {
      "resumoCategorias": [
        { $group: { _id: "$categoria", total: { $sum: 1 } } }
      ],
      "produtosMaisBaratos": [
        { $sort: { preco: 1 } },
        { $limit: 5 },
        { $project: { nome: 1, preco: 1 } }
      ]
    }
  }
])

4. Agrupamento por Faixas com `$bucket`

Categoriza documentos em grupos baseados em limites contínuos (boundaries):

db.produtos.aggregate([
  {
    $bucket: {
      groupBy: "$preco",
      boundaries: [0, 50, 150, 500], // Faixas: [0-49.99], [50-149.99], [150-499.99]
      default: "Acima de 500",      // Categoria para valores fora das fronteiras
      output: {
        quantidade: { $sum: 1 },
        produtos: { $push: "$nome" }
      }
    }
  }
])

5. Questões no Estilo Exame de Certificação

Questão 1: Considere um documento na coleção usuarios com o campo telefones: [] (array vazio). Se executarmos a pipeline db.usuarios.aggregate([ { $unwind: "$telefones" } ]), o que acontecerá com esse documento?

A) Ele será retornado com o campo telefones definido como null.
B) Ele será descartado e não aparecerá no resultado da agregação.
C) O MongoDB lançará um erro de sintaxe BSON.
D) Ele será mantido intacto no resultado.

Resposta Correta: B. Por padrão, o $unwind descarta documentos cujo array é vazio ou nulo. Para mantê-lo, é necessário passar a opção preserveNullAndEmptyArrays: true.

Questão 2: Ao utilizar a sintaxe avançada de $lookup com o parâmetro let, como deve ser referenciada uma variável definida no let dentro do bloco $expr da sub-pipeline?

A) Utilizando aspas simples sem cifrão (ex: 'id_cliente').
B) Utilizando um único cifrão (ex: "$id_cliente").
C) Utilizando dois cifrões (ex: "$$id_cliente").
D) Utilizando a notação de ponto (ex: "vars.id_cliente").

Resposta Correta: C. Na sub-pipeline do $lookup, campos do documento estrangeiro usam "$campo" e variáveis definidas no let usam obrigatoriamente cifrão duplo "$$variavel".