Suporte a Joins no MongoDB (operador $lookup)

Durante as minhas palestras sobre MongoDB, é muito comum a dúvida sobre joins — como fazer “relacionamento” em bancos não relacionais — e juntar informações de duas coleções diferentes.

É evidente que o modelo não relacional traz muita flexibilidade, para isso é necessário abrir mão de alguns recursos (como por exemplo integridade referencial) para ganhar outros recursos, como schema flexível. Até então era possível fazer algo semelhante a join via código, basta efetuar uma consulta simples e via código buscar a referência em outras coleções.

Mudanças na versão 3.2

O MongoDB 3.2 (no momento está em release candidate, mas deve ser lançado nos próximos dias) implementou o operador de agregação $lookup que nada mais é do que o left join do SQL. Esse operador realiza a busca em duas coleções diferentes e agrega os documentos que possuem o mesmo identificador. Siga o exemplo abaixo:

Uma aplicação de e-commerce possui duas coleções: produtos e pedidos.

A coleção de produtos possui o seguinte formato:

{
 "_id" : 1,
 "titulo" : "Moto X 2 Geração",
 "fabricante" : "Motorola",
 "preco" : 1099.99
}
{
 "_id" : 2,
 "titulo" : "Capinha para Moto X 2 Geração",
 "fabricante" : "Xing Ling",
 "preco" : 29.9
}
{
 "_id" : 3,
 "titulo" : "Carregador Power Turbo",
 "fabricante" : "Power Turbo",
 "preco" : 199
}

E a coleção de pedidos possui o seguinte formato:

{
"_id" : 1,
"usuario" : "Christiano Anderson",
"produto_id" : 1,
"quantidade" : 1
}
{ "_id" : 2, "usuario" : "Ivan", "produto_id" : 1, "quantidade" : 1 }
{ "_id" : 3, "usuario" : "Carol", "produto_id" : 2, "quantidade" : 3 }
{ "_id" : 4, "usuario" : "Juliana", "produto_id" : 3, "quantidade" : 1 }
{ "_id" : 5, "usuario" : "Luiz", "produto_id" : 3, "quantidade" : 1 }

Reparem que na coleção de pedidos, a chave produto_id indica qual o é o _id da coleção de produtos.

Usando o novo operador $lookup, a query de agregação ficaria da seguinte forma:

db.pedidos.aggregate([
    {
      $lookup:
        {
          from: "produtos",
          localField: "produto_id",
          foreignField: "_id",
          as: "pedidos_realizados"
        }
   }
])

A agregação é realizada na coleção de pedidos, na linha 5 (from) é necessário indicar em qual coleção a busca deve ser realizada, na linha 6 qual é a chave de busca, na linha 7 é indicada qual a chave que deverá ser encontrada (foreignField) e por final, na linha 8, um nome para o subdocumento que receberá esse cruzamento. O resultado fica assim:

{
    "_id" : 1,
    "usuario" : "Christiano Anderson",
    "produto_id" : 1,
    "quantidade" : 1,
    "pedidos_realizados" : [
        {
            "_id" : 1,
            "titulo" : "Moto X 2 Geração",
            "fabricante" : "Motorola",
            "preco" : 1099.99
        }
    ]
}
{
    "_id" : 2,
    "usuario" : "Ivan",
    "produto_id" : 1,
    "quantidade" : 1,
    "pedidos_realizados" : [
        {
            "_id" : 1,
            "titulo" : "Moto X 2 Geração",
            "fabricante" : "Motorola",
            "preco" : 1099.99
        }
    ]
}
{
    "_id" : 3,
    "usuario" : "Carol",
    "produto_id" : 2,
    "quantidade" : 3,
    "pedidos_realizados" : [
        {
            "_id" : 2,
            "titulo" : "Capinha para Moto X 2 Geração",
            "fabricante" : "Xing Ling",
            "preco" : 29.9
        }
    ]
}
{
    "_id" : 4,
    "usuario" : "Juliana",
    "produto_id" : 3,
    "quantidade" : 1,
    "pedidos_realizados" : [
        {
            "_id" : 3,
            "titulo" : "Carregador Power Turbo",
            "fabricante" : "Power Turbo",
            "preco" : 199
        }
    ]
}
{
    "_id" : 5,
    "usuario" : "Luiz",
    "produto_id" : 3,
    "quantidade" : 1,
    "pedidos_realizados" : [
        {
            "_id" : 3,
            "titulo" : "Carregador Power Turbo",
            "fabricante" : "Power Turbo",
            "preco" : 199
        }
    ]
}

No resultado o operador exibe todos os documentos da coleção incluindo um subdocumento (no exemplo acima está nomeado como “pedidos_realizados“) com o resultado do join.

É possível também combinar a agregação com outros elementos como $project, $match e outros disponíveis no aggregation framework. Basta acertar a sequência do pipeline que o aggregation realiza tarefas bem avançadas.

Nota: a versão 3.2 está em release candidate no momento em que escrevo esse post. Não use ainda em produção! A versão final deve ser lançada nos próximos dias. Atualização: a versão 3.2 já está disponível como estável, pronta para uso em produção!

Se você gostou dessa dica, curta, deixe um comentário e compartilhe nas redes sociais. 🙂


Veja também