绝地无双
这里的问题实际上是关于一些不同的东西,根本不需要$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。您在这里似乎还缺少的是,您要查找的结果根本不需要聚合或“子查询”。您已经结束或可能在其他地方找到的结构是“设计的”,以便您可以在单个查询请求中获得“节点”及其所有“子代”。这意味着只有“ query”才是真正需要的,而数据收集(由于没有真正“减少”任何内容而已)的全部工作只是迭代游标结果的功能: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 ] } ]}并证明您在这里真正需要做的就是发出“单个”查询以选择父项和子项。返回的数据是相同的,并且您在服务器或客户端上所做的所有工作都在“按摩”为另一种收集的格式。这是在这种情况下您可以“思考”如何在“关系”数据库中做事的情况之一,而没有意识到由于数据的存储方式已“改变”,因此您不再需要使用同样的方法。这正是文档示例“带有子引用的模型树结构”结构中的要点,在该示例中可以轻松地在一个查询中选择父级和子级。