web-dev-qa-db-fra.com

$ lookup sur ObjectId dans un tableau

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>}
  ],
}
79
Jason Lin

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.

120
Blakes Seven

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)

44
joseaio

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 ObjectIdarray (étrangère _id qui se trouve dans local champ/prop products).

Vous pouvez également nettoyer ou projeter les enregistrements étrangers avec stages supplémentaire, comme indiqué dans le commentaire ci-dessus.

6
user12164

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"
    }
}
4
KARTHIKEYAN.A

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")
    ...
_
0
Archimedix