2015-07-02 141 views
3

我剛剛開始使用ElasticSearch並嘗試基於它實現自動完成功能。使用ElasticSearch模擬SQL LIKE搜索

我有一個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" 
       ] 
      } 
     } 
     ] 
    } 
} 

大多數情況下,它都可以工作。在用戶必須實際鍵入整個單詞("Ri"就足夠了)之前,它確實找到了帶有city = "Rio"的文檔。

這裏是我的問題。我希望它也能返回"Rio de Janeiro"。爲了得到"Rio de Janeiro",我需要發送以下查詢:

{ 
     "query":{ 
      "multi_match":{ 
      "query":"Rio d", 
      "analyzer":"standard", 
      "fields":[ 
       "city" 
      ] 
      } 
     } 
    } 

注意的"<whitespace>d"那裏。

另外一個相關的問題是,我期望與"R"至少啓動所有城市都用下面的查詢返回:

{ 
     "query":{ 
      "multi_match":{ 
      "query":"R", 
      "analyzer":"standard", 
      "fields":[ 
       "city" 
      ] 
      } 
     } 
    } 

我期望"Rome",等...(這是一個存在於索引中的文檔),但是,我只能得到"Rio"。我希望它的行爲像SQL LIKE條件,即... LIKE 'CityName%'

我在做什麼錯?

回答

2

我會做這樣的:

  • 變化標記生成器來edge_nGram因爲你說你需要LIKE 'CityName%'(意爲前綴匹配):
"tokenizer": { 
    "autocomplete_edge": { 
     "type": "edge_nGram", 
     "min_gram": 1, 
     "max_gram": 100 
    } 
    } 
  • 有該字段將您的autocomplete_search指定爲search_analyzer。我認爲這是一個不錯的選擇,有一個keywordlowercase
"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你會指數是這樣的:

  "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應該與上面的列表匹配。

爲了達到這個目的,您希望輸入文本被縮小並保持與用戶設置完全相同,這意味着不應該對其進行分析。你爲什麼想要這個?因爲你已經用ngram分割了城市名稱,而且你不希望輸入文本也一樣。如果用戶輸入「RI」,Elasticsearch會將其小寫 - ri - 並將其與索引中的內容完全匹配。

multi_match可能更快的替代方法是使用term,但這需要您的應用程序/網站將文本小寫。原因是term根本不分析輸入文本。

{ 
    "query": { 
    "filtered": { 
     "filter": { 
     "term": { 
      "city": { 
      "value": "ri" 
      } 
     } 
     } 
    } 
    } 
} 
+0

謝謝!這工作很好:) – FullOfCaffeine