【问题标题】:Is it possible to group and sum multiple columns with MongoDB's aggregation framework?是否可以使用 MongoDB 的聚合框架对多个列进行分组和求和?
【发布时间】:2013-05-19 10:27:53
【问题描述】:

鉴于此 MongoDB 集合:

[
  { character: 'broquaint', race: 'Halfling', class: 'Hunter' },
  { character: 'broquaint', race: 'Halfling', class: 'Hunter' },
  { character: 'broquaint', race: 'Halfling', class: 'Rogue' },
  { character: 'broquaint', race: 'Naga',     class: 'Fighter' },
  { character: 'broquaint', race: 'Naga',     class: 'Hunter' }
]

我想计算每个种族和班级的数量,即

{
  race:  { 'Halfling': 3, 'Naga': 2 },
  class: { 'Hunter': 3, 'Rogue': 1, 'Fighter': 1 }
}

我一直在尝试使用聚合框架来做到这一点(到 替换现有的地图/减少),但只能达到 作为组合计数,即

{ '_id': { race: 'Halfling', class: 'Hunter' },  count: 2 }
{ '_id': { race: 'Halfling', class: 'Rogue' }    count: 1 }
{ '_id': { race: 'Naga',     class: 'Fighter' }, count: 1 }
{ '_id': { race: 'Naga',     class: 'Hunter' },  count: 1 }

这很简单,可以以编程方式减少到所需的 结果,但我希望能够将其留给 MongoDB。

参考这里是我到目前为止的代码:

db.games.aggregate(
  { '$match': { character: 'broquaint' } },
  {
    '$group': {
      _id:   { race: '$race', background: '$background'},
      count: { '$sum': 1 }
    }
  }
)

所以问题是 - 鉴于示例集合我可以到达我的 完全通过 MongoDB 的聚合框架获得所需的输出?

对于任何可能提供的帮助,提前非常感谢!

【问题讨论】:

  • 什么是“$background”?上课?
  • 抱歉,代码中它应该读作“class”而不是“background”,列名实际上是“background”,但为简洁起见,我选择了“class”,但一致性失败了。

标签: mongodb aggregation-framework


【解决方案1】:

从 MongoDB 3.4 开始,使用多个聚合管道和$facet 可以更简单地实现这一点。

取自docs

$facet

在单个阶段内处理多个聚合管道 相同的输入文档集。每个子管道都有自己的字段 其结果存储为一个数组的输出文档 文件。

因此,对于您的用例,这将通过以下方式实现:

const aggregatorOpts = [
    { $match: { character: 'broquaint' } }, // Match the character
    {
        // Seperate into 2 or more pipes that will count class and
        // race seperatly
        $facet: {
            race: [
                // Group by race and get the count:
                // [
                //   {
                //     _id: 'Halfling',
                //     count: 3
                //   }
                //   {
                //     _id: 'Naga',
                //     count: 2
                //   }
                // ]

                // $sortByCount is the same as
                // { $group: { _id: <expression>, count: { $sum: 1 } } },
                // { $sort: { count: -1 } }

                { $sortByCount: '$race' },

                // Now we want to transform the array in to 1 document,
                // where the '_id' field is the key, and the 'count' is the value.
                // To achieve this we will use $arrayToObject. According the the
                // docs, we have to first rename the fields to 'k' for the key,
                // and 'v' for the value. We use $project for this:
                {
                    $project: {
                        _id: 0,
                        k: '$_id',
                        v: '$count',
                    },
                },
            ],
            // Same as above but for class instead
            class: [
                { $sortByCount: '$class' },
                {
                    $project: {
                        _id: 0,
                        k: '$_id',
                        v: '$count',
                    },
                },
            ],
        },
    },
    {
        // Now apply the $arrayToObject for both class and race.
        $addFields: {
            // Will override the existing class and race arrays
            // with their respective object representation instead.
            class: { $arrayToObject: '$class' },
            race: { $arrayToObject: '$race' },
        },
    },
];

db.races.aggregate(aggregatorOpts)

这会产生以下内容:

[
  {
    "race": {
      "Halfling": 3,
      "Naga": 2
    },
    "class": {
      "Hunter": 3,
      "Rogue": 1,
      "Fighter": 1,
    }
  }
]

如果您对@Asya 提供的输出格式感到满意,那么您可以删除$project$addFieldsstages,并在每个子管道中保留$sortByCount 部分。

有了这些新功能,聚合更容易通过额外的计数来扩展, 只需在$facet 中添加另一个聚合管道。 计算子组甚至更容易一些,但这将是一个单独的问题。

【讨论】:

    【解决方案2】:

    是的,您可以使用聚合框架来做到这一点。它不会很漂亮,但它仍然会比使用 mapreduce 快得多......

    简而言之(输出与您提供的格式不同但内容相同的格式):

    > group1 = {
        "$group" : {
            "_id" : "$race",
            "class" : {
                "$push" : "$class"
            },
            "count" : {
                "$sum" : 1
            }
        }
    };
    > unwind = { "$unwind" : "$class" };
    > group2 = {
        "$group" : {
            "_id" : "$class",
            "classCount" : {
                "$sum" : 1
            },
            "races" : {
                "$push" : {
                    "race" : "$_id",
                    "raceCount" : "$count"
                }
            }
        }
    };
    > unwind2 = { "$unwind" : "$races" };
    > group3 ={
        "$group" : {
            "_id" : 1,
            "classes" : {
                "$addToSet" : {
                    "class" : "$_id",
                    "classCount" : "$classCount"
                }
            },
            "races" : {
                "$addToSet" : "$races"
            }
        }
    };
    > db.races.aggregate(group1, unwind, group2, unwind2, group3);
    {
        "result" : [
            {
                "_id" : 1,
                "classes" : [
                    {
                        "class" : "Fighter",
                        "classCount" : 1
                    },
                    {
                        "class" : "Hunter",
                        "classCount" : 3
                    },
                    {
                        "class" : "Rogue",
                        "classCount" : 1
                    }
                ],
                "races" : [
                    {
                        "race" : "Naga",
                        "raceCount" : 2
                    },
                    {
                        "race" : "Halfling",
                        "raceCount" : 3
                    }
                ]
            }
        ],
        "ok" : 1
    }
    

    【讨论】:

    • 如果您知道可能的比赛和班级的完整集合,可能会有不同的答案 - 在这种情况下,很容易生成您在问题中的确切格式。
    • 当我终于准备好发布答案时,我看到您已经这样做了……而且快了 40 分钟……而且少了两个命令。 ;)
    • 请叫我聚合框架大师。呃,我是说女主。如果可以的话……我可能已经做到了。
    • 效果很好,非常感谢@AsyaKamsky!现在我要完成令人羡慕的任务来了解它是如何工作的:)
    • @AsyaKamsky:它回答了这个问题,但这真的是个好主意吗?似乎效率很低。实际上只运行两个简单的 $group 聚合不是更好吗?我无法想象使用这种技术在计数中添加第三或第四个值。
    猜你喜欢
    • 2013-05-16
    • 1970-01-01
    • 2018-01-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-02
    • 1970-01-01
    • 2016-04-18
    相关资源
    最近更新 更多