12

我有一个 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示例的查询,而是在time 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)}
});

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

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

4

2 回答 2

10

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

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

于 2013-07-24T13:04:24.913 回答
6

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

如果您确定的应用程序使用模式是您依赖查询“小时”或一天中的其他时间而不想查看“日期”部分,那么最好将其作为数字值存储在文档中。像“从一天开始的毫秒数”这样的东西对于与 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
  }
})

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

最重要的是,作为在运行时不依赖于计算的真实文档属性,您可以在其上放置一个索引

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

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

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

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

于 2018-11-22T09:11:41.413 回答