我有一个与以下相似的文档组成的集合:
{
"_id" : ObjectId("5dc916a72440b14b3f0ec096"),
"date" : ISODate("2019-11-11T11:07:03.968+03:00"),
"actions" : [
{
"type" : "Type1",
"action" : true
},
{
"type" : "Type2",
"action" : true
},
{
"type" : "Type3",
"action" : false
}
]
}
我正在尝试根据actions.action属性的布尔值来计算所有动作类型。
这是我到目前为止的方式:
db.Actions.aggregate(
{
$group: {
_id: {
year: { $year: "$date" },
month: { $month: "$date" },
day: { $dayOfMonth: "$date" },
},
count: { $sum: 1 }
}
}
);
如您所见,这只为我提供了按操作日期分组的集合中文档的数量。
我需要的是这样的
{
"_id" : {
"year" : 2019,
"month" : 10,
"day" : 13
},
"Type1": 300,
"Type2": 200,
"Type3": 120,
"count" : 305
}
查询是否可行?还是应该朝着创建游标并使用游标汇总值的方向发展?
答案 0 :(得分:1)
db.Actions.aggregate([
// Unwind to de-normalize the array
{ "$unwind": "$actions" },
// Group on both day and "type"
{ "$group": {
"_id": {
"date": {
"$toDate": {
"$subtract": [
{ "$toLong": "$date" },
{ "$mod": [{ "$toLong": { "$toDate": "$date" } }, 1000 * 60 * 60 * 24 ] }
]
}
},
"type": "$actions.type"
},
"total": { "$sum": { "$toLong": "$actions.action" } }
}},
// Roll-up the grouping to just by "day"
{ "$group": {
"_id": "$_id.date",
"data": { "$push": { "k": "$_id.type", "v": "$total" } }
}},
// Convert to key/value output
{ "$replaceRoot": {
"newRoot": {
"$mergeObjects": [
{ "_id": "$_id", "count": { "$sum": "$data.v" } },
{ "$arrayToObject": "$data" }
]
}
}}
])
总结:
仅由于要对文档数组内的值“分组”而需要$unwind
。使用此“去规范化”或实质上使每个数组元素成为新文档,该数组具有该数组所驻留的文档的相同属性和所有其他“父”属性。简单来说,您将获得每个数组成员的包含文档的“副本”作为新文档。
下一个$group
主要使用“日期数学”方法将四舍五入到单数天。这比$year
和$month
等方法更漂亮,实际上返回了一个Date
对象,您选择的客户端语言会理解该对象。
这当然是复合分组键,这意味着另一部分当然是type
数组中的actions
字段。而且由于您只希望对true
个结果进行计数,因此我们再次应用$toLong
以便将Boolean
转换为$sum
的数值(当是0
或1
)。在较早的版本中,您也可以使用$cond
来执行此操作,但是简单的类型转换可以更容易地读取意图。
其余的基本上是关于转换为问题的预期“键/值” *输出的。确实,您在第一个$group
操作中就获得了理想的结果,但是要成为“键/值”,您需要使用$push
将所有这些结果放入一个数组中(当然要按“日期”),然后使用$arrayToObject
函数将该数组转换为根文档。