Elasticsearch如何按datetime字段筛选指定日期范围和时段的文档
问题原因
你写的查询无法正常运行的核心原因:Elasticsearch 对date类型字段执行range查询时,若传入的时间值缺失日期段,会自动补全默认纪元日期1970-01-01。你写的两个时间范围条件实际等价于要求start_at >= 1970-01-01 14:00 且 start_at < 1970-01-01 18:00,和前面限定的2022年6月的日期范围完全互斥,自然匹配不到任何文档。
该需求可以正常实现,以下提供两种可落地的方案:
方案1:无需修改索引,使用script查询(适合数据量小的场景)
直接在查询时通过painless脚本提取start_at的小时、分钟值做时间范围判断,不需要调整现有索引结构;缺点是脚本执行需要逐行匹配,性能较差,不适合千万级以上数据量的索引。
查询语句示例:
GET /a1/_search?pretty { "query": { "bool": { "must": [ { "range": { "start_at": { "gte": "2022-06-20", "lt": "2022-06-27", "format": "yyyy-MM-dd" } } }, { "script": { "script": { "source": """ // 提取文档时间的小时、分钟值,换算为当日分钟数做区间比较 int hour = doc['start_at'].value.getHour(); int minute = doc['start_at'].value.getMinute(); int currentTimeVal = hour * 60 + minute; // 14:00对应840分钟,18:00对应1080分钟,可通过params灵活传参调整 return currentTimeVal >= params.startTime && currentTimeVal < params.endTime; """, "params": { "startTime": 840, "endTime": 1080 } } } } ] } }, "size": 10 }
方案2:新增时间子字段(生产环境推荐,性能最优)
如果是生产环境、数据量较大,建议给start_at字段新增子字段单独存储时分部分,后续查询直接对这个子字段做range即可,查询性能和普通range查询完全一致。
步骤1:更新索引mapping新增子字段
PUT /a1/_mapping { "properties": { "start_at": { "type": "date", "format": "yyyy-MM-dd HH:mm", "fields": { "time_part": { "type": "date", "format": "HH:mm" } } } } }
执行完mapping更新后,新写入的数据会自动生成start_at.time_part子字段,历史数据可以通过执行POST /a1/_update_by_query?conflicts=proceed命令重新索引,生成对应子字段值。
步骤2:使用新子字段查询
GET /a1/_search?pretty { "query": { "bool": { "must": [ { "range": { "start_at": { "gte": "2022-06-20", "lt": "2022-06-27", "format": "yyyy-MM-dd" } } }, { "range": { "start_at.time_part": { "gte": "14:00", "lt": "18:00", "format": "HH:mm" } } } ] } }, "size": 10 }
注意:如果使用的是7.x以下版本的Elasticsearch,
date类型子字段存储HH:mm格式时可能出现纪元日期兼容问题,这种场景可以把子字段类型设为integer,存储值为小时*60+分钟的数值即可,查询逻辑和上述示例完全一致。
内容的提问来源于stack exchange,提问作者jeyGey
相关产品推荐
相关产品推荐

