【问题标题】:Multiple $sum in $group in MongoDBMongoDB中$group中的多个$sum
【发布时间】:2016-02-27 17:18:45
【问题描述】:

鉴于此 MongoDB 集合:

[
  { client: 'client1', type: 'Defect', time: 5 },
  { client: 'client1', type: 'Test', time: 5 },
  { client: 'client2', type: 'Management', time: 3 },
  { client: 'client2', type: 'Defect',     time: 3 },
  { client: 'client3', type: 'Test',     time: 4 }
]

我想从每个 issue_type 中获得总和,如下所示:

{
  client1:  { 'Defect': 5, 'Test': 5 },
  client2: { 'Management': 3, 'Defect': 3 },
  client3: { 'Test': 4 }
}

我一直在尝试使用聚合框架(以替换现有的 map/reduce)来做到这一点,但只能获得像这样的组合的计数:

{ '_id': { client: 'Client1', class: 'Defect' },  sum: 5 }
{ '_id': { client: 'Client1', class: 'Test' }    count: 5 }
{ '_id': { client: 'Client2', class: 'Management' }, count: 3 } 
{ '_id': { client: 'Client2',     class: 'Defect' },  count: 3 }
{ '_id': { client: 'Client3',     class: 'Test' },  count: 4 }

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

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

编辑

我正在添加这个聚合组

db.getCollection('issues').aggregate(
    [
        {
            $group:
            {   
                _id: {component:"$client"},
                totalTime:{$sum: "$time"   }
            }
        }
    ]
)

【问题讨论】:

  • 您的值是字符串而不是数字。如果您有两个具有相同键/值对的文档,例如{ client: 'client1', type: 'Defect', time: '5' },该怎么办?你想在这里做什么?
  • 您运行的是哪个版本的 MongoDB?
  • 请发布您的聚合代码,以便我们根据您目前的情况为您提供帮助。
  • @user3100115 我想在客户端类型中对所有小时进行总计。我正在阅读一个包含问题时间估算列表的 csv,我想获得一些分析信息。
  • @Saleem mongod 版本:3.0.9

标签: mongodb mapreduce mongodb-query aggregation-framework


【解决方案1】:

我不喜欢您建议的输出格式。你本质上要求的是什么 正在获取您的“数据”并将其转化为所产生结果的“密钥”。对我来说,这是干净的面向对象设计的对立面,因为每个“对象”都是完全不同的,你基本上需要循环键来确定它是什么类型的东西。

更好的方法是保持密钥原样,在“客户端”和“类型”的组合上使用 $group 汇总,然后将 $group 再次汇总到 $push 每个“类型”的数据为每个分组的“客户”放入一个数组:

db.getCollection('issues').aggregate([
    { "$group": {
        "_id": {
            "client": "$client",
            "type": "$type"
        },
        "totalTime": { "$sum": "$time" }
    }},
    { "$group": {
        "_id": "$_id.client",
        "data": {
            "$push": {
                "type": "$_id.type",
                "totalTime": "$totalTime"
            }
        }
    }}
])

这会给你这样的结果:

{
        "_id" : "client1",
        "data" : [
                {
                        "type" : "Test",
                        "totalTime" : 5
                },
                {
                        "type" : "Defect",
                        "totalTime" : 5
                }
        ]
}
{
        "_id" : "client2",
        "data" : [
                {
                        "type" : "Defect",
                        "totalTime" : 3
                },
                {
                        "type" : "Management",
                        "totalTime" : 3
                }
        ]
}
{
        "_id" : "client3",
        "data" : [
                {
                        "type" : "Test",
                        "totalTime" : 4
                }
        ]
}

对我来说,这是一种非常自然和结构化的结果形式,每个“客户”都作为文档,自然可迭代的列表作为内容。

如果你真的坚持使用命名键的单一对象输出格式,那么这个源很容易转换。在我看来,简单的代码再次显示了之前的结果有多好:

var output = {};

db.getCollection('issues').aggregate([
    { "$group": {
        "_id": {
            "client": "$client",
            "type": "$type"
        },
        "totalTime": { "$sum": "$time" }
    }},
    { "$group": {
        "_id": "$_id.client",
        "data": {
            "$push": {
                "type": "$_id.type",
                "totalTime": "$totalTime"
            }
        }
    }}
]).forEach(function(doc) {
    output[doc._id] = {};

    doc.data.forEach(function(data) {
        output[doc._id][data.type] = data.totalTime;
    });
});

printjson(output);

然后你得到你喜欢的对象:

{
        "client1" : {
                "Test" : 5,
                "Defect" : 5
        },
        "client2" : {
                "Defect" : 3,
                "Management" : 3
        },
        "client3" : {
                "Test" : 4
        }
}

但如果你真的坚持让服务器处理所有工作,甚至不卸载结果的重塑,那么你总是可以将它作为 mapReduce 触发:

db.getCollection('issues').mapReduce(
    function() {
        var output = { },
            data = {};

        data[this.type] = this.time;
        output[this.client] = data;

        emit(null,output)
    },
    function(key,values) {
        var result = {};

        values.forEach(function(value) {
            Object.keys(value).forEach(function(key) { 
                if ( !result.hasOwnProperty(key) )
                    result[key] = {};
                Object.keys(value[key]).forEach(function(dkey) {
                    if ( !result[key].hasOwnProperty(dkey) )
                        result[key][dkey] = 0;
                    result[key][dkey] += value[key][dkey];
                })
            })
        });
        return result;
    },
    { "out": { "inline": 1 } }
)

具有相同类型的输出:

            {
                    "_id" : null,
                    "value" : {
                            "client1" : {
                                    "Defect" : 5,
                                    "Test" : 5
                            },
                            "client2" : {
                                    "Management" : 3,
                                    "Defect" : 3
                            },
                            "client3" : {
                                    "Test" : 4
                            }
                    }
            }

但是既然是mapReduce,那么被interpeted JavaScript 是要运行的 比聚合管道的本机代码慢得多,当然永远不会扩展到生成大于 16MB BSON 限制的文档的结果,因为所有结果都被混合到一个文档中。

另外,只需看看遍历 Object 键、检查键、创建和添加的复杂性。它实际上只是一团糟,并且表明任何进一步的代码都在使用这种结构。


因此,为了我的钱,不要将格式完美的数据转换为实际“值”表示为“键”的东西。从简洁的设计角度来看,这确实没有任何意义,因为用遍历对象的键来替换“数组”的自然列表也没有任何意义。

【讨论】:

  • Wooooww,精彩的解释并完美地解决了我的问题。我不知道 $push 运算符。
猜你喜欢
  • 1970-01-01
  • 2023-02-02
  • 1970-01-01
  • 2023-03-17
  • 1970-01-01
  • 2021-05-01
  • 1970-01-01
  • 2019-01-01
  • 2021-04-22
相关资源
最近更新 更多