Domine o $lookup no MongoDB: Junte dados de múltiplas coleções!

Foto do autor

By luis

O MongoDB, um sistema de gerenciamento de banco de dados NoSQL amplamente adotado, organiza informações em coleções. Estas, por sua vez, são compostas por documentos, que contêm os dados reais em formato JSON. Para ilustrar, documentos equivalem a linhas em um banco de dados SQL relacional, enquanto coleções são análogas a tabelas.

Uma funcionalidade essencial em qualquer banco de dados é a capacidade de realizar consultas nos dados armazenados. Tal capacidade permite a recuperação de informações específicas, análise aprofundada, geração de relatórios e integração de dados em diferentes sistemas.

Para efetuar consultas eficientes, é crucial ter a possibilidade de combinar dados provenientes de várias tabelas, em bancos de dados SQL, ou de diversas coleções, em bancos de dados NoSQL, em um único conjunto de resultados.

No MongoDB, a operação $lookup permite aos usuários combinar informações de duas coleções distintas durante a execução de uma consulta, realizando uma operação similar a uma junção externa esquerda em um banco de dados SQL.

Uso e Objetivo do $lookup

Uma função primária dos bancos de dados é a transformação de dados brutos em informações úteis e relevantes.

Por exemplo, um gerente de restaurante pode querer analisar dados para determinar o faturamento diário, quais pratos são mais populares nos fins de semana ou até mesmo quantas xícaras de café são vendidas por hora.

Para tais análises, consultas simples ao banco de dados não são suficientes. É necessário executar consultas avançadas sobre os dados armazenados. Para atender a essas necessidades, o MongoDB oferece um recurso chamado pipeline de agregação.

Um pipeline de agregação é um mecanismo que compreende operações compostas, denominadas estágios, utilizados para manipular dados e gerar um resultado agregado final. Exemplos de estágios em um pipeline de agregação incluem $sort, $match, $group, $merge, $count e $lookup, entre outros.

Esses estágios podem ser aplicados em qualquer sequência dentro de um pipeline de agregação. Cada estágio executa operações específicas nos dados que são processados pelo pipeline.

$lookup, portanto, é um estágio no pipeline de agregação do MongoDB. Ele é empregado para realizar uma junção externa esquerda entre duas coleções. Uma junção externa esquerda combina todos os documentos da coleção à esquerda com os documentos correspondentes da coleção à direita.

Considere as duas coleções a seguir, apresentadas em formato tabular para facilitar a compreensão:

Coleção pedidos:

order_id customer_id order_date total_amount
1 100 2022-05-01 50.00
2 101 2022-05-02 75.00
3 102 2022-05-03 100.00

Coleção clientes:

customer_num customer_name customer_email customer_phone
100 John [email protected] [email protected]

Ao executar uma junção externa esquerda nessas coleções, utilizando o campo customer_id da coleção pedidos (considerada a coleção da esquerda) com o campo customer_num da coleção clientes (considerada a coleção da direita), o resultado conterá todos os documentos da coleção pedidos e os documentos da coleção clientes cujos valores de customer_num correspondam aos valores de customer_id presentes na coleção pedidos.

O resultado final da operação de junção externa esquerda, apresentado em formato tabular, seria semelhante a:

order_id customer_id order_date total_amount customer_num customer_name customer_email customer_phone
1 100 2022-05-01 50.00 100 John [email protected] [email protected]
2 101 2022-05-02 75.00 null null null null
3 102 2022-05-03 100.00 null null null null

Note que, para o cliente com customer_id 101 na coleção pedidos, que não possuía um valor correspondente de customer_num na coleção clientes, os valores correspondentes da tabela clientes foram preenchidos com null.

A operação $lookup realiza comparações de igualdade estrita entre os campos e recupera todo o documento correspondente, e não apenas os campos que correspondem.

Sintaxe do $lookup

A sintaxe para o $lookup é a seguinte:

{
   $lookup:
     {
       from: <coleção para junção>,
       localField: <campo dos documentos de entrada>,
       foreignField: <campo dos documentos da coleção "from">,
       as: <campo da matriz de saída>
     }
}

O $lookup possui quatro parâmetros:

  • from – indica a coleção da qual se deseja pesquisar documentos. No exemplo anterior com pedidos e clientes, colocaríamos clientes como o valor do parâmetro from.
  • localField – especifica o campo na coleção principal ou de trabalho que será usado para comparar com os campos da coleção from (clientes no nosso caso). No exemplo acima, localField seria customer_id, encontrado na coleção pedidos.
  • foreignField – é o campo da coleção especificada em from que será comparado com localField. No nosso exemplo, seria customer_num, da coleção clientes.
  • as – é o nome do novo campo que será adicionado ao documento, contendo a matriz de documentos resultantes das correspondências entre localField e foreignField. Se não houver correspondências, este campo conterá uma matriz vazia.

Considerando as duas coleções anteriores, o código abaixo seria usado para executar uma operação $lookup com pedidos como a coleção principal ou de trabalho:

{
    $lookup: {
      from: "clientes",
      localField: "customer_id",
      foreignField: "customer_num",
      as: "customer_info"
 }

O valor do campo as pode ser qualquer string. No entanto, se o nome escolhido já existir no documento de trabalho, o campo existente será substituído pelo novo.

Unindo Dados de Múltiplas Coleções

O $lookup do MongoDB é um estágio útil em um pipeline de agregação no MongoDB. Embora não seja obrigatório que um pipeline de agregação tenha um estágio $lookup, este estágio é crucial ao executar consultas complexas que exigem a combinação de dados de várias coleções.

O estágio $lookup executa uma junção externa esquerda entre duas coleções, criando um novo campo ou substituindo os valores de um campo existente por uma matriz contendo documentos da outra coleção.

Esses documentos são selecionados com base na correspondência de seus valores com os valores do campo com o qual estão sendo comparados. O resultado final é um campo contendo uma matriz de documentos caso existam correspondências, ou uma matriz vazia caso nenhuma correspondência seja encontrada.

Considere as coleções de funcionários e projetos mostradas abaixo:

Podemos usar o seguinte código para unir as duas coleções:

db.projects.aggregate([
   {
      $lookup: {
         from: "employees",
         localField: "employees",
         foreignField: "_id",
         as: "assigned_employees"
      }
   }
])

O resultado dessa operação é uma combinação das duas coleções. O resultado são os projetos e todos os funcionários designados para cada projeto. Os funcionários são representados em uma matriz.

Estágios de Pipeline que Podem ser Usados ​​em Conjunto com $lookup

Como mencionado anteriormente, $lookup é um estágio em um pipeline de agregação do MongoDB e pode ser usado em conjunto com outros estágios do pipeline de agregação. Para ilustrar como esses estágios podem ser usados ​​junto com $lookup, usaremos as duas coleções a seguir para fins de demonstração.

No MongoDB, eles são armazenados no formato JSON. Assim é como as coleções acima se parecem no MongoDB.

Alguns exemplos de estágios de pipeline de agregação que podem ser usados ​​junto com $lookup incluem:

$match

$match é um estágio de pipeline de agregação utilizado para filtrar o fluxo de documentos, permitindo que apenas os documentos que atendem à condição fornecida prossigam para o próximo estágio do pipeline de agregação. Este estágio é mais eficaz quando utilizado no início do pipeline, para remover documentos que não serão necessários e otimizar o processo de agregação.

Usando as duas coleções anteriores, você pode combinar $match e $lookup da seguinte forma:

db.users.aggregate([
   {
      $match: {
         country: "USA"
      }
   },
   {
      $lookup: {
         from: "orders",
         localField: "_id",
         foreignField: "user_id",
         as: "orders"
      }
   }
])

$match é utilizado para filtrar usuários dos EUA. O resultado de $match é combinado com $lookup para obter os detalhes dos pedidos dos usuários dos EUA. O resultado da operação acima é mostrado abaixo:

$project

$project é um estágio usado para remodelar documentos, especificando quais campos incluir, excluir ou adicionar aos documentos. Por exemplo, se você estiver processando documentos com dez campos, mas apenas quatro campos contiverem os dados necessários para o processamento, você pode usar $project para filtrar os campos desnecessários. Isso evita que dados desnecessários sejam enviados para o próximo estágio do pipeline de agregação.

Podemos combinar $lookup e $project da seguinte forma:

db.users.aggregate([
   {
      $lookup: {
         from: "orders",
         localField: "_id",
         foreignField: "user_id",
         as: "orders"
      }
   },
   {
      $project: {
         name: 1,
         _id: 0,
         total_spent: { $sum: "$orders.price" }
      }
   }
])

O código acima combina as coleções de usuários e pedidos usando $lookup e, em seguida, utiliza $project para exibir apenas o nome de cada usuário e o valor total gasto por cada um. $project também é usado para remover o campo _id dos resultados. O resultado da operação acima é mostrado abaixo:

$unwind

$unwind é um estágio de agregação usado para desconstruir ou desenrolar um campo de array, criando novos documentos para cada elemento do array. Isso é útil caso você queira executar alguma agregação sobre os valores do campo array.

Por exemplo, no exemplo abaixo, caso você queira fazer uma agregação no campo hobbies, não é possível fazê-lo diretamente porque ele é um array. No entanto, você pode utilizar o $unwind e, em seguida, executar agregações nos documentos resultantes.

Usando as coleções users e orders, podemos utilizar $lookup e $unwind juntos da seguinte forma:

db.users.aggregate([
   {
      $lookup: {
         from: "orders",
         localField: "_id",
         foreignField: "user_id",
         as: "orders"
      }
   },
   {
      $unwind: "$orders"
   }
])

No código acima, $lookup retorna um campo array chamado orders. $unwind é então usado para desenrolar o campo array. O resultado dessa operação é mostrado a seguir: note que Alice aparece duas vezes porque ela tinha dois pedidos.

Exemplos de Casos de Uso do $lookup

O $lookup é uma ferramenta útil durante o processamento de dados. Por exemplo, você pode ter duas coleções que deseja unir com base em campos de cada coleção que possuem dados semelhantes. Um simples estágio $lookup pode ser utilizado para fazer isso e adicionar um novo campo nas coleções primárias, contendo os documentos obtidos de outra coleção.

Considere as coleções de usuários e pedidos mostradas abaixo:

As duas coleções podem ser combinadas usando $lookup para obter o resultado mostrado abaixo:

$lookup também pode ser usado para executar junções mais complexas. O uso do $lookup não se limita à união de duas coleções. Você pode implementar vários estágios $lookup para executar junções em mais de duas coleções. Considere as três coleções mostradas abaixo:

Podemos usar o código abaixo para realizar uma junção mais complexa entre as três coleções, para obter todos os pedidos realizados e também detalhes dos produtos que foram pedidos.

O código abaixo permite fazer exatamente isso:

db.orders.aggregate([
   {
      $lookup: {
         from: "order_items",
         localField: "_id",
         foreignField: "order_id",
         as: "order_items"
      }
   },
   {
      $unwind: "$order_items"
   },
   {
      $lookup: {
         from: "products",
         localField: "order_items.product_id",
         foreignField: "_id",
         as: "product_details"
      }
   },
   {
      $group: {
         _id: "$_id",
         customer: { $first: "$customer" },
         total: { $sum: "$order_items.price" },
         products: { $push: "$product_details" }
      }
   }
])

O resultado da operação acima é mostrado abaixo:

Conclusão

Ao realizar processamento de dados envolvendo múltiplas coleções, o $lookup é bastante útil, pois permite combinar dados e tirar conclusões com base nos dados armazenados em diversas coleções. O processamento de dados raramente depende de apenas uma coleta.

Para obter conclusões significativas a partir dos dados, a combinação de informações em múltiplas coleções é um passo fundamental. Portanto, considere a utilização do estágio $lookup em seu pipeline de agregação do MongoDB para melhorar o processamento de dados e extrair insights importantes a partir dos dados brutos armazenados em coleções.

Você também pode explorar outros comandos e consultas do MongoDB.