这里的问题实际上是关于一些不同的东西,根本不需要$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 coalisement
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 Total size of documents in matching pipeline exceeds maximum document size,了解为什么这通常是必要的,而且是更优化的.
对于MongoDB 3.6及以后的版本,更具表现力的"子管道"通常是在任何东西返回到数组之前,您想要"过滤"外部集合的结果.
回到答案上来,它实际上描述了为什么问题需要"不加入"的原因....
Original
像这样使用$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
本身不能"还"查询,只 Select 某些数据.所以所有的"过滤"都需要在$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
个时创建的.
这里您似乎还缺少的是,您所寻找的结果根本不需要聚合或"子查询".您已经总结或可能在其他地方找到的 struct 是"设计"的,这样您就可以在一个查询请求中获得一个" node "及其所有"子 node ".
这意味着真正需要的只是"查询",而数据收集(这是所有正在发生的事情,因为没有真正"减少"任何内容)只是一个迭代光标结果的函数:
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
]
}
]
}
并证明您在这里真正需要做的就是发出"单一"查询来 Select 父项和子项.返回的数据完全相同,您在服务器或客户机上所做的一切都是"按摩"成另一种收集的格式.
在这种情况下,您可能会"陷入"思考如何在"关系"数据库中进行操作,而没有意识到由于数据的存储方式已"改变",因此不再需要使用相同的方法.
这正是文档示例"Model Tree Structures with Child References"在其 struct 中的意义所在,它使得在一个查询中 Select 父项和子项变得容易.