对MongoDB中的集合进行递归搜索

时间:2016-11-10 11:10:44

标签: mongodb aggregation-framework tree-structure

我在MongoDB中有一个带有树结构的文档列表,其中使用了Model Tree Structures with Parent References模式。我想要一个单独的聚合查询,它返回祖先列表(直到根),给出' name'属性。

结构:

{
  '_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的解决方案。但我现在不能用它。有没有办法用上面的模式使用单个聚合查询来实现它?谢谢

2 个答案:

答案 0 :(得分:15)

从MongoDB 3.4开始,我们可以使用聚合框架。

我们管道中的第一个也是最重要的阶段是$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 :(得分:2)

如果你打开使用客户端javascript,你可以在mongo shell上使用递归来实现这个目的:

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);
})

这将为您提供所有产品的完整雇佣权。样本输出:

{ "_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}))
  }
}