Elasticsearch 多字段搜索


查詢很少是對一個字段做 match 查詢,通常都是一個 query 查詢多個字段,比如一個 doc 有 title、content、pagetag 等文本字段,要在這些字段查詢含多個 term 的 query,就要對它們的相關度評分做合理的合並。這被稱為多詞(multiword)、多字段(multifield)查詢。

如果一個 query 可以結構化,如哪些詞是 title,哪些詞是 author,那么就可以直接在相關字段中查詢,使用 bool 查詢即可解決問題,bool 查詢是“匹配越多越好”,如搜“War and Peace Leo Tolstoy”,查詢語句如下:

GET /_search
{
  "query": {
    "bool": {
      "should": [
        { "match": { "title":  "War and Peace" }},
        { "match": { "author": "Leo Tolstoy"   }}
      ]
    }
  }
}

還可以對不同的字段加不同的 boost 權重。

以上被稱為多重查詢字符串,也可算是結構化查詢,不過現實中通常是一個 query 在多個字段中查詢,即單一查詢字符串。畢竟對 query 做結構化需要些 nlp 技術和額外的人力成本,且比起單一查詢字符串的效果提升也有限,所以若不是對召回效果有更高追求,還是不要輕舉妄動,就好好做一個 query 在多個字段的查詢吧。

一個 query 在多個字段中的查詢,有三種策略:best_fields、most_fields、cross_fields。

介紹這三種策略之前,先鋪墊下布爾查詢和 dis_max 查詢。

1. bool 查詢

一個 query 在多個字段中的查詢,同樣可使用 bool 查詢。

{
    "query": {
        "bool": {
            "should": [
                { "match": { "title": "Brown fox" }},
                { "match": { "body":  "Brown fox" }}
            ]
        }
    }
}

不過由於 bool 查詢評分公式的問題,效果不太好,比如一個文檔 title 和 body 都包含 brown,不包含 fox,另一個文檔在 body 字段包含了 brown 和 fox,顯然后者更符合搜索意圖,但 bool 查詢的評分前者高,為了理解導致這樣的原因,需要看下 bool 查詢是如何計算評分的:

  • 它會執行 should 語句中的兩個查詢。

  • 加和兩個查詢的評分。

  • 乘以匹配字段的總數(這里不知是否理解正確,存疑,待驗證)。

  • 除以所有語句總數(這里為: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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM