1、介紹
上篇介紹了搜索結構化數據的簡單應用示例,現在來探尋 全文搜索(full-text search) :怎樣在全文字段中搜索到最相關的文檔。
一旦談論相關性或分析這兩個方面的問題時,我們所處的語境是關於查詢的而不是過濾。
2、基於詞項和基於全文
所有查詢會或多或少的執行相關度計算,但不是所有查詢都有分析階段。 和一些特殊的完全不會對文本進行操作的查詢(如 bool 或 function_score )不同,文本查詢可以划分成兩大家族:
- 基於詞項的查詢
-
如
term或fuzzy這樣的底層查詢不需要分析階段,它們對單個詞項進行操作。用term查詢詞項Foo只要在倒排索引中查找 准確詞項 ,並且用 TF/IDF 算法為每個包含該詞項的文檔計算相關度評分_score。 -
記住
term查詢只對倒排索引的詞項精確匹配,這點很重要,它不會對詞的多樣性進行處理(如,foo或FOO)。這里,無須考慮詞項是如何存入索引的。如果是將
["Foo","Bar"]索引存入一個不分析的(not_analyzed)包含精確值的字段,或者將Foo Bar索引到一個帶有whitespace空格分析器的字段,兩者的結果都會是在倒排索引中有Foo和Bar這兩個詞。 - 基於全文的查詢
-
像
match或query_string這樣的查詢是高層查詢,它們了解字段映射的信息: -
1、如果查詢
日期(date)或整數(integer)字段,它們會將查詢字符串分別作為日期或整數對待。2、如果查詢一個(
not_analyzed)未分析的精確值字符串字段,它們會將整個查詢字符串作為單個詞項對待。3、但如果要查詢一個(
analyzed)已分析的全文字段,它們會先將查詢字符串傳遞到一個合適的分析器,然后生成一個供查詢的詞項列表。一旦組成了詞項列表,這個查詢會對每個詞項逐一執行底層的查詢,再將結果合並,然后為每個文檔生成一個最終的相關度評分。
很少直接使用基於詞項的搜索,通常情況下都是對全文進行查詢,而非單個詞項,這只需要簡單的執行一個高層全文查詢(進而在高層查詢內部會以基於詞項的底層查詢完成搜索)。
當我們想要查詢一個具有精確值的 not_analyzed 未分析字段之前, 需要考慮,是否真的采用評分查詢,或者非評分查詢會更好。
單詞項查詢通常可以用是、非這種二元問題表示,所以更適合用過濾, 而且這樣做可以有效利用緩存
3、匹配查詢
匹配查詢 match 是個 核心 查詢。無論需要查詢什么字段, match 查詢都應該會是首選的查詢方式。 它是一個高級 全文查詢 ,這表示它既能處理全文字段,又能處理精確字段。
這就是說, match 查詢主要的應用場景就是進行全文搜索,我們以下面一個簡單例子來說明全文搜索是如何工作的:
索引一些數據
首先,我們使用 bulk API 創建一些新的文檔和索引:
DELETE /my_index PUT /my_index { "settings": { "number_of_shards": 1 }} POST /my_index/my_type/_bulk { "index": { "_id": 1 }} { "title": "The quick brown fox" } { "index": { "_id": 2 }} { "title": "The quick brown fox jumps over the lazy dog" } { "index": { "_id": 3 }} { "title": "The quick brown fox jumps over the quick dog" } { "index": { "_id": 4 }} { "title": "Brown fox brown dog" }
3.1、單個詞查詢
用第一個示例來解釋使用 match 查詢搜索全文字段中的單個詞:
GET /my_index/my_type/_search { "query": { "match": { "title": "QUICK!" } } }
Elasticsearch 執行上面這個 match 查詢的步驟是:
1、檢查字段類型 。
標題 title 字段是一個 string 類型( analyzed )已分析的全文字段,這意味着查詢字符串本身也應該被分析。
2、分析查詢字符串 。
將查詢的字符串 QUICK! 傳入標准分析器中,輸出的結果是單個項 quick 。因為只有一個單詞項,所以 match 查詢執行的是單個底層 term 查詢。
3、查找匹配文檔 。
用 term 查詢在倒排索引中查找 quick 然后獲取一組包含該項的文檔,本例的結果是文檔:1、2 和 3 。
4、為每個文檔評分 。
用 term 查詢計算每個文檔相關度評分 _score ,這是種將 詞頻(term frequency,即詞 quick 在相關文檔的 title 字段中出現的頻率)和
反向文檔頻率(inverse document frequency,即詞 quick 在所有文檔的 title 字段中出現的頻率),以及字段的長度(即字段越短相關度越高)相結合的計算方式。參見 相關性的介紹 。
這個過程給我們以下(經縮減)結果:

文檔 1 最相關,因為它的 title 字段更短,即 quick 占據內容的一大部分。
文檔 3 比 文檔 2 更具相關性,因為在文檔 2 中 quick 出現了兩次
3.2、多詞查詢
如果我們一次只能搜索一個詞,那么全文搜索就會不太靈活,幸運的是 match 查詢讓多詞查詢變得簡單
GET /my_index/my_type/_search { "query": { "match": { "title": "BROWN DOG!" } } }

文檔 4 最相關,因為它包含詞 "brown" 兩次以及 "dog" 一次。
文檔 2、3 同時包含 brown 和 dog 各一次,而且它們 title 字段的長度相同,所以具有相同的評分。
文檔 1 也能匹配,盡管它只有 brown 沒有 dog 。
因為 match 查詢必須查找兩個詞( ["brown","dog"] ),它在內部實際上先執行兩次 term 查詢,然后將兩次查詢的結果合並作為最終結果輸出。為了做到這點,
它將兩個 term 查詢包入一個 bool 查詢中,詳細信息見 布爾查詢。
以上示例告訴我們一個重要信息:即任何文檔只要 title 字段里包含 指定詞項中的至少一個詞 就能匹配,被匹配的詞項越多,文檔就越相關。
提高精度
用 任意 查詢詞項匹配文檔可能會導致結果中出現不相關的長尾。 這是種散彈式搜索。可能我們只想搜索包含 所有 詞項的文檔,也就是說,不去匹配 brown OR dog ,而通過匹配 brown AND dog 找到所有文檔。
match 查詢還可以接受 operator 操作符作為輸入參數,默認情況下該操作符是 or 。我們可以將它修改成 and 讓所有指定詞項都必須匹配:
GET /my_index/my_type/_search { "query": { "match": { "title": { "query": "BROWN DOG!", "operator": "and" } } } }

match 查詢的結構需要做稍許調整才能使用 operator 操作符參數。
這個查詢可以把文檔 1 排除在外,因為它只包含兩個詞項中的一個。
4、組合查詢
在 組合過濾器 中,討論過如何使用 bool 過濾器通過 and 、 or 和 not 邏輯組合將多個過濾器進行組合。在查詢中, bool 查詢有類似的功能,只有一個重要的區別。
過濾器做二元判斷:文檔是否應該出現在結果中?但查詢更精妙,它除了決定一個文檔是否應該被包括在結果中,還會計算文檔的 相關程度 。
與過濾器一樣, bool 查詢也可以接受 must 、 must_not 和 should 參數下的多個查詢語句。比如:
GET /my_index/my_type/_search { "query": { "bool": { "must": { "match": { "title": "quick" }}, "must_not": { "match": { "title": "lazy" }}, "should": [ { "match": { "title": "brown" }}, { "match": { "title": "dog" }} ] } } }

以上的查詢結果返回 title 字段包含詞項 quick 但不包含 lazy 的任意文檔。目前為止,這與 bool 過濾器的工作方式非常相似。
區別就在於兩個 should 語句,也就是說:一個文檔不必包含 brown 或 dog 這兩個詞項,但如果一旦包含,我們就認為它們 更相關
文檔 3 會比文檔 1 有更高評分是因為它同時包含 brown 和 dog 。
評分計算
bool 查詢會為每個文檔計算相關度評分 _score , 再將所有匹配的 must 和 should 語句的分數 _score求和,最后除以 must 和 should 語句的總數。
must_not 語句不會影響評分; 它的作用只是將不相關的文檔排除。
控制精度
所有 must 語句必須匹配,所有 must_not 語句都必須不匹配,但有多少 should 語句應該匹配呢? 默認情況下,沒有 should 語句是必須匹配的,
只有一個例外:那就是當沒有 must 語句的時候,至少有一個 should 語句必須匹配。
可以通過 minimum_should_match 參數控制需要匹配的 should 語句的數量, 它既可以是一個絕對的數字,又可以是個百分比:
GET /my_index/my_type/_search { "query": { "bool": { "should": [ { "match": { "title": "brown" }}, { "match": { "title": "fox" }}, { "match": { "title": "dog" }} ], "minimum_should_match": 2 } } }
這個查詢結果會將所有滿足以下條件的文檔返回: title 字段包含 "brown" AND "fox" 、 "brown" AND "dog" 或 "fox" AND "dog" 。如果有文檔包含所有三個條件,它會比只包含兩個的文檔更相關。
5、布爾匹配
多詞匹配查詢只是簡單地將生成的 term 查詢包裹 在一個 bool 查詢中。如果使用默認的 or 操作符,每個 term 查詢都被當作 should 語句,這樣就要求必須至少匹配一條語句。以下兩個查詢是等價的:
GET /my_index/my_type/_search { "query": { "match": { "title": "brown fox"} } } GET /my_index/my_type/_search { "query": { "bool": { "should": [ { "term": { "title": "brown" }}, { "term": { "title": "fox" }} ] } } }
如果使用 and 操作符,所有的 term 查詢都被當作 must 語句,所以 所有(all) 語句都必須匹配。以下兩個查詢是等價的:
GET /my_index/my_type/_search { "query": { "match": { "title": { "query": "brown fox", "operator": "and" } } } } GET /my_index/my_type/_search { "query": { "bool": { "must": [ { "term": { "title": "brown" }}, { "term": { "title": "fox" }} ] } } }
如果指定參數 minimum_should_match ,它可以通過 bool 查詢直接傳遞,使以下兩個查詢等價:
GET /my_index/my_type/_search { "query": { "match": { "title": { "query": "quick brown fox", "minimum_should_match": "75%" } } } } GET /my_index/my_type/_search { "query": { "bool": { "should": [ { "term": { "title": "brown" }}, { "term": { "title": "fox" }}, { "term": { "title": "quick" }} ], "minimum_should_match": 2 } } }
因為只有三條語句,match 查詢的參數 minimum_should_match 值 75% 會被截斷成 2 。即三條 should 語句中至少有兩條必須匹配。
