Val*_*Val 4 regex lucene analyzer odata elasticsearch
我们有一个符合OData的API,它将一些全文搜索需求委托给Elasticsearch集群.由于OData表达式可能变得非常复杂,我们决定将它们简单地转换为等效的Lucene查询语法并将其提供给query_string查询.
我们支持一些与文本相关的OData过滤器表达式,例如:
startswith(field,'bla')endswith(field,'bla')substringof('bla',field)name eq 'bla'我们对匹配字段可以是analyzed,not_analyzed或两者(通过多场IE).所搜索的文本可以是一个单一的令牌(例如table),只有其(例如一部分tab),或数个标记(例如table 1.,table 10等).搜索必须不区分大小写.
以下是我们需要支持的行为的一些示例:
startswith(name,'table 1')必须匹配" 表1 "," 表1 00"," 表1 .5"," 表1 12上层"endswith(name,'table 1')必须匹配"房间1,表1 ","子表1 "," 表1 ","杰夫表1 "substringof('table 1',name)必须匹配"Big Table 1 back"," table 1 "," Table 1 ","Small Table1 2"name eq 'table 1'必须匹配" 表1 "," 表1 "," 表1 "所以基本上,我们接受用户输入(即传递给startswith/ 的第二个参数的内容endswith,或者相应的第一个参数substringof,相应的右侧值eq)并尝试完全匹配它,令牌是否完全匹配或仅部分.
现在,我们正在摆脱下面突出显示的笨拙的解决方案,该解决方案效果很好,但远非理想.
在我们中query_string,我们not_analyzed使用正则表达式语法匹配字段.由于字段是not_analyzed并且搜索必须不区分大小写,我们在准备正则表达式时会自己进行标记,以便提供类似的内容,即这相当于OData过滤器endswith(name,'table 8')(=>匹配)所有name以"表8"结尾的文件
"query": {
"query_string": {
"query": "name.raw:/.*(T|t)(A|a)(B|b)(L|l)(E|e) 8/",
"lowercase_expanded_terms": false,
"analyze_wildcard": true
}
}
Run Code Online (Sandbox Code Playgroud)
所以,即使这个解决方案工作得很好而且性能也不差(出乎意料的是),我们希望以不同的方式做到这一点并充分利用分析仪的全部功能,以便在索引时转移所有这些负担时间而不是搜索时间.但是,由于重新索引我们的所有数据需要数周时间,因此我们首先要研究是否有一个令牌过滤器和分析器的良好组合,这将有助于我们实现上面列举的相同搜索要求.
我的想法是理想的解决方案将包含一些明智的混合带状疱疹(即几个令牌在一起)和edge-nGram(即在令牌的开头或结尾匹配).但是,我不确定是否可以使它们一起工作以匹配多个令牌,其中一个令牌可能没有被用户完全输入).例如,如果索引名称字段是"Big Table 123",我需要substringof('table 1',name)匹配它,因此"table"是完全匹配的标记,而"1"只是下一个标记的前缀.
在此预先感谢您分享您的braincells.
更新1:在测试了Andrei的解决方案之后
=>完全匹配(eq)并startswith完美地工作.
A. endswith毛刺
搜索substringof('table 112', name)产量107个文档.搜索更具体的情况,例如endswith(name, 'table 112')yield 1525 docs,同时它应该产生更少的docs(后缀匹配应该是子字符串匹配的子集).检查更深入我发现了一些不匹配,例如"社交俱乐部,表12"(不包含"112")或"订单312"(既不包含"表"也不包含"112").我想这是因为它们以"12"结尾,这是令牌"112"的有效克,因此匹配.
B. substringof毛刺
搜索substringof('table',name)匹配"Party table","Alex on big table"但不匹配"Table 1","table 112"等.搜索substringof('tabl',name)匹配任何内容
更新2
这有点暗示,但我忘了明确提到解决方案必须使用query_string查询,主要是因为OData表达式(无论它们可能是多么复杂)将继续转换为它们的Lucene等价物.我知道我们正在利用Lucene的查询语法来处理Elasticsearch Query DSL的强大功能,这种语法的功能不那么强大且表现力较弱,但这是我们无法真正改变的.不过,我们相当接近!
这是一个有趣的用例.这是我的看法:
{
"settings": {
"analysis": {
"analyzer": {
"my_ngram_analyzer": {
"tokenizer": "my_ngram_tokenizer",
"filter": ["lowercase"]
},
"my_edge_ngram_analyzer": {
"tokenizer": "my_edge_ngram_tokenizer",
"filter": ["lowercase"]
},
"my_reverse_edge_ngram_analyzer": {
"tokenizer": "keyword",
"filter" : ["lowercase","reverse","substring","reverse"]
},
"lowercase_keyword": {
"type": "custom",
"filter": ["lowercase"],
"tokenizer": "keyword"
}
},
"tokenizer": {
"my_ngram_tokenizer": {
"type": "nGram",
"min_gram": "2",
"max_gram": "25"
},
"my_edge_ngram_tokenizer": {
"type": "edgeNGram",
"min_gram": "2",
"max_gram": "25"
}
},
"filter": {
"substring": {
"type": "edgeNGram",
"min_gram": 2,
"max_gram": 25
}
}
}
},
"mappings": {
"test_type": {
"properties": {
"text": {
"type": "string",
"analyzer": "my_ngram_analyzer",
"fields": {
"starts_with": {
"type": "string",
"analyzer": "my_edge_ngram_analyzer"
},
"ends_with": {
"type": "string",
"analyzer": "my_reverse_edge_ngram_analyzer"
},
"exact_case_insensitive_match": {
"type": "string",
"analyzer": "lowercase_keyword"
}
}
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
my_ngram_analyzer用于将每个文本拆分成小块,块的大小取决于您的用例.出于测试目的,我选择了25个字符.lowercase因为你说不区分大小写所以使用.基本上,这是用于的标记器substringof('table 1',name).查询很简单:{
"query": {
"term": {
"text": {
"value": "table 1"
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
my_edge_ngram_analyzer用于从开头开始拆分文本,这是专门用于startswith(name,'table 1')用例.同样,查询很简单:{
"query": {
"term": {
"text.starts_with": {
"value": "table 1"
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
endswith(name,'table 1').为此,我限定my_reverse_edge_ngram_analyzer其使用keyword一起标记生成器以lowercase及一个edgeNGram过滤器由一个前面和后面reverse滤波.这个标记器基本上做的是在edgeNGrams中分割文本,但边缘是文本的结尾,而不是开头(就像常规一样edgeNGram).查询:{
"query": {
"term": {
"text.ends_with": {
"value": "table 1"
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
name eq 'table 1'情况,一个简单的keyword标记化lowercase器和一个过滤器应该这样做查询:{
"query": {
"term": {
"text.exact_case_insensitive_match": {
"value": "table 1"
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
关于query_string,这会稍微改变解决方案,因为我指望term不分析输入文本并将其与索引中的一个术语完全匹配.
但是,query_string 如果analyzer为其指定了适当的值,则可以"模拟" .
解决方案将是一组查询,如下所示(始终使用该分析器,仅更改字段名称):
{
"query": {
"query_string": {
"query": "text.starts_with:(\"table 1\")",
"analyzer": "lowercase_keyword"
}
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
2364 次 |
| 最近记录: |