Elasticsearch能否将文档中嵌入的日期字符串索引为date类型并支持日期范围查询?
1. 能不能把文档里嵌入的日期字符串索引成date类型?
完全可以!不过因为日期是嵌在文本里,而且数量不固定,没法用常规的单个date字段。我们可以通过摄入管道(Ingest Pipeline)把所有符合格式的日期提取出来,存到一个date类型的数组字段里——这样既保留了日期的结构化属性,又能应对从几个到几百个不等的嵌入日期。
2. 针对你的具体场景的实现步骤
你的需求很明确:要索引一堆带<month> <day>, <year>格式日期的文档,之后要能查到满足两个条件的内容:一是文档里有至少一个日期≥2021-01-01,二是短语"held on"出现在这个日期的前后5个词范围内。下面是具体的实现方法:
第一步:创建摄入管道提取并转换日期
先做一个Ingest Pipeline,用grok匹配你的日期格式,再把提取到的日期转成标准date类型,存到embedded_dates数组里。这里假设你的文本内容存在content字段里:
PUT _ingest/pipeline/extract-embedded-dates { "description": "提取文本中所有<month> <day>, <year>格式的日期并转为date类型", "processors": [ { "grok": { "field": "content", "patterns": ["%{MONTH:temp_date.month} %{NUMBER:temp_date.day:int}, %{NUMBER:temp_date.year:int}"], "pattern_definitions": { "MONTH": "(January|February|March|April|May|June|July|August|September|October|November|December)" }, "match_only_text": true, "append": true, "target_field": "temp_dates" } }, { "script": { "source": """ List<String> dateList = new ArrayList<>(); if (ctx.temp_dates != null) { for (def datePart : ctx.temp_dates) { // 把提取到的月日年拼成字符串,再转成ISO日期格式 String dateStr = datePart.month + " " + datePart.day + ", " + datePart.year; ZonedDateTime zdt = DateTimeFormatter.ofPattern("MMMM dd, yyyy").parse(dateStr, ZonedDateTime::from); dateList.add(zdt.toInstant().toString()); } ctx.embedded_dates = dateList; } // 清理临时字段 ctx.remove("temp_dates"); """, "lang": "painless" } } ] }
这个管道会遍历content里所有符合格式的日期,转成Elasticsearch能识别的ISO格式,存到embedded_dates数组里,不管有多少个日期都能处理。
第二步:创建索引并设置映射
创建索引时,要指定embedded_dates为date类型,content保留为text类型(默认会开启位置索引,这对后续的词距查询很重要):
PUT event_docs_index { "mappings": { "properties": { "content": { "type": "text" }, "embedded_dates": { "type": "date" } } } }
第三步:用管道索引文档
索引文档的时候,指定用我们刚才创建的管道,这样日期会自动被提取转换:
PUT event_docs_index/_doc/1?pipeline=extract-embedded-dates { "content": "The annual summit was held on June 20, 2020, but the follow-up meeting was held on January 5, 2021." }
第四步:编写目标查询
要同时满足日期范围和词距两个条件,我们用bool查询结合span near查询(处理词距)和range查询(处理日期范围):
GET event_docs_index/_search { "query": { "bool": { "must": [ // 条件1:至少有一个嵌入日期≥2021-01-01 { "range": { "embedded_dates": { "gte": "2021-01-01" } } }, // 条件2:"held on"和符合格式的日期在前后5个词范围内 { "span_near": { "clauses": [ { "span_multi": { "match": { "match_phrase": { "content": "held on" } } } }, { "span_multi": { "match": { "regexp": { "content": "(January|February|March|April|May|June|July|August|September|October|November|December) \\d{1,2}, \\d{4}" } } } } ], "slop": 5, "in_order": false } } ] } } }
这里的slop:5表示两个短语之间最多可以隔5个词,in_order:false不限制"held on"和日期的先后顺序。同时range查询保证文档里有符合条件的结构化日期。
额外提示
- 如果你的日期有月份缩写(比如"Jul"代替"July"),可以修改grok里的
MONTH模式,加入(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)这种缩写形式。 - 要是觉得painless脚本麻烦,也可以用
date处理器配合循环,但脚本处理多日期会更灵活。 - 这些功能在Elasticsearch 7.x及以上版本都支持,如果你用的是更老的版本,可能需要调整一些语法。
内容的提问来源于stack exchange,提问作者davidavr

