在 MongoDb 中按 15 分钟的时间间隔对结果进行分组

Posted

技术标签:

【中文标题】在 MongoDb 中按 15 分钟的时间间隔对结果进行分组【英文标题】:Group result by 15 minutes time interval in MongoDb 【发布时间】:2015-01-05 00:33:06 【问题描述】:

我有一个像这样的结构的“状态”集合 -


    _id: ObjectId("545a0b63b03dbcd1238b4567"),
    status: 1004,
    comment: "Rem dolor ipsam placeat omnis non. Aspernatur nobis qui nisi similique.",
    created_at: ISODate("2014-11-05T11:34:59.804Z")
,

    _id: ObjectId("545a0b66b03dbcd1238b4568"),
    status: 1001,
    comment: "Sint et eos vero ipsa voluptatem harum. Hic unde voluptatibus et blanditiis quod modi.",
    created_at: ISODate("2014-11-05T11:35:02.814Z")

....
....

我需要从该集合中获取按 15 分钟间隔分组的结果。

【问题讨论】:

所提供的答案中的内容是否不清楚或不适用于您的情况?注意到它仍然不被接受。 别费心了,他已经回答过了,何必费心接受回答呢。 【参考方案1】:

有几种方法可以做到这一点。

第一个是Date Aggregation Operators,它允许您剖析文档中的“日期”值。专门针对“分组”作为主要意图:

db.collection.aggregate([
   "$group": 
    "_id": 
      "year":  "$year": "$created_at" ,
      "dayOfYear":  "$dayOfYear": "$created_at" ,
      "hour":  "$hour": "$created_at" ,
      "interval": 
        "$subtract": [ 
           "$minute": "$created_at" ,
           "$mod": [ "$minute": "$created_at", 15] 
        ]
      
    ,
    "count":  "$sum": 1 
  
])

第二种方法是使用一个小技巧,即从另一个日期对象中减去一个日期对象(或其他直接数学运算),然后结果是一个表示两个对象之间的纪元时间戳毫秒的数值。因此,只需使用纪元日期即可获得纪元毫秒表示。然后使用日期数学作为间隔:

db.collection.aggregate([
     "$group": 
        "_id": 
            "$subtract": [
                 "$subtract": [ "$created_at", new Date("1970-01-01") ] ,
                 "$mod": [ 
                     "$subtract": [ "$created_at", new Date("1970-01-01") ] ,
                    1000 * 60 * 15
                ]
            ]
        ,
        "count":  "$sum": 1 
    
])

所以这取决于你想要分组间隔的输出格式。两者基本上代表相同的事物,并且有足够的数据在您的代码中重新构造为“日期”对象。

您可以在分组_id 之后的“分组运算符”部分中添加任何其他内容。我只是使用基本的“计数”示例来代替你自己关于你真正想做的任何真实陈述。


MongoDB 4.x 及更高版本

自最初编写以来,日期聚合运算符添加了一些内容,但从 MongoDB 4.0 开始,将有实际的“真正的类型转换”,而不是此处使用 BSON 日期转换完成的基本数学技巧。

例如,我们可以在这里使用$toLong$toDate 作为新的助手:

db.collection.aggregate([
   "$group": 
    "_id": 
      "$toDate": 
        "$subtract": [
           "$toLong": "$created_at" ,
           "$mod": [  "$toLong": "$created_at" , 1000 * 60 * 15 ] 
        ]
      
    ,
    "count":  "$sum": 1 
  
])

这有点短,并且不需要将“epoch”值的外部 BSON 日期定义为定义管道时的常量,因此它对于所有语言实现都非常一致。

这些只是类型转换的“辅助”方法中的两个,它们都与$convert 方法相关联,这是一种“更长”的实现形式,允许对null 进行自定义处理或转换错误。

甚至可以通过这种转换从主键的ObjectId 中获取Date 信息,因为这将是“创建”日期的可靠来源:

db.collection.aggregate([
   "$group": 
    "_id": 
      "$toDate": 
        "$subtract": [
           "$toLong":  "$toDate": "$_id"   ,
           "$mod": [  "$toLong":  "$toDate": "$_id"  , 1000 * 60 * 15 ] 
        ]
      
    ,
    "count":  "$sum": 1 
  
])

因此,使用这种转换的“转换类型”可能是非常强大的工具。

警告 - ObjectId 值仅限于精确到 ,仅适用于构成其数据的一部分的内部时间值,允许 $toDate 转换。实际插入的“时间”很可能取决于使用的驱动程序。在需要 精度 的地方,仍然建议使用离散的 BSON 日期字段,而不是依赖 ObjectId 值。

【讨论】:

太糟糕了,我不能接受他 - 非常有用的答案! 我完全同意@Petrov 感谢您提供这些好的解决方案!我认为您的第一个示例中可能存在一个小错误。你错过了按小时分组(为了检索 15 分钟的间隔——我假设——应该按小时)。所以你需要在dayOfYear-line 之后添加"hour": "$hour": "$created_at" , Mongodb 4.0 已于 2018 发布,您知道 2014 的这些聚合...如何??? @AnthonyWinzlet,他在 2018 年 4 月 26 日编辑了他的答案。【参考方案2】:

我喜欢这里的另一个答案,主要是为了使用日期数学而不是聚合日期运算符,这虽然有帮助,但也可能有点晦涩。

我想在这里添加的唯一一件事是,您还可以通过这种方法从聚合框架返回一个Date 对象,而不是作为结果的“数字”时间戳。使用$add,只是在相同原理上进行了一点额外的数学运算:

db.collection.aggregate([
     "$group": 
        "_id": 
            "$add": [
                 "$subtract": [
                     "$subtract": [ "$current_date", new Date(0) ] ,
                     "$mod": [ 
                         "$subtract": [ "$current_date", new Date(0) ] ,
                        1000 * 60 * 15
                    ]
                ] ,
                new Date(0)
            ]
        ,
        "count":  "$sum": 1 
    
])

这里 javascript 中的 Date(0) 结构以较短的形式表示相同的“纪元”日期,因为从纪元开始的 0 毫秒就是纪元。但要点是,当使用数字标识符对另一个 BSON 日期对象进行“添加”时,所描述的条件的反面为真,最终结果现在实际上是 Date

所有驱动程序都会通过这种方法将原生 Date 类型返回到他们的语言。

【讨论】:

【参考方案3】:

对 mongo db.version()

db.collection.aggregate([
    $match: created_at:$exists:1,
    $group: 
        _id: $add:[
            $dayOfYear: "$created_at" ,
            $multiply: [$year: "$created_at", 1000]
        ],
        count: $sum: 1 
    ,
    $sort:_id:-1
])

【讨论】:

【参考方案4】:

另一个有用的方法:

db.collection.aggregate([
  $group: 
    _id:  
      overallTime:  
        $dateToString:  format: "%Y-%m-%dT%H", date: "$created_at"  
      ,
      interval:  $trunc:  $divide: [ $minute: "$created_at" , 15 ]
    ,
  ,
])

minhourday 间隔更容易:

var format = "%Y-%m-%dT%H:%M"; // 1 min
var format = "%Y-%m-%dT%H"; // 1 hour
var format = "%Y-%m-%d"; // 1 day

db.collection.aggregate([
  $group: 
    _id:  $dateToString:  format: format, date: "$created_at"  ,
  ,
])

【讨论】:

【参考方案5】:

@Neil Lunn 在https://***.com/a/26814496/8474325 对 MongoDb 4.x 以上的回答非常棒。但是代码中有一个小错误,他使用 ObjectId 进行聚合。必须将行 "$toDate": "_id" 更改为 "$toDate": "$_id" 才能使代码正常工作。

这是更正后的代码。

db.collection.aggregate([
     "$group": 
      "_id": 
          "$toDate": 
              "$subtract": [
                   "$toLong":  "$toDate": "$_id"   ,
                   "$mod": [  "$toLong":  "$toDate": "$_id"  , 1000 * 60 * 15 ] 
              ]
          
      ,
      "count":  "$sum": 1 
   
])

【讨论】:

【参考方案6】:

MongoDB 5.x 及更高版本

现在在聚合管道中支持日期截断,例如:


  $group: 
   "_id":  "$dateTrunc":  date: "$created_at", unit: "minute", binSize: 15  ,
   "count" :  $sum: 1 
  
,

您还可以找到有关窗口函数和 dateTrunc here的有用信息

【讨论】:

$count: 而不是 $sum: 1

以上是关于在 MongoDb 中按 15 分钟的时间间隔对结果进行分组的主要内容,如果未能解决你的问题,请参考以下文章

在 Oracle SQL 中按时间间隔聚合数据

mongoDB:$dateToString 格式以 15 分钟间隔显示时间

mongoDB:$dateToString 格式以 15 分钟间隔显示时间

调用函数node.js的动态时间间隔

MongoDB:如何在 3.6 mongoDb 版本中解析日期?

MongoDB:如何在 3.6 mongoDb 版本中解析日期?