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 - 在几个小时的时间范围内查询的主要内容,如果未能解决你的问题,请参考以下文章
Kafka 连接器记录写入器因缺少要分配的内存而卡在 S3OutputStream 中,但在几个小时内保持空闲状态并没有失败