Elasticsearch 多字段搜索

查詢不多是對一個字段作 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。(待驗證)

 

參考資料

  • Elasticsearch: 權威指南:https://www.elastic.co/guide/cn/elasticsearch/guide/current/_best_fields.html
  • Elasticsearch Reference:https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html
相關文章
相關標籤/搜索