如何在$ lookup之后添加过滤器,还是有其他方法可以执行此操作?
我的数据收集测试是:
{ "_id" : ObjectId("570557d4094a4514fc1291d6"), "id" : 100, "value" : "0", "contain" : [ ] }
{ "_id" : ObjectId("570557d4094a4514fc1291d7"), "id" : 110, "value" : "1", "contain" : [ 100 ] }
{ "_id" : ObjectId("570557d4094a4514fc1291d8"), "id" : 120, "value" : "1", "contain" : [ 100 ] }
{ "_id" : ObjectId("570557d4094a4514fc1291d9"), "id" : 121, "value" : "2", "contain" : [ 100, 120 ] }
我选择id 100并聚合孩子:
db.test.aggregate([ {
$match : {
id: 100
}
}, {
$lookup : {
from : "test",
localField : "id",
foreignField : "contain",
as : "childs"
}
}]);
我回来了:
{
"_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 ]
},
{
"_id":ObjectId("570557d4094a4514fc1291d9"),
"id":121,
"value":"2",
"contain":[ 100, 120 ]
}
]
}
但我只想要与"值匹配的孩子:1"
最后我期待这个结果:
{
"_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 ]
}
]
}
答案 0 :(得分:55)
这里的问题实际上是关于不同的东西,根本不需要$lookup
。但对于任何到达这里的人来说,纯粹来自"在$ lookup"之后进行过滤。那么这些技巧适合你:
db.test.aggregate([
{ "$match": { "id": 100 } },
{ "$lookup": {
"from": "test",
"let": { "id": "$id" },
"pipeline": [
{ "$match": {
"value": "1",
"$expr": { "$in": [ "$$id", "$contain" ] }
}}
],
"as": "childs"
}}
])
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,了解为什么这通常是必要的并且更加优化。< / p>
对于MongoDB 3.6及更高版本的版本,那么更具表现力的&#34;子管道&#34;通常是你想要的&#34;过滤&#34;在将任何内容返回到数组之前,外部集合的结果。
回到答案,虽然它实际上描述了为什么问题需要&#34;没有加入&#34;一点......
使用$lookup
这样的效率并不是最高效的&#34;在这里做你想做的事。但是稍后会更多。
作为一个基本概念,只需在结果数组上使用$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
本身不能&#34;然而&#34;查询只选择某些数据。所以&#34;过滤&#34;需要在$lookup
但真的对于这种类型的&#34;自我加入&#34;你最好不要使用$lookup
并避免额外读取的开销和#34;哈希合并&#34;完全。只需获取相关项目,然后$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 ]
}
]
}
所以这里唯一真正的问题是&#34;过滤&#34;数组中的任何null
结果,在处理项目$push
时当前文档为parent
时创建。
您在这里似乎也缺少的是您要查找的结果不需要聚合或&#34;子查询&#34;一点都不你已经在其他地方找到或可能找到的结构是&#34;设计&#34;这样你就可以获得一个节点&#34;以及所有这些&#34;孩子&#34;在单个查询请求中。
这意味着只是&#34;查询&#34;是所有真正需要的东西,数据收集(这是因为没有内容实际上正在发生的所有事情&#34;简化&#34;)只是迭代游标结果的函数:
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
]
}
]
}
并且证明你在这里真正需要做的就是发出&#34;单身&#34;查询以选择父项和子项。返回的数据是一样的,你在服务器或客户端上所做的只是&#34;按摩&#34;另一种收集的格式。
这是你可以赶上的案例之一&#34;思考你是如何在一个关系中做事的。数据库,并没有意识到,由于数据的存储方式已经改变了#34;,您不再需要使用相同的方法。
这正是文档示例"Model Tree Structures with Child References"在其结构中的重点,它可以在一个查询中轻松选择父项和子项。