Elasticsearch中URI Search和RequestBody Search分析

雙子孤狼發表於2021-06-15

前言

Elasticsearch 作為一款分散式搜尋工具,其搜尋功能非常強大,本文主要介紹下 Elasticsearch 中高階搜尋的使用。

Search APIs

搜尋 APIs 按照查詢方式主要可以分為兩大類,那就是:URI earchRequest Body Search。在查詢語句中,一般使用 _search 來表示當前是一個搜尋語句。

  • /_search:查詢叢集上的所有索引資料,一般不建議這麼使用。
  • index1,index2/_search:查詢指定一個或者多個索引的資料。
  • index*/_search:利用萬用字元查詢當前叢集上的索引資料。

顧名思義,URI Search 指的是直接使用 URL 進行查詢,引數直接拼在 URL 上。

URI Search 中,主要有以下引數:

  • q:指定查詢語句,使用 Query String Syntax 語法(KV 鍵值對)。
  • df:預設欄位,如果不指定,則會對所有欄位進行查詢。
  • sort:排序。
  • explain:對每一個結果,都會返回 _explanation 結果,包含了當前資料分值的計算方式和結果。
  • from/size:用於分頁,from 表示從哪條資料開始,size 表示當前需要查詢多少條資料。
  • _source:false 表示不返回源資料(_source 欄位),預設為 true
  • _source_includes:表示 _source 內只返回當前指定的欄位。
  • _source_excludes:表示 _source 內不返回當前指定的欄位,當前引數優先順序大於 _source_includes
  • timeout:指定超時時間,預設沒有超時時間。

bulk 插入演示資料

為了便於後面演示,我們通過 bulk 操作來批量插入一些比較直觀的資料:

POST index_001/_doc/_bulk
{"index":{}}
{"id":"1","name":"lonely wolf","result":true}
{"index":{}}
{"id":"2","name":"lonely hello wolf","result":true}
{"index":{}}
{"id":"3","name":"lonely hello word wolf","result":true}
{"index":{}}
{"id":"4","name":"lonely","result":false}
{"index":{}}
{"id":"5","name":"wolf","result":false}

或者執行以下語句:

POST /_bulk
{"index":{"_index":"index_001"}}
{"id":"1","name":"lonely wolf","result":true}
{"index":{"_index":"index_001"}}
{"id":"2","name":"lonely hello wolf","result":true}
{"index":{"_index":"index_001"}}
{"id":"3","name":"lonely hello word wolf","result":true}
{"index":{"_index":"index_001"}}
{"id":"4","name":"lonely","result":false}
{"index":{"_index":"index_001"}}
{"id":"5","name":"wolf","result":false}

基礎查詢

  • 指定欄位查詢:
# 指定name欄位查詢
GET index_001/_search?q=name:wolf
  • 使用預設欄位查詢:
GET index_001/_search?q=wolf&df=name

上面這兩句話查詢效果是一樣的,均可以查詢出 4 條資料,執行 profile 分析一下,確實只匹配了 name 一個欄位:

  • 泛查詢(不指定任何欄位)
GET index_001/_search?q=wolf

這條語句也是返回 4 條資料,但是這條語句和上面不同的是其沒有通過 q 指定篩選欄位,也沒有通過 df 指定預設欄位,所以會查詢所有欄位:

執行 profile 查詢可以發現,這條語句會查詢所有欄位,而且有些型別不匹配則會報錯,所以這種查詢效率是很低的,生產環境中應該儘量避免。

  • 指定 source 查詢

再看下面的一個 source 查詢例子:

GET index_001/_search?q=name:wolf&_source_includes=name,result&_source_excludes=result&timeout=1ms

這個例子中因為同時指定了 _source_excludes_source_includes,但是因為 _source_excludes 優先順序比較高,故而最終只會返回 name 一個欄位:

Term 查詢

GET /index_001/_search?q=name:lonely wolf

這個查詢會返回所有資料,因為預設情況下這個查詢會使用 Term 查詢,會查詢 namelonely 或者 wolf 的欄位,而如果想把 lonely wolf 作為一個整體,則可以使用 Phrase 查詢。

布林操作

在上面 Term 查詢中,我們發現當兩個 Term 查詢在一起,預設使用的是 or 的操作,而如果要使用 and,則可以使用布林操作。

布林操作支援以下符號(必須大寫):ANDORNOT&&||!。如下例子則只會查詢出 3 條資料。

GET /index_001/_search?q=name:lonely AND wolf
# 建議使用 () 來明確表示分組
GET /index_001/_search?q=name:(lonely AND wolf)

同時,布林操作還支援一些高階查詢,如:+ 表示 must- 表示 must not

GET /index_001/_search?q=name:(+lonely -wolf)

這句話就只能查詢出 id4 的這條資料,name 含有 lonely 關鍵字且不含 wolf 關鍵字。

Phrase 查詢

假如我們想把一句話當成一個整體來查詢,則可以使用 Phrase 查詢:

GET /index_001/_search?q=name:"lonely wolf"

這個時候就只會查詢出一條資料。

萬用字元和正則查詢

萬用字元查詢中,? 表示 1 個字元,* 表示 0 或者多個字元。

# 沒有符合條件的資料GET /index_001/_search?q=name:lone?# 1-4條資料都符合條件GET /index_001/_search?q=name:lone*

萬用字元查詢是一種 like 查詢,效率相對會比較低,所以一般也不建議使用。

此外,還可以通過正規表示式查詢:

# 查詢出 id 為 2 或 3 的資料GET /index_001/_search?q=id:(2|3)

近似查詢

有些時候我們查百度的時候發現輸錯了字也能被查出來,這就是利用了近似查詢,如下所示:

# 輸錯一個字元,查詢不出結果GET /index_001/_search?q=name:loneyy# 允許一個字元錯誤,查詢出 4 條資料GET /index_001/_search?q=name:loneyy~1# 允許兩個字元錯誤,查詢出 4 條資料GET /index_001/_search?q=name:loniyy~2

另外,針對上面的 Phrase 查詢中,因為是把兩個單詞作為一個整體,那麼也可以通過近似查詢來設定允許中間有其他字元,如下:

GET /index_001/_search?q=name:"lonely wolf" ~1

這裡表示允許 lonelywolf 之間插入一個其他字元,所以可以查詢出 2 條資料:

Request Body Query

Request Body 查詢是 Elasticsearch 中基於 json 格式提供的一種 DSL 語言(Query Domain Specific Language)。一般情況下,相比較於 URI Request,雖然說 URI Query 也可以實現一定複雜程度的查詢,但是一般情況下我們還是更推薦使用 Request Body 查詢來實現更加複雜的一些組合查詢。

通過 URI Search 中能實現的搜尋方式,都可以通過 Request Body 來實現,下面就讓我們一起來看看如何利用 Request Body 來進行搜尋查詢。

分頁查詢

Request Body 分頁查詢也是通過 fromsize 來實現:

POST index_001/_search
{
  "from": 0,
  "size": 2
}

排序

排序通過 sort 來實現。注意,預設 text 型別不能排序,如果需要排序則使用 field.keyword 來查詢:

POST index_001/_search
{
  "sort": [
    {
      "name.keyword": {
        "order": "desc"
      }
    }
  ]
}

source 查詢

同樣的,Request Body Query 也可以使用 source 來指定返回欄位,而且也支援萬用字元的方式來指定:

POST index_001/_search
{
  "_source": ["id","*e*"]
}

這時候查詢會返回 idnameresult 三個欄位。

使用 match 查詢

如果需要指定條件查詢,在 Request Body 查詢中則通過 match 來實現:

POST index_001/_search
{
  "query": {
    "match": {
      "name": "lonely wolf"
    }
  }
}

同樣的,這個查詢預設的也是“或”的關係,所以這裡也是能將 5 條資料全部查詢出來,如果要使用 AND,則可以通過以下方式實現:

POST index_001/_search
{
  "query": {
    "match": {
      "name": {
        "query": "lonely wolf",
        "operator": "and"
      }
    }
  }
}

這樣子就只能查詢出前三條資料。

如果要查詢全部資料,則可以利用 match_all 進行查詢:

POST index_001/_search
{
  "query": {
    "match_all": {}
  }
}

match phrase 查詢

上面的查詢中,即使指定了 and,也能查詢出 3 條資料,如果需要精確查詢,那麼可以使用 match phrase 查詢:

POST index_001/_search
{
  "query": {
    "match_phrase": {
      "name": {
        "query": "lonely wolf",
        "slop": 0
      }
    }
  }
}

這裡如果不加 slot,則預設為 0,也就是不允許 lonely wolf 兩個單詞之間存在其他字元,這樣子就只能查詢出第一條資料。

指令碼 script field 查詢

有時候我們需要利用指令碼來對搜尋結果進行進一步處理,這時候就可以指令碼來實現一些功能,不過需要注意的是,text 欄位預設也是不能使用指令碼,如果需要是用同樣需要加上 .keyword

POST index_001/_search
{
  "script_fields": {
    "id-name": {
      "script": {
        "lang": "painless",
        "source": "doc['id.keyword'].value + '-' + doc['name.keyword']"
      }
    }
  },
  "query": {
    "match_all": {}
  }
}

返回結果如下,新的結果會作為欄位 id-name 返回:

總結

本文主要介紹了 Elasticsearch 中的兩種查詢方式,並分別介紹了一些非常常用的查詢方式,下一篇我們會專門介紹下本文提到的 Term 查詢和 Phrase 查詢,並徹底理解這兩種查詢方式的區別。

請關注我,和孤狼一起學習進步

相關文章