To pytanie dotyczy w rzeczywistości czegoś innego i nie wymaga $lookup
w ogóle. Ale dla każdego, kto przybywa tutaj wyłącznie z tytułu „filtrowanie po $lookup”, oto techniki dla ciebie:
MongoDB 3.6 — podpotok
db.test.aggregate([
{ "$match": { "id": 100 } },
{ "$lookup": {
"from": "test",
"let": { "id": "$id" },
"pipeline": [
{ "$match": {
"value": "1",
"$expr": { "$in": [ "$$id", "$contain" ] }
}}
],
"as": "childs"
}}
])
Wcześniej – $wyszukaj + $odpręż + $dopasuj połączenie
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" }
}}
])
Jeśli zastanawiasz się, dlaczego miałbyś $unwind
w przeciwieństwie do używania $filter
na tablicy, a następnie przeczytaj Aggregate $lookup Całkowity rozmiar dokumentów w dopasowanym potoku przekracza maksymalny rozmiar dokumentu, aby poznać wszystkie szczegóły, dlaczego jest to ogólnie konieczne i znacznie bardziej optymalne.
W przypadku wydań MongoDB 3.6 i późniejszych, bardziej wyrazisty „podpotok” jest zazwyczaj tym, co chcesz „filtrować” wyniki obcego zbioru, zanim cokolwiek w ogóle zostanie zwrócone do tablicy.
Wracając do odpowiedzi, która właściwie opisuje, dlaczego zadane pytanie w ogóle nie wymaga dołączania....
Oryginał
Korzystanie z $lookup
w ten sposób nie jest to najbardziej „efektywny” sposób robienia tego, co chcesz tutaj. Ale o tym później.
Jako podstawową koncepcję, po prostu użyj $filter
na wynikowej tablicy:
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" ] }
}
}
}}
]);
Lub użyj $redact
zamiast tego:
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"
}
}}
]);
Oba uzyskują ten sam wynik:
{
"_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 ]
}
]
}
Najważniejsze jest to, że $lookup
samo nie może "jeszcze" wysyłać zapytań, aby wybrać tylko określone dane. Tak więc całe "filtrowanie" musi nastąpić po $lookup
Ale tak naprawdę dla tego typu „samodzielnego dołączenia” lepiej nie używać $lookup
w ogóle i unikając narzutów związanych z dodatkowym odczytem i „scalaniem skrótów”. Po prostu pobierz powiązane elementy i $group
zamiast tego:
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 ] }
}
}
}}
])
Co wychodzi tylko trochę inaczej, ponieważ celowo usunąłem obce pola. Dodaj je w sobie, jeśli naprawdę chcesz:
{
"_id" : 100,
"value" : "0",
"childs" : [
{
"_id" : ObjectId("570557d4094a4514fc1291d7"),
"id" : 110,
"value" : "1",
"contain" : [ 100 ]
},
{
"_id" : ObjectId("570557d4094a4514fc1291d8"),
"id" : 120,
"value" : "1",
"contain" : [ 100 ]
}
]
}
Więc jedynym prawdziwym problemem jest tutaj "filtrowanie" dowolnych null
wynik z tablicy utworzonej, gdy bieżący dokument był parent
w przetwarzaniu elementów do $push
.
Wydaje się również, że brakuje Ci tutaj tego, że wynik, którego szukasz, w ogóle nie wymaga agregacji ani „podzapytań”. Struktura, którą zawarłeś lub prawdopodobnie znalazłeś gdzie indziej, jest „zaprojektowana” tak, abyś mógł uzyskać „węzeł” i wszystkie jego „dzieci” w jednym żądaniu zapytania.
Oznacza to, że tylko „zapytanie” jest wszystkim, co jest naprawdę potrzebne, a zbieranie danych (czyli wszystko, co się dzieje, ponieważ żadna zawartość nie jest tak naprawdę „redukowana”) jest tylko funkcją iteracji wyniku kursora:
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);
To robi dokładnie to samo:
{
"_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
]
}
]
}
I służy jako dowód, że wszystko, co naprawdę musisz zrobić, to wydać „pojedyncze” zapytanie, aby wybrać zarówno rodzica, jak i dzieci. Zwrócone dane są takie same, a wszystko, co robisz na serwerze lub kliencie, to "masowanie" do innego zebranego formatu.
Jest to jeden z tych przypadków, w których możesz „przyłapać się” na myśleniu o tym, jak robiłeś rzeczy w „relacyjnej” bazie danych i nie zdawać sobie sprawy, że ponieważ sposób przechowywania danych „zmienił się”, nie musisz już używać to samo podejście.
Dokładnie o to chodzi w przykładzie dokumentacji "Modelowe struktury drzew z odniesieniami podrzędnymi" w swojej strukturze, która ułatwia wybór rodziców i dzieci w ramach jednego zapytania.