2016-11-10 104 views
10

我有一個MongoDB中具有樹結構的文檔列表,其中使用Model Tree Structures with Parent References模式。我想要一個聚合查詢返回祖先列表(直到根),給定'name'屬性。遞歸搜索MongoDB中的集合

結構:

{ 
    '_id': '1', 
    'name': 'A', 
    'parent': '', 
}, 
{ 
    '_id': '2', 
    'name': 'B', 
    'parent': 'A', 
}, 
{ 
    '_id': '3', 
    'name': 'C', 
    'parent': 'B', 
}, 
{ 
    '_id': '4', 
    'name': 'D', 
    'parent': 'C', 
} 

聚合的結果:(考慮,名字= 'd')

{ 
    '_id': '4', 
    'name': 'D', 
    'ancestors': [{name:'C'}, {name:'B'}, {name:'A'}] 
} 

Note:我現在不能更改文檔的結構。這會造成很多問題。我看到許多解決方案建議使用Model Tree Structures with an Array of Ancestors。但我現在無法使用它。有沒有什麼辦法可以通過使用單個聚合查詢來實現上述模式?謝謝

+0

爲什麼'_id'字符串? – styvane

+0

@Styvane這只是一個例子。實際的文檔將有ObjectId – RaR

+0

@RaR有沒有關於Styvane的答案,不適合你,這是提示賞金? – JohnnyHK

回答

11

從MongoDB 3.4開始,我們可以用Aggregation Framework來做到這一點。

我們管道中第一個也是最重要的階段是$graphLookup階段。 $graphLookup允許我們遞歸匹配「父」和「名」字段。因此,我們得到每個「名稱」的祖先。

在管道中的下一個階段是$match階段,我們只需選擇「名」,我們感興趣的

最後一個階段是我們應用的體現了「老祖宗」的$addFields$project階段數組使用$map數組運算符。

當然與$reverseArray運營商我們reverse our array爲了得到預期的結果。

db.collection.aggregate(
    [ 
     { "$graphLookup": { 
      "from": "collection", 
      "startWith": "$parent", 
      "connectFromField": "parent", 
      "connectToField": "name", 
      "as": "ancestors" 
     }}, 
     { "$match": { "name": "D" } }, 
     { "$addFields": { 
      "ancestors": { 
       "$reverseArray": { 
        "$map": { 
         "input": "$ancestors", 
         "as": "t", 
         "in": { "name": "$$t.name" } 
        } 
       } 
      } 
     }} 
    ] 
) 
1

如果您打開使用客戶端的JavaScript,你可以使用遞歸的蒙戈外殼來實現這一目標:

var pushAncesstors = function (name, doc) { 
    if(doc.parent) { 
    db.collection.update({name : name}, {$addToSet : {"ancesstors" : {name : doc.parent}}}); 
    pushAncesstors(name, db.collection.findOne({name : doc.parent})) 
    } 
} 

db.collection.find().forEach(function (doc){ 
    pushAncesstors(doc.name, doc); 
}) 

這會給你一個完整的hirearchy的所有產品。輸出示例:

{ "_id" : "1", "name" : "A", "parent" : "" } 
{ "_id" : "2", "name" : "B", "parent" : "A", "ancesstors" : [ { "name" : "A" } ] } 
{ "_id" : "3", "name" : "C", "parent" : "B", "ancesstors" : [ { "name" : "B" }, { "name" : "A" } ] } 
{ "_id" : "4", "name" : "D", "parent" : "C", "ancesstors" : [ { "name" : "C" }, { "name" : "B" }, { "name" : "A" } ] } 

如果你的要求是不更新正確的集合,在不同影響集合插入數據和更新在那裏。 pushAncesstors函數將更改爲:

var pushAncesstors = function (name, doc) { 
    if(doc.parent) { 
    db.outputColl.save(doc) 
    db.outputColl.update({name : name}, {$addToSet : {"ancesstors" : {name : doc.parent}}}); 
    pushAncesstors(name, db.collection.findOne({name : doc.parent})) 
    } 
} 
+0

感謝您的回答。是的,我願意使用客戶端JavaScript。但是,上面的那個會更新現有的文件,對吧?需要的是獲取層次結構,而不是更新文檔。 – RaR

+0

已更新答案以保持當前集合未修改。 – ares