【问题标题】:Recursive search on a collection in MongoDB在 MongoDB 中对集合进行递归搜索
【发布时间】:2018-06-13 21:43:01
【问题描述】:

我在 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',
}

聚合结果:(Given, name = 'D')

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

Note:我现在无法更改文档结构。会引起很多问题。我看到许多建议使用Model Tree Structures with an Array of Ancestors 的解决方案。但我现在不能使用它。有没有办法使用单个聚合查询通过上述模式来实现它?谢谢

【问题讨论】:

  • 为什么是_id 字符串?
  • @Styvane 这只是一个例子。实际文档将具有 ObjectId
  • @RaR Styvane 的回答是否有什么对你不起作用的东西促使赏金?
  • @JohnnyHK Styvane 的回答将适用于 MongoDB v3.4 我一直在寻找一种适用于 MongoDB v2.6 及更高版本的方法

标签: mongodb aggregation-framework tree-structure


【解决方案1】:

从 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" }
                    } 
                } 
            }
        }}
    ]
)

【讨论】:

  • 嗨,如果我的名字是 name = 'A' 而不是 name = 'D',我该如何更改?我需要所有祖先为 A 的文档。
  • @user3629892,我不确定我是否理解您的问题。你的意思是你有'A'而不是'D'?好吧,如果是这种情况,只需将 'D' 替换为 'A' 甚至更好,您可以将 name 分配给一个变量并在您的管道中使用该变量。
  • 我的意思是我有条目,每个条目都引用了它们的父级。给定一个叶节点(没有子节点),我想找到它的父节点和父节点的父节点等等......作为输入,我没有得到父节点,而是叶节点,所以在 OP 的示例中它会是 D 而不是 A。我会试试你的建议
  • 嗨,我有类似的情况,但是,我的用户有追随者,而那些追随者有很多追随者,我需要递归地获取第一个用户的追随者以递归方式下降。
【解决方案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}))
  }
}

【讨论】:

  • 感谢您的回答。是的,我愿意使用客户端 javascript。但是,上面的会更新现有的文件,对吧?需要的是获取层次结构而不是更新文档。
  • 更新了答案以保持当前集合不变。
  • 这是将数据带入逻辑(代码)的经典示例,非常昂贵。我建议您使用 mongo api(便宜的操作)将您的逻辑(代码)发送到数据。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-12-02
  • 1970-01-01
  • 2015-11-29
  • 1970-01-01
  • 2014-07-31
  • 1970-01-01
  • 2021-04-17
相关资源
最近更新 更多