查詢不多是對一個字段作 match 查詢,一般都是一個 query 查詢多個字段,好比一個 doc 有 title、content、pagetag 等文本字段,要在這些字段查詢含多個 term 的 query,就要對它們的相關度評分作合理的合併。這被稱爲多詞(multiword)、多字段(multifield)查詢。html
若是一個 query 能夠結構化,如哪些詞是 title,哪些詞是 author,那麼就能夠直接在相關字段中查詢,使用 bool 查詢便可解決問題,bool 查詢是「匹配越多越好」,如搜「War and Peace Leo Tolstoy」,查詢語句以下:curl
GET /_search
{
"query": {
"bool": {
"should": [
{ "match": { "title": "War and Peace" }},
{ "match": { "author": "Leo Tolstoy" }}
]
}
}
}
還能夠對不一樣的字段加不一樣的 boost 權重。elasticsearch
以上被稱爲多重查詢字符串,也可算是結構化查詢,不過現實中一般是一個 query 在多個字段中查詢,即單一查詢字符串。畢竟對 query 作結構化須要些 nlp 技術和額外的人力成本,且比起單一查詢字符串的效果提高也有限,因此若不是對召回效果有更高追求,仍是不要輕舉妄動,就好好作一個 query 在多個字段的查詢吧。ide
一個 query 在多個字段中的查詢,有三種策略:best_fields、most_fields、cross_fields。ui
介紹這三種策略以前,先鋪墊下布爾查詢和 dis_max 查詢。url
1. bool 查詢spa
一個 query 在多個字段中的查詢,一樣可以使用 bool 查詢。日誌
{
"query": {
"bool": {
"should": [
{ "match": { "title": "Brown fox" }},
{ "match": { "body": "Brown fox" }}
]
}
}
}
不過因爲 bool 查詢評分公式的問題,效果不太好,好比一個文檔 title 和 body 都包含 brown,不包含 fox,另外一個文檔在 body 字段包含了 brown 和 fox,顯而後者更符合搜索意圖,但 bool 查詢的評分前者高,爲了理解致使這樣的緣由,須要看下 bool 查詢是如何計算評分的:code
它會執行 should 語句中的兩個查詢。htm
加和兩個查詢的評分。
乘以匹配字段的總數(這裏不知是否理解正確,存疑,待驗證)。
除以全部語句總數(這裏爲:2)。
注意這裏的「乘以匹配語句的總數」是關鍵,這會致使匹配字段越多,分值越大。(後面的 most_fields 也是使用這個計算,才使得匹配字段數越多,分值越大)
解決方案是,使用最佳匹配字段的分值做爲整個查詢的總體分值,讓包含 query 兩個單詞的字段有更高的權重,而不是在不一樣的字段中重複出現的相同單詞。dis_max 查詢應運而生。
2. dis_max
dis_max 查詢就是返回匹配了 query 的文檔,分值是最佳匹配字段產生的分值。加上 tie_breaker 可得出很好的搜索效果。
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "Quick pets" }},
{ "match": { "body": "Quick pets" }}
],
"tie_breaker": 0.3
}
}
}
3. best_fields
multi_match 查詢提供了一個簡便的方法對多個字段執行相同的查詢。默認狀況下,該查詢以 best_fields 類型執行,它會爲每一個字段生成一個 match 查詢,而後將這些查詢包含在一個 dis_max 查詢中。
例如:
GET /_search
{
"query": {
"multi_match" : {
"query": "brown fox",
"type": "best_fields",
"fields": [ "subject", "message" ],
"tie_breaker": 0.3
}
}
}
執行時就變成了:
GET /_search
{
"query": {
"dis_max": {
"queries": [
{ "match": { "subject": "brown fox" }},
{ "match": { "message": "brown fox" }}
],
"tie_breaker": 0.3
}
}
}
可經過 caret 語法(^) 對個別字段加權,如:
{
"multi_match": {
"query": "Quick brown fox",
"fields": [ "title", "chapter_title^2" ]
}
}
best_fields 和 most_fields 都是以字段爲中心的查詢,參數 operator 和 minimum_should_match 也是針對每一個字段生效的,至少有一個字段知足要求,纔會經過篩選並進入下一步計分,計分時也只有符合要求的字段纔會參與計分。
operator 默認爲 or,若是設置爲 and,那麼字段必須匹配全部 query 分詞。當 operator 設爲默認值 or 時,minimum_should_match 纔會生效,設置每一個字段應匹配分詞數。
因此有些 query 信息是分佈在多個字段上的,這時就不適合設置 operator 爲 and,會減小召回量。若是確認 query 信息必定徹底在某個字段上,則可設爲 and。
爲與 cross_fields 作對比,這裏舉個實際應用的例子。
搜索詞爲「蘋果8plus國行」,文檔有三個字段:cateName、title、content,其中 cateName 和 content 用 ik_smart 分詞,title 用 ik_max_word 分詞(不一樣字段的分詞方法差別會在 cross_fields 中有所體現)。
看下 best_fields 查詢的實際執行。
ES 語句:
curl -XGET 'ip:port/indexname/_validate/query?explain&pretty' -d' { "query": { "function_score": { "query": { "bool": { "should": [{ "multi_match": { "query": "蘋果8plus國行", "fields": ["cate_name^1.0", "content^1.0", "title^1.0"], "type": "best_fields", "operator": "AND", "tie_breaker": 0.3 } }] } } } } } '
返回解釋:
(
(+cate_name:蘋果8 +cate_name:plus +cate_name:國行) |
(+content:蘋果8 +content:plus +content:國行) |
(+title:蘋果8 +title:蘋果 +title:8plus +title:8 +title:plus +title:國 +title:行)
)~0.3
明顯的以字段爲中心的查詢。
tips:字段名能夠經過通配符指定,如:
{
"multi_match": {
"query": "Quick brown fox",
"fields": "*_title"
}
}
4. most_fields
有時爲了儘量多地匹配文檔,會將同一文本的不一樣形式索引到多個字段。
ES語句(注意不要加 operator 或 minimum_should_match,否則就跟 best_fields 同樣了):
curl -XGET 'ip:port/indexname/_validate/query?explain&pretty' -d' { "query": { "function_score": { "query": { "bool": { "should": [{ "multi_match": { "query": "蘋果8plus國行", "fields": ["cate_name^1.0", "content^1.0", "title^1.0"], "type": "most_fields" } }] } } } } } '
返回解釋:
(
(cate_name:蘋果8 cate_name:plus cate_name:國 cate_name:行) |
(content:蘋果8 content:plus content:國 content:行) |
(title:蘋果8 title:蘋果 title:8plus title:8 title:plus title:國 title:行)
)~1.0
根據文檔,most_fields 查詢是用 bool 查詢將兩個字段語句包在裏面,而不是像 best_fields 同樣用 dis_max。(不知這個怎麼驗證,在本身的 ES 裏試了下,看 explain 日誌,沒看出跟 best_fields 時 tie_breaker=1 有什麼差異)
5. cross_fields
ES語句:
curl -XGET 'ip:port/indexname/_validate/query?explain&pretty' -d' { "query": { "function_score": { "query": { "bool": { "should": [{ "multi_match": { "query": "蘋果8plus國行", "fields": ["cate_name^1.0", "content^1.0", "title^1.0"], "type": "cross_fields", "operator": "AND", "tie_breaker": 0.3 } }] } } } } } '
返回解釋:
(
(+blended(terms:[cate_name:蘋果8, content:蘋果8]) +
blended(terms:[cate_name:plus, content:plus]) +
blended(terms:[cate_name:國行])) |
(+title:蘋果8 +title:蘋果 +title:8plus +title:8 +title:plus +title:國 +title:行)
)~0.3
這裏 title 要和 cate_name、content 分開計算的緣由,是由於兩部分的分詞方法不一樣,term 也不一樣。
根據《Elasticsearch: 權威指南》,關於 蘋果8 的 IDF,會在 cate_name 和 content 中取最小值做爲兩個字段的 IDF。(待驗證)
參考資料