A questão aqui é na verdade sobre algo diferente e não precisa de
$lookup
de forma alguma. Mas para quem chega aqui puramente pelo título de "filtragem após $ lookup", estas são as técnicas para você:MongoDB 3.6 - Sub-pipeline
db.test.aggregate([
{ "$match": { "id": 100 } },
{ "$lookup": {
"from": "test",
"let": { "id": "$id" },
"pipeline": [
{ "$match": {
"value": "1",
"$expr": { "$in": [ "$$id", "$contain" ] }
}}
],
"as": "childs"
}}
])
Anteriormente - $lookup + $unwind + $match coalescence
db.test.aggregate([
{ "$match": { "id": 100 } },
{ "$lookup": {
"from": "test",
"localField": "id",
"foreignField": "contain",
"as": "childs"
}},
{ "$unwind": "$childs" },
{ "$match": { "childs.value": "1" } },
{ "$group": {
"_id": "$_id",
"id": { "$first": "$id" },
"value": { "$first": "$value" },
"contain": { "$first": "$contain" },
"childs": { "$push": "$childs" }
}}
])
Se você questionar por que você
$unwind
ao invés de usar $filter
no array, então leia Agregado $lookup O tamanho total dos documentos no pipeline de correspondência excede o tamanho máximo do documento para todos os detalhes sobre por que isso é geralmente necessário e muito mais ideal. Para versões do MongoDB 3.6 e posteriores, o "sub-pipeline" mais expressivo geralmente é o que você deseja "filtrar" os resultados da coleção estrangeira antes que qualquer coisa seja retornada ao array.
De volta à resposta, que realmente descreve por que a pergunta feita não precisa de "nenhuma junção" ....
Original
Usando
$lookup
como esta não é a maneira mais "eficiente" de fazer o que você quer aqui. Mas mais sobre isso mais tarde. Como conceito básico, basta usar
$filter
na matriz resultante:db.test.aggregate([
{ "$match": { "id": 100 } },
{ "$lookup": {
"from": "test",
"localField": "id",
"foreignField": "contain",
"as": "childs"
}},
{ "$project": {
"id": 1,
"value": 1,
"contain": 1,
"childs": {
"$filter": {
"input": "$childs",
"as": "child",
"cond": { "$eq": [ "$$child.value", "1" ] }
}
}
}}
]);
Ou use
$redact
em vez de:db.test.aggregate([
{ "$match": { "id": 100 } },
{ "$lookup": {
"from": "test",
"localField": "id",
"foreignField": "contain",
"as": "childs"
}},
{ "$redact": {
"$cond": {
"if": {
"$or": [
{ "$eq": [ "$value", "0" ] },
{ "$eq": [ "$value", "1" ] }
]
},
"then": "$$DESCEND",
"else": "$$PRUNE"
}
}}
]);
Ambos obtêm o mesmo resultado:
{
"_id":ObjectId("570557d4094a4514fc1291d6"),
"id":100,
"value":"0",
"contain":[ ],
"childs":[ {
"_id":ObjectId("570557d4094a4514fc1291d7"),
"id":110,
"value":"1",
"contain":[ 100 ]
},
{
"_id":ObjectId("570557d4094a4514fc1291d8"),
"id":120,
"value":"1",
"contain":[ 100 ]
}
]
}
A conclusão é que
$lookup
em si não pode consultar "ainda" para selecionar apenas determinados dados. Portanto, toda a "filtragem" precisa acontecer após o $lookup
Mas realmente para este tipo de "auto-junção" é melhor não usar
$lookup
e evitando a sobrecarga de uma leitura adicional e "hash-merge" inteiramente. Basta buscar os itens relacionados e $group
em vez de:db.test.aggregate([
{ "$match": {
"$or": [
{ "id": 100 },
{ "contain.0": 100, "value": "1" }
]
}},
{ "$group": {
"_id": {
"$cond": {
"if": { "$eq": [ "$value", "0" ] },
"then": "$id",
"else": { "$arrayElemAt": [ "$contain", 0 ] }
}
},
"value": { "$first": { "$literal": "0"} },
"childs": {
"$push": {
"$cond": {
"if": { "$ne": [ "$value", "0" ] },
"then": "$$ROOT",
"else": null
}
}
}
}},
{ "$project": {
"value": 1,
"childs": {
"$filter": {
"input": "$childs",
"as": "child",
"cond": { "$ne": [ "$$child", null ] }
}
}
}}
])
O que só sai um pouco diferente porque eu removi deliberadamente os campos estranhos. Adicione-os em você mesmo se você realmente quiser:
{
"_id" : 100,
"value" : "0",
"childs" : [
{
"_id" : ObjectId("570557d4094a4514fc1291d7"),
"id" : 110,
"value" : "1",
"contain" : [ 100 ]
},
{
"_id" : ObjectId("570557d4094a4514fc1291d8"),
"id" : 120,
"value" : "1",
"contain" : [ 100 ]
}
]
}
Portanto, o único problema real aqui é "filtrar" qualquer
null
resultado do array, criado quando o documento atual era o parent
no processamento de itens para $push
. O que você também parece estar faltando aqui é que o resultado que você está procurando não precisa de agregação ou "sub-consultas". A estrutura que você concluiu ou possivelmente encontrou em outro lugar é "projetada" para que você possa obter um "nó" e todos os seus "filhos" em uma única solicitação de consulta.
Isso significa que apenas a "consulta" é tudo o que é realmente necessário, e a coleta de dados (que é tudo o que está acontecendo, pois nenhum conteúdo está realmente sendo "reduzido") é apenas uma função de iterar o resultado do cursor:
var result = {};
db.test.find({
"$or": [
{ "id": 100 },
{ "contain.0": 100, "value": "1" }
]
}).sort({ "contain.0": 1 }).forEach(function(doc) {
if ( doc.id == 100 ) {
result = doc;
result.childs = []
} else {
result.childs.push(doc)
}
})
printjson(result);
Isso faz exatamente a mesma coisa:
{
"_id" : ObjectId("570557d4094a4514fc1291d6"),
"id" : 100,
"value" : "0",
"contain" : [ ],
"childs" : [
{
"_id" : ObjectId("570557d4094a4514fc1291d7"),
"id" : 110,
"value" : "1",
"contain" : [
100
]
},
{
"_id" : ObjectId("570557d4094a4514fc1291d8"),
"id" : 120,
"value" : "1",
"contain" : [
100
]
}
]
}
E serve como prova de que tudo o que você realmente precisa fazer aqui é emitir a consulta "única" para selecionar o pai e os filhos. Os dados retornados são exatamente os mesmos, e tudo o que você está fazendo no servidor ou no cliente é "massagem" em outro formato coletado.
Este é um daqueles casos em que você pode ficar "pego" pensando em como fez as coisas em um banco de dados "relacional" e não perceber que, como a maneira como os dados são armazenados "mudou", você não precisa mais usar a mesma abordagem.
Esse é exatamente o objetivo do exemplo de documentação "Modelo de estruturas de árvore com referências filho" em sua estrutura, onde facilita a seleção de pais e filhos em uma consulta.