ここで欠けているのは、$lookup
as
で指定された出力フィールドに「配列」を生成します その議論の中で。これは、MongoDBの「関係」の一般的な概念であり、ドキュメント間の「関係」は、ドキュメント自体の「内部」にある「サブプロパティ」として表され、多くの場合、単一または「配列」のいずれかになります。
MongoDBは「スキーマレス」であるため、$lookup
の一般的な推定 つまり、「多く」を意味するため、結果は「常に」配列になります。したがって、「SQLと同じ結果」を探す場合は、$unwind
する必要があります。 $lookup
の後のその配列 。それが「1つ」であるか「多く」であるかは、依然として「常に」配列であるため、重要ではありません。
db.getCollection.('tb1').aggregate([
// Filter conditions from the source collection
{ "$match": { "status": { "$ne": "closed" } }},
// Do the first join
{ "$lookup": {
"from": "tb2",
"localField": "id",
"foreignField": "profileId",
"as": "tb2"
}},
// $unwind the array to denormalize
{ "$unwind": "$tb2" },
// Then match on the condtion for tb2
{ "$match": { "tb2.profile_type": "agent" } },
// join the second additional collection
{ "$lookup": {
"from": "tb3",
"localField": "tb2.id",
"foreignField": "id",
"as": "tb3"
}},
// $unwind again to de-normalize
{ "$unwind": "$tb3" },
// Now filter the condition on tb3
{ "$match": { "tb3.status": 0 } },
// Project only wanted fields. In this case, exclude "tb2"
{ "$project": { "tb2": 0 } }
])
ここで、翻訳に欠けている他のことに注意する必要があります:
シーケンスは「重要」です
集約パイプラインは、SQLよりも「簡潔に表現力があります」。実際、これらは「一連のステップ」と見なすのが最適です。 データを照合および変換するためにデータソースに適用されます。これに最もよく似ているのは、次のような「パイプ」コマンドライン命令です。
ps -ef | grep mongod | grep -v grep | awk '{ print $1 }'
「パイプ」の場所|
MongoDBアグリゲーション「パイプライン」の「パイプラインステージ」と見なすことができます。
そのため、$match
最初の操作として「ソース」コレクションからのものをフィルタリングするため。また、これは、必要な条件を満たさなかったドキュメントを以降の条件から削除するため、一般的には良い方法です。 「コマンドラインパイプ」の例で起こっていることと同じように、「入力」を取得してから「パイプ」をgrep
に送信します。 「削除」または「フィルタリング」します。
パスの問題
ここで次に行うことは、$lookup
を介して「参加」することです。 。結果は、"from"
のアイテムの「配列」です。 "as"
に出力するために提供されたフィールドと一致するコレクション引数 「配列」としての「フィールドパス」。
ソースコレクションの「ドキュメント」は、「結合」のすべてのアイテムがその指定されたパスに存在すると見なすため、ここで選択した名前は重要です。これを簡単にするために、新しい「パス」の「結合」と同じ「コレクション」名を使用します。
したがって、最初の「結合」から開始して、出力は"tb2"
になります。 そして、それはそのコレクションからのすべての結果を保持します。次の$unwind
のシーケンスには、注意すべき重要な点もあります。 次に$match
、MongoDBが実際にクエリを処理する方法について。
特定のシーケンスは「本当に」重要です
「見た目」は「3つの」パイプラインステージがあるため、$lookup
次に$unwind
次に$match
。しかし、「事実」では、MongoDBは実際に何か他のことを行います。これは、{ "explain": true }
の出力で示されます。 .aggregate()
に追加されました コマンド:
{
"$lookup" : {
"from" : "tb2",
"as" : "tb2",
"localField" : "id",
"foreignField" : "profileId",
"unwinding" : {
"preserveNullAndEmptyArrays" : false
},
"matching" : {
"profile_type" : {
"$eq" : "agent"
}
}
}
},
{
"$lookup" : {
"from" : "tb3",
"as" : "tb3",
"localField" : "tb2.id",
"foreignField" : "id",
"unwinding" : {
"preserveNullAndEmptyArrays" : false
},
"matching" : {
"status" : {
"$eq" : 0.0
}
}
}
},
したがって、$match
を配置する必要がある場所に適用する「シーケンス」の最初のポイントは別として、 それらが必要であり、「最も良い」ことを行うステートメント、これは実際には「結合」の概念で「本当に重要」になります。ここで注意すべきことは、$lookup
のシーケンスです。 次に$unwind
次に$match
、実際には、MongoDBによって$lookup
として処理されます。 ステージ。他の操作は、それぞれ1つのパイプラインステージに「ロールアップ」されます。
これは、$lookup
によって取得された結果を「フィルタリング」する他の方法との重要な違いです。 。この場合、$match
からの「join」の実際の「クエリ」条件は 結果が親に返される「前」に結合するためにコレクションに対して実行されます。
これを$unwind
と組み合わせて (これはunwinding
に変換されます )上記のように、MongoDBは、「結合」によってソースドキュメントにコンテンツの配列が生成され、16MBのBSON制限を超える可能性を実際に処理する方法を示しています。これは、結合される結果が非常に大きい場合にのみ発生しますが、同じ利点は、結果が返される「前」にターゲットコレクションにある「フィルター」が実際に適用される場合です。
SQL JOINと同じ動作に最もよく「相関」するのは、この種の処理です。したがって、$lookup
から結果を取得するための最も効果的な方法でもあります。 単に「外部」キー値の「ローカル」以外に、JOINに適用する他の条件がある場合。
また、他の動作の変更は、本質的に$lookup
によって実行されるLEFTJOINからのものであることに注意してください。 ここで、「ソース」ドキュメントは、「ターゲット」コレクションに一致するドキュメントが存在するかどうかに関係なく、常に保持されます。代わりに、$unwind
$match
の追加条件により、「ターゲット」から一致するものがなかった「ソース」からの結果を「破棄」することにより、これに追加します。 。
実際、暗黙の
preserveNullAndEmptyArrays: false
が原因で、事前に破棄されることもあります。 これは含まれており、「ローカル」キーと「外部」キーが2つのコレクション間でさえ一致しなかった場合はすべて破棄されます。 「結合」はこれらの値を「等しく」することを目的としているため、これはこの特定のタイプのクエリにとっては良いことです。
結論
前述のように、MongoDBは通常、「リレーショナルデータベース」またはRDBMSの使用方法とは大きく異なる方法で「リレーショナル」を処理します。 「関係」の一般的な概念は、実際には、単一のプロパティまたは配列としてデータを「埋め込む」ことです。
あなたは実際にそのような出力を望むかもしれません、それはまた$unwind
なしでそれが理由の一部です ここで、$lookup
の出力をシーケンスします。 実際には「配列」です。ただし、$unwind
を使用する このコンテキストでは、実際に最も効果的な方法であり、「結合された」データによって、その「結合」の結果として前述のBSON制限を実際に超えないことが保証されます。
実際に出力の配列が必要な場合、ここで行う最善の方法は、$group
を使用することです。 パイプラインステージ、および$unwind
の「結果を正規化」および「元に戻す」ための複数のステージとして
{ "$group": {
"_id": "$_id",
"tb1_field": { "$first": "$tb1_field" },
"tb1_another": { "$first": "$tb1_another" },
"tb3": { "$push": "$tb3" }
}}
この場合、実際に"tb1"
から必要なすべてのフィールドをリストします。 $first
を使用してプロパティ名で 「最初の」発生のみを保持する(基本的に"tb2"
の結果によって繰り返される) および"tb3"
巻き戻し)、次に$push
"tb3"
の「詳細」 "tb1"
との関係を表す「配列」に 。
ただし、与えられた集約パイプラインの一般的な形式は、「結合」の結果として「非正規化」された出力である元のSQLから結果がどのように取得されるかを正確に表したものです。この後、結果を再度「正規化」するかどうかはあなた次第です。