不喜欢在输出中使用“数据”作为“键”,因为通常最好将“数据”保留为“数据”,并且更符合面向对象的设计模式,其中键在对象之间是一致的,而不是不一致每个结果都不同。毕竟,一开始就有人很明智地以这种方式设计初始数据。
所以你真正需要的只是一个多级分组,这很简单,只需将一个$group 阶段的输出提供给另一个阶段:
db.collection.aggregate([
{ "$group": {
"_id": {
"date": "$date",
"parentId": "$parentId",
"status": "$status"
},
"total": { "$sum": 1 }
}},
{ "$group": {
"_id": {
"date": "$_id.date",
"parentId": "$_id.parentId"
},
"data": { "$push": {
"status": "$_id.status",
"total": "$total"
}}
}},
{ "$group": {
"_id": "$_id.date",
"parents": { "$push": {
"parentId": "$_id.parentId",
"data": "$data"
}}
}}
])
这将在跟随初始组之后逐步将数据嵌套到每个“日期”键下的数组中,以按照最精细的细节级别进行累积。结果基本上是通过$push 压缩成数组,将结构“汇总”到每个键的单个文档中:
[
{
"_id": "2015-12-01",
"parents": [
{
"parentId": 1,
"data": [
{ "status": "resolved", "total": 1 }
]
},
{
"parentId": 2,
"data": [
{ "status": "resolved", "total": 2 },
{ "status": "waiting", "total": 1 }
]
}
]
},
{
"_id": "2015-12-02",
"parents": [
{
"parentId": 1,
"data": [
{ "status": "resolved", "total": 1 }
]
},
{
"parentId": 2,
"data": [
{ "status": "waiting", "total": 2 }
]
}
]
},
{
"_id": "2015-12-03",
"parents": [
{
"parentId": 1,
"data": [
{ "status": "resolved", "total": 1 }
]
}
]
}
]
或者,如果你能接受它,那么你可以更扁平地将所有相关子数据放在一个数组中,而不是嵌套一个:
db.collection.aggregate([
{ "$group": {
"_id": {
"date": "$date",
"parentId": "$parentId",
"status": "$status"
},
"total": { "$sum": 1 }
}},
{ "$group": {
"_id": "$_id.date",
"data": { "$push": {
"parentId": "$_id.parentId",
"status": "$_id.status",
"total": "$total"
}}
}}
])
只有一个子数组,只保留所有数据键:
[
{
"_id": "2015-12-01",
"data": [
{
"parentId": 1,
"status": "resolved",
"total": 1
},
{
"parentId": 2,
"status": "resolved",
"total": 2
},
{
"parentId": 2,
"status": "waiting",
"total": 1
}
]
},
{
"_id": "2015-12-02",
"data": [
{
"parentId": 1,
"status": "resolved",
"total": 1
},
{
"parentId": 2,
"status": "waiting",
"total": 2
}
]
},
{
"_id": "2015-12-03",
"data": [
{
"parentId": 1,
"status": "resolved",
"total": 1
}
]
}
]
这里的主要本质是“事物列表”以数组的形式保存为与它们相关的事物的子项,以任何一种形式,只是程度不同。这可以说比从对象中确定“键”并迭代它们更容易处理,也更合乎逻辑,因为无论如何你基本上都可以迭代一个自然列表。
聚合框架不支持(非常刻意地)尝试以任何方式从数据中按摩键,大多数 MongoDB 查询操作也同意这一理念,因为它对于本质上是“数据库”的东西很有意义。
如果您确实必须作为键进行按摩,建议在检索聚合结果后在客户端处理中执行此操作。您甚至可以在传递到远程客户端时在流处理中执行此操作,但作为一个基本的转换示例:
var out = db.collection.aggregate([
{ "$group": {
"_id": {
"date": "$date",
"parentId": "$parentId",
"status": "$status"
},
"total": { "$sum": 1 }
}},
{ "$group": {
"_id": {
"date": "$_id.date",
"parentId": "$_id.parentId"
},
"data": { "$push": {
"status": "$_id.status",
"total": "$total"
}}
}},
{ "$group": {
"_id": "$_id.date",
"parents": { "$push": {
"parentId": "$_id.parentId",
"data": "$data"
}}
}}
]).toArray();
out.forEach(function(doc) {
var obj = {};
obj[doc._id] = {};
doc.parents.forEach(function(parent) {
obj[doc._id][parent.parentId] = {};
parent.data.forEach(function(data) {
obj[doc._id][parent.parentId][data.status] = data.total;
});
});
printjson(obj);
});
这基本上会按照您的结构产生输出,但当然是作为稍后解释的单个文档:
{
"2015-12-01": {
"1": {
"resolved": 1
},
"2": {
"resolved": 2,
"waiting": 1
}
}
},
{
"2015-12-02": {
"1": {
"resolved": 1
},
"2": {
"waiting": 2
},
}
},
{
"2015-12-03": {
"1": {
"resolved": 1
}
}
}
或者您可以使用 mapReduce 和基于 JavaScript 的处理在服务器上强制执行此操作,但由于整体效率不如聚合处理有效,因此再次不明智:
db.collection.mapReduce(
function() {
var obj = {};
obj[this.parentId] = {};
obj[this.parentId][this.status] = 1;
emit(this.date,obj);
},
function(key,values) {
var result = {};
values.forEach(function(value) {
Object.keys(value).forEach(function(parent) {
if (!result.hasOwnProperty(parent))
result[parent] = {};
Object.keys(parent).forEach(function(status) {
if (!result[parent].hasOwnProperty(status))
result[parent][status] = 0;
result[parent][status] += value[parent][status];
});
});
});
return result;
},
{ "out": { "inline": 1 } }
);
几乎相同的结果,但使用特定的输出格式 mapReduce 总是产生:
{
"_id": "2015-12-01",
"value": {
"1": {
"resolved": 1
},
"2": {
"resolved": 2,
"waiting": 1
}
}
},
{
"_id": "2015-12-02",
"value": {
"1": {
"resolved": 1
},
"2": {
"waiting": 2
},
}
},
{
"_id": "2015-12-03",
"value": {
"1": {
"resolved": 1
}
}
}
请注意,特别是如果您不熟悉 mapReduce 的工作原理,那么结构在 mapper 和 reducer 之间一致地发出和遍历以及对发出的值求和而不是简单地递增是一个非常重要的原因。这是 mapReduce 的一个属性,reducer 的输出最终会再次通过 reducer,直到得到一个结果。
正如前面提到的,正如您所说的,“新事物” 的一个很大的警告是,您真的从不想要将结果压缩到单个对象中如您的问题所示进行回复。
它不仅是糟糕设计的另一个属性(前面已经介绍过),而且对 MongoDB 和许多合理系统的输出大小也存在现实的“硬性限制”。单个文档的 BSON 大小限制为 16MB,在任何实际情况下尝试这样做时几乎肯定会超过此限制。
此外,“作为列表的列表”是有意义的,试图人为地表示在单个文档对象中使用唯一键是没有意义的。当您为预期目的使用正确的数据结构类型时,事情的处理和流式传输会变得更加容易。
所以这些是处理输出的方法。无论采用何种方法,这实际上只是聚合的基本数据操作。但希望您能看到保持它尽可能高效和简单的常识,因为它可以通过聚合直接处理,并且对于处理接收到的结果的最终代码更有意义。