Operadores do Pipeline de Agregação do MongoDB: $group, $unwind, $cond e Outros

  1. União de coleções por período

Para consultar documentos espalhados por várias coleções — por exemplo, uma coleção por trimestre — utilize $unionWith combinado com $set para identificar a origem de cada grupo.

db.vendas_2023_q1.aggregate([
  { $set: { origem: "2023_q1" } },
  {
    $unionWith: {
      coll: "vendas_2023_q2",
      pipeline: [{ $set: { origem: "2023_q2" } }]
    }
  }
])
  1. Agrupamento por múltiplos campos

No estágio $group, atribua aliases para cada chave do documento _id. Quando precisar referenciar o resultado em um novo $group, use a notação $_id.alias.

db.vendas_2023.aggregate([
  { $set: { origem: "2023" } },
  {
    $unionWith: {
      coll: "vendas_2022",
      pipeline: [{ $set: { origem: "2022" } }]
    }
  },
  {
    $match: {
      codigo_loja: "L00002",
      categoria: { $in: [1, 2, 3] },
      data_registro: { $gte: "2023-01-01", $lte: "2023-10-13" }
    }
  },
  {
    $project: {
      data_registro: 1,
      ano: { $substr: ["$data_registro", 0, 4] },
      quantidade: 1,
      previsto: 1,
      duracao: 1
    }
  },
  {
    $group: {
      _id: { data_registro: "$data_registro", ano: "$ano" },
      total_quantidade: { $sum: "$quantidade" },
      total_previsto: { $sum: "$previsto" },
      media_duracao: { $avg: "$duracao" }
    }
  },
  {
    $group: {
      _id: "$_id.ano",
      total_quantidade: { $sum: "$total_quantidade" },
      total_previsto: { $sum: "$total_previsto" },
      media_duracao: { $avg: "$media_duracao" }
    }
  },
  {
    $project: {
      total_quantidade: 1,
      media_duracao: { $round: [{ $divide: ["$media_duracao", 60] }, 2] },
      taxa_conversao: {
        $round: [
          { $divide: ["$total_quantidade", { $add: ["$total_quantidade", "$total_previsto"] }] },
          4
        ]
      }
    }
  },
  { $sort: { _id: 1 } }
])
  1. Múltiplos estágios $group no mesmo pipeline

É comum encadear vários $group quando se precisa de granularidades diferentes. $match e $project podem aparecer em qualquer posição do pipeline.

db.vendas_2023.aggregate([
  { $set: { origem: "2023" } },
  {
    $unionWith: {
      coll: "vendas_2022",
      pipeline: [{ $set: { origem: "2022" } }]
    }
  },
  {
    $match: {
      codigo_loja: "L001",
      categoria: { $in: [2, 3, 4] },
      data_registro: { $gte: "2023-01-01", $lte: "2023-10-13" }
    }
  },
  {
    $project: {
      data_registro: 1,
      semana_iso: {
        $isoWeek: {
          date: { $dateFromString: { dateString: "$data_registro" } },
          timezone: "America/Sao_Paulo"
        }
      },
      quantidade: 1,
      custo: 1
    }
  },
  {
    $group: {
      _id: { data_registro: "$data_registro", semana_iso: "$semana_iso" },
      total_dia: { $sum: "$quantidade" }
    }
  },
  {
    $group: {
      _id: "$_id.semana_iso",
      total_semana: { $sum: "$total_dia" },
      media_diaria: { $avg: "$total_dia" }
    }
  },
  {
    $project: {
      _id: 1,
      total_semana: 1,
      media_diaria: { $round: ["$media_diaria", 0] }
    }
  },
  { $sort: { _id: 1 } }
])
  1. Desmembramento de arrays com $unwind

Use $unwind para transformar cada elemento de um array em um documento separado. Os campos internos são acessados com $nomeArray.campo.

db.vendas_2023.aggregate([
  { $set: { origem: "2023" } },
  {
    $unionWith: {
      coll: "vendas_2022",
      pipeline: [{ $set: { origem: "2022" } }]
    }
  },
  {
    $match: {
      codigo_produto: "P001",
      data_registro: { $gte: "2022-12-02", $lte: "2023-10-08" }
    }
  },
  { $unwind: "$detalhes_estadia" },
  {
    $group: {
      _id: "$detalhes_estadia.faixa_tempo",
      total_ocorrencias: { $sum: "$detalhes_estadia.ocorrencias" }
    }
  },
  { $project: { _id: 1, total_ocorrencias: 1 } },
  { $sort: { _id: 1 } }
])
  1. Acesso a propriedades de objetos

Para ler um atributo de um objeto, utilize a notação de ponto: $nomeObjeto.atributo.

db.vendas_2023.aggregate([
  {
    $match: {
      codigo_produto: "P001",
      data_registro: { $gte: "2023-01-01", $lte: "2023-03-13" }
    }
  },
  {
    $group: {
      _id: "$data_registro",
      um: { $sum: "$classificacao.um" },
      dois: { $sum: "$classificacao.dois" },
      tres: { $sum: "$classificacao.tres" },
      quatro_ou_mais: { $sum: "$classificacao.quatro_ou_mais" }
    }
  },
  { $sort: { _id: 1 } }
])

Ordenação por múltiplos campos:

{ $sort: { codigo_produto: 1, mes: 1 } }
  1. Acumuladores após $group

  • $max: maior valor
  • $min: menor valor
  • $sum: soma
  • $avg: média
  • $first: primeiro documento do grupo
  • $last: último documento do grupo
// Maior valor
{ $group: { _id: null, ultima_atualizacao: { $max: "$data_atualizacao" } } }

// Primeiro documento de cada dia
db.vendas.aggregate([
  {
    $match: {
      codigo_cidade: "320100",
      data_hora: {
        $gte: "2023-05-01T00:00:00",
        $lte: "2023-07-13T23:59:59"
      }
    }
  },
  { $sort: { data_hora: -1 } },
  {
    $group: {
      _id: { dia: { $substr: ["$data_hora", 0, 10] } },
      valor: { $first: "$valor" }
    }
  },
  {
    $project: {
      _id: "$_id.dia",
      valor: 1
    }
  },
  { $sort: { _id: 1 } }
])

$sort pode ser utilizado várias vezes e em qualquer ponto do pipeline.

  1. Operadores lógicos: $not, $and, $or e $nor

  • $not: negação
  • $and: conjunção
  • $or: disjunção
  • $nor: nenhuma das condições é verdadeira
// $not
db.usuarios.find({ idade: { $ne: 18 } })
db.usuarios.find({ idade: { $not: { $eq: 18 } } })

// $or
{
  $or: [
    { origem: { $in: ["2"] } },
    { destino: { $in: ["2"] } }
  ]
}

// $nor
{
  $nor: [
    { origem: { $in: ["18", "19", "20", "21"] } },
    { destino: { $in: ["18", "19", "20", "21"] } }
  ]
}

// Combinação em $match
{
  $match: {
    data: { $gte: "2023-08-05", $lte: "2023-09-28" },
    loja: "L00001",
    $or: [
      { origem: { $in: ["2"] } },
      { destino: { $in: ["2"] } }
    ],
    $nor: [
      { origem: { $in: ["18", "19", "20", "21"] } },
      { destino: { $in: ["18", "19", "20", "21"] } }
    ],
    sexo: { $in: [0, 1] }
  }
}
  1. Condicional com $cond

Sintaxe:

{ $cond: { if: <expressao-booleana>, then: <caso-positivo>, else: <caso-negativo> } }

Se a expressão for verdadeira, retorna then; caso contrário, retorna else.

db.produtos.aggregate([
  {
    $project: {
      item: 1,
      desconto: {
        $cond: {
          if: { $gte: ["$estoque", 250] },
          then: 30,
          else: 20
        }
      }
    }
  }
])

// Resultado:
// { _id: 1, item: "abc1", desconto: 30 }
// { _id: 2, item: "abc2", desconto: 20 }
// { _id: 3, item: "xyz1", desconto: 30 }
  1. Operadores aritméticos

  • $add: adição
  • $subtract: subtração
  • $multiply: multiplicação
  • $divide: divisão
  • $mod: módulo (resto)
taxa_conversao: {
  $round: [
    { $divide: ["$quantidade", { $add: ["$quantidade", "$previsao"] }] },
    4
  ]
}

indice: {
  $round: [
    { $divide: ["$total_quantidade", { $multiply: [56, 5] }] },
    2
  ]
}
  1. $strLenCP

Retorna a quantidade de caracteres Unicode (UTF-8) de uma string, contando cada caractere como uma unidade.

{ $strLenCP: "Hello World!" }  // 12
{ $strLenCP: "" }                // 0
{ $strLenCP: "寿司" }            // 2
  1. $size para arrays

db.colecao.find({ tags: { $size: 2 } })
  1. Atualização de campos

Atualizando um elemanto de array com base em uma condição:

db.colecao.update(
  {
    codigo: "P00001",
    tipo: { $in: [0] },
    data_registro: { $gte: "2023-10-07" },
    "itens.categoria": 6
  },
  { $set: { "itens.$.quantidade": 33 } }
)

Atualizando um atributo de objeto:

db.colecao.update(
  {
    codigo: "P00001",
    tipo: { $in: [0] },
    data_registro: { $gte: "2023-10-07" }
  },
  { $set: { "classificacao.um": 121 } }
)
  1. Expressões regulares com $regex

// Começa com P00001
db.colecao.aggregate([
  { $match: { codigo: { $regex: /^P00001/ } } }
])

// Termina com S00401
db.colecao.aggregate([
  { $match: { codigo: { $regex: /S00401$/ } } }
])

// Ignorando maiúsculas/minúsculas
db.colecao.aggregate([
  { $match: { codigo: { $regex: /^P00001/i } } }
])
  1. Contagem de valores distintos por grupo

Para contar quantos valores distintos de um campo existem em cada grupo, utilize $addToSet para criar um conjunto sem repetições e depois $size para obter a cardinalidade.

db.vendas_2024_3.aggregate([
  { $unwind: "$transacoes" },
  {
    $project: {
      origem: "$transacoes.origem",
      local: "$transacoes.local_id",
      destino: "$transacoes.destino",
      cliente: "$cliente_id",
      data: "$data"
    }
  },
  {
    $match: {
      data: { $gte: "2024-03-14", $lte: "2024-04-05" },
      destino: { $in: ["unkn", "101", "102", "103", "104", "105"] },
      local: { $in: ["unkn", "101", "102", "103", "104", "105"] },
      origem: "101"
    }
  },
  {
    $group: {
      _id: { origem: "$origem", local: "$local", destino: "$destino" },
      clientes_unicos: { $addToSet: "$cliente" }
    }
  },
  {
    $project: {
      _id: 0,
      origem: "$_id.origem",
      local: "$_id.local",
      destino: "$_id.destino",
      total_distintos: { $size: "$clientes_unicos" }
    }
  }
])

A abordagem com $addToSet remove duplicidades antes da contagem, diferente de { $sum: 1 }, que contaria linhas repetidas.

Tags: mongodb Aggregation Pipeline $group $unwind $cond

Publicado em 7-14 17:23