Uma das perguntas mais frequentes que recebo quando se trata de NoSQL é sobre o assunto de unir dados de vários documentos em um único resultado de consulta. Embora essa pergunta seja feita com mais frequência por desenvolvedores de RDBMS, também a recebo de desenvolvedores de NoSQL.
Quando se trata de união de dados, cada banco de dados faz isso de forma diferente, sendo que alguns deles exigem que isso seja feito por meio da camada do aplicativo, e não da camada do banco de dados. Vamos explorar algumas opções de união de dados entre tecnologias de banco de dados.
Neste blog, compararemos o processo de união de documentos NoSQL usando o operador $lookup do MongoDB com o processo de união de documentos NoSQL usando o operador $lookup do MongoDB. Couchbasemais intuitiva do N1QL.
Os dados da amostra
Para este exemplo, basearemos o MongoDB e o Couchbase em dois documentos de amostra. Suponhamos que estejamos trabalhando com um exemplo clássico de pedido e estoque. No caso do inventário, nossos documentos podem ser mais ou menos assim:
1 2 3 4 5 6 |
{ "id": "product-1", "type" (tipo): "product", "name": "Pokemon Red", "price": 29.99 } |
Embora plano, o documento acima pode explicar adequadamente um produto específico. Ele tem um ID exclusivo que será envolvido durante o processo de união. Para pedidos, podemos ter um documento parecido com o seguinte:
1 2 3 4 5 6 7 8 9 10 |
{ "id": "order-1", "type": "order", "products": [ { "product_id": "product-1", "quantity": 2 } ] } |
O objetivo aqui será unir esses dois documentos em uma única consulta usando o MongoDB e o Couchbase. No entanto, deixando de lado a linguagem de consulta, esses documentos sempre podem ser unidos por meio da camada de aplicativo por meio de várias consultas. No entanto, esse não é o resultado que estamos buscando.
Unindo documentos com o MongoDB e o operador $lookup
Nas versões recentes do MongoDB, as consultas de união envolvem um $lookup
que faz parte das consultas de agregação. De acordo com o MongoDB documentaçãoEsse operador funciona da seguinte forma:
Executa uma união externa esquerda com uma coleção não fragmentada no mesmo banco de dados para filtrar os documentos da coleção "unida" para processamento. O estágio $lookup faz uma correspondência de igualdade entre um campo dos documentos de entrada e um campo dos documentos da coleção "unida".
Para usar o $lookup
operador, você teria algo parecido com isto:
1 2 3 4 5 6 7 8 9 10 11 |
db.collection.aggregate([ { $lookup: { from: , localField: , foreignField: , como: } } ]) |
Isso é ótimo, mas não funciona em relacionamentos encontrados em matrizes. Isso significa que o $lookup
não pode participar da operação product_id
encontrado no produtos
para outro documento. Em vez disso, a matriz deve ser "desenrolada" ou "aninhada" primeiro, o que acrescenta uma complexidade extra à nossa consulta:
1 2 3 4 5 6 7 8 9 10 11 |
db.orders.aggregate([ { $unwind: "$products" }, { $lookup: { de: "products", localField: "products.product_id", foreignField: "_id", como: "productObjects" } } ]) |
O $unwind
achatará a matriz e, em seguida, fará uma junção dos objetos agora achatados que foram produzidos. O resultado dessa consulta teria a seguinte aparência:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
{ "_id" : ObjectId("58a3869acbf64c4ace55e713"), "products" : { "product_id" : ObjectId("58a3851b2f14a900caa7a731"), "quantity" : 2 }, "productObjects" : [ { "_id" : ObjectId("58a3851b2f14a900caa7a731"), "name" : "Pokemon Red", "price" : 29,99 } ] } |
Se houvesse mais de uma referência na matriz, haveria mais resultados retornados. No entanto, o que é retornado não é muito atraente. Ainda temos o antigo produtos
e agora um objeto productsObject
matriz. É necessário realizar outras manipulações no fluxo de dados.
O productsObject
A matriz deve ser "desenrolada" e depois reconstruída de acordo com o que desejamos. Isso pode ser feito da seguinte forma:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
db.orders.aggregate([ { $unwind: "$products" }, { $lookup: { de: "products", localField: "products.product_id", foreignField: "_id", como: "productObjects" } }, { $unwind: "$productObjects"}, { $project: { produtos: { "quantity" (quantidade): "$products.quantity", "name": "$productObjects.name", "price": "$productObjects.price" } } } ]) |
Observe que o agregado
A consulta agora está ficando mais complexa. Depois de fazer a união, o resultado é "desenrolado" e, em seguida, o resultado é reconstruído usando a função Projeto $
operador.
Nesse ponto, outras manipulações do resultado podem ser feitas, como agrupar os resultados de modo que o produtos
tornam-se novamente um único array. Cada manipulação do conjunto de dados requer mais código de agregação, que pode facilmente se tornar confuso, complicado e difícil de ler.
É nesse ponto que o Couchbase N1QL se torna muito mais agradável de se trabalhar.
Usando o Couchbase e o N1QL para unir documentos NoSQL
Vamos usar o mesmo exemplo de documento que usamos para o MongoDB. Desta vez, vamos escrever consultas SQL com o N1QL para realizar o trabalho.
A primeira coisa que vem à mente pode ser usar um JUNTAR
em SQL. Nossa consulta pode ser semelhante a esta:
1 2 3 4 |
SELECT pedidos.*, produto FROM exemplo AS pedidos JOIN example AS product ON KEYS orders.products[*].product_id WHERE orders.type = 'order' |
No exemplo acima, ambos os documentos existem no mesmo Bucket do Couchbase. A JUNTAR
em relação aos ids de documentos ocorre com base no product_id
valores encontrados no produtos
array. A consulta acima produziria resultados parecidos com os seguintes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
[ { "id": "order-1", "product": { "id": "product-1", "name": "Pokemon Red", "price": 29.99, "type": "product" }, "products": [ { "product_id": "product-1", "quantity": 2 } ], "type": "order" } ] |
Assim como no MongoDB, haverá um resultado para cada item da tabela produtos
que corresponde. Para ser justo, embora a versão N1QL fosse mais fácil de escrever, ela não era necessariamente mais difícil do que a linguagem de consulta do MongoDB nesse ponto. À medida que manipulamos mais os dados, o Couchbase se torna muito mais fácil em comparação.
Por exemplo, digamos que queremos limpar os resultados:
1 2 3 4 5 |
SELECT orders.id, orders.type, OBJECT_PUT(product, "quantity", products.quantity) AS product FROM exemplo AS pedidos UNNEST orders.products AS products JOIN example AS product ON KEYS products.product_id WHERE orders.type = 'order' |
Há algumas diferenças importantes no que estamos fazendo no exemplo acima, mas pequenas diferenças na forma como estamos fazendo. Em vez de unir diretamente a matriz, estamos primeiro achatando ou "aninhando" a matriz, como vimos no MongoDB $unwind
operador. A união agora está acontecendo em cada um dos resultados achatados. Por fim, o operador quantidade
do objeto original é adicionado ao novo objeto.
O resultado da consulta acima seria algo parecido com isto:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
[ { "id": "order-1", "product": { "id": "product-1", "name": "Pokemon Red", "price": 29.99, "quantidade": 2, "type": "product" }, "type": "order" } ] |
Digamos que o original produtos
tinha mais de uma referência de produto. Em vez de retornar vários objetos com base no JUNTAR
Como vimos acima, pode fazer sentido reempacotar a matriz original.
1 2 3 4 5 6 |
SELECT orders.id, orders.type, ARRAY_AGG(OBJECT_PUT(product, "quantity", products.quantity)) AS products FROM exemplo AS pedidos UNNEST orders.products AS products JOIN example AS product ON KEYS products.product_id WHERE orders.type = 'order' GROUP BY pedidos |
Na consulta acima, adicionamos apenas ARRAY_AGG
e um GRUPO POR
mas, como resultado, cada documento unido aparece no produtos
em vez do valor de id.
Não quero usar um JUNTAR
operador? Em vez disso, tente usar uma subconsulta SQL.
Conclusão
No NoSQL, a união de dados é uma preocupação muito popular entre os desenvolvedores que são veteranos experientes em RDBMS. Como o MongoDB é uma tecnologia NoSQL muito popular, achei que seria bom usá-lo para comparar como o Couchbase lida com a união de documentos. Para operações leves, $lookup
é tolerável, mas à medida que as consultas de união no MongoDB se tornam mais complexas, talvez seja necessário dar um passo atrás. Com o N1QL, escrever consultas complexas que incluem operações de união se torna muito fácil e continua sendo fácil, independentemente da complexidade da consulta.
Para obter mais informações sobre o N1QL e o Couchbase, visite o site Portal do desenvolvedor do Couchbase.