最佳字段(Best Fields)
假設我們有一個讓用戶搜索博客文章的網站,就像這兩份文檔一樣:
PUT /my_index/my_type/1
{
"title": "Quick brown rabbits", "body": "Brown rabbits are commonly seen." } PUT /my_index/my_type/2 { "title": "Keeping pets healthy", "body": "My quick brown fox eats rabbits on a regular basis." }
用戶輸入了"Brown fox",然后按下了搜索鍵。我們無法預先知道用戶搜索的詞條會出現在博文的title
或者body
字段中,但是用戶是在搜索和他輸入的單詞相關的內容。以上的兩份文檔中,文檔2似乎匹配的更好一些,因為它包含了用戶尋找的兩個單詞。
讓我們運行下面的bool
查詢:
{
"query": { "bool": { "should": [ { "match": { "title": "Brown fox" }}, { "match": { "body": "Brown fox" }} ] } } }
然后我們發現文檔1的分值更高:
{
"hits": [ { "_id": "1", "_score": 0.14809652, "_source": { "title": "Quick brown rabbits", "body": "Brown rabbits are commonly seen." } }, { "_id": "2", "_score": 0.09256032, "_source": { "title": "Keeping pets healthy", "body": "My quick brown fox eats rabbits on a regular basis." } } ] }
要理解原因,想想bool
查詢是如何計算得到其分值的:
- 運行
should
子句中的兩個查詢 - 相加查詢返回的分值
- 將相加得到的分值乘以匹配的查詢子句的數量
- 除以總的查詢子句的數量
文檔1在兩個字段中都包含了brown
,因此兩個match
查詢都匹配成功並擁有了一個分值。文檔2在body
字段中包含了brown
以及fox
,但是在title
字段中沒有出現任何搜索的單詞。因此對body
字段查詢得到的高分加上對title
字段查詢得到的零分,然后在乘以匹配的查詢子句數量1,最后除以總的查詢子句數量2,導致整體分值比文檔1的低。
在這個例子中,title
和body
字段是互相競爭的。我們想要找到一個最佳匹配(Best-matching)的字段。
如果我們不是合並來自每個字段的分值,而是使用最佳匹配字段的分值作為整個查詢的整體分值呢?這就會讓包含有我們尋找的兩個單詞的字段有更高的權重,而不是在不同的字段中重復出現的相同單詞。
dis_max查詢
相比使用bool
查詢,我們可以使用dis_max
查詢(Disjuction Max Query)。Disjuction的意思"OR"(而Conjunction的意思是"AND"),因此Disjuction Max Query的意思就是返回匹配了任何查詢的文檔,並且分值是產生了最佳匹配的查詢所對應的分值:
{
"query": { "dis_max": { "queries": [ { "match": { "title": "Brown fox" }}, { "match": { "body": "Brown fox" }} ] } } }
它會產生我們期望的結果:
{
"hits": [ { "_id": "2", "_score": 0.21509302, "_source": { "title": "Keeping pets healthy", "body": "My quick brown fox eats rabbits on a regular basis." } }, { "_id": "1", "_score": 0.12713557, "_source": { "title": "Quick brown rabbits", "body": "Brown rabbits are commonly seen." } } ] }
最佳字段查詢的調優
如果用戶搜索的是"quick pets",那么會發生什么呢?兩份文檔都包含了單詞quick
,但是只有文檔2包含了單詞pets
。兩份文檔都沒能在一個字段中同時包含搜索的兩個單詞。
一個像下面那樣的簡單dis_max
查詢會選擇出擁有最佳匹配字段的查詢子句,而忽略其他的查詢子句:
{
"query": { "dis_max": { "queries": [ { "match": { "title": "Quick pets" }}, { "match": { "body": "Quick pets" }} ] } } }
{
"hits": [ { "_id": "1", "_score": 0.12713557, "_source": { "title": "Quick brown rabbits", "body": "Brown rabbits are commonly seen." } }, { "_id": "2", "_score": 0.12713557, "_source": { "title": "Keeping pets healthy", "body": "My quick brown fox eats rabbits on a regular basis." } } ] }
可以發現,兩份文檔的分值是一模一樣的。
我們期望的是同時匹配了title
字段和body
字段的文檔能夠擁有更高的排名,但是結果並非如此。需要記住:dis_max
查詢只是簡單的使用最佳匹配查詢子句得到的_score
。
tie_breaker
但是,將其它匹配的查詢子句考慮進來也是可能的。通過指定tie_breaker
參數:
{
"query": { "dis_max": { "queries": [ { "match": { "title": "Quick pets" }}, { "match": { "body": "Quick pets" }} ], "tie_breaker": 0.3 } } }
它會返回以下結果:
{
"hits": [ { "_id": "2", "_score": 0.14757764, "_source": { "title": "Keeping pets healthy", "body": "My quick brown fox eats rabbits on a regular basis." } }, { "_id": "1", "_score": 0.124275915, "_source": { "title": "Quick brown rabbits", "body": "Brown rabbits are commonly seen." } } ] }
現在文檔2的分值比文檔1稍高一些。
tie_breaker
參數會讓dis_max
查詢的行為更像是dis_max
和bool
的一種折中。它會通過下面的方式改變分值計算過程:
- 取得最佳匹配查詢子句的
_score
。 - 將其它每個匹配的子句的分值乘以
tie_breaker
。 - 將以上得到的分值進行累加並規范化。
通過tie_breaker
參數,所有匹配的子句都會起作用,只不過最佳匹配子句的作用更大。
NOTE
tie_breaker
的取值范圍是0
到1
之間的浮點數,取0
時即為僅使用最佳匹配子句(譯注:和不使用tie_breaker
參數的dis_max
查詢效果相同),取1
則會將所有匹配的子句一視同仁。它的確切值需要根據你的數據和查詢進行調整,但是一個合理的值會靠近0
,(比如,0.1
-0.4
),來確保不會壓倒dis_max
查詢具有的最佳匹配性質。