按月分组并计数

15

我有一个预订表,想要获取一个月内的预订数,即按月分组。

但是我不确定如何从日期中获取月份。

这是我的模式:

{
    "_id" : ObjectId("5485dd6af4708669af35ffe6"),
    "bookingid" : 1,
    "operatorid" : 1,
    ...,
    "bookingdatetime" : "2012-10-11T07:00:00Z"
}
{
    "_id" : ObjectId("5485dd6af4708669af35ffe7"),
    "bookingid" : 2,
    "operatorid" : 1,
    ...,
    "bookingdatetime" : "2014-07-26T05:00:00Z"
}
{
    "_id" : ObjectId("5485dd6af4708669af35ffe8"),
    "bookingid" : 3,
    "operatorid" : 2,
    ...,
    "bookingdatetime" : "2014-03-17T11:00:00Z"
}

这是我尝试过的:

db.booking.aggregate([
  { $group: {
    _id: new Date("$bookingdatetime").getMonth(),
    numberofbookings: { $sum: 1 }
  }}
])

但它返回:

{ "_id" : NaN, "numberofbookings" : 3 }

我哪里做错了?


"bookingdatetime":"2012-10-11T07:00:00Z" 是一个字符串吗? - Lalit Agarwal
2
您需要将其转换为ISODate,然后进行聚合处理。否则,使用Map/Reduce。 - Lalit Agarwal
字符串使用 new Date($bookingdate) 转换为 ISODate。 new Date("2012-10-11T07:00:00Z") 是输出结果。 输出结果为 ISODate("2012-10-11T07:00:00Z")。 - user1584253
我使用了以下查询语句:db.booking.aggregate({$project: {month:{$month:new Date("$bookingdatetime")}}}, {$group:{_id:{month:"$month"},numberofbookings:{$sum:1}}},但是它给出了错误的输出结果:{ "_id" : { "month" : 8 }, "numberofbookings" : 3 }。 - user1584253
4个回答

31
你需要在你的组中使用$month关键字。你的new Date().getMonth()调用仅会发生一次,并尝试从字符串"$bookingdatetime"创建一个月份。

你需要在你的组中使用$month关键字。你的new Date().getMonth()调用仅会发生一次,并尝试从字符串"$bookingdatetime"创建一个月份。

db.booking.aggregate([    {$group: {        _id: {$month: "$bookingdatetime"},         numberofbookings: {$sum: 1}     }}]);

2
它会出现这个异常信息:"异常:无法将BSON类型字符串转换为日期"。 - user1584253
我没有看到你将日期存储为字符串。这会阻碍你,因为在聚合语句内部进行此转换没有简单的方法。已经有很多人在SO上面对过这个问题:https://dev59.com/-2Up5IYBdhLWcg3wJlAH - Will Shaver
1
@user1584253 可能在字段前面忘记了 "$" 符号(例如 $bookingdatetime)。 - Ed Staub
13
我猜OP不想把所有的十月份都归为同一组,因此我建议您使用_id: { month: { $month: "$bookingdatetime" }, year: { $year: "$bookingdatetime" } } - Ed Staub
@WillShaver 在这个解决方案中,我们只能在数据库中获取到已有的月份。那么如果我们想要得到结果集中不存在的月份,应该怎么办呢?请查看这个问题并给出答案。http://stackoverflow.com/questions/38437797/filter-and-re-arrange-results-using-node-js-and-mongodb-according-to-datemonth/38438154#38438154 - Chanaka De Silva
1
查询语句有误,将导致MongoError错误:FieldPath字段名称不能以“$”开头。请参考Eb Staubs的评论获取正确的解决方案。 - Adam Reis

26

在聚合管道中,您无法包含任意的JavaScript代码,因此,由于您将bookingdatetime存储为string而不是Date,您无法使用$month运算符。

但是,由于您的日期字符串遵循严格的格式,因此可以使用$substr运算符从字符串中提取月份值:

db.test.aggregate([
    {$group: {
        _id: {$substr: ['$bookingdatetime', 5, 2]}, 
        numberofbookings: {$sum: 1}
    }}
])

输出:

{
    "result" : [ 
        {
            "_id" : "03",
            "numberofbookings" : 1
        }, 
        {
            "_id" : "07",
            "numberofbookings" : 1
        }, 
        {
            "_id" : "10",
            "numberofbookings" : 1
        }
    ],
    "ok" : 1
}

1
@JohnnyHK,这里有一个与此相同的问题。但不同之处在于,如果某个月份没有值,则需要返回0.....请检查并给出答案。http://stackoverflow.com/questions/38437797/filter-and-re-arrange-results-using-node-js-and-mongodb-according-to-datemonth/38438154#38438154 - Chanaka De Silva
假设您只对当前年份感兴趣,那么一旦年份滚动,这个代码就会失效吗? - colefner
@colefner 如果需要的话,您可以在管道的开头添加一个$match阶段来过滤当前年份的文档。 - JohnnyHK

3

从Mongo 4开始,您可以使用$toDate操作符将字符串转换为日期(基于Will Shaver提供的答案):

// { date: "2012-10-11T07:00:00Z" }
// { date: "2012-10-23T18:30:00Z" }
// { date: "2012-11-02T21:30:00Z" }
db.bookings.aggregate([
  { $group: {
    _id: { month: { $month: { $toDate: "$date" } } },
    bookings: { $sum: 1 }
  }}
])
// { "_id" : { "month" : 10 }, "bookings" : 2 }
// { "_id" : { "month" : 11 }, "bookings" : 1 }

2
如果您想按月份获取数据,即使您的数据跨越多年,您可以使用$dateFromString$dateToString的组合(以"%Y-%m"格式化日期,例如2012-10):
// { date: "2012-10-11T07:00:00Z" }
// { date: "2012-10-23T18:30:00Z" }
// { date: "2012-11-02T21:30:00Z" }
// { date: "2013-01-11T18:30:00Z" }
// { date: "2013-10-07T14:15:00Z" }
db.bookings.aggregate([
  { $group: {
    _id: {
      $dateToString: {
        date: { $dateFromString: { dateString: "$date" } },
        format: "%Y-%m"
      }
    },
    bookings: { $count: {} } // or { $sum: 1 } prior to Mongo 5
  }}
])
// { _id: "2012-10", bookings: 2 }
// { _id: "2012-11", bookings: 1 }
// { _id: "2013-01", bookings: 1 }
// { _id: "2013-10", bookings: 1 }

这个操作:

  • 首先将日期字符串转换为字符串:$dateFromString: { dateString: "$date" }
  • 为了将日期格式化为%Y-%m$dateToString: { date: { }, format: "%Y-%m" }
  • 这两个操作($dateFromString/$dateToString)组合起来被用作我们的分组键
  • 最后,我们使用$count (或在Mongo 5之前使用{ $sum: 1 })来计算我们分组的预订数

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接