$ lookup em ObjectId’s em um array

Qual é a syntax para fazer um $ lookup em um campo que é uma matriz de ObjectIds em vez de apenas um único ObjectId?

Documento de pedido de exemplo:

{ _id: ObjectId("..."), products: [ ObjectId("...."), ObjectId("....") ] } 

Não está funcionando consulta:

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

Resultado desejado

 { _id: ObjectId("..."), products: [ ObjectId("...."), ObjectId("....") ], productObjects: [ {}, {} ], } 

O estágio de pipeline de agregação de $lookup não funcionará diretamente com uma matriz. A intenção principal do design é para uma “junit esquerda” como um tipo de junit “um para muitos” (ou realmente uma “pesquisa”) sobre os possíveis dados relacionados. Mas o valor é destinado a ser singular e não um array.

Portanto, você deve “desnormalizar” o conteúdo primeiro antes de executar a operação $lookup para que isso funcione. E isso significa usar $unwind :

 db.orders.aggregate([ // Unwind the source { "$unwind": "$products" }, // Do the lookup matching { "$lookup": { "from": "products", "localField": "products", "foreignField": "_id", "as": "productObjects" }}, // Unwind the result arrays ( likely one or none ) { "$unwind": "$productObjects" }, // Group back to arrays { "$group": { "_id": "$_id", "products": { "$push": "$products" }, "productObjects": { "$push": "$productObjects" } }} ]) 

Depois que $lookup corresponde a cada membro da matriz, o resultado é um array, então você $unwind novamente e $group to $push novos arrays para o resultado final.

Observe que qualquer correspondência “left join” que não for encontrada criará uma matriz vazia para os “productObjects” no produto em questão e, assim, negará o documento para o elemento “product” quando o segundo $unwind for chamado.

Embora um aplicativo direto para um array seja bom, é apenas como isso funciona atualmente, combinando um valor singular para um número possível.

Como $lookup é basicamente muito novo, atualmente funciona como seria familiar para aqueles que estão familiarizados com o mangusto como uma “versão pobre do homem” do método .populate() oferecido lá. A diferença é que o $lookup oferece processamento do “lado do servidor” do “join” em oposição ao cliente e que parte da “maturidade” no $lookup está faltando no momento que o .populate() oferece (como interpolar o lookup diretamente em uma matriz).

Este é realmente um problema atribuído para a melhoria SERVER-22881 , por isso com alguma sorte isso iria acertar o próximo lançamento ou um logo depois.

Como princípio de design, sua estrutura atual não é boa nem ruim, mas apenas sujeita a despesas gerais ao criar qualquer “junit”. Como tal, aplica-se o princípio básico permanente do MongoDB no início, onde se você “pode” viver com os dados “pré-unidos” na coleção, então é melhor fazê-lo.

A outra coisa que pode ser dita sobre o $lookup como princípio geral, é que a intenção da “junit” aqui é trabalhar ao contrário do mostrado aqui. Portanto, em vez de manter os “ids relacionados” dos outros documentos dentro do documento “pai”, o princípio geral que funciona melhor é onde os “documentos relacionados” contêm uma referência ao “pai”.

Portanto, $lookup pode ser considerado como “funciona melhor” com um “design de relação” que é o inverso de como algo como o mongoose .populate() executa suas junções do lado do cliente. Ao idenificar o “um” dentro de cada “muitos”, você simplesmente puxa os itens relacionados sem precisar $unwind o array primeiro.

O estágio de pipeline de agregação de $lookup NOW trabalha diretamente com uma matriz (na versão 3.3.4).

Consulte: pesquisa entre matriz local (múltipla) de valores e valor externo (único)

use $ unwind você vai pegar o primeiro object ao invés de array de objects

inquerir:

 db.getCollection('vehicles').aggregate([ { $match: { status: "AVAILABLE", vehicleTypeId: { $in: Array.from(newSet(d.vehicleTypeIds)) } } }, { $lookup: { from: "servicelocations", localField: "locationId", foreignField: "serviceLocationId", as: "locations" } }, { $unwind: "$locations" } ]); 

resultado:

 { "_id" : ObjectId("59c3983a647101ec58ddcf90"), "vehicleId" : "45680", "regionId" : 1.0, "vehicleTypeId" : "10TONBOX", "locationId" : "100", "description" : "Isuzu/2003-10 Ton/Box", "deviceId" : "", "earliestStart" : 36000.0, "latestArrival" : 54000.0, "status" : "AVAILABLE", "accountId" : 1.0, "locations" : { "_id" : ObjectId("59c3afeab7799c90ebb3291f"), "serviceLocationId" : "100", "regionId" : 1.0, "zoneId" : "DXBZONE1", "description" : "Masafi Park Al Quoz", "locationPriority" : 1.0, "accountTypeId" : 0.0, "locationType" : "DEPOT", "location" : { "makani" : "", "lat" : 25.123091, "lng" : 55.21082 }, "deliveryDays" : "MTWRFSU", "timeWindow" : { "timeWindowTypeId" : "1" }, "address1" : "", "address2" : "", "phone" : "", "city" : "", "county" : "", "state" : "", "country" : "", "zipcode" : "", "imageUrl" : "", "contact" : { "name" : "", "email" : "" }, "status" : "", "createdBy" : "", "updatedBy" : "", "updateDate" : "", "accountId" : 1.0, "serviceTimeTypeId" : "1" } } { "_id" : ObjectId("59c3983a647101ec58ddcf91"), "vehicleId" : "81765", "regionId" : 1.0, "vehicleTypeId" : "10TONBOX", "locationId" : "100", "description" : "Hino/2004-10 Ton/Box", "deviceId" : "", "earliestStart" : 36000.0, "latestArrival" : 54000.0, "status" : "AVAILABLE", "accountId" : 1.0, "locations" : { "_id" : ObjectId("59c3afeab7799c90ebb3291f"), "serviceLocationId" : "100", "regionId" : 1.0, "zoneId" : "DXBZONE1", "description" : "Masafi Park Al Quoz", "locationPriority" : 1.0, "accountTypeId" : 0.0, "locationType" : "DEPOT", "location" : { "makani" : "", "lat" : 25.123091, "lng" : 55.21082 }, "deliveryDays" : "MTWRFSU", "timeWindow" : { "timeWindowTypeId" : "1" }, "address1" : "", "address2" : "", "phone" : "", "city" : "", "county" : "", "state" : "", "country" : "", "zipcode" : "", "imageUrl" : "", "contact" : { "name" : "", "email" : "" }, "status" : "", "createdBy" : "", "updatedBy" : "", "updateDate" : "", "accountId" : 1.0, "serviceTimeTypeId" : "1" } } 

Você também pode usar o estágio de pipeline para verificar a matriz

Aqui está o exemplo usando python (desculpe, eu sou pessoas de cobra).

 db.products.aggregate([ {'$loookup': {'from': 'products', 'let': {'pid': '$products'}, 'pipeline': [ {'$match': {'expr': {'$in': ['$_id', '$$pid']}}} # Additional stages here ], 'as':'productObjects' }} ]) 

A captura aqui é para combinar todos os objects na matriz object_id (foreign ‘_id’ que está em ‘produtos’ locais).

Você também pode limpar ou projetar os registros estrangeiros com etapas adicionais.

Agregar com $lookup e subseqüente $group é bastante complicado, então se (e isso é um meio se) você estiver usando node & Mongoose ou uma biblioteca de suporte com algumas dicas no esquema, você poderia usar um .populate() para buscar aqueles documentos:

 var mongoose = require("mongoose"), Schema = mongoose.Schema; var productSchema = Schema({ ... }); var orderSchema = Schema({ _id : Number, products: [ { type: Schema.Types.ObjectId, ref: "Product" } ] }); var Product = mongoose.model("Product", productSchema); var Order = mongoose.model("Order", orderSchema); ... Order .find(...) .populate("products") ...