Quelle est la syntaxe pour effectuer une recherche $ sur un champ qui est un tableau d'ObjectIds plutôt qu'un simple ObjectId?
Exemple de document de commande:
{
_id: ObjectId("..."),
products: [
ObjectId("..<Car ObjectId>.."),
ObjectId("..<Bike ObjectId>..")
]
}
Requête ne fonctionnant pas:
db.orders.aggregate([
{
$lookup:
{
from: "products",
localField: "products",
foreignField: "_id",
as: "productObjects"
}
}
])
Résultat désiré
{
_id: ObjectId("..."),
products: [
ObjectId("..<Car ObjectId>.."),
ObjectId("..<Bike ObjectId>..")
],
productObjects: [
{<Car Object>},
{<Bike Object>}
],
}
L'étape de pipeline d'agrégation $lookup
ne fonctionnera pas directement avec un tableau. L'objectif principal de la conception est de créer une "jointure gauche" en tant que type de jointure "un à plusieurs" (ou en réalité une "recherche") sur les données associées possibles. Mais la valeur est destinée à être singulière et non un tableau.
Par conséquent, vous devez d'abord "dé-normaliser" le contenu avant d'exécuter l'opération _$lookup
_ pour que cela fonctionne. Et cela signifie que vous utilisez $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" }
}}
])
_
Après que _$lookup
_ corresponde à chaque membre du tableau, le résultat est un tableau lui-même. Vous devez donc _$unwind
_ encore et $group
à $Push
= nouveaux tableaux pour le résultat final.
Notez que toute correspondance "jointure à gauche" non trouvée créera un tableau vide pour les "productObjects" sur le produit donné et annulera ainsi le document pour l'élément "product" lorsque le second _$unwind
_ est appelé.
Bien qu'une application directe à un tableau serait Nice, c'est juste comment cela fonctionne actuellement en faisant correspondre une valeur singulière à un grand nombre possible.
Comme _$lookup
_ est fondamentalement très nouveau, il fonctionne actuellement comme le savent ceux qui sont familiers avec mangouste en tant que "version pauvre" de la méthode .populate()
proposée ici. La différence étant que _$lookup
_ offre un traitement "côté serveur" de la "jointure" par opposition au client et qu'une partie de la "maturité" dans _$lookup
_ manque actuellement de ce que .populate()
offres (telles que l’interpolation de la recherche directement sur un tableau).
Il s’agit en fait d’un problème à améliorer SERVER-22881 . C’est donc avec un peu de chance que la nouvelle version ou peu de temps après l’apparaîtra.
En tant que principe de conception, votre structure actuelle n'est ni bonne ni mauvaise, elle est simplement sujette à des frais généraux lors de la création d'une "jointure". En tant que tel, le principe permanent de base de MongoDB à l’origine s’applique: si vous "pouvez" vivre avec les données "pré-jointes" dans la même collection, il est préférable de le faire.
L’autre chose que l’on peut dire de _$lookup
_ en tant que principe général est que l’intention de la "jointure" ici est de fonctionner dans le sens inverse de ce qui est montré ici. Ainsi, plutôt que de conserver les "identifiants liés" des autres documents dans le document "parent", le principe général qui fonctionne le mieux est celui où les "documents connexes" contiennent une référence au "parent".
Donc, on peut dire que _$lookup
_ "fonctionne le mieux" avec une "conception de relation" qui est l'inverse de la manière dont quelque chose comme mangouste .populate()
effectue ses jointures côté client. En identifiant le "un" dans chaque "plusieurs" à la place, vous insérez simplement les éléments associés sans avoir besoin de _$unwind
_ le tableau en premier.
L'étape de pipeline d'agrégation $lookup
fonctionne maintenant directement avec un tableau (sur la version 3.3.4).
Voir: recherche entre un tableau de valeurs local (multiple) et une valeur étrangère (unique)
Vous pouvez également utiliser l’étape pipeline
pour effectuer des vérifications sur un tableau de sous-documents.
Voici l'exemple utilisant python
(désolé, je suis un peuple serpent).
db.products.aggregate([
{ '$lookup': {
'from': 'products',
'let': { 'pid': '$products' },
'pipeline': [
{ '$match': { '$expr': { '$in': ['$_id', '$$pid'] } } }
// Add additional stages here
],
'as':'productObjects'
}
])
Le problème ici est de faire correspondre tous les objets de la ObjectId
array
(étrangère _id
qui se trouve dans local
champ/prop products
).
Vous pouvez également nettoyer ou projeter les enregistrements étrangers avec stage
s supplémentaire, comme indiqué dans le commentaire ci-dessus.
utiliser $ dérouler vous obtiendrez le premier objet au lieu du tableau d'objets
requête:
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"
}
]);
résultat:
{
"_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"
}
}
L'agrégation avec _$lookup
_ et les _$group
_ suivants est assez fastidieux, donc si (et c'est un support si) vous utilisez node & Mongoose ou une bibliothèque de support avec quelques astuces dans le schéma, vous pouvez utiliser - .populate()
pour récupérer ces documents:
_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")
...
_