我正在尝试对以下结构进行分组和计算:
[{
"_id" : ObjectId("5479c4793815a1f417f537a0"),
"status" : "canceled",
"date" : ISODate("2014-11-29T00:00:00.000Z"),
"offset" : 30,
"devices" : [
{
"name" : "Mouse",
"cost" : 150,
},
{
"name" : "Keyboard",
"cost" : 200,
}
],
},
{
"_id" : ObjectId("5479c4793815a1f417d557a0"),
"status" : "done",
"date" : ISODate("2014-10-20T00:00:00.000Z"),
"offset" : 30,
"devices" : [
{
"name" : "LCD",
"cost" : 150,
},
{
"name" : "Keyboard",
"cost" : 200,
}
],
}
,
{
"_id" : ObjectId("5479c4793815a1f417f117a0"),
"status" : "done",
"date" : ISODate("2014-12-29T00:00:00.000Z"),
"offset" : 30,
"devices" : [
{
"name" : "Headphones",
"cost" : 150,
},
{
"name" : "LCD",
"cost" : 200,
}
],
}]
我需要小组并计算类似的东西:
"result" : [
{
"_id" : {
"status" : "canceled"
},
"count" : 1
},
{
"_id" : {
"status" : "done"
},
"count" : 2
},
totaldevicecost: 730,
],
"ok" : 1
}
我在计算子阵列“设备”中的成本总和时遇到的问题。怎么做?
答案 0 :(得分:1)
看起来你有一个开始,但你迷失了一些其他概念。在文档中使用数组时有一些基本的事实,但是让我们从你离开的地方开始:
db.sample.aggregate([
{ "$group": {
"_id": "$status",
"count": { "$sum": 1 }
}}
])
这就是使用$group
管道来收集关于" status"的不同值的文档。字段然后还产生另一个字段" count"当然"计数"通过将1
的值传递给找到的每个文档的$sum
运算符,可以出现分组键。这使您处于与您描述的非常相似的地步:
{ "_id" : "done", "count" : 2 }
{ "_id" : "canceled", "count" : 1 }
这是第一阶段并且很容易理解,但现在你需要知道如何从数组中获取值。一旦你理解了"dot notation"概念就可以做出类似的事情,那么你可能会受到诱惑:
db.sample.aggregate([
{ "$group": {
"_id": "$status",
"count": { "$sum": 1 },
"total": { "$sum": "$devices.cost" }
}}
])
但你会发现"总计"实际上每个结果都会0
:
{ "_id" : "done", "count" : 2, "total" : 0 }
{ "_id" : "canceled", "count" : 1, "total" : 0 }
为什么呢?像这样的MongoDB聚合操作在分组时实际上并不遍历数组元素。为此,聚合框架有一个名为$unwind
的概念。这个名字相对不言自明。 MongoDB中的嵌入式数组就像拥有一对多的#34;链接数据源之间的关联。那么$unwind
所做的正是那种"加入"结果,由此产生的"文件"基于数组的内容和每个父级的重复信息。
因此,为了对数组元素进行操作,您需要先使用$unwind
。这应该在逻辑上引导您进行如下代码:
db.sample.aggregate([
{ "$unwind": "$devices" },
{ "$group": {
"_id": "$status",
"count": { "$sum": 1 },
"total": { "$sum": "$devices.cost" }
}}
])
然后结果:
{ "_id" : "done", "count" : 4, "total" : 700 }
{ "_id" : "canceled", "count" : 2, "total" : 350 }
但这不是很正确吗?还记得你刚刚从$unwind
学到了什么,以及它如何与父信息进行非规范化连接?所以现在每个文档都有重复,因为它们都有两个数组成员。所以虽然"总计"字段是正确的,"计数"是每种情况下的两倍。
需要更加小心,所以不要在单个$group
阶段进行,而是在两个阶段完成:
db.sample.aggregate([
{ "$unwind": "$devices" },
{ "$group": {
"_id": "$_id",
"status": { "$first": "$status" },
"total": { "$sum": "$devices.cost" }
}},
{ "$group": {
"_id": "$status",
"count": { "$sum": 1 },
"total": { "$sum": "$total" }
}}
])
现在获得的结果是正确的总数:
{ "_id" : "canceled", "count" : 1, "total" : 350 }
{ "_id" : "done", "count" : 2, "total" : 700 }
现在数字是正确的,但它仍然不是你要求的。我认为你应该停在那里,因为你期望的那种结果实际上并不适合单独聚合的单一结果。你正在寻找内部"内部"结果。它真的不属于那里,但对于小数据,它是可以的:
db.sample.aggregate([
{ "$unwind": "$devices" },
{ "$group": {
"_id": "$_id",
"status": { "$first": "$status" },
"total": { "$sum": "$devices.cost" }
}},
{ "$group": {
"_id": "$status",
"count": { "$sum": 1 },
"total": { "$sum": "$total" }
}},
{ "$group": {
"_id": null,
"data": { "$push": { "count": "$count", "total": "$total" } },
"totalCost": { "$sum": "$total" }
}}
])
最终结果形式:
{
"_id" : null,
"data" : [
{
"count" : 1,
"total" : 350
},
{
"count" : 2,
"total" : 700
}
],
"totalCost" : 1050
}
但是,"不要这样做" 。 MongoDB的响应文件限制为16MB,这是BSON规范的限制。在小结果上你可以做这种方便包装,但是在更大的方案中,你希望结果是早期的形式,或者是单独的查询,或者实时迭代整个结果,以便从所有文档中获得总数。 / p>
您似乎使用的是小于2.6的MongoDB版本,或者从不支持最新版本功能的RoboMongo shell复制输出。从MongoDB 2.6虽然聚合的结果可以是一个"游标"而不是单个BSON阵列。因此整体响应可能远大于16MB,但只有当您没有压缩到单个文档时才会显示结果,如上一个示例所示。
在你是"分页"的情况下尤其如此。结果,100到100个结果行,但你只想要一个"总计"当您只返回"页面时返回API响应"一次25个结果。
无论如何,这应该为您提供合理的指导,帮助您了解如何从常用文档表单中获得所期望的结果类型。请记住$unwind
以便处理数组,并且通常$group
多次处理,以便从文档和集合分组中获得不同分组级别的总计。