https://unsplash.com/photos/_DyUcJalGAc
在上一篇,筆者介紹了一些 ElasticSearch 的寫入操作,以及少數的查詢操作。本文一開始會引進範例資料,著重於使用 DSL 撰寫「相等」與「範圍」的查詢條件,並將它們用於字串或數值欄位上。接著講解如何組合出更複雜的條件,實現「NOT」與「OR」的邏輯。最後提及全文檢索,並介紹字串在 ES 儲存的方式,讓讀者認識關鍵字查詢的原理。
一、範例資料介紹
由於筆者會在本文示範許多條件的查詢,因此將範例資料展示如下。
這些是描述學生資料的 document,以下為欄位說明。
讀者在練習前可逐一新增 document。
在認識範例資料後,接下來的小節,筆者會示範使用 ES 的 DSL 進行查詢。DSL 的全名是 Domain-Specific Language,中文為「領域特定語言」,意思是專門用於特定應用領域的語言。對 ES 來說,就是專屬於它的語言。
二、相等查詢
(一)term
使用「term」符號,並傳入欄位與值,代表查詢條件是跟某個特定的值相等。以下範例是查詢 1 年級的學生。
{ "query": { "term": { "grade": 1 } } }
此例的查詢結果為:Winnie。
若要對字串欄位做查詢,需在欄位名稱後方加上「.keyword」,這樣條件才會是與給定的字串值完全相等。以下範例是查詢叫做「Dora Pan」的學生。
{ "query": { "term": { "name.keyword": "Dora Pan" } } }
(二)terms
使用「terms」符號,並傳入欄位與陣列,則代表條件是與陣列中的其中一個值相等。以下範例是查詢擔任衛生股長或康樂股長的學生。
{ "query": { "terms": { "job.name.keyword": ["衛生股長", "康樂股長"] } } }
此例的查詢結果為:Dora、Mario。
三、範圍查詢
使用「range」符號,並傳入欄位,以及範圍的上限或下限,即可對欄位做範圍查詢。在定義上下限時,可使用的符號包含 gt(大於)、gte(大於等於)、lt(小於)與 lte(小於等於)。讀者可選擇一個上限、一個下限,或兩者都用。
(一)數值欄位
以下範例是查詢操性成績 70 ~ 79 分的學生。
{ "query": { "range": { "conductScore": { "gte": 70, "lte": 79 } } } }
此例的查詢結果為:Dora、Winnie。
關於範圍的上限與下限,其最大、最小值剛好與 Java 的 long 型態相同。也就是最大值為「9,223,372,036,854,775,807」,最小值為「-9,223,372,036,854,775,808」。
(二)日期欄位
以下範例是查詢英文檢定通過日在 2021-07-01 之後的學生。
{ "query": { "range": { "englishIssuedDate": { "gte": "2021-07-01" } } } }
此例的查詢結果為:Winnie、Mario。
或是讀者想要使用「UNIX 時間戳」,也是行得通的。要留意的是,範圍上下限的參數是以毫秒為單位。
{ "query": { "range": { "englishIssuedDate": { "gte": 1625097600000 } } } }
不論是使用字串還是時間戳來表示日期,都可用於查詢。原因是 ES 有在 index 的「mapping」將 englishIssuedDate 欄位設定為「date」類型。
Mapping 是一份資料,用來定義 ES 要如何儲存欄位值。透過「GET /student/_mapping」的 API,我們可看到各欄位的 mapping。
從官方文件可知,對於 date 類型,預設是以「時間戳」與「字串(以減號區隔)」來做為解析日期的格式(format)。因此這兩種參數才能正確地查詢。
四、欄位是否存在
使用「exists」符號,並傳入欄位名稱,代表查詢條件是指定的欄位需存在。
(一)字串欄位存在
以下範例是查詢具有血型資料的學生。
{ "query": { "exists": { "field": "bloodType" } } }
此例的查詢結果為:Vincent、Dora、Mario。
(二)字串陣列欄位存在
以下範例是查詢有提供電話號碼的學生,語法與一般欄位相同。
{ "query": { "exists": { "field": "phoneNumbers" } } }
要注意的是,若陣列欄位存在,但裡面卻沒有元素,同樣會被 ES 視為欄位不存在。因此 exists 是個用來判斷是否為空陣列的好方法。
此例的查詢結果為:Vincent、Dora。
五、複合查詢
(一)bool
前面的範例都是只有一個條件。若想使用 AND、OR 或 NOT 邏輯,那就得使用「bool」符號了。
該符號內部可再囊括「must」、「filter」「mustNot」與「should」四個符號,語法結構如下:
{ "query": { "bool": { "must": [], "mustNot": [], "should": [], "filter": [] } } }
陣列中可存放前面介紹的查詢條件,或是另一個 bool 物件。而這四個符號各自有不同的目的,接下來將分別說明。
(二)must 與 filter
這兩個符號代表「必須符合」的意思,也就是「AND」的邏輯。以下範例是查詢就讀「財務金融」系,且有修習「程式設計」課程的學生。
{ "query": { "bool": { "must": [ { "term": { "departments.keyword": "財務金融" } }, { "term": { "courses.name.keyword": "程式設計" } } ] } } }
此例的查詢結果為:Vincent。
至於 filter 符號,其用途與 must 相同,唯一的差別在於當 document 符合 must 的條件時,ES 會給予分數,而 filter 不會。
(三)mustNot
此符號代表「必須不符合」的意思,也就是「NOT」的邏輯。以下範例是查詢操行成績不低於 80 分的學生。
{ "query": { "bool": { "mustNot": [ { "range": { "conductScore": { "lt": 80 } } } ] } } }
此例的查詢結果為:Vincent、Mario。
(四)should
此符號代表「OR」的邏輯,只要 document 達成其中一個條件,should 的部份就算符合。以下範例是查詢就讀「會計」系,或有修習「會計學」課程的學生。
{ "query": { "bool": { "should": [ { "term": { "departments.keyword": "會計" } }, { "term": { "courses.name.keyword": "會計學" } } ] } } }
本次的查詢結果為:Mario、Vincent、Winnie。其中 Mario 符合 2 個條件,而其他人只符合 1 個,因此 Mario 分數更高。
雖然 should 預設是只要符合 1 個條件就好,但我們可另外使用 minimum_should_match 符號,規定最少要符合的條件數量,藉此讓查詢結果的相符程度有一定水準。
以下筆者對可選擇的參數做個整理。
種類 | 用途 | 參數範例 | 範例說明 |
---|---|---|---|
正整數 | 至少要符合的數量 | 2 | - |
負整數 | 最多容許多少條件不符合 | -3 | - |
正百分比 | 至少要符合多少比例的條件(小數點捨去) | "75%" | 假設條件總數為 9,則至少應符合 9×75%=6 個條件。 |
負百分比 | 最多容許多少比例的條件不符合(小數點捨去) | "-25%" | 假設條件總數為 9,則容許 9×25%=2 個條件不符。 |
組合 | 當條件總數大於指定數量時,才可部份符合 | "2<-25%" | 條件總數小於等於 2 時,需全部符合; 大於 2 時,可容許 25% 的條件不符。 |
多組合 | 根據條件總數所落在的區間,決定符合的程度 | "2<-1 6<75%" | 條件總數小於等於 2 時,需全部符合; 大於 2 但小於等於 6 時,可容許 1 個條件不符; 大於 6 時,需符合 75% 的條件。 |
以下範例是查詢「就讀會計系」、「就讀資訊管理系」、「修習會計學」或「修習程式設計」,至少符合 2 項的學生。
{ "query": { "bool": { "should": [ { "term": { "departments.keyword": "會計" } }, { "term": { "departments.keyword": "資訊管理" } }, { "term": { "courses.name.keyword": "會計學" } }, { "term": { "courses.name.keyword": "程式設計" } } ], "minimum_should_match": 2 } } }
此例的查詢結果為:Vincent、Mario。
六、全文檢索
(一)字串的倒排索引(inverted index)
當 document 具有字串欄位,ES 理所當然會在該 document 儲存該字串。但為了做全文檢索,ES 還會另外將該字串拆解成多個單詞(term),儲存在一個叫做「倒排索引」的地方。
假設有以下兩個 document:
{ "_id": "S123", "_source": { "name": "Mario Chang", "grade": 2 } } { "_id": "S456", "_source": { "name": "Luegi Chang", "grade": 1 } }
則以下是 name 欄位的倒排索引之示意內容:
term | _id |
---|---|
chang | "S123", "S456" |
luegi | "S456" |
mario | "S123" |
若我們想找出某欄位包含指定文字的 document,例如 name 欄位包含「mario」,ES 便可從倒排索引快速地知道哪些 document 符合條件,提升查詢效率。
(二)match
使用此符號,並傳入欄位與關鍵字,即可查詢欄位值包含關鍵字的 document。關鍵字之間用空格隔開,ES 會拆解成不同的單詞,並利用倒排索引進行查詢。
在上一篇文章中,筆者示範過針對一個欄位做關鍵字查詢。以下的範例稍微有變化,是查詢 name 或 introduction 欄位包含「programming」、「accounting」或「vincent」的 document。
{ "query": { "bool": { "should": [ { "match": { "name": "programming accounting vincent" } }, { "match": { "introduction": "programming accounting vincent" } } ] } } }
此例的查詢結果為:Vincent、Mario、Dora。
讀者在實務開發上,可先決定哪些欄位能夠被搜尋,再配合輸入的關鍵字,組合出查詢條件。
上一篇:【ElasticSearch 8】透過 REST API 進行 CRUD 操作
留言
張貼留言