如何使用正则表达式在 Elasticsearch 中搜索包含以空格分隔的段的字段
How to search with regex for fields containing whitespace-separated segments in Elasticsearch
我有一个文档,其中包含一个名为 info_list 的字段,它基本上是一个由 space 分隔的 9 段的字符串。
字段的映射是
"info_list": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
源文件看起来像
"_source": {
"id": "1234",
"date": "1614556800000",
"info_list": [
"1234 2D 5678 8765 5678 1111 2222 3333 1"
]
}
信息列表基本上由9个部分组成。出于疑问,我们可以说 a,b,c,d,e,f,g,h,i 是那 9 个片段。
info_list = a+ ' ' + b+ ' ' + c +' ' + d+ ' ' e + ' ' + f + ' ' + g + ' ' + h + ' ' + i
现在假设我想搜索值为 5678 的 c,当前实现使用 match_phrase 查询类似这样的东西
GET test/_search
{
"query": {
"match_phrase": {
"info_list": "5678"
}
}
}
上述方法的问题在于,即使我希望搜索结果具有 c = 5678,现在如果 info_list 字符串中的任何段具有 5678,它将匹配它,从而导致错误的搜索结果。
我尝试使用类似
的正则表达式查询
GET /test/_search
{
"query" : {
"query_string" :
{ "fields" : ["info_list"],
"query" : ".* .* 5678 .*"
}
}
}
但这似乎并没有 work.Should 我更改字段的映射?任何帮助或建议将不胜感激,因为我是 Elastic 搜索的新手。
修复正则表达式
您必须让正则表达式引擎知道确切会有多少 前面的组。最重要的是,您需要使用默认情况下应用于 text
字段的 .keyword
field because the standard
analyzer 会将原始字符串拆分为空格并将每个标记转换为小写 - 这两种情况都应避免如果您打算与捕获组一起工作。
话虽如此,这是一个有效的 regexp
query:
GET /test/_search
{
"query": {
"regexp": {
"info_list.keyword": "( ?[a-zA-Z0-9]+){2} 5678 .*"
}
}
}
提取组 before 摄取
Should I change the mapping of the field?
我会说去吧。当您知道您将针对哪个组时,理想情况下,您应该在提取文档之前提取组。
你看,对于你的情况我会做的如下:
- 保留原来的
info_list
作为关键字以保持一致性
- 用您选择的编程语言提取组,用键
a
到 i
对它们进行注释(类似于您自然想到所述组的方式)。
- 将它们存储在
nested
field in order to guarantee that the connections between the keys and the values aren't lost due to array flattening. 中
具体来说:
- 设置映射
PUT extracted-groups-index
{
"mappings": {
"properties": {
"info_list": {
"type": "keyword"
},
"info_list_groups": {
"type": "nested",
"properties": {
"group_key": {
"type": "keyword"
},
"value": {
"type": "keyword"
}
}
}
}
}
}
- 摄取文档
POST extracted-groups-index/_doc
{
"info_list": "1234 2D 5678 8765 5678 1111 2222 3333 1",
"info_list_groups": [
{
"group_key": "a",
"value": "1234"
},
{
"group_key": "b",
"value": "2D"
},
{
"group_key": "c",
"value": "5678"
},
{ ... } // omitted for brevity
]
}
- 利用一对
nested
term
queries:
POST extracted-groups-index/_search
{
"query": {
"nested": {
"path": "info_list_groups",
"query": {
"bool": {
"must": [
{
"term": {
"info_list_groups.group_key": "c"
}
},
{
"term": {
"info_list_groups.value": "5678"
}
}
]
}
}
}
}
}
充分利用 Elasticsearch
nested
方法的缺点是它会增加您的索引大小。另外,查询往往会变得非常冗长和混乱。如果您不想走那条路,您可以利用所谓的 自定义分析器 .
此类分析器通常由以下部分组成:
- a
tokenizer
(接收字符流并输出标记流——通常是单词)
- 和一些 token
filters
的作用是将令牌塑造成所需的形式。
具体来说,这里的目的是:
把字符串1234 2D 5678 8765 5678 1111 2222 3333 1
作为一个整体
找到由空格分隔的各个组
--> (1234) (2D) (5678) (8765) (5678) (1111) (2222) (3333) (1)
用其字母索引注释每个组
--> a:1234 b:2D c:5678 d:8765 e:5678 f:1111 g:2222 h:3333 i:1
最后用空格拆分结果字符串,以便使用 a:1234
和 c:5678
等查询
所有这些都可以通过“noop”的组合来实现 keyword
tokenizer, and pattern_replace
+ pattern_capture
filters:
PUT power-of-patterns
{
"mappings": {
"properties": {
"info_list": {
"type": "text",
"fields": {
"annotated_groups": {
"type": "text",
"analyzer": "info_list_analyzer"
}
}
}
}
},
"settings": {
"analysis": {
"analyzer": {
"info_list_analyzer": {
"type": "custom",
"tokenizer": "keyword",
"filter": ["pattern_grouper", "pattern_splitter"]
}
},
"filter": {
"pattern_grouper": {
"type": "pattern_replace",
"pattern": "((?<a>(?:\b ?[a-zA-Z0-9]+){0}.*?([a-zA-Z0-9]+) ?))((?<b>(?:\b ?[a-zA-Z0-9]+){0}.*?([a-zA-Z0-9]+) ?))((?<c>(?:\b ?[a-zA-Z0-9]+){0}.*?([a-zA-Z0-9]+) ?))((?<d>(?:\b ?[a-zA-Z0-9]+){0}.*?([a-zA-Z0-9]+) ?))((?<e>(?:\b ?[a-zA-Z0-9]+){0}.*?([a-zA-Z0-9]+) ?))((?<f>(?:\b ?[a-zA-Z0-9]+){0}.*?([a-zA-Z0-9]+) ?))((?<g>(?:\b ?[a-zA-Z0-9]+){0}.*?([a-zA-Z0-9]+) ?))((?<h>(?:\b ?[a-zA-Z0-9]+){0}.*?([a-zA-Z0-9]+) ?))((?<i>(?:\b ?[a-zA-Z0-9]+){0}.*?([a-zA-Z0-9]+) ?))",
"replacement": "a:${a}b:${b}c:${c}d:${d}e:${e}f:${f}g:${g}h:${h}i:${i}"
},
"pattern_splitter": {
"type" : "pattern_capture",
"preserve_original" : true,
"patterns" : [
"([a-i]\:[a-zA-Z0-9]+)"
]
}
}
}
}
}
注意friendly-looking regex from above is nothing more than a repetitive named group捕手
设置映射后,您可以摄取文档:
POST power-of-patterns/_doc
{
"info_list": [
"1234 2D 5678 8765 5678 1111 2222 3333 1"
]
}
然后以一种易于阅读的形式搜索所需的片段:
POST power-of-patterns/_search
{
"query": {
"term": {
"info_list.annotated_groups": "c:5678"
}
}
}
将您的查询更改为:
"query" : "([^ ]+ ){2}5678( [^ ]){6}"
英语是“two terms-with-a-space,然后是你的搜索词,然后是 6 spaces-with-terms”
我有一个文档,其中包含一个名为 info_list 的字段,它基本上是一个由 space 分隔的 9 段的字符串。
字段的映射是
"info_list": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
源文件看起来像
"_source": {
"id": "1234",
"date": "1614556800000",
"info_list": [
"1234 2D 5678 8765 5678 1111 2222 3333 1"
]
}
信息列表基本上由9个部分组成。出于疑问,我们可以说 a,b,c,d,e,f,g,h,i 是那 9 个片段。
info_list = a+ ' ' + b+ ' ' + c +' ' + d+ ' ' e + ' ' + f + ' ' + g + ' ' + h + ' ' + i
现在假设我想搜索值为 5678 的 c,当前实现使用 match_phrase 查询类似这样的东西
GET test/_search
{
"query": {
"match_phrase": {
"info_list": "5678"
}
}
}
上述方法的问题在于,即使我希望搜索结果具有 c = 5678,现在如果 info_list 字符串中的任何段具有 5678,它将匹配它,从而导致错误的搜索结果。
我尝试使用类似
的正则表达式查询GET /test/_search
{
"query" : {
"query_string" :
{ "fields" : ["info_list"],
"query" : ".* .* 5678 .*"
}
}
}
但这似乎并没有 work.Should 我更改字段的映射?任何帮助或建议将不胜感激,因为我是 Elastic 搜索的新手。
修复正则表达式
您必须让正则表达式引擎知道确切会有多少 前面的组。最重要的是,您需要使用默认情况下应用于 text
字段的 .keyword
field because the standard
analyzer 会将原始字符串拆分为空格并将每个标记转换为小写 - 这两种情况都应避免如果您打算与捕获组一起工作。
话虽如此,这是一个有效的 regexp
query:
GET /test/_search
{
"query": {
"regexp": {
"info_list.keyword": "( ?[a-zA-Z0-9]+){2} 5678 .*"
}
}
}
提取组 before 摄取
Should I change the mapping of the field?
我会说去吧。当您知道您将针对哪个组时,理想情况下,您应该在提取文档之前提取组。
你看,对于你的情况我会做的如下:
- 保留原来的
info_list
作为关键字以保持一致性 - 用您选择的编程语言提取组,用键
a
到i
对它们进行注释(类似于您自然想到所述组的方式)。 - 将它们存储在
nested
field in order to guarantee that the connections between the keys and the values aren't lost due to array flattening. 中
具体来说:
- 设置映射
PUT extracted-groups-index
{
"mappings": {
"properties": {
"info_list": {
"type": "keyword"
},
"info_list_groups": {
"type": "nested",
"properties": {
"group_key": {
"type": "keyword"
},
"value": {
"type": "keyword"
}
}
}
}
}
}
- 摄取文档
POST extracted-groups-index/_doc
{
"info_list": "1234 2D 5678 8765 5678 1111 2222 3333 1",
"info_list_groups": [
{
"group_key": "a",
"value": "1234"
},
{
"group_key": "b",
"value": "2D"
},
{
"group_key": "c",
"value": "5678"
},
{ ... } // omitted for brevity
]
}
- 利用一对
nested
term
queries:
POST extracted-groups-index/_search
{
"query": {
"nested": {
"path": "info_list_groups",
"query": {
"bool": {
"must": [
{
"term": {
"info_list_groups.group_key": "c"
}
},
{
"term": {
"info_list_groups.value": "5678"
}
}
]
}
}
}
}
}
充分利用 Elasticsearch
nested
方法的缺点是它会增加您的索引大小。另外,查询往往会变得非常冗长和混乱。如果您不想走那条路,您可以利用所谓的 自定义分析器 .
此类分析器通常由以下部分组成:
- a
tokenizer
(接收字符流并输出标记流——通常是单词) - 和一些 token
filters
的作用是将令牌塑造成所需的形式。
具体来说,这里的目的是:
把字符串
1234 2D 5678 8765 5678 1111 2222 3333 1
作为一个整体找到由空格分隔的各个组
-->
(1234) (2D) (5678) (8765) (5678) (1111) (2222) (3333) (1)
用其字母索引注释每个组
-->
a:1234 b:2D c:5678 d:8765 e:5678 f:1111 g:2222 h:3333 i:1
最后用空格拆分结果字符串,以便使用
等查询a:1234
和c:5678
所有这些都可以通过“noop”的组合来实现 keyword
tokenizer, and pattern_replace
+ pattern_capture
filters:
PUT power-of-patterns
{
"mappings": {
"properties": {
"info_list": {
"type": "text",
"fields": {
"annotated_groups": {
"type": "text",
"analyzer": "info_list_analyzer"
}
}
}
}
},
"settings": {
"analysis": {
"analyzer": {
"info_list_analyzer": {
"type": "custom",
"tokenizer": "keyword",
"filter": ["pattern_grouper", "pattern_splitter"]
}
},
"filter": {
"pattern_grouper": {
"type": "pattern_replace",
"pattern": "((?<a>(?:\b ?[a-zA-Z0-9]+){0}.*?([a-zA-Z0-9]+) ?))((?<b>(?:\b ?[a-zA-Z0-9]+){0}.*?([a-zA-Z0-9]+) ?))((?<c>(?:\b ?[a-zA-Z0-9]+){0}.*?([a-zA-Z0-9]+) ?))((?<d>(?:\b ?[a-zA-Z0-9]+){0}.*?([a-zA-Z0-9]+) ?))((?<e>(?:\b ?[a-zA-Z0-9]+){0}.*?([a-zA-Z0-9]+) ?))((?<f>(?:\b ?[a-zA-Z0-9]+){0}.*?([a-zA-Z0-9]+) ?))((?<g>(?:\b ?[a-zA-Z0-9]+){0}.*?([a-zA-Z0-9]+) ?))((?<h>(?:\b ?[a-zA-Z0-9]+){0}.*?([a-zA-Z0-9]+) ?))((?<i>(?:\b ?[a-zA-Z0-9]+){0}.*?([a-zA-Z0-9]+) ?))",
"replacement": "a:${a}b:${b}c:${c}d:${d}e:${e}f:${f}g:${g}h:${h}i:${i}"
},
"pattern_splitter": {
"type" : "pattern_capture",
"preserve_original" : true,
"patterns" : [
"([a-i]\:[a-zA-Z0-9]+)"
]
}
}
}
}
}
注意friendly-looking regex from above is nothing more than a repetitive named group捕手
设置映射后,您可以摄取文档:
POST power-of-patterns/_doc
{
"info_list": [
"1234 2D 5678 8765 5678 1111 2222 3333 1"
]
}
然后以一种易于阅读的形式搜索所需的片段:
POST power-of-patterns/_search
{
"query": {
"term": {
"info_list.annotated_groups": "c:5678"
}
}
}
将您的查询更改为:
"query" : "([^ ]+ ){2}5678( [^ ]){6}"
英语是“two terms-with-a-space,然后是你的搜索词,然后是 6 spaces-with-terms”