Website logo

Robert Chang

技術部落格

Elasticsearch - 如何一次搜尋多個欄位

全文搜尋 的文章中,我們只有介紹了使用 match 來進行搜尋。

但其實 Elasticsearch ( 以下簡稱 ES ) 還支援同時搜尋多個欄位的功能,有幾種方式可以做到這樣的效果,其中一個就是使用 multi_match 的搜尋條件。

Multi Match

使用起來其實非常直覺,如下面的示範:

// GET /movies/_search
{
  "query": {
    "multi_match": {
      "query": "Horror",
      "fields": ["genres", "title"]
    }
  }
}

結果如下圖:

screen shot

非常不意外的,genres 以及 title 內含有 Horror 的資料都會被返回在結果之中。

但這邊我們希望可以讓更多 title 中含有 Horror 的資料排名往前,也就是我們希望可以 boost 一下這個 title 的欄位,我們希望它變得更重要,我們可以怎麼做呢?

透過在欄位後方加入 ^2 的方式,當然你要 ^3 也可以。

// GET /movies/_search
{
  "query": {
    "multi_match": {
      "query": "Horror",
      "fields": ["genres", "title^2"]
    }
  }
}

接著再次搜尋,可以看到第一個原本只有 7 分相關的資料,馬上變成 14 分,是因為我們把 title 這個欄位的權重給提高了。

大概可以猜到 ^2 就是乘以 2 的概念。

screen shot

Multi Match 是怎麼運作的?

其實在 ES 的內部,可以簡單地想像它把剛剛我們搜尋的內容拆解成兩個部分:

{
  "query": {
    "match": {
      "genres": "Horror",
    }
  }
}

// 以及

{
  "query": {
    "match": {
      "title": "Horror",
    }
  }
}

所以得出的結果是 OR 而不是 AND 也是很正確的,這部分可以透過 operator 的方式去調整,我們有在 全文搜尋 中介紹過。

而 ES 是如何在這麼多的資料中排名分數的呢?以預設的狀況下,ES 採用的是 best_fields 的策略,也就是找到所有資料,並且以分數最高的那個欄位作為該 document 的 _score

以三份 documents 來舉例,7.16 是第一份 document 的最高分,就會是這份 document 的 _score,以此類推:

// document #1
{
  "title": "The Amityville Horror", // 7.164084
  "genres": ["Horror", "Supernatural"] // 5.123333
}

// document #2
{
  "title": "Book of Shadows: Blair Witch 2", // N/A
  "genres": ["Horror"] // 3.0575924
}

// document #3
{
  "title": "Dracula 2000", // N/A
  "genres": ["Horror"] // 3.0575924
}

所以如果你也跑一次相同的搜尋,你會看到最高分就是 7.164084,而後面的資料都是相同的分數,是因為 genres 內只含有 Horror 這個單字的資料都是 3.0575924 這個分數。

再次說明這樣的取分排名方式,是預設的 best_fields,ES 有提供很多不一樣的策略,讓開發者可以根據需求去更改搜尋的策略。

Multi Match Query 的 types,官方連結

Tie Breaker

這邊要介紹一種方式,可以把其餘的分數也一起加進來計算的參數,叫做 tie_breaker,這個名詞很常在一些競技比賽中出現,中文叫做『 突破僵局制 』。

而 ES 在這邊要帶進來的概念就是,不要捨棄掉一些也有被計分的欄位,讓它們一起加入計算,並且給予它們權重,可以算是某一種突破僵局,我覺得蠻有趣的,這樣可以進一步地讓資料之間的分數不相同,變相的打破很多資料的分數相同的僵局。

以下是範例:

// GET /movies/_search
{
  "query": {
    "multi_match": {
      "query": "American",
      "fields": ["extract", "title"],
      "tie_breaker": 0.3
    }
  }
}

這樣會怎麼排列資料呢?

以預設情況來說,會找到最高分的欄位,並且當作該 document 的 _score;但加入了 tie_breaker 之後,會加上 extract 欄位的分數乘以 0.3 當作 _score 來計算:

{
  "title": "American Psycho", // 5.39793
  "extract": "American Psycho is a 2000 satirical horror film directed by Mary Harron, who co-wrote the...." // 0.33 * 0.3
}

// 5.39793 + ( 0.33 * 0.3 ) = 5.49793...

所以總分不再是以最高計算,而是會加入其餘欄位的計分乘以 tie_breaker 的參數。

如果你嘗試再跑一次上面的搜尋,沒有 tie_breaker 和帶有 tie_breaker 的搜尋排序結果是不同的,原本第一名的 American Psycho 會被放到後面去,反而被 American Wedding 給取代,這就是因為 American Weddingextract 欄位分數乘以 0.3 之後的表現更好。

小小的實驗,如果 tie_breaker 帶的比 0.000001 還小,第一名依然會是 American Psycho,其實這樣大概就可以求出 American Weddingextract 分數大約落在哪裡了。

所以根據不同的使用場景,這個 tie_breaker 的值可能是需要做調整和變動的,而這就是工程師的責任了!

上一篇文章Elasticsearch - 介紹評分 ( Scoring )

下一篇文章Elasticsearch - Phrase Search