Актуализация за 2017 г.
Такъв добре поставен въпрос заслужава съвременен отговор. Видът на филтрирането на масива, който се иска, всъщност може да бъде направен в съвременните версии на MongoDB след 3.2 чрез просто $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()
работа, производителността тук е основно наравно с тази и операция.
Обърнете внимание, че когато намерението е действително да работите „всички документи“ за да съберете подробности от "множество" документи, а не от "един", тогава това обикновено изисква някакъв тип $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
два пъти. Заедно с другите операции.