【问题标题】:MongoDB grouping aggregate query with relationshipMongoDB分组聚合查询与关系
【发布时间】:2020-03-19 23:37:30
【问题描述】:

假设我在 association 集合中有以下文档:

{
    "id" : 1,
    "parentId" : 1,
    "position" : {
        "x" : 1,
        "y" : 1
    },
    "tag" : "Beta"
},
{
    "id" : 2,
    "parentId" : 2,
    "position" : {
        "x" : 2,
        "y" : 2
    },
    "tag" : "Alpha"
},
{
    "id" : 3,
    "parentId" : 1,
    "position" : {
        "x" : 3,
        "y" : 3
    },
    "tag" : "Delta"
},
{
    "id" : 4,
    "parentId" : 1,
    "position" : {
        "x" : 4,
        "y" : 4
    },
    "tag" : "Gamma"
},
{
    "id" : 5,
    "parentId" : 2,
    "position" : {
        "x" : 5,
        "y" : 6
    },
    "tag" : "Epsilon"
}

我想创建一个聚合查询以产生以下结果:

{
    "_id" : 2,
    "position" : {
        "x" : 2,
        "y" : 2
    },
    "tag" : "Alpha",
    "children" : [
        {
            "position" : {
                "x" : 5,
                "y" : 6
            },
            "tag" : "Epsilon"
        }
    ]
},
{
    "_id" : 1,
    "position" : {
        "x" : 1,
        "y" : 1
    },
    "tag" : "Beta"
    "children" : [
        {
            "position" : {
                "x" : 3,
                "y" : 3
            },
            "tag" : "Delta"
        },
        {
            "position" : {
                "x" : 4,
                "y" : 4
            },
            "tag" : "Gamma"
        }
    ]
}

但是,我只能创建以下分组查询,将“所有相关”文档放入子数组中:

db.association.aggregate([{
   $group  : {
       _id : "$parentId",
       children : {
           $push :  {
                   position : "$position",
                   tag :"$tag"
               }
       }
   }
}])

我不知道如何过滤掉特定于“父”点的“位置”和“标签”并将它们放在顶层。

【问题讨论】:

    标签: mongodb aggregation-framework grouping


    【解决方案1】:

    虽然 Valijon 的答案是有效的,但它需要在之前进行排序。 这是一个不需要排序的解决方案,而是使用graphLookup 阶段(非常适合实现您的需要)

    db.collection.aggregate([
      {
        $graphLookup: {
          from: "collection",
          startWith: "$id",
          connectFromField: "id",
          connectToField: "parentId",
          as: "children",
    
        }
      },
      {
        $match: {
          $expr: {
            $gt: [
              {
                $size: "$children"
              },
              0
            ]
          }
        }
      },
      {
        $addFields: {
          children: {
            $filter: {
              input: "$children",
              as: "child",
              cond: {
                $ne: [
                  "$id",
                  "$$child.id"
                ]
              }
            }
          }
        }
      }
    ])
    
    • 第一阶段是做这项工作。
    • 第二个是在这里过滤没有任何孩子的文档。
    • 第三个仅用于从子数组中删除父级。但是,如果您可以删除父项中的自引用,则将不再需要最后一个阶段。

    You can try it here

    【讨论】:

    • 嘿,$graphLookup 是一个选项 - 在这种情况下,它比 $group 做得更好!
    • @Lukasz @matthpen 我也想过$graphLookup,但我一直认为*Lookup直到现在表现不佳。我做了小基准测试:插入 abot 1.7M 记录并运行:$group$graphLookup$lookup(有 3 种可能的解决方案)。 Robo3t,执行聚合并返回前 50 条记录。所以我已经由不同的操作员单独执行每个解决方案,这里是执行时间:$group:18.5 秒,$graphLookup:5.4 秒和$lookup:96.8 秒浪费时间。确实$graphLookup 是最好的解决方案,赞成
    【解决方案2】:

    通过确保文档排序(父 - 子 1 - 子 2 ... - 子 n),我们可以合并grouped document 和第一个孩子(parent)。最后一步,我们需要从children数组中删除parent

    试试这个:

    db.association.aggregate([
      {
        $sort: {
          parentId: 1,
          id: 1
        }
      },
      {
        $group: {
          _id: "$parentId",
          children: {
            $push: {
              position: "$position",
              tag: "$tag"
            }
          }
        }
      },
      {
        $replaceRoot: {
          newRoot: {
            $mergeObjects: [
              "$$ROOT",
              {
                $arrayElemAt: [
                  "$children",
                  0
                ]
              }
            ]
          }
        }
      },
      {
        $addFields: {
          children: {
            $slice: [
              "$children",
              1,
              {
                $size: "$children"
              }
            ]
          }
        }
      }
    ])
    

    MongoPlayground

    【讨论】:

    • 嘿,我喜欢你的解决方案,但我不能假设我的数据会被订购,但我会为你提出的解决方案 +1,谢谢!
    • @Lukasz 您可以按parentId + id 字段排序(确保您有这些字段的索引)