Frågan här handlar faktiskt om något annat och behöver inte $lookup
alls. Men för alla som kommer hit enbart från titeln "filtrering efter $lookup" så är dessa tekniker för dig:
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"
}}
])
Tidigare - $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" }
}}
])
Om du frågar varför skulle du $unwind
i motsats till att använda $filter
på arrayen, läs sedan Aggregate $lookup Den totala storleken på dokument i matchande pipeline överstiger den maximala dokumentstorleken för alla detaljer om varför detta generellt är nödvändigt och mycket mer optimalt.
För utgåvor av MongoDB 3.6 och senare är den mer uttrycksfulla "sub-pipeline" i allmänhet vad du vill "filtrera" resultaten av den utländska samlingen innan någonting alls returneras till arrayen.
Tillbaka till svaret dock som faktiskt beskriver varför den ställda frågan behöver "ingen join" alls....
Original
Använder $lookup
som att detta inte är det mest "effektiva" sättet att göra vad du vill här. Men mer om detta senare.
Som ett grundläggande koncept, använd bara $filter
på den resulterande arrayen:
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" ] }
}
}
}}
]);
Eller använd $redact
istället:
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"
}
}}
]);
Båda får samma resultat:
{
"_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 ]
}
]
}
Slutsatsen är att $lookup
själv kan inte "ännu" fråga för att bara välja vissa data. Så all "filtrering" måste ske efter $lookup
Men egentligen för den här typen av "self join" är det bättre att du inte använder $lookup
överhuvudtaget och undvika omkostnader för ytterligare en läsning och "hash-merge" helt. Hämta bara de relaterade objekten och $group
istället:
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 ] }
}
}
}}
])
Vilket bara blir lite annorlunda eftersom jag medvetet tagit bort de främmande fälten. Lägg till dem själv om du verkligen vill:
{
"_id" : 100,
"value" : "0",
"childs" : [
{
"_id" : ObjectId("570557d4094a4514fc1291d7"),
"id" : 110,
"value" : "1",
"contain" : [ 100 ]
},
{
"_id" : ObjectId("570557d4094a4514fc1291d8"),
"id" : 120,
"value" : "1",
"contain" : [ 100 ]
}
]
}
Så det enda verkliga problemet här är att "filtrera" någon null
resultat från arrayen, skapad när det aktuella dokumentet var parent
vid bearbetning av objekt till $push
.
Det du också verkar sakna här är att resultatet du letar efter inte alls behöver aggregering eller "sub-queries". Strukturen som du har kommit fram till eller möjligen hittat någon annanstans är "designad" så att du kan få en "nod" och alla dess "barn" i en enda frågeförfrågan.
Det betyder att bara "frågan" är allt som verkligen behövs, och datainsamlingen (vilket är allt som händer eftersom inget innehåll verkligen "minskas") är bara en funktion av att iterera markörresultatet:
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);
Detta gör exakt samma sak:
{
"_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
]
}
]
}
Och fungerar som ett bevis på att allt du verkligen behöver göra här är att skicka en "enda" fråga för att välja både förälder och barn. Den returnerade informationen är precis densamma, och allt du gör på antingen servern eller klienten är att "massera" till ett annat insamlat format.
Detta är ett av de fall där du kan bli "fångad" av att tänka på hur du gjorde saker i en "relationell" databas, och inte inse att eftersom hur data lagras har "förändrats" behöver du inte längre använda samma tillvägagångssätt.
Det är precis vad poängen med dokumentationsexemplet "Modellträdstrukturer med underordnade referenser" i sin struktur, där det gör det enkelt att välja föräldrar och barn inom en fråga.