我只是从ElasticSearch开始,然后尝试基于它来实现自动完成功能。
我有一个autocomplete带有citytype 字段的索引string。这是存储在该索引中的文档的示例:
autocomplete
city
string
{ "_index":"autocomplete_1435797593949", "_type":"listing", "_id":"40716", "_source":{ "city":"Rome", "tags":[ "listings" ] } }
分析配置如下所示:
{ "analyzer":{ "autocomplete_term":{ "tokenizer":"autocomplete_edge", "filter":[ "lowercase" ] }, "autocomplete_search":{ "tokenizer":"keyword", "filter":[ "lowercase" ] } }, "tokenizer":{ "autocomplete_edge":{ "type":"nGram", "min_gram":1, "max_gram":100 } } }
映射:
{ "autocomplete_1435795884170":{ "mappings":{ "listing":{ "properties":{ "city":{ "type":"string", "analyzer":"autocomplete_term" }, } } } } }
我将以下查询发送到ES:
{ "query":{ "multi_match":{ "query":"Rio", "analyzer":"autocomplete_search", "fields":[ "city" ] } } }
结果,我得到以下信息:
{ "took":2, "timed_out":false, "_shards":{ "total":5, "successful":5, "failed":0 }, "hits":{ "total":1, "max_score":2.7742395, "hits":[ { "_index":"autocomplete_1435795884170", "_type":"listing", "_id":"53581", "_score":2.7742395, "_source":{ "city":"Rio", "tags":[ "listings" ] } } ] } }
在大多数情况下,它是有效的。city = "Rio"在用户必须实际键入整个单词之前,它确实找到了带有的文档("Ri"足够了)。
city = "Rio"
"Ri"
这就是我的问题。我也希望它返回"Rio de Janeiro"。要获取"Rio de Janeiro",我需要发送以下查询:
"Rio de Janeiro"
{ "query":{ "multi_match":{ "query":"Rio d", "analyzer":"standard", "fields":[ "city" ] } } }
注意"<whitespace>d"那里。
"<whitespace>d"
另一个相关问题是,我希望至少所有以开头的城市都"R"将通过以下查询返回:
"R"
{ "query":{ "multi_match":{ "query":"R", "analyzer":"standard", "fields":[ "city" ] } } }
我期望"Rome",等等…(这是索引中存在的文档),但是,我只能"Rio"再次得到。我希望它的行为类似于SQL LIKE条件,即... LIKE 'CityName%'。
"Rome"
"Rio"
SQL LIKE
... LIKE 'CityName%'
我究竟做错了什么?
我会这样做:
edge_nGram
LIKE 'CityName%'
"tokenizer": { "autocomplete_edge": { "type": "edge_nGram", "min_gram": 1, "max_gram": 100 } }
autocomplete_search
search_analyzer
keyword
lowercase
"mappings": { "listing": { "properties": { "city": { "type": "string", "index_analyzer": "autocomplete_term", "search_analyzer": "autocomplete_search" } } } }
{ "query": { "multi_match": { "query": "R", "fields": [ "city" ] } } }
详细的解释是这样的:将城市名称分割为ngram。例如,Rio de Janeiro您将为以下内容编制索引:
Rio de Janeiro
"city": [ "r", "ri", "rio", "rio ", "rio d", "rio de", "rio de ", "rio de j", "rio de ja", "rio de jan", "rio de jane", "rio de janei", "rio de janeir", "rio de janeiro" ]
您会注意到所有内容都是小写的。现在,您希望查询采用任何文本(是否为小写),并将其与索引中的内容匹配。因此,R应该与上面的列表匹配。
R
为此,您希望输入文本小写并与用户设置的文本保持一致,这意味着不应对其进行分析。你为什么要这个?因为您已经用ngram分割了城市名称,并且不想为输入文本使用相同的名称。如果用户输入“ RI”,Elasticsearch将小写ri--并将其与索引中的内容完全匹配。
ri
可能更快的替代方法multi_match是使用term,但这要求您的应用程序/网站将文本小写。这样做的原因是term根本不分析输入文本。
multi_match
term
{ "query": { "filtered": { "filter": { "term": { "city": { "value": "ri" } } } } } }