これが私のコレクションです。
collection1:
{
user1: 1,
user2: 2,
percent: 0.56
}
collection2:
{
user1: 1,
user2: 2,
percent: 0.3
}
「user1」と「user2」で2つのコレクションに参加します。
次のような結果:
{
user1: 1,
user2: 2,
percent1: 0.56,
percent2: 0.3
}
パイプラインを作成するにはどうすればよいですか?
バージョン3.6以降の $lookup
集約パイプライン演算子を使用して、複数の結合条件を実行できます。
let
オプションフィールドを使用して、フィールドの値を変数に割り当てる必要があります。次に、コレクションで実行するパイプラインを指定するpipeline
フィールドステージでこれらの変数にアクセスします。
$match
ステージでは、 $expr
評価クエリ演算子を使用してフィールドの値を比較することに注意してください。
パイプラインの最後のステージは $replaceRoot
集約パイプラインステージで、そこでは $lookup
演算子を使用して$$ROOT
結果を$mergeObjects
ドキュメントの一部と単純にマージします。
db.collection2.aggregate([
{
$lookup: {
from: "collection1",
let: {
firstUser: "$user1",
secondUser: "$user2"
},
pipeline: [
{
$match: {
$expr: {
$and: [
{
$eq: [
"$user1",
"$$firstUser"
]
},
{
$eq: [
"$user2",
"$$secondUser"
]
}
]
}
}
}
],
as: "result"
}
},
{
$replaceRoot: {
newRoot: {
$mergeObjects:[
{
$arrayElemAt: [
"$result",
0
]
},
{
percent1: "$$ROOT.percent1"
}
]
}
}
}
]
)
このパイプラインは、次のようなものを生成します。
{
"_id" : ObjectId("59e1ad7d36f42d8960c06022"),
"user1" : 1,
"user2" : 2,
"percent" : 0.3,
"percent1" : 0.56
}
バージョン3.6以降を使用していない場合は、最初にフィールド「user1」のいずれかを使用して参加し、そこから $unwind
集約パイプライン演算子を使用して、一致するドキュメントの配列を展開できます。パイプラインの次のステージは $redact
ステージです。このステージでは、 $$KEEP
を使用して、「結合」コレクションの「user2」の値と入力ドキュメントが等しくないドキュメントをフィルタリングします。 および $$Prune
システム変数。その後、ドキュメントを $project
段階で再形成できます。
db.collection1.aggregate([
{ "$lookup": {
"from": "collection2",
"localField": "user1",
"foreignField": "user1",
"as": "collection2_doc"
}},
{ "$unwind": "$collection2_doc" },
{ "$redact": {
"$cond": [
{ "$eq": [ "$user2", "$collection2_doc.user2" ] },
"$$KEEP",
"$$Prune"
]
}},
{ "$project": {
"user1": 1,
"user2": 1,
"percent1": "$percent",
"percent2": "$collection2_doc.percent"
}}
])
生成するもの:
{
"_id" : ObjectId("572daa87cc52a841bb292beb"),
"user1" : 1,
"user2" : 2,
"percent1" : 0.56,
"percent2" : 0.3
}
コレクション内のドキュメントの構造が同じで、この操作を頻繁に実行している場合は、2つのコレクションを1つにマージするか、これらのコレクション内のドキュメントを新しいコレクションに挿入することを検討する必要があります。
db.collection3.insertMany(
db.collection1.find({}, {"_id": 0})
.toArray()
.concat(db.collection2.find({}, {"_id": 0}).toArray())
)
次に、 $group
「user1」と「user2」によるドキュメント
db.collection3.aggregate([
{ "$group": {
"_id": { "user1": "$user1", "user2": "$user2" },
"percent": { "$Push": "$percent" }
}}
])
生成されるもの:
{ "_id" : { "user1" : 1, "user2" : 2 }, "percent" : [ 0.56, 0.3 ] }
データをモデル化しようとして、mongodbが複数のフィールドで結合を実行できるかどうかを確認するためにここに来た場合は、先に進んでください。
MongoDBは結合を実行できますが、アプリケーションアクセスパターンに従ってデータをモデル化する自由もあります。データが質問で示されているように単純な場合、次のような単一のコレクションを維持できます。
{
user1: 1,
user2: 2,
percent1: 0.56,
percent2: 0.3
}
これで、このコレクションで、結合することで実行するすべての操作を実行できます。なぜ結合を避けようとしているのですか?シャードコレクション( docs )ではサポートされていないため、必要に応じてスケールアウトできなくなります。データの正規化(個別のテーブル/コレクションを持つ)はSQLで非常にうまく機能しますが、Mongoに関しては、ほとんどの場合、結合を回避すると結果なしで利点が得られます。他に選択肢がない場合にのみ、MongoDBで正規化を使用します。 docs から:
一般に、正規化されたデータモデルを使用します。
- 埋め込みによってデータの重複が発生するが、重複の影響を上回る十分な読み取りパフォーマンスの利点が得られない場合。
- より複雑な多対多の関係を表すため。
- 大規模な階層データセットをモデル化します。
here をチェックして、埋め込みと、正規化よりもそれを選択する理由の詳細をお読みください。
$ matchおよび$ projectパイプラインを使用して、複数のフィールド一致を実行できます。 (ここで詳細な回答を参照してください- 複数のフィールドでのmongoDB Join )
db.collection1.aggregate([
{"$lookup": {
"from": "collection2",
"localField": "user1",
"foreignField": "user1",
"as": "c2"
}},
{"$unwind": "$c2"},
{"$project": {
"user2Eq": {"$eq": ["$user2", "$c2.user2"]},
"user1": 1, "user2": 1,
"percent1": "$percent", "percent2": "$c2.percent"
}},
{"$match": {
{"user2Eq": {"$eq": True}}
}},
{"$project": {
"user2Eq": 0
}}
])