在MongoDb中按15分钟时间间隔分组结果

64

我有一个名为“status”的集合,结构如下 -

{
    _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分钟间隔分组获取结果。


15
提供的答案中有什么不清楚或者不适用于你的情况吗?请注意,它仍未被接受。 - Neil Lunn
4
别管了,他已经采纳了答案,你为什么还要费心去接受答案呢。 - nurgasemetey
7个回答

172

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

第一种方法是使用日期聚合运算符,它允许您分解文档中的“日期”值。 特别是针对“分组”作为主要意图:

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分组之后,您可以在“分组运算符”部分放入任何其他内容。我只是使用基本的“count”示例,代替您真正想做什么的陈述。


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 }
  }}
])

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

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

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

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

因此,“casting types”通过这种转换可以成为一个非常强大的工具。

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


12
太糟糕了,我不能接受他的请求 - 真的是非常有用的回答! - Petrov
2
我非常同意 @Petrov - aiapatag
2
感谢提供这些好的解决方案!我认为你的第一个例子可能有一个小错误。你漏掉了按小时分组(以便检索15分钟间隔 - 我假设 - 应该是按小时)。所以你需要在dayOfYear行后添加"hour": { "$hour": "$created_at" }, - skofgar
Mongodb 4.0于2018年发布,你怎么知道这些聚合操作是从2014年开始的呢? - Ashh
3
@AnthonyWinzlet 在2018年4月26日编辑了他的回答。 - Paul
有人可以帮忙回答这个相关的问题吗?https://stackoverflow.com/questions/61131299/mongodb-datetostring-format-to-show-time-by-15min-interval - newdeveloper

17

我喜欢这里的另一个答案,主要是因为使用了日期计算而不是聚合日期操作符,虽然后者也有帮助,但有时候会有些晦涩。

我想补充的是,你还可以通过这种方法从聚合框架中返回一个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 }
    }}
])

Date(0)在JavaScript中代表相同的“纪元”日期,它是以更短的形式表示的,因为从"epoch"开始0毫秒就是epoch。但主要问题是,当使用数字标识符向另一个BSON日期对象进行“加法”运算时,所述条件的逆条件是真实的,并且最终结果实际上是一个Date

所有的驱动程序都会通过这种方式返回其语言的原生Date类型。


9
另一种有用的方法:
db.collection.aggregate([
  {$group: {
    _id: { 
      overallTime: { 
        $dateToString: { format: "%Y-%m-%dT%H", date: "$created_at" } 
      },
      interval: { $trunc: { $divide: [{ $minute: "$created_at" }, 15 ]}}
    },
  }},
])

对于分钟小时的时间间隔,更加容易:

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" } },
  }},
])

7

让mongo db.version() < 3.0更美观一些

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}}
])

6

MongoDB 5.x及以上版本

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

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

你还可以在这里找到关于窗口函数和dateTrunc的有用信息。


或者使用{ $count: {} }代替{ $sum: 1 } - Wernfried Domscheit

2

Neil Lunn在https://dev59.com/518d5IYBdhLWcg3wch2i#26814496中提供的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 }
   }}
])

1

在MongoDB v5.0+中,您可以使用$setWindowFields对附近的文档(即15分钟内的文档)执行计算。在下面的示例中,它将计算当前文档之前或之后15分钟内的所有文档。您可以通过更改window参数来进行调整。

db.collection.aggregate([
  {
    $setWindowFields: {
      partitionBy: null,
      sortBy: {
        created_at: 1
      },
      output: {
        count: {
          $count: {},
          window: {
            range: [
              -15,
              15
            ],
            unit: "minute"
          }
        }
      }
    }
  }
])

这里是您参考的Mongo Playground


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