MongoDB中$ group中的多个$ sum

时间:2016-02-27 17:18:45

标签: mongodb mapreduce mongodb-query aggregation-framework

鉴于此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"   }
            }
        }
    ]
)

1 个答案:

答案 0 :(得分:2)

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

更好的方法是保持密钥不变,使用$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,所以嵌入式JavaScript将会运行 比聚合管道的本机代码慢得多,当然也不会扩展到产生大于16MB BSON限制的文档的结果,因为所有结果都被嵌入到一个文档中。

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

因此,对于我的钱,远离将完美格式良好的数据转换为实际“值”表示为“键”的东西。从干净的设计角度看它确实没有意义,因为用遍历对象的键替换自然的“数组”列表也没有意义。