Elasticsearch 如何实现时间差查询?

1、Elasticsearch 线上实战问题

问个问题啊,es能通过两个字段差值进行查询吗?类似select * from myindex where endtimes- starttime > 10这种?

——问题来源:死磕Elasticsearch 知识星球

那么问题来了,Elasticsearch 如何实现时间差的查询呢?

2、先说一下 MySQL 实现

2.1 MySQL 表结构

3051d2fc0f41beadb0a5c69dd515ce52.png

2.2 MySQL 样例数据

f5dd8ef747b0f4546672816cfaf98381.png

2.3 MySQL 计算时间差?

select timestampdiff(MINUTE, start_time, end_time) as span from test;

结果如下:

48af9d9b1e0a9db271d4eba61ff16d19.png

结果 15 代表 15 分钟的意思。

3、Elasticsearch 实现拆解

3.1 创建索引

PUT test-index-001
{
  "mappings": {
    "properties": {
      "starttime": {
        "type": "date"
      },
      "endtime": {
        "type": "date"
      }
    }
  }
}

3.2 插入数据

POST test-index-001/_bulk
{"index":{"_id":1}}
{"starttime":"2022-06-08T10:00:00Z","endtime":"2022-06-08T10:15:00Z"}

3.3 方案一:直接类MySQL 查询实现

POST test-index-001/_search
{
  "query": {
    "bool": {
      "filter": {
        "script": {
          "script": {
            "source": "doc['endtime'].date.minuteOfDay - doc['starttime'].date.minuteOfDay >= 15",
            "lang": "expression"
          }
        }
      }
    }
  }
}

解读一下:

lang 指的是脚本语言,这里使用的是:expression,不是 painless 无痛脚本,所以写法和往常会不同。

2e8daeee278ba911932c7297eeeb360c.png

更多推荐查看:

383ba5781766f26151348bd9d3333732.png

3.4 方案二:ingest 预处理空间换时间实现

核心使用的是:painless 无痛脚本。在对时间的脚本处理上略显笨拙(大家有好的方法可以交流)。

  • 步骤1:时间字段转成字符串;

  • 步骤2:字符串转成 ZonedDateTime 字段类型;

  • 步骤3:ZonedDateTime 字段类型转成 long 长整形。

  • 步骤4:求解两个整形之差就可以了。

实现如下代码所示:

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 的实现完全可以转换为预处理+setting环节的 default_pipeline 方式实现,确保写入环节直接生成span字段值,确保候选实现空间换时间,提高检索效率。

default_pipeline 实现如下:

PUT test-20220619-10-02
{
  "settings": {
    "default_pipeline": "my_pipeline_20220618"
  },
  "mappings": {
    "properties": {
      "start_time": {
        "type": "date"
      },
      "end_time": {
        "type": "date"
      }
    }
  }
}


### 步骤2:导入数据
PUT test-20220619-10-02/_doc/1
{
  "start_time": "2022-01-01T12:00:30Z",
  "end_time": "2022-01-01T12:15:30Z"
} 

### 方案二优势地方:时间差值已经成为我们新的字段,直接用这个字段
POST test-20220619-10-02/_search
{
  "query": {
    "range": {
      "span": {
        "gte": 15
      }
    }
  }
}

如上实现,更简洁写法如下:

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 实时检索实现

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

核心:同样是转化为毫秒,然后做的计算。

注意:fields 要设置,否则数据 _source 下不显示。

2eef97ea0bd5e8be6565ffa9081b9a3d.png

4、小结

关于 Elasticsearch 实现时间差查询,本文给出三种不同方案实现,视频解读如下。

从简洁程度推荐方案 1 或者方案 3。

从性能角度推荐方案 2 ——空间换时间,方案 2 可以优化为写入的时候指定 default_pipeline 全部预处理实现。

你的业务环境有没有遇到类似问题,你是如何实现的呢?

参考

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

推荐阅读

  1. 如何从0到1打磨一门 Elasticsearch 线上直播课?

  2. 重磅 | 死磕 Elasticsearch 方法论认知清单(2021年国庆更新版)

  3. 如何系统的学习 Elasticsearch ?

  4. Elasticsearch 预处理没有奇技淫巧,请先用好这一招!

  5. Elasticsearch的ETL利器——Ingest节点

9006bcb0bcadd050e533cbfb50f6a093.png

更短时间更快习得更多干货!

和全球 1600+ Elastic 爱好者一起精进!

cf42f425e5eb3b60181367e1d650f1eb.gif

比同事抢先一步学习进阶干货!

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

铭毅天下

和你一起,死磕Elastic!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值