Elasticsearch 如何實(shí)現(xiàn)時(shí)間差查詢?
1、Elasticsearch 線上實(shí)戰(zhàn)問(wèn)題
問(wèn)個(gè)問(wèn)題啊,es能通過(guò)兩個(gè)字段差值進(jìn)行查詢嗎?類似select * from myindex where endtimes- starttime > 10這種?
——問(wèn)題來(lái)源:死磕Elasticsearch 知識(shí)星球
那么問(wèn)題來(lái)了,Elasticsearch 如何實(shí)現(xiàn)時(shí)間差的查詢呢?
2、先說(shuō)一下 MySQL 實(shí)現(xiàn)
2.1 MySQL 表結(jié)構(gòu)

2.2 MySQL 樣例數(shù)據(jù)

2.3 MySQL 計(jì)算時(shí)間差?
select timestampdiff(MINUTE, start_time, end_time) as span from test;
結(jié)果如下:

結(jié)果 15 代表 15 分鐘的意思。
3、Elasticsearch 實(shí)現(xiàn)拆解
3.1 創(chuàng)建索引
PUT test-index-001
{
"mappings": {
"properties": {
"starttime": {
"type": "date"
},
"endtime": {
"type": "date"
}
}
}
}
3.2 插入數(shù)據(jù)
POST test-index-001/_bulk
{"index":{"_id":1}}
{"starttime":"2022-06-08T10:00:00Z","endtime":"2022-06-08T10:15:00Z"}
3.3 方案一:直接類MySQL 查詢實(shí)現(xiàn)
POST test-index-001/_search
{
"query": {
"bool": {
"filter": {
"script": {
"script": {
"source": "doc['endtime'].date.minuteOfDay - doc['starttime'].date.minuteOfDay >= 15",
"lang": "expression"
}
}
}
}
}
}
解讀一下:
lang 指的是腳本語(yǔ)言,這里使用的是:expression,不是 painless 無(wú)痛腳本,所以寫(xiě)法和往常會(huì)不同。

更多推薦查看:

3.4 方案二:ingest 預(yù)處理空間換時(shí)間實(shí)現(xiàn)
核心使用的是:painless 無(wú)痛腳本。在對(duì)時(shí)間的腳本處理上略顯笨拙(大家有好的方法可以交流)。
步驟1:時(shí)間字段轉(zhuǎn)成字符串; 步驟2:字符串轉(zhuǎn)成 ZonedDateTime字段類型;步驟3:ZonedDateTime 字段類型轉(zhuǎn)成 long長(zhǎng)整形。步驟4:求解兩個(gè)整形之差就可以了。
實(shí)現(xiàn)如下代碼所示:
PUT _ingest/pipeline/my_pipeline_20220618
{
"processors": [
{
"script": {
"lang": "painless",
"source": """
String start_datetime = ctx.starttime;
ZonedDateTime start_zdt = ZonedDateTime.parse(start_datetime);
String end_datetime =ctx.endtime;
ZonedDateTime end_zdt = ZonedDateTime.parse(end_datetime);
long start_millisDateTime = start_zdt.toInstant().toEpochMilli();
long end_millisDateTime = end_zdt.toInstant().toEpochMilli();
long elapsedTime = end_millisDateTime - start_millisDateTime;
ctx.span = elapsedTime/1000/60;
"""
}
}
]
}
POST test-index-001/_update_by_query?pipeline=my_pipeline_20220618
{
"query": {
"match_all": {}
}
}
POST test-index-001/_search
{
"query": {
"range": {
"span": {
"gte": 15
}
}
}
}
如上 update_by_query 的實(shí)現(xiàn)完全可以轉(zhuǎn)換為預(yù)處理+setting環(huán)節(jié)的 default_pipeline 方式實(shí)現(xiàn),確保寫(xiě)入環(huán)節(jié)直接生成span字段值,確保候選實(shí)現(xiàn)空間換時(shí)間,提高檢索效率。
default_pipeline 實(shí)現(xiàn)如下:
PUT test-20220619-10-02
{
"settings": {
"default_pipeline": "my_pipeline_20220618"
},
"mappings": {
"properties": {
"start_time": {
"type": "date"
},
"end_time": {
"type": "date"
}
}
}
}
### 步驟2:導(dǎo)入數(shù)據(jù)
PUT test-20220619-10-02/_doc/1
{
"start_time": "2022-01-01T12:00:30Z",
"end_time": "2022-01-01T12:15:30Z"
}
### 方案二優(yōu)勢(shì)地方:時(shí)間差值已經(jīng)成為我們新的字段,直接用這個(gè)字段
POST test-20220619-10-02/_search
{
"query": {
"range": {
"span": {
"gte": 15
}
}
}
}
如上實(shí)現(xiàn),更簡(jiǎn)潔寫(xiě)法如下:
PUT _ingest/pipeline/my_pipeline_20220618_03
{
"processors": [
{
"script": {
"lang": "painless",
"source": """
// create a Instant object
Instant start_instant = Instant.parse(ctx.starttime);
// get millisecond value using toEpochMilli()
long start_millisDateTime = start_instant.toEpochMilli();
// create a Instant object
Instant end_instant= Instant.parse(ctx.endtime);
// get millisecond value using toEpochMilli()
long end_millisDateTime = end_instant.toEpochMilli();
long elapsedTime = end_millisDateTime - start_millisDateTime;
ctx.span = elapsedTime/1000/60;
"""
}
}
]
}
3.5 方案三:runtime_field 實(shí)時(shí)檢索實(shí)現(xiàn)
POST test-index-001/_search
{
"fields": [
"*"
],
"runtime_mappings": {
"span_value": {
"type": "long",
"script": {
"source": "emit((doc['endtime'].getValue().toInstant().toEpochMilli() - doc['starttime'].getValue().toInstant().toEpochMilli())/60000)"
}
}
}
}
核心:同樣是轉(zhuǎn)化為毫秒,然后做的計(jì)算。
注意:fields 要設(shè)置,否則數(shù)據(jù) _source 下不顯示。

4、小結(jié)
關(guān)于 Elasticsearch 實(shí)現(xiàn)時(shí)間差查詢,本文給出三種不同方案實(shí)現(xiàn),視頻解讀如下。
從簡(jiǎn)潔程度推薦方案 1 或者方案 3。
從性能角度推薦方案 2 ——空間換時(shí)間,方案 2 可以優(yōu)化為寫(xiě)入的時(shí)候指定 default_pipeline 全部預(yù)處理實(shí)現(xiàn)。
你的業(yè)務(wù)環(huán)境有沒(méi)有遇到類似問(wèn)題,你是如何實(shí)現(xiàn)的呢?
參考
https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting-expression.html
https://www.elastic.co/guide/en/elasticsearch/reference/5.0/modules-scripting-expression.html#datefield_api
https://www.elastic.co/guide/en/elasticsearch/painless/master/painless-datetime.html#_datetime_input_from_an_indexed_document
https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-api-reference-shared-java-time.html
推薦閱讀
更短時(shí)間更快習(xí)得更多干貨!
和全球 1600+ Elastic 愛(ài)好者一起精進(jìn)!

