• 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 创建索引

    1. PUT test-index-001
    2. {
    3.   "mappings": {
    4.     "properties": {
    5.       "starttime": {
    6.         "type""date"
    7.       },
    8.       "endtime": {
    9.         "type""date"
    10.       }
    11.     }
    12.   }
    13. }

    3.2 插入数据

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

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

    1. POST test-index-001/_search
    2. {
    3.   "query": {
    4.     "bool": {
    5.       "filter": {
    6.         "script": {
    7.           "script": {
    8.             "source""doc['endtime'].date.minuteOfDay - doc['starttime'].date.minuteOfDay >= 15",
    9.             "lang""expression"
    10.           }
    11.         }
    12.       }
    13.     }
    14.   }
    15. }

    解读一下:

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

    2e8daeee278ba911932c7297eeeb360c.png

    更多推荐查看:

    383ba5781766f26151348bd9d3333732.png

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

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

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

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

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

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

    实现如下代码所示:

    1. PUT _ingest/pipeline/my_pipeline_20220618
    2. {
    3.   "processors": [
    4.     {
    5.       "script": {
    6.         "lang""painless",
    7.         "source""""
    8.         
    9.         String start_datetime = ctx.starttime; 
    10.         ZonedDateTime start_zdt = ZonedDateTime.parse(start_datetime); 
    11.         
    12.         String end_datetime =ctx.endtime; 
    13.         ZonedDateTime end_zdt = ZonedDateTime.parse(end_datetime); 
    14.         long start_millisDateTime = start_zdt.toInstant().toEpochMilli();
    15.         long end_millisDateTime = end_zdt.toInstant().toEpochMilli();
    16.         long elapsedTime = end_millisDateTime - start_millisDateTime;
    17.       
    18.         ctx.span = elapsedTime/1000/60;
    19.         """
    20.       }
    21.     }
    22.   ]
    23. }
    24. POST test-index-001/_update_by_query?pipeline=my_pipeline_20220618
    25. {
    26.   "query": {
    27.     "match_all": {}
    28.   }
    29. }
    30. POST test-index-001/_search
    31. {
    32.   "query": {
    33.     "range": {
    34.       "span": {
    35.         "gte"15
    36.       }
    37.     }
    38.   }
    39. }

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

    default_pipeline 实现如下:

    1. PUT test-20220619-10-02
    2. {
    3.   "settings": {
    4.     "default_pipeline""my_pipeline_20220618"
    5.   },
    6.   "mappings": {
    7.     "properties": {
    8.       "start_time": {
    9.         "type""date"
    10.       },
    11.       "end_time": {
    12.         "type""date"
    13.       }
    14.     }
    15.   }
    16. }
    17. ### 步骤2:导入数据
    18. PUT test-20220619-10-02/_doc/1
    19. {
    20.   "start_time""2022-01-01T12:00:30Z",
    21.   "end_time""2022-01-01T12:15:30Z"
    22. ### 方案二优势地方:时间差值已经成为我们新的字段,直接用这个字段
    23. POST test-20220619-10-02/_search
    24. {
    25.   "query": {
    26.     "range": {
    27.       "span": {
    28.         "gte"15
    29.       }
    30.     }
    31.   }
    32. }

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

    1. PUT _ingest/pipeline/my_pipeline_20220618_03
    2. {
    3.   "processors": [
    4.     {
    5.       "script": {
    6.         "lang""painless",
    7.         "source""""
    8.         
    9.           // create a Instant object
    10.         Instant start_instant = Instant.parse(ctx.starttime);
    11.  
    12.         // get millisecond value using toEpochMilli()
    13.         long start_millisDateTime = start_instant.toEpochMilli();
    14.         
    15.         // create a Instant object
    16.         Instant end_instant= Instant.parse(ctx.endtime);
    17.  
    18.         // get millisecond value using toEpochMilli()
    19.         long end_millisDateTime = end_instant.toEpochMilli();
    20.         long elapsedTime = end_millisDateTime - start_millisDateTime;
    21.         ctx.span = elapsedTime/1000/60;
    22.         """
    23.       }
    24.     }
    25.   ]
    26. }

    3.5 方案三:runtime_field 实时检索实现

    1. POST test-index-001/_search
    2. {
    3.   "fields": [
    4.     "*"
    5.   ],
    6.   "runtime_mappings": {
    7.     "span_value": {
    8.       "type""long",
    9.       "script": {
    10.         "source""emit((doc['endtime'].getValue().toInstant().toEpochMilli() - doc['starttime'].getValue().toInstant().toEpochMilli())/60000)"
    11.       }
    12.     }
    13.   }
    14. }

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

    注意: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

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

  • 相关阅读:
    ctfshow萌新计划web9-14(正则匹配绕过)
    数据结构——B树
    001:vue3 实现自定义指令v-copy复制
    夜天之书 #90 再谈 Rust 项目社群治理
    vue使用百度地图实现地点查询
    CSAPP实验记录(1)--------- DataLab
    Postgresql源码(116)提升子查询案例分析
    勒索病毒最新变种.mkp勒索病毒来袭,如何恢复受感染的数据?
    泰安双软申请指南
    将爱心代码设为电脑屏保,俘获少女芳心,还能假装黑客,在酷炫的界面中保护隐私
  • 原文地址:https://blog.csdn.net/wojiushiwo987/article/details/125383034