MongoDB - 在几个小时的时间范围内查询

Posted

技术标签:

【中文标题】MongoDB - 在几个小时的时间范围内查询【英文标题】:MongoDB - Querying between a time range of hours 【发布时间】:2013-07-23 23:07:27 【问题描述】:

我有一个存储位置数据的 MongoDB 数据存储,如下所示:


"_id" : ObjectId("51d3e161ce87bb000792dc8d"),
"datetime_recorded" : ISODate("2013-07-03T05:35:13Z"),
"loc" : 
    "coordinates" : [
        0.297716,
        18.050614
    ],
    "type" : "Point"
,
"vid" : "11111-22222-33333-44444"

我希望能够执行类似于date range 示例的查询,但要在时间范围上执行。即检索在上午 12 点到下午 4 点之间记录的所有点(也可以使用 1200 和 1600 24 小时时间完成)。

例如

有积分:

"datetime_recorded" : ISODate("2013-05-01T12:35:13Z"), "datetime_recorded" : ISODate("2013-06-20T05:35:13Z"), "datetime_recorded" : ISODate("2013-01-17T07:35:13Z"), "datetime_recorded" : ISODate("2013-04-03T15:35:13Z"),

查询

db.points.find('datetime_recorded': 
    $gte: Date(1200 hours),
    $lt: Date(1600 hours)
);

只会产生第一个和最后一个点。

这可能吗?还是我必须每天都这样做?

【问题讨论】:

由于 Mongodb 没有用于普通查询的日期/时间运算符,我绝对建议您更改架构以将时间作为不同的字段包含在内。没有它,您将无法有效地使用索引来缩小结果范围。 【参考方案1】:

嗯,解决这个问题的最佳方法是分别存储分钟。但是您可以使用聚合框架解决这个问题,尽管这不会很快:

db.so.aggregate( [ 
     $project: 
        loc: 1,
        vid: 1,
        datetime_recorded: 1, 
        minutes:  $add: [
             $multiply: [  $hour: '$datetime_recorded' , 60 ] , 
             $minute: '$datetime_recorded'  
        ]  
     ,
     $match:  'minutes' :  $gte : 12 * 60, $lt : 16 * 60   
] );

在第一步$project,我们从hour * 60 + min 计算分钟数,然后在第二步匹配:$match

【讨论】:

你是对的——不快!但它确实完成了工作:-) 赞成,但只是想看看是否有人可以提供更快的方法。非常感谢!可能会用一个小 pymongo 脚本分别循环更新一个小时/分钟... 已接受答案 - 目前正在处理小型数据集,同时添加了分钟/小时字段。非常感谢@Derick yuk,这怎么可能是 mongoDB 中的最佳解决方案?按时间范围查询不应该是火箭时间【参考方案2】:

添加一个答案,因为我不同意其他答案,因为即使您可以使用聚合框架做很多很棒的事情,但这确实不是执行此类查询的最佳方式。

如果您确定的应用程序使用模式是您依赖查询“小时”或一天中的其他时间而不想查看“日期”部分,那么最好将其作为数值存储在文档。 “从一天开始的毫秒数” 这样的东西对于与 BSON 日期一样多的用途来说足够精细,但当然可以提供更好的性能而无需计算 用于每个文档。

设置

这确实需要一些设置,因为您需要将新字段添加到现有文档中,并确保将这些字段添加到代码中的所有新文档中。一个简单的转换过程可能是:

MongoDB 4.2 及更高版本

这实际上可以在单个请求中完成,因为现在“更新”语句中允许聚合操作。

db.collection.updateMany(
  ,
  [ "$set": 
    "timeOfDay": 
      "$mod": [
         "$toLong": "$datetime_recorded" ,
        1000 * 60 * 60 * 24
      ]
    
  ]
)

旧版 MongoDB

var batch = [];

db.collection.find( "timeOfDay":  "$exists": false  ).forEach(doc => 
  batch.push(
    "updateOne": 
      "filter":  "_id": doc._id ,
      "update": 
        "$set": 
          "timeOfDay":  doc.datetime_recorded.valueOf() % (60 * 60 * 24 * 1000)
        
      
    
  );

  // write once only per reasonable batch size
  if ( batch.length >= 1000 ) 
    db.collection.bulkWrite(batch);
    batch = [];
  
)

if ( batch.length > 0 ) 
  db.collection.bulkWrite(batch);
  batch = [];

如果您有能力写入新集合,则不需要循环和重写:

db.collection.aggregate([
   "$addFields": 
    "timeOfDay": 
      "$mod": [
         "$subtract": [ "$datetime_recorded", Date(0) ] ,
        1000 * 60 * 60 * 24
      ]
    
  ,
   "$out": "newcollection" 
])

或使用 MongoDB 4.0 及更高版本:

db.collection.aggregate([
   "$addFields": 
    "timeOfDay": 
      "$mod": [
         "$toLong": "$datetime_recorded" ,
        1000 * 60 * 60 * 24
      ]
    
  ,
   "$out": "newcollection" 
])

全部使用相同的基本转换:

1000 毫秒为一秒 一分钟 60 秒 一小时 60 分钟 一天 24 小时

数字自纪元以来的毫秒数的模数,实际上是内部存储为BSON日期的值,很容易提取为当前当天的毫秒数

查询

查询非常简单,根据问题示例:

db.collection.find(
  "timeOfDay": 
    "$gte": 12 * 60 * 60 * 1000, "$lt": 16 * 60 * 60 * 1000
  
)

当然使用从小时到毫秒的相同时间尺度转换来匹配存储的格式。但就像之前一样,您可以根据实际需要制作任何规模。

最重要的是,作为不依赖于运行时计算的真实文档属性,您可以在此放置index:

db.collection.createIndex( "timeOfDay": 1 )

因此,这不仅可以消除计算的运行时开销,而且使用索引可以避免集合扫描,如 MongoDB 索引链接页面中所述。

为了获得最佳性能,您永远不想计算这样的事情,因为在任何现实世界的规模中,处理集合中的所有文档只是为了找出您想要的文档,而不是简单地引用一个索引,只需要一个数量级的时间获取那些文件。

聚合框架可能只是可以帮助您重写这里的文档,但它确实不应该用作返回此类数据的生产系统方法。单独存储时间。

【讨论】:

对于基于 timeOfDay 的范围查询,是否使用像 db.collection.createIndex( "timeOfDay": 1 ) 和非常大的文档数(10M-20M)这样的索引?跨度>

以上是关于MongoDB - 在几个小时的时间范围内查询的主要内容,如果未能解决你的问题,请参考以下文章

JS 在几小时内得到两个时间不同的 GMT 问题

Kafka 连接器记录写入器因缺少要分配的内存而卡在 S3OutputStream 中,但在几个小时内保持空闲状态并没有失败

mongodb怎么查询一天中24个小时内的各个数据集合

从 Vava 到 MongoDB 查询:查找特定日期范围内的事件

WPF 应用程序,“时钟文本框”在几个小时后冻结

Python 脚本在几个小时后停止