iT邦幫忙

2023 iThome 鐵人賽

DAY 23
0
Software Development

30 天學習 Elasticsearch系列 第 23

Day 23 Elasticsearch - Term Level Query

  • 分享至 

  • xImage
  •  

該文章同步發佈於:我的部落格

也歡迎關注我的 Facebook 以及 Instagram 接收軟體相關的資訊!

上一篇文章 中,對於搜尋已經有了基礎的概念,這篇文章將會介紹 Term Level 的搜尋以及 Range 搜尋方式。

Term Level Query

Term 在中文翻譯應該可以翻作單字,或是特定的術語,下面是劍橋字典的解釋:

a word or expression used in relation to a particular subject, often to describe something official or technical

所以我們可以理解在 Elasticsearch ( 以下簡稱 ES ) 之中使用 term level 的搜尋意味著精準地針對欄位進行搜尋,常見的使用場景像是搜尋品牌的名稱,或是產品的分類等等。

直接進入示範,使用以下的搜尋條件來篩選 genreskeyword 類型是喜劇的電影:

// GET /movies/_search
{
  "query": {
    "term": {
      "genres.keyword": "Comedy"
    }
  }
}

結果如下:

不太意外地命中了 979 筆資料,這邊我們做一個實驗,把 Comedy 改成 comedy 試試看:

馬上變成 0 筆資料,這是為什麼呢?

就要講到 term level 的一個特點,搜尋的單字本身不會被 tokenize,也意味搜尋的單字本身是不會受到大小寫轉換的,還記得 tokenize 的作用嗎?不記得的可以回去看 這篇文章 中 ES 的 Analyzer 做了什麼。

所以這邊搜尋中的 comedy 確實沒辦法在 inverted indices 中找到和 comedy 相符的文件,因為當初這個欄位是以 keyword 的資料類型儲存,而且儲存的內容本身是 Comedy 大寫的格式,所以一定找不到。

那你可能心裡就想說,這樣真的好麻煩,好難用,有沒有什麼辦法可以改變這件事?

有,我們可以用更具體的搜尋方式 ( Explicit Query ) 搭配 case_insensitive 來查詢:

// GET /movies/_search
{
  "query": {
    "term": {
      "genres.keyword": {
        "value": "comedy",
        "case_insensitive": true
      }
    }
  }
}

就可以得到原本我們期待的 979 筆資料。

所以使用 term level 搜尋最需要注意的就是搜尋的單字本身可能會因為不被 Analyzer 處理過而受限於大小寫的影響,當然這也和我們查詢的欄位資料類型是 keyword 有關係。

接著你會聯想到,那我去查詢 text 資料類型的欄位呢?試試看:

我們嘗試使用 28 Days 這個標題去搜尋電影,我很確定有一部電影的標題就叫做 28 Days 沒錯。

// GET /movies/_search
{
  "query": {
    "term": {
      "title": "28 Days"
    }
  }
}

答案是一筆都不會對到:

為什麼會這樣呢?回想一下 text 的資料類型是怎麼被 Analyzer 處理和儲存的,下面是 28 Days 轉換成 inverted index 的樣子:

而搭配上我們說的 term level 並不會對搜尋的單字進行分析,所以是直接用 28 Days 這樣的 key 下去找,當然找不到囉!

換個想法,只要我們使用 28 / days / 28 days 都可以找到這個 document 才對:

// GET /movies/_search
{
  "query": {
    "term": {
      "title": "28"
    }
  }
}

結果出爐,確實有一部電影叫做 28 Days 沒錯!

所以在使用 term level 搜尋時,切記不要針對 text 資料類型的欄位進行搜尋,這很容易導致一些你很難 debug 的狀況,像是剛剛的情境,一開始使用 days 去搜尋會找到想要的資料,覺得沒問題了,部署上去後,想說使用一個看起來更精準的 28 Days 搜尋卻找不到,這時候如果不理解 tokenizer 是如何作用的話,真的會一個頭兩個大。

使用 ids 進行搜尋

這是一個很直覺的用法,就像在使用關聯式資料庫一樣,直接使用範例就能夠明白了:

// GET /movies/_search
{
  "query": {
    "ids": {
      "values": ["1", "10", "100"]
    }
  }
}

分別得到 ID 是 1, 10 以及 100 的資料:

這個用法的好處在哪呢?可以在 index 資料時搭配關聯式資料庫的 ID 來建立,這樣在應用程式層面要互動也會比較簡單,甚至是 debug 時也很好找資料。

使用 Range 進行搜尋

Range 搜尋的使用場景百百種,常見的有數字大小的區間,還有一個很重要的就是時間的區間,在 ES 之中當然也支援時間的區間搜尋,因為我當初提供的 movies 的資料中,忘記加入時間的欄位,所以下面就用假設的方式來示範 ( 你拿去查詢會壞掉 ):

// GET /movies/_search
{
  "query": {
    "range": {
      "release_date": {
        // ES 也支援 2001/01/01 不含時間的寫法
        "gt": "2001/01/01 00:00:00",
        "lt": "2005/12/31 23:59:59"
      }
    }
  }
}

至於要用哪一種寫法呢?要加上時分秒嗎?端看你的 mapping 當初設定的格式是什麼。

date 資料欄位的格式也是在建立 mapping 時需要知道,常見的三種格式:

  1. yyyy/MM/dd HH:mm:ss
  2. yyyy/MM/dd
  3. epoch_millis

其中需要稍微介紹的就是 epoch_millis,其實就是 1970-01-01T00:00:00Z 到某個時間點的毫秒數,舉例來說,1588302800000 表示 2020-05-01T00:00:00Z

至於要用什麼就取決於你們應用程式需要的精準程度,只需要特別注意如果是選擇 yyyy/MM/dd 的格式,ES 會自動帶入 00:00:00 作為搜尋的起始點,所以如果你的搜尋要更準確的話,一開始設計的時候就要考慮到這件事情。

而關於搜尋時間這件事,ES 還支援了 format 以及 time_zone 的兩種參數,關於 format 就是特殊的時間格式,因為 mapping 的 date 格式是可以客製的,所以有些人的格式可能是 dd/MM/yyyy,這樣在搜尋時就要特別加入,像是這樣:

// GET /movies/_search
{
  "query": {
    "range": {
      "release_date": {
        "format": "dd/MM/yyyy",
        "gt": "01/01/2000",
        "lt": "31/12/2005"
      }
    }
  }
}

至於 time_zone 的參數呢?則是因應 ES 儲存的時間都是 UTC,所以我們可以利用給搜尋時間加上 time_zone,讓 ES 在執行查詢之前先將你的查詢轉換為指定的 UTC 時間。

怎麼用呢?很簡單:

// GET /movies/_search
{
  "query": {
    "range": {
      "release_date": {
        "time_zone": "+08:00",
        "gt": "2001/01/01 00:00:00",
        "lt": "2005/12/31 23:59:59"
      }
    }
  }
}

這樣實際上在搜尋時會被轉換成下面的格式,才執行真正的查詢!

{
  "gt": "2000/12/31 16:00:00",
  "lt": "2005/12/31 15:59:59"
}

下一篇文章會繼續來討論一些搜尋的用法和該注意的細節!


上一篇
Day 22 Elasticsearch - 搜尋簡介
下一篇
Day 24 Elasticsearch - Full Text Query ( 全文搜尋 )
系列文
30 天學習 Elasticsearch30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言