这里的问题实际上是关于不同的东西,根本不需要$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
]
}
]
}
并证明您在这里真正需要做的就是发出“单一”查询以选择父项和子项。返回的数据是一样的,你在服务器或客户端上所做的一切都是“按摩”成另一种收集的格式。
这是其中一种情况,您可能会“陷入”思考如何在“关系”数据库中做事,而没有意识到由于数据的存储方式已经“改变”,您不再需要使用同样的方法。
这正是文档示例“带有子引用的模型树结构”在其结构中的意义所在,它可以轻松地在一个查询中选择父项和子项。