Ответ 1
Вопрос здесь на самом деле о чем-то другом и вообще не нуждается в $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
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
]
}
]
}
И служит доказательством того, что все, что вам действительно нужно сделать, - это выполнить "одиночный" запрос, чтобы выбрать и родителя, и потомка. Возвращенные данные точно такие же, и все, что вы делаете на сервере или клиенте, это "массирует" в другой собранный формат.
Это один из тех случаев, когда вы можете "догнать" себя, думая о том, как вы работали в "реляционной" базе данных, и не понимать, что, поскольку способ хранения данных "изменился", вам больше не нужно использовать тот же подход.
Это именно то, что указывает на пример документации "Структуры дерева моделей с дочерними ссылками" в его структуре, где он позволяет легко выбирать родителей и детей в одном запросе.