Uppdatering 2017
En sådan välställd fråga förtjänar ett modernt svar. Den typ av array-filtrering som efterfrågas kan faktiskt göras i moderna MongoDB-utgåvor efter 3.2 via helt enkelt $match
och $project
pipeline-steg, ungefär som den ursprungliga vanliga frågeoperationen avser.
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 ] }
]
}
}
}
}}
])
Detta använder sig av $filter
och $map
operatörer att endast returnera elementen från arrayerna som skulle uppfylla villkoren, och är mycket bättre för prestanda än att använda $unwind
. Eftersom pipeline-stegen effektivt speglar strukturen för "query" och "project" från en .find()
drift, prestandan här är i princip i nivå med sådan och drift.
Observera att där avsikten är att faktiskt arbeta "över dokument" för att sammanföra detaljer från "flera" dokument snarare än "ett", då skulle detta vanligtvis kräva någon typ av $unwind
operation för att göra det, vilket gör det möjligt för arrayobjekten att vara tillgängliga för "gruppering".
Detta är i grunden tillvägagångssättet:
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"
}}
}}
])
Detta är "matrisfiltrering" på mer än en enda matchning som de grundläggande projektionsmöjligheterna för .find()
kan inte göra.
Du har "kapslade" arrayer och därför måste du bearbeta $unwind
dubbelt. Tillsammans med de andra operationerna.