鉴于此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" }
}
}
]
)
答案 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键,检查键,创建和添加的复杂性。它实际上只是一团糟,并且是使用这种结构的任何进一步代码的指示器。
因此,对于我的钱,远离将完美格式良好的数据转换为实际“值”表示为“键”的东西。从干净的设计角度看它确实没有意义,因为用遍历对象的键替换自然的“数组”列表也没有意义。