この構造に類似したドキュメントを含むコレクション「アカウント」があります。
{
"email" : "[email protected]",
"groups" : [
{
"name" : "group1",
"contacts" : [
{ "localId" : "c1", "address" : "some address 1" },
{ "localId" : "c2", "address" : "some address 2" },
{ "localId" : "c3", "address" : "some address 3" }
]
},
{
"name" : "group2",
"contacts" : [
{ "localId" : "c1", "address" : "some address 1" },
{ "localId" : "c3", "address" : "some address 3" }
]
}
]
}
Via
q = { "email" : "[email protected]", "groups" : { $elemMatch: { "name" : "group1" } } }
p = { "groups.name" : 0, "groups" : { $elemMatch: { "name" : "group1" } } }
db.accounts.find( q, p ).pretty()
興味のある特定のアカウントのグループのみを取得できます。
質問:特定の「アカウント」の特定の「グループ」内の「連絡先」の限定リストを取得するにはどうすればよいですか?私は次の引数を持っているとしましょう:
これらの議論を考えて、私は次の結果を得たいと思います:
{
"groups" : [
{
"name" : "group1", (might be omitted)
"contacts" : [
{ "localId" : "c1", "address" : "some address 1" },
{ "localId" : "c3", "address" : "some address 3" }
]
}
]
}
結果として生じる連絡先以外には何も必要ありません。
アプローチ
すべてのクエリは、簡単にするために、一致する連絡先のリストではなく、一致する連絡先を1つだけ取得しようとします。私は成功せずに次のクエリを試しました:
p = { "groups.name" : 0, "groups" : { $elemMatch: { "name" : "group1", "contacts" : { $elemMatch: { "localId" : "c1" } } } } }
p = { "groups.name" : 0, "groups" : { $elemMatch: { "name" : "group1", "contacts.localId" : "c1" } } }
not working: returns whole array or nothing depending on localId
p = { "groups.$" : { $elemMatch: { "localId" : "c1" } } }
error: {
"$err" : "Can't canonicalize query: BadValue Cannot use $elemMatch projection on a nested field.",
"code" : 17287
}
p = { "groups.contacts" : { $elemMatch: { "localId" : "c1" } } }
error: {
"$err" : "Can't canonicalize query: BadValue Cannot use $elemMatch projection on a nested field.",
"code" : 17287
}
どんな助けも大歓迎です!
このような適切な質問は、現代的な対応に値します。要求された配列フィルタリングの種類は、3.2以降の最新のMongoDBリリースでは、単純な $match
および $project
パイプラインステージを介して実際に実行できます。
db.accounts.aggregate([
{ "$match": {
"email" : "[email protected]",
"groups": {
"$elemMatch": {
"name": "group1",
"contacts.localId": { "$in": [ "c1","c3", null ] }
}
}
}},
{ "$addFields": {
"groups": {
"$filter": {
"input": {
"$map": {
"input": "$groups",
"as": "g",
"in": {
"name": "$$g.name",
"contacts": {
"$filter": {
"input": "$$g.contacts",
"as": "c",
"cond": {
"$or": [
{ "$eq": [ "$$c.localId", "c1" ] },
{ "$eq": [ "$$c.localId", "c3" ] }
]
}
}
}
}
}
},
"as": "g",
"cond": {
"$and": [
{ "$eq": [ "$$g.name", "group1" ] },
{ "$gt": [ { "$size": "$$g.contacts" }, 0 ] }
]
}
}
}
}}
])
これは、 $filter
および $map
演算子を使用して、条件を満たす要素のみを配列から返し、 を使用するよりもパフォーマンスがはるかに優れています$unwind
。パイプラインステージは.find()
操作からの「クエリ」と「プロジェクト」の構造を効果的に反映するため、ここでのパフォーマンスは基本的にそのような操作と同等です。
実際に「ドキュメント全体」で「1つ」ではなく「複数の」ドキュメントの詳細をまとめることを目的とする場合、通常は何らかのタイプの$unwind
が必要になります。そのため、「グループ化」のために配列項目にアクセスできるようにします。
これは基本的にアプローチです:
db.accounts.aggregate([
// Match the documents by query
{ "$match": {
"email" : "[email protected]",
"groups.name": "group1",
"groups.contacts.localId": { "$in": [ "c1","c3", null ] },
}},
// De-normalize nested array
{ "$unwind": "$groups" },
{ "$unwind": "$groups.contacts" },
// Filter the actual array elements as desired
{ "$match": {
"groups.name": "group1",
"groups.contacts.localId": { "$in": [ "c1","c3", null ] },
}},
// Group the intermediate result.
{ "$group": {
"_id": { "email": "$email", "name": "$groups.name" },
"contacts": { "$Push": "$groups.contacts" }
}},
// Group the final result
{ "$group": {
"_id": "$_id.email",
"groups": { "$Push": {
"name": "$_id.name",
"contacts": "$contacts"
}}
}}
])
これは、.find()
の基本的な投影機能では実行できない複数の一致に対する「配列フィルタリング」です。
「ネストされた」配列があるため、 $unwind
を2回処理する必要があります。他の操作と一緒に。
集約フレームワークの $ unwind 演算子を使用できます。例えば:
db.contact.aggregate({$unwind:'$groups'}, {$unwind:'$groups.contacts'}, {$match:{email:'[email protected]', 'groups.name':'group1', 'groups.contacts.localId':{$in:['c1', 'c3', 'whatever']}}});
次の結果が得られます。
{ "_id" : ObjectId("5500103e706342bc096e2e14"), "email" : "[email protected]", "groups" : { "name" : "group1", "contacts" : { "localId" : "c1", "address" : "some address 1" } } }
{ "_id" : ObjectId("5500103e706342bc096e2e14"), "email" : "[email protected]", "groups" : { "name" : "group1", "contacts" : { "localId" : "c3", "address" : "some address 3" } } }
1つのオブジェクトのみが必要な場合は、 $ group 演算子を使用できます。