3 回答

TA貢獻1946條經驗 獲得超4個贊
這里的問題實際上是關于一些不同的東西,根本不需要$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
]
}
]
}
并證明您在這里真正需要做的就是發出“單個”查詢以選擇父項和子項。返回的數據是相同的,并且您在服務器或客戶端上所做的所有工作都在“按摩”為另一種收集的格式。
這是在這種情況下您可以“思考”如何在“關系”數據庫中做事的情況之一,而沒有意識到由于數據的存儲方式已“改變”,因此您不再需要使用同樣的方法。
這正是文檔示例“帶有子引用的模型樹結構”結構中的要點,在該示例中可以輕松地在一個查詢中選擇父級和子級。

TA貢獻1757條經驗 獲得超7個贊
就“性能”而言$lookup
,實際上是在“服務器”上進行“兩個”查詢。“客戶端”可以執行“兩個”查詢,但是當然會涉及網絡和響應開銷,這將減慢該過程。因此,為什么要這么做$lookup
。但是我也是說您的案子不需要這個。所有項目已經在同一個集合中,因此只需簡單地選擇它們并$group
相應地選擇它們即可。就“性能”而言,到目前為止,這是三個中更好的選擇。
添加回答
舉報