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

时间:2013-07-24 12:52:19

标签: mongodb datetime time pymongo

我设置了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小时时间内完成)。

e.g。

有分:

  • "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)}
});

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

这可能吗?或者我每天都必须这样做?

2 个答案:

答案 0 :(得分:7)

嗯,解决此问题的最佳方法是分别存储分钟。但你可以通过聚合框架解决这个问题,尽管会非常快:

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

答案 1 :(得分:2)

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

如果您确定的应用程序使用方式是您依赖查询“小时”或一天中的其他时间,而不希望查看“日期”部分,那么最好将其作为数字值存储在文献。像“从一天开始算起的毫秒数” 这样的东西对于BSON Date而言可以满足许多目的,但当然可以提供更好的性能,而无需 计算 (用于每个文档)。

设置

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

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进行索引时链接页面上概述的集合扫描。

为了获得最佳性能,您永远不需要计算任何东西,因为在任何现实世界中,仅处理一个集合中的所有文档只花一个数量级就可以确定您想要哪些文档,而不是仅仅引用一个索引而仅提取这些文档。

聚合框架可能仅能帮助您在此处重写文档,但实际上不应将其用作返回此类数据的生产系统方法。分别存储时间。