聚合以从值创建动态密钥

时间:2017-06-11 02:36:29

标签: mongodb aggregation-framework

我有一个聚合表达式如下:

db.Booking.aggregate([
      { $match: { created_at: {$exists:true} } },
      { $sort : { created_at : -1 } },
      { $group: {
          _id: {
            year : { $year : "$created_at" },
            month : { $month : "$created_at" },
            accepted: '$accepted'
          },
          total: {
            $sum: '$total'
          }
        } 
      }
    ])

会产生如下结果:

[
  {"_id":{"year":2017,"month":4,"accepted":"expired"},"total":0},
  {"_id":{"year":2017,"month":4,"accepted":"Expired"},"total":25},
  {"_id":{"year":2017,"month":4,"accepted":"Pending"},"total":390},
  {"_id":{"year":2017,"month":5,"accepted":"Pending"},"total":1400}
]

我如何得到这样的结果:

[
  {"_id":{"year":2017,"month":4},"expired":0},
  {"_id":{"year":2017,"month":4},"Expired":25},
  {"_id":{"year":2017,"month":4},"Pending":390},
  {"_id":{"year":2017,"month":5},"Pending":1400}
]

我是否必须使用常规javascript进行修改,而不是依赖Mongodb来完成工作

1 个答案:

答案 0 :(得分:1)

你真的“应该”处理结果,在接收代码中命名这些键,而不是依靠数据库来执行此操作。虽然最新的当前版本可能会有一些争吵。

这至少需要 MongoDB 3.4

db.Booking.aggregate([
  { "$match": { "created_at": { "$exists": true } } },
  { "$sort": { "created_at": -1 } },
  { "$group": {
    "_id": {
      "year": { "$year": "$created_at" },
      "month": { "$month": "$created_at" },
      "accepted": {
        "$concat": [
          { "$toUpper": { "$substrCP": [ "$accepted", 0, 1 ] } },
          { "$toLower": { "$substrCP": [ "$accepted", 1, { "$strLenCP": "$accepted" } ] } }
        ]
      }
    },
    "total": { "$sum": "$total" }
  }},
  { "$replaceRoot": {
    "newRoot": { 
      "$arrayToObject": {
        "$concatArrays": [
          [
            { "k": "_id", "v": { "year": "$_id.year", "month": "$_id.month" } },
            { "k": "$_id.accepted", "v": "$total" }
          ]
        ]
      }
    }
  }}
])

大部分内容由$arrayToObject$replaceRoot管道阶段处理。可以注意到$concatArrays这里的使用似乎是必要的,因为$arrayToObject抱怨如果只是提供数组文字作为参数。因此,我们使用一个运算符,它从它自己的给定表达式返回一个数组,而$concatArrays似乎是最简短的表示法。

这似乎是一个错误(有待确认),但可能只是作为预期用法使用,因为对于大多数聚合运算符,数组[]参数通常用于多个参数。

此外,您的常规$group语句中似乎存在问题,因为"accepted"值的大小写会有所不同,MongoDB会对其进行不同的处理。您可以使用$toLower$toUpper等运算符来更正此问题。在这里,我使用一些现代运算符将一致的首字母大写为大写,其余为小写,但基本的大小写函数在支持.aggregate()的所有版本中都可用。

当然,后期处理非常简单,可以在任何版本中完成。对于MongoDB shell中的JavaScript,它简单如下:

db.Booking.aggregate([
  { "$match": { "created_at": { "$exists": true } } },
  { "$sort": { "created_at": -1 } },
  { "$group": {
    "_id": {
      "year": { "$year": "$created_at" },
      "month": { "$month": "$created_at" },
      "accepted": {
        "$concat": [
          { "$toUpper": { "$substrCP": [ "$accepted", 0, 1 ] } },
          { "$toLower": { "$substrCP": [ "$accepted", 1, { "$strLenCP": "$accepted" } ] } }
        ]
      }
    },
    "total": { "$sum": "$total" }
  }}
]).forEach(doc => {
  doc[doc._id.accepted] = doc.total;
  delete doc._id.accepted;
  delete doc.total;
  printjson(doc)
})

两者产生相同的东西:

{ "_id" : { "year" : 2017, "month" : 4 }, "Expired" : 25 }
{ "_id" : { "year" : 2017, "month" : 4 }, "Pending" : 390 }
{ "_id" : { "year" : 2017, "month" : 5 }, "Pending" : 1400 }

如果你实际上想要在分组结果中“同时”使用“待定”和“过期”键,那么这就更简单了:

db.Booking.aggregate([
  { "$match": { "created_at": { "$exists": true } } },
  { "$group": {
    "_id": { 
      "year": { "$year": "$created_at" },
      "month": { "$month": "$created_at" }
    },
    "Expired": {
      "$sum": {
        "$cond": [
          { "$eq": [ { "$toLower": "$accepted" }, "expired" ] },
          "$total",
          0  
        ]
      }
    },
    "Pending": {
      "$sum": {
        "$cond": [
          { "$eq": [ { "$toLower": "$accepted" }, "pending" ] },
          "$total",
          0  
        ]
      }
    }
  }},
  { "$sort": { "_id": 1 } }
])

返回:

{ "_id" : { "year" : 2017, "month" : 4 }, "Expired" : 25, "Pending" : 390 }
{ "_id" : { "year" : 2017, "month" : 5 }, "Expired" : 0, "Pending" : 1400 }

由于文档中的键有“固定”格式,但它们没有变化,因此这是一个简单的布局,我们只需使用$cond来决定在$sum下每个键下累积哪些值。 3}}