Въпросът тук всъщност е за нещо различно и не се нуждае от $lookup
изобщо. Но за всеки, който пристига тук само от заглавието на „филтриране след $lookup“, това са техниките за вас:
MongoDB 3.6 – Под-тръбопровод
db.test.aggregate([
{ "$match": { "id": 100 } },
{ "$lookup": {
"from": "test",
"let": { "id": "$id" },
"pipeline": [
{ "$match": {
"value": "1",
"$expr": { "$in": [ "$$id", "$contain" ] }
}}
],
"as": "childs"
}}
])
По-рано – $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" }
}}
])
Ако се питате защо бихте $unwind
за разлика от използването на $filter
в масива, след което прочетете Aggregate $lookup Общият размер на документите в съответстващия тръбопровод надвишава максималния размер на документа за всички подробности относно това защо това обикновено е необходимо и далеч по-оптимално.
За версии на MongoDB 3.6 и по-нови, тогава по-изразителният „под-тръбопровод“ обикновено е това, което искате да „филтрирате“ резултатите от чуждата колекция, преди изобщо нещо да бъде върнато в масива.
Обратно към отговора, който всъщност описва защо зададеният въпрос изобщо не се нуждае от „без присъединяване“...
Оригинал
Използване на $lookup
като това не е най-ефикасният начин да правите това, което искате тук. Но повече за това по-късно.
Като основна концепция просто използвайте $filter
върху получения масив:
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" ] }
}
}
}}
]);
Или използвайте $redact
вместо това:
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"
}
}}
]);
И двете получават един и същ резултат:
{
"_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 ]
}
]
}
В крайна сметка $lookup
самата не може "все още" да прави заявка, за да избере само определени данни. Така че цялото "филтриране" трябва да се случи след $lookup
Но наистина за този тип "само присъединяване" е по-добре да не използвате $lookup
изобщо и избягване на допълнителните разходи за допълнително четене и "хеш-сливане" изцяло. Просто извлечете свързаните елементи и $group
вместо това:
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 ] }
}
}
}}
])
Което излиза малко по-различно, защото нарочно премахнах външните полета. Добавете ги в себе си, ако наистина искате:
{
"_id" : 100,
"value" : "0",
"childs" : [
{
"_id" : ObjectId("570557d4094a4514fc1291d7"),
"id" : 110,
"value" : "1",
"contain" : [ 100 ]
},
{
"_id" : ObjectId("570557d4094a4514fc1291d8"),
"id" : 120,
"value" : "1",
"contain" : [ 100 ]
}
]
}
Така че единственият реален проблем тук е "филтрирането" на всеки null
резултат от масива, създаден, когато текущият документ е бил parent
при обработка на елементи към $push
.
Това, което изглежда също ви липсва тук, е, че резултатът, който търсите, изобщо не се нуждае от агрегиране или „подзаявки“. Структурата, която сте сключили или евентуално сте намерили другаде, е „проектирана“ така, че да можете да получите „възел“ и всички негови „деца“ в една заявка за заявка.
Това означава, че само „заявката“ е всичко, което наистина е необходимо, а събирането на данни (което е всичко, което се случва, тъй като никакво съдържание не се „намалява“) е просто функция на повторение на резултата от курсора:
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);
Това прави точно същото нещо:
{
"_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
]
}
]
}
И служи като доказателство, че всичко, което наистина трябва да направите тук, е да издадете "единичната" заявка, за да изберете както родител, така и деца. Върнатите данни са едни и същи и всичко, което правите на сървър или клиент, е „масажиране“ в друг събран формат.
Това е един от онези случаи, в които можете да се „хванете“ да мислите как сте правили нещата в „релационна“ база данни и да не осъзнаете, че тъй като начинът, по който се съхраняват данните, се е „променил“, вече не е необходимо да използвате същия подход.
Точно това е смисълът на примера за документация „Моделни дървовидни структури с препратки към деца“ в неговата структура, където улеснява избора на родители и деца в рамките на една заявка.