【问题标题】:MongoDB's $graphLookup trying to get a tree structureMongoDB 的 $graphLookup 试图获取树结构
【发布时间】:2017-11-08 23:36:42
【问题描述】:

我正在尝试使用新的 MongoDB v3.4 $graphLookup 聚合管道。我有这个简单的树集合,有一些节点和一个父 DBRef:

{ "_id" : ObjectId("59380657bbdbfb36c18a80f2"), "name" : "Root node 1" },
{ "_id" : ObjectId("5938068abbdbfb36c18a80f5"), "name" : "Child 1.1", "parent" : ObjectId("59380657bbdbfb36c18a80f2") },
{ "_id" : ObjectId("593806b0bbdbfb36c18a80f7"), "name" : "Subchild 1.1.1", "parent" : ObjectId("5938068abbdbfb36c18a80f5") },
{ "_id" : ObjectId("5938068abbdbfb36c18a80f6"), "name" : "Child 1.2", "parent" : ObjectId("59380657bbdbfb36c18a80f2") },
{ "_id" : ObjectId("59380657bbdbfb36c18a80f3"), "name" : "Root node 2" }

我想得到这种树结构:

- Root node 1
    - Child 1.1
        - Subchild 1.1.1
    - Child 1.2
- Root node 2

所以,我正在尝试使用新的 $graphLookup 聚合管道,如下所示:

db.getCollection('tree').aggregate([
    { $match: { parent: { $exists: false } } },
    {
        $graphLookup: {
            from: "tree",
            startWith: "$_id",
            connectFromField: "_id",
            connectToField: "parent",
            as: "children"
        }
   },
   { $sort: { name: 1 } }
])

但我的问题是我在一个集合中获得了“根节点 1”的所有子节点:

{
    "_id" : ObjectId("59380657bbdbfb36c18a80f2"),
    "name" : "Root node 1",
    "children" : [
        { "_id" : ObjectId("593806b0bbdbfb36c18a80f7"), "name" : "Subchild 1.1.1", "parent" : ObjectId("5938068abbdbfb36c18a80f5") },
        { "_id" : ObjectId("5938068abbdbfb36c18a80f6"), "name" : "Child 1.2", "parent" : ObjectId("59380657bbdbfb36c18a80f2") },
        { "_id" : ObjectId("5938068abbdbfb36c18a80f5"), "name" : "Child 1.1", "parent" : ObjectId("59380657bbdbfb36c18a80f2") }
    ]
},
{
    "_id" : ObjectId("59380657bbdbfb36c18a80f3"),
    "name" : "Root node 2",
    "children" : [ ]
}

我不知道如何在“Child 1.1”的儿童集合中递归查找儿童以获得“Subchild 1.1.1”。 我正在寻找任何建议。谢谢:)

【问题讨论】:

    标签: mongodb


    【解决方案1】:

    $graphLookup 不会产生依赖关系的层次结构——它对连接的文档执行递归搜索,但结果会被展平为一维数组。这是文档中的引用:

    对于每个匹配的文档,$graphLookup 获取 _id 并检查 tree 集合中的每个文档 一个匹配的 parent 值。对于每个匹配,$graphLookup 添加 将 from 集合中的文档匹配到数组 children。这一步递归地继续,直到不再 找到匹配的文档,或者直到操作到达 maxDepth 参数指定的递归深度。

    即它递归地搜索依赖文档,但无论孩子位于多“深”,每个找到的文档都会添加到父文档的相同子数组中。


    注意 - 您看不到 Child 1.1 及其连接的 Subchild 1.1.1,因为您在 match 阶段过滤掉了这些文档:

    { $match: { parent: { $exists: false } } }
    

    只选择没有父级的文档 - "Root node 1""Root node 2"。如果您将删除此过滤器,则将返回所有其他具有其依赖层次结构的文档:

    {
        "name" : "Child 1.1",
        "children" : [ 
            { "name" : "Subchild 1.1.1" }
        ]
    },
    {
        "name" : "Child 1.2"
        "children" : []
    },
    {
        "name" : "Root node 1",
        "children" : [ 
            { "name" : "Subchild 1.1.1" }, 
            { "name" : "Child 1.2" }, 
            { "name" : "Child 1.1" }
        ]
    },
    {
        "name" : "Root node 2",
        "children" : []
    },
    {
        "name" : "Subchild 1.1.1"
        "children" : []
    }
    

    如果您不想在单个子数组中混合来自不同“深度”树的子数组,请查看文档中的有趣评论

    将 maxDepth 字段设置为 0 等价于非递归 $lookup 搜索阶段。

    这意味着每个文档都会将其所有直接子元素放入 children 数组中,之后查找将停止,无需任何进一步的递归搜索。输出将是

    {
        "name" : "Child 1.1",
        "children" : [ 
            { "name" : "Subchild 1.1.1" }
        ]
    },
    {
        "name" : "Child 1.2"
        "children" : []
    },
    {
        "name" : "Root node 1",
        "children" : [ 
            { "name" : "Child 1.2" }, 
            { "name" : "Child 1.1" }
        ]
    },
    {
        "name" : "Root node 2",
        "children" : []
    },
    {
        "name" : "Subchild 1.1.1"
        "children" : []
    }
    

    【讨论】:

    • 感谢您非常完整的回答!您的解释更清楚:“它对连接的文档执行递归搜索,但结果被展平为一维数组”。所以基本上, $graphLookup 不能处理依赖的层次结构。如果我需要计算一个完整的树结构,那么它不是合适的工具。我可以重新计算您在评论中提到的结果(没有 $match)或寻找其他解决方案:)
    • @JeremyBarthe 我添加了小更新(关于 maxDepth 参数)。这似乎更接近你想要实现的目标。至少不同层次的孩子不会混在一起。
    • 感谢您的帮助! maxDepth 绝对可以是树结构中有趣的东西。我认为结论是我无法计算整个树(我的意思是在嵌套级别中),但无论如何 $lookup 和 $graphLookup 都是非常好的功能(它们在 MongoDB 中显然缺少)。
    • @SergeyBerezovskiy 这是否意味着在 mongoDB 中我们无法检索具有 N 级树层次结构的结果,我有完全相同的问题,我想检索具有子节点和子节点的节点-儿童等......在N个子级别中。如果使用 graphLookup 操作无法做到这一点,我们还有其他方法吗?请帮忙!
    【解决方案2】:

    没有找到用maxDepth 参数解决它的方法,但是depthField 一个(在我的例子中设置为'depth')来提供帮助,Jeremy Barthe suggestion 重新计算结果。

    这是一个递归重建$graphLookup结果的函数。

    def list_tree_lookup(keys, data, depth=0):
        result = []
        for a in data:
            if a['depth'] + depth == 0 or (a['depth'] == depth and a['_id'] in keys):
                b = copy.deepcopy(a)
                b['children'] = list_tree_lookup(a['children'], data, depth+1)
                del b['depth']
                result.append(b)
        return result
    

    我使用import copycopy.deepcopy 只是为了确保一切都在原位。

    所以,当你有

    rootNode1 = {
        "_id" : "Root node 1",
        "children" : [
            { "_id" : "Subchild 1.1.1", "depth" : 1 },
            { "_id" : "Child 1.2", "depth" : 0 },
            { "_id" : "Child 1.1", "depth" : 0 }
        ]
    }
    

    调用list_tree_lookup([], rootNode1['children']) 会产生

    [
        {
            "_id": "Child 1.1"
            "children": [
                { "_id": "Subchild 1.1.1" }
            ]
        },
        {
            "_id": "Child 1.2"
            "children": []
        }
    ]
    

    并且可以分配回rootNode1['children']

    【讨论】:

      【解决方案3】:
      const allItems = await db.getCollection('tree').find({});
          if (allItems.length) {
              allItems = allItems.map((item) =>
                  item.toObject()
              );
              allItems = allItems
                  .map((item) => {
                      let parentObj = null;
                      if (item.parent_id) {
                          parentObj = allItems.find(
                              (findItem) =>
                                  findItem._id.toString() ===
                                  item.parent_id.toString()
                          );
                      }
                      if (parentObj) {
                          if (!parentObj.children) {
                              parentObj.children = [];
                          }
                          parentObj.children.push(item);
                      }
                      return item;
                  })
                  .filter((item) => !item.parent_id);
          } else {
              console.error("items not found");
          }
      

      【讨论】:

      • 感谢您的回答。你能扩展一下吗?如果你有一些参考,请添加任何参考。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-11-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多