$ búsqueda en ObjectId’s en una matriz

¿Cuál es la syntax para hacer una búsqueda $ en un campo que es una matriz de ObjectIds en lugar de solo un ObjectId?

Ejemplo de documento de pedido:

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

No está trabajando consulta:

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

Resultado deseado

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

La etapa de canalización de agregación $lookup no funcionará directamente con una matriz. La intención principal del diseño es una “unión izquierda” como un tipo de unión “uno a muchos” (o realmente una “búsqueda”) sobre los posibles datos relacionados. Pero el valor está destinado a ser singular y no a una matriz.

Por lo tanto, debe “desnormalizar” el contenido primero antes de realizar la operación $lookup para que esto funcione. Y eso 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" } }} ]) 

Después de $lookup coincide con cada miembro de la matriz el resultado es una matriz en sí, por lo que $unwind nuevo y $group a $push nuevas matrices para el resultado final.

Tenga en cuenta que cualquier coincidencia “left join” que no se encuentre creará una matriz vacía para los “productObjects” en el producto dado y, por lo tanto, negará el documento para el elemento “producto” cuando se invoque el segundo $unwind .

Aunque una aplicación directa a una matriz sería agradable, así es como funciona actualmente al unir un valor singular a un posible número.

Como $lookup es básicamente muy nuevo, actualmente funciona como sería familiar para aquellos que están familiarizados con la mongoose como una “versión de hombre pobre” del método .populate() ofrecido allí. La diferencia es que $lookup ofrece el procesamiento “servidor” de “join” en lugar de en el cliente y que parte de la “madurez” en $lookup falta actualmente de lo que ofrece .populate() (como la interpolación de la búsqueda) directamente en una matriz).

Este es en realidad un problema asignado para mejorar SERVER-22881 , por lo que con un poco de suerte esto afectará a la próxima versión o una poco después.

Como principio de diseño, su estructura actual no es ni buena ni mala, solo está sujeta a los gastos generales al crear cualquier “combinación”. Como tal, se aplica el principio básico permanente de MongoDB en el inicio, donde si “puedes” vivir con los datos “preunificados” en una colección, entonces es mejor hacerlo.

La otra cosa que se puede decir de $lookup como principio general, es que la intención de la “unión” aquí es trabajar al revés que se muestra aquí. Entonces, en lugar de mantener los “identificadores relacionados” de los otros documentos dentro del documento “principal”, el principio general que funciona mejor es cuando los “documentos relacionados” contienen una referencia al “padre”.

Por lo tanto, se puede decir que $lookup “funciona mejor” con un “diseño de relación” que es el reverso de cómo algo como .populate() realiza sus uniones de cliente. Al idendificar el “uno” dentro de cada “muchos” en su lugar, entonces simplemente inserta los elementos relacionados sin necesidad de $unwind primero el conjunto.

La etapa de canalización de agregación $lookup NOW funciona directamente con una matriz (en la versión 3.3.4).

Ver: búsqueda entre la matriz de valores local (múltiple) y el valor extranjero (único)

use $ unwind obtendrá el primer objeto en lugar de una matriz de objetos

consulta:

 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" } } 

También puede usar la etapa de canalización para verificar la matriz

Aquí está el ejemplo usando Python (lo siento, soy gente de serpiente).

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

El truco aquí es hacer coincidir todos los objetos en la matriz object_id (‘_id’ extranjera que está en ‘productos’ locales).

También puede limpiar o proyectar los registros extranjeros con etapas adicionales.

Agregar agregados $lookup y el siguiente $group es bastante engorroso, por lo que si (y eso es un medio si) está usando node & Mongoose o una biblioteca .populate() con algunas sugerencias en el esquema, puede usar un .populate() para obtener esos 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") ...