1. 程式人生 > >elasticsearch 請求體查詢

elasticsearch 請求體查詢

請求體查詢

簡易 查詢 —query-string search— 對於用命令列進行即席查詢(ad-hoc)是非常有用的。 然而,為了充分利用查詢的強大功能,你應該使用 請求體 search API, 之所以稱之為請求體查詢(Full-Body Search),因為大部分引數是通過 Http 請求體而非查詢字串來傳遞的。

請求體查詢 —下文簡稱 查詢—不僅可以處理自身的查詢請求,還允許你對結果進行片段強調(高亮)、對所有或部分結果進行聚合分析,同時還可以給出 你是不是想找 的建議,這些建議可以引導使用者快速找到他想要的結果。

空查詢

讓我們以 最簡單的 search API 的形式開啟我們的旅程,空查詢將返回所有索引庫(indices)中的所有文件:

GET /_search
{} 

這是一個空的請求體。

只用一個查詢字串,你就可以在一個、多個或者 _all 索引庫(indices)和一個、多個或者所有types中查詢:

GET /index_2014*/type1,type2/_search
{}

同時你可以使用 from 和 size 引數來分頁:

GET /_search
{
  "from": 30,
  "size": 10
}

一個帶請求體的 GET 請求?

某些特定語言(特別是 JavaScript)的 HTTP 庫是不允許 GET 請求帶有請求體的。 事實上,一些使用者對於 GET

 請求可以帶請求體感到非常的吃驚。

而事實是這個RFC文件 RFC 7231— 一個專門負責處理 HTTP 語義和內容的文件 — 並沒有規定一個帶有請求體的 GET 請求應該如何處理!結果是,一些 HTTP 伺服器允許這樣子,而有一些 — 特別是一些用於快取和代理的伺服器 — 則不允許。

對於一個查詢請求,Elasticsearch 的工程師偏向於使用 GET 方式,因為他們覺得它比 POST 能更好的描述資訊檢索(retrieving information)的行為。然而,因為帶請求體的 GET 請求並不被廣泛支援,所以 search API 同時支援 POST 請求:

POST /_search
{
  "from": 30,
  "size": 10
}

類似的規則可以應用於任何需要帶請求體的 GET API。

我們將在聚合 聚合 章節深入介紹聚合(aggregations),而現在,我們將聚焦在查詢。

相對於使用晦澀難懂的查詢字串的方式,一個帶請求體的查詢允許我們使用 查詢領域特定語言(query domain-specific language) 或者 Query DSL 來寫查詢語句。

查詢表示式編輯

查詢表示式(Query DSL)是一種非常靈活又富有表現力的 查詢語言。 Elasticsearch 使用它可以以簡單的 JSON 介面來展現 Lucene 功能的絕大部分。在你的應用中,你應該用它來編寫你的查詢語句。它可以使你的查詢語句更靈活、更精確、易讀和易除錯。

要使用這種查詢表示式,只需將查詢語句傳遞給 query 引數:

GET /_search
{
    "query": YOUR_QUERY_HERE
}

空查詢(empty search) —{}— 在功能上等價於使用 match_all 查詢, 正如其名字一樣,匹配所有文件:

GET /_search
{
    "query": {
        "match_all": {}
    }
}

查詢語句的結構編輯

一個查詢語句 的典型結構:

{
    QUERY_NAME: {
        ARGUMENT: VALUE,
        ARGUMENT: VALUE,...
    }
}

如果是針對某個欄位,那麼它的結構如下:

{
    QUERY_NAME: {
        FIELD_NAME: {
            ARGUMENT: VALUE,
            ARGUMENT: VALUE,...
        }
    }
}

舉個例子,你可以使用 match 查詢語句 來查詢 tweet 欄位中包含 elasticsearch 的 tweet:

{
    "match": {
        "tweet": "elasticsearch"
    }
}

完整的查詢請求如下:

GET /_search
{
    "query": {
        "match": {
            "tweet": "elasticsearch"
        }
    }
}

合併查詢語句

查詢語句(Query clauses) 就像一些簡單的組合塊 ,這些組合塊可以彼此之間合併組成更復雜的查詢。這些語句可以是如下形式:

  • 葉子語句(Leaf clauses) (就像 match 語句) 被用於將查詢字串和一個欄位(或者多個欄位)對比。
  • 複合(Compound) 語句 主要用於 合併其它查詢語句。 比如,一個 bool 語句 允許在你需要的時候組合其它語句,無論是 must 匹配、 must_not 匹配還是 should 匹配,同時它可以包含不評分的過濾器(filters):
{
    "bool": {
        "must":     { "match": { "tweet": "elasticsearch" }},
        "must_not": { "match": { "name":  "mary" }},
        "should":   { "match": { "tweet": "full text" }},
        "filter":   { "range": { "age" : { "gt" : 30 }} }
    }
}

一條複合語句可以合併 任何 其它查詢語句,包括複合語句,瞭解這一點是很重要的。這就意味著,複合語句之間可以互相巢狀,可以表達非常複雜的邏輯。

例如,以下查詢是為了找出信件正文包含 business opportunity 的星標郵件,或者在收件箱正文包含business opportunity 的非垃圾郵件:

{
    "bool": {
        "must": { "match":   { "email": "business opportunity" }},
        "should": [
            { "match":       { "starred": true }},
            { "bool": {
                "must":      { "match": { "folder": "inbox" }},
                "must_not":  { "match": { "spam": true }}
            }}
        ],
        "minimum_should_match": 1
    }
}

到目前為止,你不必太在意這個例子的細節,我們會在後面詳細解釋。最重要的是你要理解到,一條複合語句可以將多條語句 — 葉子語句和其它複合語句 — 合併成一個單一的查詢語句。

查詢與過濾編輯

Elasticsearch 使用的查詢語言(DSL) 擁有一套查詢元件,這些元件可以以無限組合的方式進行搭配。這套元件可以在以下兩種情況下使用:過濾情況(filtering context)和查詢情況(query context)。

當使用於 過濾情況 時,查詢被設定成一個“不評分”或者“過濾”查詢。即,這個查詢只是簡單的問一個問題:“這篇文件是否匹配?”。回答也是非常的簡單,yes 或者 no ,二者必居其一。

  • created 時間是否在 2013 與 2014 這個區間?
  • status 欄位是否包含 published 這個單詞?
  • lat_lon 欄位表示的位置是否在指定點的 10km 範圍內?

當使用於 查詢情況 時,查詢就變成了一個“評分”的查詢。和不評分的查詢類似,也要去判斷這個文件是否匹配,同時它還需要判斷這個文件匹配的有 _多好_(匹配程度如何)。 此查詢的典型用法是用於查詢以下文件:

  • 查詢與 full text search 這個詞語最佳匹配的文件
  • 包含 run 這個詞,也能匹配 runs 、 running 、 jog 或者 sprint
  • 包含 quick 、 brown 和 fox 這幾個詞 — 詞之間離的越近,文件相關性越高
  • 標有 lucene 、 search 或者 java 標籤 — 標籤越多,相關性越高

一個評分查詢計算每一個文件與此查詢的 _相關程度_,同時將這個相關程度分配給表示相關性的欄位 `_score`,並且按照相關性對匹配到的文件進行排序。這種相關性的概念是非常適合全文搜尋的情況,因為全文搜尋幾乎沒有完全 “正確” 的答案。

注意

自 Elasticsearch 問世以來,查詢與過濾(queries and filters)就獨自成為 Elasticsearch 的元件。但從 Elasticsearch 2.0 開始,過濾(filters)已經從技術上被排除了,同時所有的查詢(queries)擁有變成不評分查詢的能力。

然而,為了明確和簡單,我們用 "filter" 這個詞表示不評分、只過濾情況下的查詢。你可以把 "filter" 、 "filtering query" 和 "non-scoring query" 這幾個詞視為相同的。

相似的,如果單獨地不加任何修飾詞地使用 "query" 這個詞,我們指的是 "scoring query" 。

效能差異

過濾查詢(Filtering queries)只是簡單的檢查包含或者排除,這就使得計算起來非常快。考慮到至少有一個過濾查詢(filtering query)的結果是 “稀少的”(很少匹配的文件),並且經常使用不評分查詢(non-scoring queries),結果會被快取到記憶體中以便快速讀取,所以有各種各樣的手段來優化查詢結果。

相反,評分查詢(scoring queries)不僅僅要找出 匹配的文件,還要計算每個匹配文件的相關性,計算相關性使得它們比不評分查詢費力的多。同時,查詢結果並不快取。

多虧倒排索引(inverted index),一個簡單的評分查詢在匹配少量文件時可能與一個涵蓋百萬文件的filter表現的一樣好,甚至會更好。但是在一般情況下,一個filter 會比一個評分的query效能更優異,並且每次都表現的很穩定。

過濾(filtering)的目標是減少那些需要通過評分查詢(scoring queries)進行檢查的文件。

如何選擇查詢與過濾

通常的規則是,使用 查詢(query)語句來進行 全文 搜尋或者其它任何需要影響 相關性得分 的搜尋。除此以外的情況都使用過濾(filters)。

最重要的查詢

雖然 Elasticsearch 自帶了很多的查詢,但經常用到的也就那麼幾個。我們將在 深入搜尋 章節詳細討論那些查詢的細節,接下來我們對最重要的幾個查詢進行簡單介紹。

match_all 查詢

match_all 查詢簡單的 匹配所有文件。在沒有指定查詢方式時,它是預設的查詢:

{ "match_all": {}}

它經常與 filter 結合使用--例如,檢索收件箱裡的所有郵件。所有郵件被認為具有相同的相關性,所以都將獲得分值為 1 的中性 `_score`。

match 查詢

無論你在任何欄位上進行的是全文搜尋還是精確查詢,match 查詢是你可用的標準查詢。

如果你在一個全文欄位上使用 match 查詢,在執行查詢前,它將用正確的分析器去分析查詢字串:

{ "match": { "tweet": "About Search" }}

如果在一個精確值的欄位上使用它, 例如數字、日期、布林或者一個 not_analyzed 字串欄位,那麼它將會精確匹配給定的值:

{ "match": { "age":    26           }}
{ "match": { "date":   "2014-09-01" }}
{ "match": { "public": true         }}
{ "match": { "tag":    "full_text"  }}

對於精確值的查詢,你可能需要使用 filter 語句來取代 query,因為 filter 將會被快取。接下來,我們將看到一些關於 filter 的例子。

不像我們在 輕量 搜尋 章節介紹的字串查詢(query-string search), match 查詢不使用類似 +user_id:2 +tweet:search 的查詢語法。它只是去查詢給定的單詞。這就意味著將查詢欄位暴露給你的使用者是安全的;你需要控制那些允許被查詢欄位,不易於丟擲語法異常。

multi_match 查詢

multi_match 查詢可以在多個欄位上執行相同的 match 查詢:

{
    "multi_match": {
        "query":    "full text search",
        "fields":   [ "title", "body" ]
    }
}

range 查詢

range 查詢找出那些落在指定區間內的數字或者時間:

{
    "range": {
        "age": {
            "gte":  20,
            "lt":   30
        }
    }
}

被允許的操作符如下:

gt

大於

gte

大於等於

lt

小於

lte

小於等於

term 查詢

term 查詢被用於精確值 匹配,這些精確值可能是數字、時間、布林或者那些 not_analyzed 的字串:

{ "term": { "age":    26           }}
{ "term": { "date":   "2014-09-01" }}
{ "term": { "public": true         }}
{ "term": { "tag":    "full_text"  }}

term 查詢對於輸入的文字不 分析 ,所以它將給定的值進行精確查詢。

terms 查詢

terms 查詢和 term 查詢一樣,但它允許你指定多值進行匹配。如果這個欄位包含了指定值中的任何一個值,那麼這個文件滿足條件:

{ "terms": { "tag": [ "search", "full_text", "nosql" ] }}

和 term 查詢一樣,terms 查詢對於輸入的文字不分析。它查詢那些精確匹配的值(包括在大小寫、重音、空格等方面的差異)。

exists 查詢和 missing 查詢

exists 查詢和 missing 查詢被用於查詢那些指定欄位中有值 (exists) 或無值 (missing) 的文件。這與SQL中的 IS_NULL (missing) 和 NOT IS_NULL (exists) 在本質上具有共性:

{
    "exists":   {
        "field":    "title"
    }
}

這些查詢經常用於某個欄位有值的情況和某個欄位缺值的情況。

組合多查詢

現實的查詢需求從來都沒有那麼簡單;它們需要在多個欄位上查詢多種多樣的文字,並且根據一系列的標準來過濾。為了構建類似的高階查詢,你需要一種能夠將多查詢組合成單一查詢的查詢方法。

你可以用 bool 查詢來實現你的需求。這種查詢將多查詢組合在一起,成為使用者自己想要的布林查詢。它接收以下引數:

must

文件 必須 匹配這些條件才能被包含進來。

must_not

文件 必須不 匹配這些條件才能被包含進來。

should

如果滿足這些語句中的任意語句,將增加 _score ,否則,無任何影響。它們主要用於修正每個文件的相關性得分。

filter

必須 匹配,但它以不評分、過濾模式來進行。這些語句對評分沒有貢獻,只是根據過濾標準來排除或包含文件。

由於這是我們看到的第一個包含多個查詢的查詢,所以有必要討論一下相關性得分是如何組合的。每一個子查詢都獨自地計算文件的相關性得分。一旦他們的得分被計算出來, bool 查詢就將這些得分進行合併並且返回一個代表整個布林操作的得分。

下面的查詢用於查詢 title 欄位匹配 how to make millions 並且不被標識為 spam 的文件。那些被標識為 starred 或在2014之後的文件,將比另外那些文件擁有更高的排名。如果 _兩者_ 都滿足,那麼它排名將更高:

{
    "bool": {
        "must":     { "match": { "title": "how to make millions" }},
        "must_not": { "match": { "tag":   "spam" }},
        "should": [
            { "match": { "tag": "starred" }},
            { "range": { "date": { "gte": "2014-01-01" }}}
        ]
    }
}

提示

如果沒有 must 語句,那麼至少需要能夠匹配其中的一條 should 語句。但,如果存在至少一條 must 語句,則對 should 語句的匹配沒有要求。

增加帶過濾器(filtering)的查詢

如果我們不想因為文件的時間而影響得分,可以用 filter 語句來重寫前面的例子:

{
    "bool": {
        "must":     { "match": { "title": "how to make millions" }},
        "must_not": { "match": { "tag":   "spam" }},
        "should": [
            { "match": { "tag": "starred" }}
        ],
        "filter": {
          "range": { "date": { "gte": "2014-01-01" }} 
        }
    }
}

range 查詢已經從 should 語句中移到 filter 語句

通過將 range 查詢移到 filter 語句中,我們將它轉成不評分的查詢,將不再影響文件的相關性排名。由於它現在是一個不評分的查詢,可以使用各種對 filter 查詢有效的優化手段來提升效能。

所有查詢都可以借鑑這種方式。將查詢移到 bool 查詢的 filter 語句中,這樣它就自動的轉成一個不評分的 filter 了。

如果你需要通過多個不同的標準來過濾你的文件,bool 查詢本身也可以被用做不評分的查詢。簡單地將它放置到 filter 語句中並在內部構建布林邏輯:

{
    "bool": {
        "must":     { "match": { "title": "how to make millions" }},
        "must_not": { "match": { "tag":   "spam" }},
        "should": [
            { "match": { "tag": "starred" }}
        ],
        "filter": {
          "bool": { 
              "must": [
                  { "range": { "date": { "gte": "2014-01-01" }}},
                  { "range": { "price": { "lte": 29.99 }}}
              ],
              "must_not": [
                  { "term": { "category": "ebooks" }}
              ]
          }
        }
    }
}

將 bool 查詢包裹在 filter 語句中,我們可以在過濾標準中增加布爾邏輯

通過混合布林查詢,我們可以在我們的查詢請求中靈活地編寫 scoring 和 filtering 查詢邏輯。

constant_score 查詢

儘管沒有 bool 查詢使用這麼頻繁,constant_score 查詢也是你工具箱裡有用的查詢工具。它將一個不變的常量評分應用於所有匹配的文件。它被經常用於你只需要執行一個 filter 而沒有其它查詢(例如,評分查詢)的情況下。

可以使用它來取代只有 filter 語句的 bool 查詢。在效能上是完全相同的,但對於提高查詢簡潔性和清晰度有很大幫助。

{
    "constant_score":   {
        "filter": {
            "term": { "category": "ebooks" } 
        }
    }
}

term 查詢被放置在 constant_score 中,轉成不評分的 filter。這種方式可以用來取代只有 filter 語句的 bool 查詢。

驗證查詢

查詢可以變得非常的複雜,尤其 和不同的分析器與不同的欄位對映結合時,理解起來就有點困難了。不過 validate-query API 可以用來驗證查詢是否合法。

GET /gb/tweet/_validate/query
{
   "query": {
      "tweet" : {
         "match" : "really powerful"
      }
   }
}

以上 validate 請求的應答告訴我們這個查詢是不合法的:

{
  "valid" :         false,
  "_shards" : {
    "total" :       1,
    "successful" :  1,
    "failed" :      0
  }
}

理解錯誤資訊

為了找出 查詢不合法的原因,可以將 explain 引數 加到查詢字串中:

GET /gb/tweet/_validate/query?explain 
{
   "query": {
      "tweet" : {
         "match" : "really powerful"
      }
   }
}

explain 引數可以提供更多關於查詢不合法的資訊。

很明顯,我們將查詢型別(match)與欄位名稱 (tweet)搞混了:

{
  "valid" :     false,
  "_shards" :   { ... },
  "explanations" : [ {
    "index" :   "gb",
    "valid" :   false,
    "error" :   "org.elasticsearch.index.query.QueryParsingException:
                 [gb] No query registered for [tweet]"
  } ]
}

理解查詢語句

對於合法查詢,使用 explain 引數將返回可讀的描述,這對準確理解 Elasticsearch 是如何解析你的 query 是非常有用的:

GET /_validate/query?explain
{
   "query": {
      "match" : {
         "tweet" : "really powerful"
      }
   }
}

我們查詢的每一個 index 都會返回對應的 explanation ,因為每一個 index 都有自己的對映和分析器:

{
  "valid" :         true,
  "_shards" :       { ... },
  "explanations" : [ {
    "index" :       "us",
    "valid" :       true,
    "explanation" : "tweet:really tweet:powerful"
  }, {
    "index" :       "gb",
    "valid" :       true,
    "explanation" : "tweet:realli tweet:power"
  } ]
}

從 explanation 中可以看出,匹配 really powerful 的 match 查詢被重寫為兩個針對 tweet 欄位的 single-term 查詢,一個single-term查詢對應查詢字串分出來的一個term。

當然,對於索引 us ,這兩個 term 分別是 really 和 powerful ,而對於索引 gb ,term 則分別是 realli 和 power 。之所以出現這個情況,是由於我們將索引 gb 中 tweet 欄位的分析器修改為 english分析器。