在MongoDB上按字段值汇总

时间:2019-11-11 09:37:26

标签: mongodb

我有一个与以下相似的文档组成的集合:

{
    "_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
}

查询是否可行?还是应该朝着创建游标并使用游标汇总值的方向发展?

1 个答案:

答案 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的数值(当是01)。在较早的版本中,您也可以使用$cond来执行此操作,但是简单的类型转换可以更容易地读取意图。

其余的基本上是关于转换为问题的预期“键/值” *输出的。确实,您在第一个$group操作中就获得了理想的结果,但是要成为“键/值”,您需要使用$push将所有这些结果放入一个数组中(当然要按“日期”),然后使用$arrayToObject函数将该数组转换为根文档。