我目前正在使用Symfony2中的FOSElasticaBundle,我正在努力构建匹配最长前缀的搜索。
我知道互联网上有100个使用此功能执行自动完成搜索的示例。但是,我的问题有点不同。
在自动完成类型的搜索中,数据库保存最长的字母数字字符串(字符长度),用户只提供最短的部分,假设用户输入“jho”,Elasticsearch可以轻松提供“Jhon,Jhonny,Jhonas ”。
我的问题是倒退,我想提供最长的字母数字字符串,我希望Elasticsearch为我提供数据库中最大的匹配。
例如:我可以提供“123456789”,我的数据库可以有[12,123,14,156,16,7,1234,1,67,8,9,123456,0],在这种情况下,最长的前缀匹配用户提供的数字的数据库是“123456”。
我刚开始使用Elasticsearch,所以我真的没有接近工作设置或任何东西。
如果有任何不清楚或遗漏的信息,请告诉我,我会提供更多详细信息。
更新1(使用Val的第二次更新)
Settings:
curl -XPUT localhost:9200/tests -d '{
"settings": {
"analysis": {
"analyzer": {
"edge_ngram_analyzer": {
"tokenizer": "edge_ngram_tokenizer",
"filter": [ "lowercase" ]
}
},
"tokenizer": {
"edge_ngram_tokenizer": {
"type": "edgeNGram",
"min_gram": "2",
"max_gram": "25"
}
}
}
},
"mappings": {
"test": {
"properties": {
"my_string": {
"type": "string",
"fields": {
"prefix": {
"type": "string",
"analyzer": "edge_ngram_analyzer"
}
}
}
}
}
}
}'
Query:
curl -XPOST localhost:9200/tests/test/_search?pretty=true -d '{
"size": 1,
"sort": {
"_script": {
"script": "doc.my_string.value.length()",
"type": "number",
"order": "desc"
},
"_score": "desc"
},
"query": {
"filtered": {
"query": {
"match": {
"my_string.prefix": "8092232423"
}
},
"filter": {
"script": {
"script": "doc.my_string.value.length() <= maxlength",
"params": {
"maxlength": 10
}
}
}
}
}
}'
With this configuration the query returns the following results:
{
"took" : 61,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"failed" : 0
},
"hits" : {
"total" : 1754,
"max_score" : null,
"hits" : [ {
"_index" : "tests",
"_type" : "test",
"_id" : "AU8LqQo4FbTZPxBtq3-Q",
"_score" : 0.13441172,
"_source":{"my_string":"80928870"},
"sort" : [ 8.0, 0.13441172 ]
} ]
}
}
加分问题
我想为该搜索提供一个数字数组,并以有效的方式获取每个搜索的匹配前缀,而不必每次都执行查询
答案 0 :(得分:1)
这是我的看法。
基本上,我们需要做的是在索引时使用edgeNGram
tokenizer(下面称为my_string
)对字段(下面称为edge_ngram_tokenizer
)进行切片和切块。这样,123456789
之类的字符串将被标记为12
,123
,1234
,12345
,123456
,1234567
, 12345678
,123456789
和所有令牌都将被编入索引并可搜索。
因此,让我们创建一个tests
索引,一个名为edge_ngram_analyzer
分析器的自定义分析器和一个test
映射,其中包含一个名为my_string
的字符串字段。您会注意到my_string
字段是一个多字段,声明一个prefixes
子字段,其中包含所有标记化的前缀。
curl -XPUT localhost:9200/tests -d '{
"settings": {
"analysis": {
"analyzer": {
"edge_ngram_analyzer": {
"tokenizer": "edge_ngram_tokenizer",
"filter": [ "lowercase" ]
}
},
"tokenizer": {
"edge_ngram_tokenizer": {
"type": "edgeNGram",
"min_gram": "2",
"max_gram": "25"
}
}
}
},
"mappings": {
"test": {
"properties": {
"my_string": {
"type": "string",
"fields": {
"prefixes": {
"type": "string",
"index_analyzer": "edge_ngram_analyzer"
}
}
}
}
}
}
}
然后,使用_bulk
API索引一些test
个文档:
curl -XPOST localhost:9200/tests/test/_bulk -d '
{"index":{}}
{"my_string":"12"}
{"index":{}}
{"my_string":"1234"}
{"index":{}}
{"my_string":"1234567890"}
{"index":{}}
{"my_string":"abcd"}
{"index":{}}
{"my_string":"abcdefgh"}
{"index":{}}
{"my_string":"123456789abcd"}
{"index":{}}
{"my_string":"abcd123456789"}
'
我发现特别棘手的是匹配结果可能比输入字符串更长或更短。为了实现这一点,我们必须组合两个查询,一个查找较短的匹配,另一个查找较长的匹配。因此,match
查询将查找具有较短&#34;前缀的文档&#34;匹配输入和query_string
查询(在输入字符串上应用edge_ngram_analyzer
!)将搜索&#34;前缀&#34;比输入字符串长。两者都包含在一个bool/should
中,并按一个递减的字符串长度(即最长的字符串长度)排序,这样做就可以了。
让我们做一些查询,看看展开了什么:
此查询将返回与#34; 123456789&#34;的最长匹配的一个文档,即&#34; 123456789abcd&#34;。在这种情况下,结果比输入长。
curl -XPOST localhost:9200/tests/test/_search -d '{
"size": 1,
"sort": {
"_script": {
"script": "doc.my_string.value.length()",
"type": "number",
"order": "desc"
}
},
"query": {
"bool": {
"should": [
{
"match": {
"my_string.prefixes": "123456789"
}
},
{
"query_string": {
"query": "123456789",
"default_field": "my_string.prefixes",
"analyzer": "edge_ngram_analyzer"
}
}
]
}
}
}'
第二个查询将返回最长匹配的一个文档&#34; 123456789abcdef&#34;,即&#34; 123456789abcd&#34;。在这种情况下,结果比输入短。
curl -XPOST localhost:9200/tests/test/_search -d '{
"size": 1,
"sort": {
"_script": {
"script": "doc.my_string.value.length()",
"type": "number",
"order": "desc"
}
},
"query": {
"bool": {
"should": [
{
"match": {
"my_string.prefixes": "123456789abcdef"
}
},
{
"query_string": {
"query": "123456789abcdef",
"default_field": "my_string.prefixes",
"analyzer": "edge_ngram_analyzer"
}
}
]
}
}
}'
我希望涵盖它。如果没有,请告诉我。
至于您的红利问题,我建议您使用_msearch
API并立即发送所有查询。
更新:最后,请确保使用以下内容在elasticsearch.yml
文件中启用了脚本:
# if you have ES <1.6
script.disable_dynamic: false
# if you have ES >=1.6
script.inline: on
更新2 我离开上述内容,因为用例可能符合其他人的需求。现在,因为你只需要&#34;更短&#34;前缀(有意义!!),我们需要稍微改变映射和查询。
映射将如下:
{
"settings": {
"analysis": {
"analyzer": {
"edge_ngram_analyzer": {
"tokenizer": "edge_ngram_tokenizer",
"filter": [
"lowercase"
]
}
},
"tokenizer": {
"edge_ngram_tokenizer": {
"type": "edgeNGram",
"min_gram": "2",
"max_gram": "25"
}
}
}
},
"mappings": {
"test": {
"properties": {
"my_string": {
"type": "string",
"fields": {
"prefixes": {
"type": "string",
"analyzer": "edge_ngram_analyzer" <--- only change
}
}
}
}
}
}
}
现在查询会有所不同,但总是只返回最长的前缀,但输入字符串的长度更短或相等。请试一试。我建议重新索引您的数据,以确保所有设置都正确。
{
"size": 1,
"sort": {
"_script": {
"script": "doc.my_string.value.length()",
"type": "number",
"order": "desc"
},
"_score": "desc" <----- also add this line
},
"query": {
"filtered": {
"query": {
"match": {
"my_string.prefixes": "123" <--- input string
}
},
"filter": {
"script": {
"script": "doc.my_string.value.length() <= maxlength",
"params": {
"maxlength": 3 <---- this needs to be set to the length of the input string
}
}
}
}
}
}