単一の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>}
],
}
$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
する必要なく、関連するアイテムを取得するだけです。
$lookup
集計パイプラインステージは、配列(3.3.4バージョン)で直接動作します。
$ 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"
}
}
pipeline
ステージを使用して、サブドキュメント配列のチェックを実行することもできます
以下はpython
を使用した例です(ごめんなさい、私はヘビの人々です)。
db.products.aggregate([
{ '$lookup': {
'from': 'products',
'let': { 'pid': '$products' },
'pipeline': [
{ '$match': { '$expr': { '$in': ['$_id', '$$pid'] } } }
// Add additional stages here
],
'as':'productObjects'
}
])
ここでのキャッチは、ObjectId
array
(local
field/prop products
にある外部の_id
)内のすべてのオブジェクトと一致させることです。
上記のコメントで示されているように、追加のstage
sを使用して外部レコードをクリーンアップまたは投影することもできます。
$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")
...