La domanda qui riguarda in realtà qualcosa di diverso e non ha bisogno di $lookup
affatto. Ma per chiunque arrivi qui esclusivamente dal titolo di "filtraggio dopo $lookup", queste sono le tecniche per te:
MongoDB 3.6 - Sottopipeline
db.test.aggregate([
{ "$match": { "id": 100 } },
{ "$lookup": {
"from": "test",
"let": { "id": "$id" },
"pipeline": [
{ "$match": {
"value": "1",
"$expr": { "$in": [ "$$id", "$contain" ] }
}}
],
"as": "childs"
}}
])
Precedente - $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 ti chiedi perché dovresti $unwind
invece di usare $filter
sull'array, quindi leggi Aggregate $lookup La dimensione totale dei documenti nella pipeline corrispondente supera la dimensione massima del documento per tutti i dettagli sul motivo per cui questo è generalmente necessario e molto più ottimale.
Per le versioni di MongoDB 3.6 e successive, la "sotto-pipeline" più espressiva è generalmente quella che vuoi "filtrare" i risultati della raccolta straniera prima che qualcosa venga restituito nell'array.
Torniamo alla risposta, che in realtà descrive perché la domanda posta non ha bisogno di "nessuna partecipazione"....
Originale
Usando $lookup
come questo non è il modo più "efficiente" per fare quello che vuoi qui. Ma ne parleremo più avanti.
Come concetto di base, usa semplicemente $filter
sull'array risultante:
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" ] }
}
}
}}
]);
Oppure usa $redact
invece:
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"
}
}}
]);
Entrambi ottengono lo stesso risultato:
{
"_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 ]
}
]
}
La conclusione è che $lookup
di per sé non può "ancora" interrogare per selezionare solo determinati dati. Quindi tutti i "filtri" devono essere eseguiti dopo il $lookup
Ma davvero per questo tipo di "partecipazione automatica" è meglio non usare $lookup
del tutto ed evitando il sovraccarico di una lettura aggiuntiva e "unione hash" del tutto. Basta recuperare gli elementi correlati e $group
invece:
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 ] }
}
}
}}
])
Il che risulta solo leggermente diverso perché ho deliberatamente rimosso i campi estranei. Aggiungili tu stesso se lo desideri davvero:
{
"_id" : 100,
"value" : "0",
"childs" : [
{
"_id" : ObjectId("570557d4094a4514fc1291d7"),
"id" : 110,
"value" : "1",
"contain" : [ 100 ]
},
{
"_id" : ObjectId("570557d4094a4514fc1291d8"),
"id" : 120,
"value" : "1",
"contain" : [ 100 ]
}
]
}
Quindi l'unico vero problema qui è "filtrare" qualsiasi null
risultato dell'array, creato quando il documento corrente era il parent
nell'elaborazione di elementi in $push
.
Quello che sembra anche che manchi qui è che il risultato che stai cercando non ha bisogno di aggregazioni o "sottoquery". La struttura che hai concluso o eventualmente trovato altrove è "progettata" in modo da poter ottenere un "nodo" e tutti i suoi "figli" in un'unica richiesta di query.
Ciò significa che solo la "query" è tutto ciò che è veramente necessario e la raccolta dei dati (che è tutto ciò che sta accadendo poiché nessun contenuto viene effettivamente "ridotto") è solo una funzione dell'iterazione del risultato del cursore:
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);
Questo fa esattamente la stessa cosa:
{
"_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 come prova che tutto ciò che devi davvero fare qui è emettere la query "singola" per selezionare sia il genitore che i figli. I dati restituiti sono gli stessi e tutto ciò che stai facendo sul server o sul client è "massaggiare" in un altro formato raccolto.
Questo è uno di quei casi in cui puoi rimanere "preso" nel pensare a come hai fatto le cose in un database "relazionale" e non renderti conto che poiché il modo in cui i dati sono archiviati è "cambiato", non è più necessario utilizzare lo stesso approccio.
Questo è esattamente il punto dell'esempio di documentazione "Strutture ad albero del modello con riferimenti figlio" nella sua struttura, dove semplifica la selezione di genitori e figli all'interno di una query.