web-dev-qa-db-ja.com

配列内のObjectIdの$ lookup

単一のObjectIdではなく、ObjectIdの配列であるフィールドで$ lookupを実行するための構文は何ですか?

注文ドキュメントの例:

{
  _id: ObjectId("..."),
  products: [
    ObjectId("..<Car ObjectId>.."),
    ObjectId("..<Bike ObjectId>..")
  ]
}

クエリが機能しない:

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

望ましい結果

{
  _id: ObjectId("..."),
  products: [
    ObjectId("..<Car ObjectId>.."),
    ObjectId("..<Bike ObjectId>..")
  ],
  productObjects: [
    {<Car Object>},
    {<Bike Object>}
  ],
}
77
Jason Lin

$lookup 集約パイプラインステージは、配列で直接動作しません。設計の主な目的は、可能性のある関連データの「1対多」タイプの結合(または実際には「ルックアップ」)としての「左結合」です。しかし、この値は配列ではなく、特異なものであることを意図しています。

したがって、これを機能させるには、$lookup操作を実行する前に、まずコンテンツを「非正規化」する必要があります。そして、それは $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" }
    }}
])

$lookupが各配列メンバーと一致した後、結果は配列自体になります。そのため、再び$unwindを使用し、 $group から $Push に最終結果を取得します。

見つからない「左結合」の一致は、特定の製品の「productObjects」の空の配列を作成するため、2番目の$unwindが呼び出されたときに「product」要素のドキュメントを無効にします。

配列に直接適用するのはいいことですが、特異値を考えられる多くの値に一致させることで、これが現在どのように機能するかがわかります。

$lookupは基本的に非常に新しいため、現在提供されている.populate()メソッドの「貧弱なバージョン」として mongoose に精通している人にはおなじみのように機能します。違いは、$lookupがクライアントではなく「サーバー側」の「結合」処理を提供することと、$lookupの「成熟度」の一部が現在.populate()が提供するものに欠けていることです(配列で直接ルックアップを補間するなど) 。

これは実際には改善のために割り当てられた問題です SERVER-22881 ですので、運が良ければ次のリリースまたはすぐにリリースされるでしょう。

設計原則として、現在の構造は良いものでも悪いものでもありませんが、「結合」を作成するときにオーバーヘッドが発生するだけです。そのため、MongoDBの基本的な永続原理が適用されます。1つのコレクションでデータが「事前結合」された状態で「生きる」ことができる場合は、そうすることが最善です。

一般的な原則として$lookupについて言えるもう1つのことは、ここでの「結合」の意図は、ここに示されているものとは別の方法で動作することです。そのため、「親」ドキュメント内に他のドキュメントの「関連ID」を保持するのではなく、「関連ドキュメント」に「親」への参照が含まれるのが最も一般的な原則です。

したがって、$lookupは、mongoose .populate()のようなものがクライアント側の結合を実行する方法の逆である「関係設計」で「最適に動作する」と言うことができます。代わりに、各「多く」内の「1」を識別することにより、最初に配列を$unwindする必要なく、関連するアイテムを取得するだけです。

115
Blakes Seven

$lookup集計パイプラインステージは、配列(3.3.4バージョン)で直接動作します。

参照: 値のローカル(複数)配列と外部(単一)値の間のルックアップ

42
joseaio

$ unwindを使用して、オブジェクトの配列ではなく最初のオブジェクトを取得します

クエリ:

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"
  }
]);

結果:

{
    "_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

pipelineステージを使用して、サブドキュメント配列のチェックを実行することもできます

以下はpythonを使用した例です(ごめんなさい、私はヘビの人々です)。

db.products.aggregate([
  { '$lookup': {
      'from': 'products',
      'let': { 'pid': '$products' },
      'pipeline': [
        { '$match': { '$expr': { '$in': ['$_id', '$$pid'] } } }
        // Add additional stages here 
      ],
      'as':'productObjects'
  }
])

ここでのキャッチは、ObjectIdarraylocal field/prop productsにある外部の_id)内のすべてのオブジェクトと一致させることです。

上記のコメントで示されているように、追加のstagesを使用して外部レコードをクリーンアップまたは投影することもできます。

4
user12164

$lookupと後続の$groupとの集約は非常に面倒なので、ノードとMongooseまたはサポートライブラリを使用してスキーマ内のいくつかのヒントを使用している場合は、 .populate() を使用できますそれらのドキュメントを取得します。

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