本文記錄以字段爲中心的查詢和以詞爲中心的查詢這兩種查詢方式的區別以及在ElasticSearch中的實現接口,而後以ElasticSearch官方文檔爲參數資料,比較 query_string dismax 查詢 和 multi_match cross_fields查詢異同點。html
當Client發起查詢請求時,ES會計算查詢字符串與文檔之間的相關性,即計算一個得分,得分越高的文檔與查詢字符串越相關。單字段搜索這裏不討論,由於只有一個字段,針對這個字段計算出來的得分就是整個文檔的得分。java
而對於多字段搜索,由於有查詢字符串,也有待查詢的多個字段,此時得分有兩種計算方案:api
以字段爲中心app
基於ES的索引字段計算,好比索引中有2個字段:title、overviewelasticsearch
查詢字符串"Basketball with cartoon aliens",Analyzer成一個個的term,"輸入"到 title 字段,計算出一個得分\(S_{title}\),相似於,針對 overview 字段計算出一個得分\(S_{overview}\),將 \(S_{title}\)和\(S_{overview}\)按某種方式再組合成一個最終的得分\(S_{doc1}\),\(S_{doc1}\)就是文檔doc1 關於 查詢字符串"Basketball with cartoon aliens"的最終得分。ES索引中的每篇文檔都按這種方式計算出得分:\(S_{doc2}\)、\(S_{doc3}\)……,對這些得分排序,返回TOP-N 文檔。ide
這裏的某種方式,可理解成Search Type,有三種:best_fields、most_fields、cross_fieldsui
best_fields取 \(S_{title}\) 和 \(S_{overview}\)中的最大值,做爲文檔的最終得分\(S_{doc1}\)。spa
most_fields 將 \(S_{title}\)和\(S_{overview}\)兩者的得分求和,做爲文檔的最終得分\(S_{doc1}\)。debug
到這裏,還有一種狀況:認爲 title字段比overview字段重要,這既不符合best_fields那樣由徹底由\(S_{title}\)說了算(假設\(S_{title} > S_{overview}\))也不知足most_fields對 titile字段和overview字段徹底平等對待的思路。因而,就引入了 tie_breaker 參數。code
因而,文檔得分就變成了以下計算公式:(假設還有額外兩個字段:cast.name和directors.name)
ElasticSearch中mulit_match查詢,默認採用best_fields方式計算得分。當 best_fields 知足不了要求時,可採用tie_break 進行一些微調。
cross_fields
multi_match 採用 cross_fields方式查詢時,就是以詞爲中心的得分計算方式。
/** * Uses a blended DocumentFrequency to dynamically combine the queried * fields into a single field given the configured analysis is identical. * This type uses a tie-breaker to adjust the score based on remaining * matches per analyzed terms */ CROSS_FIELDS(MatchQuery.Type.BOOLEAN, 0.0f, new ParseField("cross_fields")),
它也有一個tie_breaker參數來實現得分的」平衡「調節,從上面註釋看:uses a tie-breaker to adjust the score based on remaining matches per analyzed terms。這裏,不太明白cross_fields查詢時,tie_breaker如何對各個 term 進行調節的(個人理解是,在使用multi_match cross fields 查詢時,既然是以詞爲中心,當查詢字符串analyze 成 一個個的term時,其中,確定有一些term命中了mutli_match指定的全部字段,也有一些term只命中了部分字段,那麼給term的打分就會不同,而 tie_breaker 參數就是用來調節各個term得分權重的)。
以詞爲中心
對查詢字符串Analyzer,分解成一個個的Term,基於Term計算得分。
針對Term:Basketball,」輸入「到各個字段中,計算出一個得分:\(S_{Basketball}\)、針對Term Cartoon,輸入到各個字段中,計算出一個得分\(S_{Cartoon}\),而後再按某種方式組合成一個最終的得分\(S_{doc1}\)。
以TF-IDF計算term權重爲例,有兩個索引字段: first_name字段、last_name字段。當查詢「Will Smith」時,因爲 詞 Will 是一個很常見的first_name,所以Will在first_name字段的得分會很低。同理,詞Smith是一個很常見的last_name,所以Smith在last_name字段的得分也會很低。
假設有一篇文檔的內容是:「Smith Jones」,其中Smith 在索引的first_name字段,Jones在索引的last_name字段。而根據人類起名字的習慣,不多有人把 詞 Smith 做爲first_name,而TF-IDF衡量詞的權重時,傾向於給不常見的詞賦予高權重,當詞 Smith 出如今 first_name字段時,相應的文檔得分就會異常的高。而如今恰好有個「奇葩」名字 "Smith Jones",Smith是first_name,所以,用戶查詢的是:「Will Smith」,可是返回的最佳匹配文檔是:「Smith Jones」,沒有匹配到 "Will" 這個詞---這種針對多字段搜索的方式未徹底匹配中用戶的查詢字符串。而這就是白象化問題。
這兩種查詢都是以詞爲中心的查詢。
query_string
在查詢字符串中能夠指定 Operator(好比 OR 、AND)。所以這是一種更接近Lucene原生查詢語法的查詢。它查詢時指定的操做符,所以不像 mulit_match cross_fields查詢那樣會爲文檔計算得分。
好比官網上給的示例:
GET /_search { "query": { "query_string" : { "default_field" : "content", "query" : "(new york city) OR (big apple)" } } }
query_string 中的多字段查詢:
基於dis_max查詢實現,可採用tie_break 放大term 在某個字段中的影響。而dis_max查詢實際上是一種組合查詢。見compound search
multi_match with cross_fields type
The
cross_fields
type is particularly useful with structured documents where multiple fields should match
structured Document:索引的文檔有明確的字段定義,好比user profile 有暱稱、簽名 這樣的字段。
若是用戶的查詢意圖並非針對某個具體的字段查詢,用戶不關注搜索內容出如今哪一個字段,其查詢意圖傾向於對整篇文檔的查詢,cross_fields就比較合適。因爲TF-IDF評分模型傾向於給詞頻較低的詞高得分,cross_fields查詢能緩解「白象化」問題。值得注意的是,multi_match with cross_fields 也有一個tie_breaker 參數微調評分。
基於ElasticSearch6.3.2 Rest High Level API。因爲JAVA API變化很快,通常是基於下面四步:(模板方法模式)
org.elasticsearch.client.RestHighLevelClient#search
在構造QueryBuilder的時候,對查詢進行微調:
MultiMatchQueryBuilder multiMatchQueryBuilder = QueryBuilders.multiMatchQuery(keyword, fields); //採用 MOST_FIELDS 方式查詢 multiMatchQueryBuilder.type(MultiMatchQueryBuilder.Type.MOST_FIELDS);
若不肯定QueryBuilder的寫法,其實能夠在debug的時候,查看QueryBuilder: