[版權聲明]:本文章由danvid發佈於http://danvid.cnblogs.com/,如需轉載或部分使用請註明出處數據庫
在業務中常常會遇到相似數據庫的"like"的模糊匹配需求,而es基於分詞的全文檢索也是有相似的功能,這個就是短語匹配match_phrase,但每每業務需求都不是那麼簡單,他想要有like的功能,又要容許有必定的容錯(就是我搜索"東方賓館"時,"廣州花園賓館酒店"也要出來,這個就不是單純的"like"),下面就是我須要解析的問題(在此吐槽一下業務就是這麼變態。。)api
描述一個問題時首先須要描述業務場景:假設es中有一索引字段name存儲有如下文本信息:elasticsearch
doc[1]:{"name":"廣州東方賓館酒店"}性能
doc[2]:{"name":"廣州花園賓館酒店"}spa
doc[3]:{"name":"東方公園賓館"}code
需求要求在輸入:"東方賓館"的時候doc[1]排最前面doc[3]排第二doc[2]排第三,對於這個需求從簡單的全文檢索match來講,doc[3]:{"name":"東方公園賓館"}應該是第一位(注意:爲了簡化原理分析,分詞咱們使用standard即按單個字分詞)blog
業務分析:顯然對於上面的業務場景若是單獨使用match的話,顯然是不合適,由於按照standard分詞,doc[3]的詞條長度要比doc[1]的詞條長度短,而詞頻又是都出現了[東][方][賓][館]4個詞,使用match匹配的話就會吧doc[3]排到最前面,顯然業務但願把輸入的文字順序匹配度最高的數據排前面,由於我確實要找的是"廣州東方賓館酒店"而不是"東方公園賓館"你不能把doc[3]給我排前面,OK業務邏輯好像是對的那麼怎麼解決問題;索引
解決問題前介紹一哈match_phrase原理(match的原理我就不說了本身回去看文檔),簡單點說match_phrase就是高級"like"。api以下:文檔
GET test_index/_search { "query": { "match_phrase" : { "message" : { "query" : "東方賓館", "slop" : 2 } } } }
es在給文本分詞的時候,除了分詞以外還有一個詞條標記,就是position,例如我按照standard對以上三個doc的name進行分詞會變成這樣:get
doc[1]:廣[0],州[1],東[2],方[3],賓[4],館[5],酒[6],店[7]; doc[2]:廣[0],州[1],花[2],園[3],賓[4],館[5],酒[6],店[7]; doc[3]:東[0],方[1],公[2],園[3],賓[4],館[5]; query文本分詞:東[0],方[1],賓[2],館[3];
使用match_phrase時:
1.es會先過濾掉不符合的query條件的doc,即doc[2]中沒有"東方"兩個詞彙,會被過濾掉
2.es會根據分詞的position對分詞進行過濾和評分,這個是就slop參數,默認是0,意思是查詢分詞只須要通過距離爲0的轉換就能夠變成跟doc同樣的文檔數據,例如:對於doc[1]來講slop就是0了,對於doc[3]slop就是2,即"賓"和"館"最大位移這兩個分詞只須要最多移動2個位置就能夠變成"東方賓館"(反過來也同樣,query的文本中的"賓"和"館"只須要移動2個位置就能夠變成"東方**賓館"),用數學的理解就是doc[3]的賓[4]-東[0]=4,query文本中的賓[2]-東[0]=2,那麼轉換距離slop就是4-2=2,同理doc[3]的館[5]-東[0]=5,query的是3,slop結果也是2,那麼"賓"和"館"最大的slop就是2,則query時設置slop等於2就能把doc[3]匹配出來,當設置爲0時就是咱們數據庫的"like"
原理解析完了,就知道使用match只能匹配相關度即tf/idf,而分詞之間的位置關係卻沒法保證,而match_phrase能保證分詞間的鄰近關係,那麼就能夠利用二者優點,結合搜索進行評分
GET test_index/_search { "query": { "bool": { "must": { "match": { "name": { "query": "東方賓館" } } }, "should": { "match_phrase": { "name": { "query": "東方賓館", "slop": 0 } } } } } }
這樣就的結果就是至關於match_phrase幫match進行了相關度分數的加分,固然你也能夠經過修改slop的參數來進行步控制分數,這個就根據用戶需求來了;
性能問題:其實使用match_phrase性能是要比單純的全文檢索性能低的,由於他要計算位置嘛,那麼想提升性能能夠經過先使用match進行過濾數據,而後利用rescore api對已經match的結果進行加分,這樣就減小了部分沒必要要的非match過濾:
GET test_index/_search { "query": { "match": { "name":"東方賓館" } },
"rescore": { "window_size": 30, "query": { "rescore_query": { "match_phrase": { "name": { "query": "東方賓館", "slop": 0 } } } } } }
#window_size 是每一分片進行從新評分的頂部文檔數量這個只要大於你可能分頁的總數*每頁的數量便可(pageNumber*pageSize)實際上不須要這麼多由於翻頁不可能很深,這個根據業務調整便可。
總結及注意點:
1.rescore其實跟bool結合同樣是評分的相加,評分不在這裏細說了;
2.雖然能夠提升相關度評分,可是仍是存在可能match很低+一個很低的match_phrase結果沒有單獨只匹配了一個match的分數高的狀況,可是這是很極限了,也是符合相關度評分原理的;
3.因爲match_phrase是在搜索階段進行的計算,會影響搜索效率,聽說比term查詢慢20倍,因此不要進行大文本量的字段搜索,儘可能進行標題,名字這種類型的搜索才使用這個;
4.本文章沒有討論在文本數據重複時的狀況,即文本中有多個"[東][方][賓][館]"和query文本中有多個"[東][方][賓][館]"分詞的狀況,可是原理是同樣的仍是取距離轉換的最小值;
5.文中使用了standard分詞,實際上可能會用不一樣的分詞器,可是建議使用match_phrase時使用標準的一個個分詞,這樣是方便進行鄰近搜索的控制的,若是使用ik等分詞,執行match_phrase時分詞是不可控的,因此結果也是不可控。match使用ik,match_phrase用standard結合一塊兒使用也是能夠的;
6.鄰近搜索效率較低,其實能夠經過增長詞庫的方式進行單純使用match匹配效率是最高的,前提是你知道客戶會搜索什麼,這又是另外一個研究話題了
更新[2019-05-22]:
補充:短語匹配match_phrase必需要知足下面的要求才能認定和["東方賓館"]這個詞條匹配(以standard分析器爲例)
1.搜索的詞必須有且僅有["東","方","賓","館"]這幾個詞(對於中文是字)的一個或者多個,若是有其餘的詞(對於中文是字)是不會匹配到的,slop不是徹底等同於萊文斯坦距離,能夠理解成字符的偏移
2.查詢詞中偏移量應該跟文檔中詞的偏移量同樣,或者在slop的誤差範圍內,就是上文解析的意思。
這裏講解一下fuzzy和match_phrase的區別
1.fuzzy是詞/項級別的模糊匹配,match_phrase是基於短語級別的
例如對於英文(standard分析器)來講"dog cat bird"來講"dog"就是一個詞/詞項,而"dog cat"就是一個短語,所以做用範圍不同
2.fuzzy是基於萊文斯坦距離的,因此fuzzy是能夠容錯的例如你輸入"dcg" 你也能夠匹配到"dog cat bird",可是這裏注意的是你的查詢只能是單詞條的查詢,不能"dcg cat",若是你須要查詢短語裏面的拼寫錯誤,可使用match的fuzziness參數,match_phrase是不容許出現不存在的詞條的。
下面是對於fuzzy和match_phrase和match 添加fuzziness參數進行對比
文檔內容是{"name":"dog cat bird"} 分析器是standard
--------------------------------------------------------------------------------------------------
1.使用拼寫錯誤的"cot"可使用fuzzy匹配可是,若是是下面這種,短語是不能夠的,輸入應當是詞條,而不是短語
GET test_save/_search { "query": { "fuzzy": { "name":{ "value": "bird cot", "fuzziness": 1 } } } }
--------------------------------------------------------------------------------------------------
2.這裏能夠匹配到由於match先分解成bird 和 cot 其中bird能夠匹配到,同時cot也是能夠匹配到,只不過度數要比輸入"bird cat"要低
GET test_save/_search { "query": { "match": { "name":{ "query": "bird cot", "fuzziness":1 } } } }
-----------------------------------------------------------------------------------------------
3.這裏因爲cot和文本中的cat不是同一個詞,因此是沒法匹配到的
GET test_save/_search { "query": { "match_phrase": { "name": { "query": "bird cot", "slop": 1 } } } }
以上是對於英文的單詞的解析,對於中文其實也是同樣,只是因爲中文若是使用standard一個詞項就是一個字,所以使用所以分詞後的數據對於fuzzy模糊匹配來講意義不大,可是可使用keyword進行
GET test_save/_search { "query": { "fuzzy": { "name.keyword":{ "value": "東日賓館", "fuzziness": 1 } } } }
這樣是能夠匹配到"東方賓館"的數據的,可是沒法匹配"廣州東方賓館"由於萊文斯坦距離已經不止1了
其實短語匹配應該叫臨近查詢更適合些
以上就是對模糊查詢和短語匹配的解析和補充~
[說明]:elasticsearch版本5.6.4