Atualização de 2017
Uma pergunta tão bem colocada merece uma resposta moderna. O tipo de filtragem de array solicitado pode realmente ser feito nas versões modernas do MongoDB pós 3.2 através de simplesmente
$match
e $project
estágios de pipeline, bem como a operação de consulta simples original pretende. 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 ] }
]
}
}
}
}}
])
Isso faz uso do
$filter
e $map
operadores para retornar apenas os elementos dos arrays conforme as condições, e é muito melhor para o desempenho do que usar $unwind
. Como os estágios do pipeline refletem efetivamente a estrutura de "consulta" e "projeto" de um .find()
operação, o desempenho aqui é basicamente a par com tal e operação. Observe que onde a intenção é realmente trabalhar "entre documentos" para reunir detalhes de "vários" documentos em vez de "um", isso normalmente exigiria algum tipo de
$unwind
operação para fazê-lo, permitindo assim que os itens da matriz sejam acessíveis para "agrupamento". Esta é basicamente a abordagem:
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"
}}
}}
])
Isso é "filtragem de matriz" em mais de uma única correspondência que os recursos básicos de projeção de
.find()
Não pode fazer. Você tem arrays "aninhados", portanto, você precisa processar
$unwind
duas vezes. Junto com as outras operações.