1. 程式人生 > >ElasticSearch(七)--請求體查詢

ElasticSearch(七)--請求體查詢

簡單查詢lite search (字串查詢)是一種有效的命令列ad hoc 查詢,但是想要善用搜索,必須使用請求體查詢request  body search API.之所以這麼稱呼,是因為大多數的引數以JSON格式所容納,而不是查詢字串.

請求體查詢不但可以處理查詢,而且還可以高亮返回結果中的片段.

1.空查詢

GET _search
{}
同字串查詢一樣,你可以查詢一個,或多個索引及型別
GET /index_2014*/type1,type2/_search
{}
也可以使用from, size引數進行分頁pagination:
GET /website/_search
{
  "from":1,
  "size":3
}
注意,from和size數值均可以與實際不符,返回的不過是個空的陣列,並不會出錯.

那麼,這中請求體查詢,使用的是攜帶內容的GET請求方式?

任何一種語言(特別是js)的HTTP庫都不允許GET請求中攜帶互動資料,使用者會很驚訝GET請求會允許攜帶互動資料.

但是真實情況是,一份關於HTTP協議的標準文件RFC中並未定義一個GET請求攜帶請求體會發生什麼!所以,

ES的作者們傾向於使用GET提交查詢請求,因為它們覺得這個詞相比於POST能更好的描述這種行為.然而,因為攜帶請求體的GET請求並不被廣泛支援,所以search API同樣支援POST請求.

POST /website/_search
{
  "from":1,
  "size":3
}
相比於神祕的字串查詢方式,請求體查詢允許我們通過使用query DSL(Domian Specific Language)來寫入引數.

2. Query DSL

query DSL是一種靈活的,表現力強的查詢語言,ES通過一個簡單的JSON介面使用DSL來表現lucene絕大多數的能力.

應當在你的產品中使用這種方式進行查詢,它是你的查詢更加靈活,精準,易於閱讀,且易於debug.

為了使用query DSL,傳遞一個查詢給query引數:

GET /_search
{
    "query": YOUR_QUERY_HERE
}
例如,空查詢,其實就相當於使用了一個match_all查詢子句
POST /website/_search
{
  "query": {
    "match_all": {}
  }
}
match_all是一個查詢子句,正如其名字一樣,查詢所有文件.

查詢子句的結構

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

{
    QUERY_NAME: {
        ARGUMENT: VALUE,
        ARGUMENT: VALUE,...
    }
}
如果它是與特定欄位有關的:
{
    QUERY_NAME: {
        FIELD_NAME: {
            ARGUMENT: VALUE,
            ARGUMENT: VALUE,...
        }
    }
}
例如,你可以使用match查詢子句,查詢在欄位tweet中有elasticsearch的:

查詢自己的格式:

{
    "match": {
        "tweet": "elasticsearch"
    }
}
請求:
GET /_search
{
    "query": {
        "match": {
            "tweet": "elasticsearch"
        }
    }
}
稱之為查詢子句query clause,代表其都是放到query語句下的.

合併多子句

查詢子句像一個簡單的積木塊一樣,可以和其他的子句組合,構成複雜的查詢.

子句可以分為:

葉子子句leaf clause,被用作字串與欄位的比較.

複合子句compound clause,備用做合併其他的子句.例如一個bool子句,允許合併其他的子句:must 匹配, must_not,should.它還允許包含non-scoring, filters作為結構化搜尋:

{
    "bool": {
        "must":     { "match": { "tweet": "elasticsearch" }},
        "must_not": { "match": { "name":  "mary" }},
        "should":   { "match": { "tweet": "full text" }},
        "filter":   { "range": { "age" : { "gt" : 30 }} }
    }
}
非常重要的指出,一個複合查詢子句可以包含其他任何查詢子句,或者別的複合子句.這意味著複合子句可以被相互巢狀,允許複雜的邏輯表達.

例如,下邊的例子,查詢郵件,滿足:包含business opportunity,同時被標星的郵件;或者同時在folder,indbox,但是沒有被標記為spam的郵件.

{
    "bool": {
        "must": { "match":   { "email": "business opportunity" }},
        "should": [
            { "match":       { "starred": true }},
            { "bool": {
                "must":      { "match": { "folder": "inbox" }},
                "must_not":  { "match": { "spam": true }}
            }}
        ],
        "minimum_should_match": 1
    }
}
不要擔心這些例子的細節,我們後續會解釋.重點是明白複合語句可以組合多個子句,包括葉子子句或這複合子句到一個簡單的查詢中.

3. 查詢和過濾

ES使用DSL將查詢子句放到一個簡單的集裡,這種簡單集合可以被用作兩種環境:過濾上下文Filtering context和查詢上下文query context

當被用到過濾環境中,查詢query被稱作non-scoring or filtering query,這樣的查詢會這樣問問題,'這個文件是否匹配?'答案是二選一,是或否.

例如;

created 的日期範圍是否介於2013-2014?

status欄位是否包含詞published?

las_lon欄位的地理位置是否與目標相距不超過10km?

當被使用在查詢環境中,查詢成為scoring query,它這樣問"這個文件的匹配程度如何?"

查詢典型的使用:

查詢與full text search 最佳匹配的文件

包含單詞run,也可能是running,runs,jog, sprint

同時包含quick , brown, fox,它們離得越近,文件的匹配相關性越高.

標記著lucene, search, java,標識詞越多,文件的相關性越高.

一個scoring query,計算文件與查詢的相關性,並賦值給欄位_score,用作依據相關性排序的標準.這種概念同樣適用於全文搜尋.

注意:

歷史上,在ES中,查詢和過濾是分開做的,在ES2.0開始,過濾被技術性的消除,同時,查詢開始支援non-scoring式的查詢.

然而為了區分和簡便,我們仍用"過濾"一詞來描述non-socring的查詢.你可以把filter , filter query , non-scoring query當作一樣的.

同樣的,如果查詢一詞被單獨的使用,我們就認為是scoring的查詢.

效能差異

過濾查詢是一個簡單的包含與不包含的檢查,這是它們計算非常快速.

有各種優化,對於至少有一條過濾查詢是很少有文件匹配,同時被頻繁的用作non-scoring的查詢,可以被放到記憶體中,更快速獲取.

相比之下,scoring查詢不但需要查詢匹配的文件,並且還要計算相關性,這使得其繁重於non-scoring查詢,同時查詢的結果是不能夠被快取的.

幸虧有倒排索引,使得一個簡單的scoring查詢,僅匹配一些文件,效能可以與過濾相比,甚至優於過濾,在跨越數以百萬計的檔案中.

但是一般情況下,過濾是優於查詢的.

過濾的目的是減少文件的數量,這些文件必須被scoring query檢查.

什麼時候使用?

一般原則,在全文查詢,或者需要相關性評分時,使用查詢scoring query,其他時候都是使用過濾non-scoring query.

4. 重要的查詢語句

ES有很多查詢語句,只有少部分經常被使用,我們會在後續的深入查詢一章詳細學習,現在快速介紹一些重要的語句.

match_all

match_all查詢簡單的匹配所有文件

{ "match_all": {}}
這個查詢經常和過濾器一起使用.

match

match查詢是一個標準的查詢,無論是查詢一個全文文字還是精確值.

如果使用match對全文文字欄位進行查詢,執行查詢之前,先使用針對該欄位正確的分析器對查詢字串進行分析.

{ "match": { "tweet": "About Search" }}
如果使用該語句在一個欄位上匹配精確值,數值,日期,布林,以及not_analyzed字串,
{ "match": { "age":    26           }}
{ "match": { "date":   "2014-09-01" }}
{ "match": { "public": true         }}
{ "match": { "tag":    "full_text"  }}
對於確切值搜尋,你可能想使用過濾語句,而不是查詢,我們很快看到過濾的例子.

相比於字串查詢,match語句查詢的語法更加安全.

multi_match

multi_match查詢允許在多個欄位上進行match一樣的查詢

{
    "multi_match": {
        "query":    "full text search",
        "fields":   [ "title", "body" ]
    }
}
range
range查詢允許查詢數值或日期在一個指定的區間裡,該子句接受如下引數:

gt : greater than

gte : greater than or equal to

lt : less than

lte : less than or equal to

{
    "range": {
        "age": {
            "gte":  20,
            "lt":   30
        }
    }
}
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" ] }}
exist, missing

exist, missing查詢被用作查詢指定的欄位存在的文件(exist)或者不存在的文件(missing),exist返回存在該欄位的文件,missing返回不存在該欄位的文件

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

5. 組合查詢

現實應用中的查詢從來都不是簡單的,使用多個輸入值查詢多個欄位,依據一系列標準的過濾器.構造一個複雜查詢,你需要一種組合多個查詢子句在一個搜尋請求中的方式.

為了達到這個要求,可以使用bool查詢,這個查詢接受如下引數:

must: 必須是匹配的文件被包含進來

must_not: 一定是不匹配的文件被包含進來

should: 如果匹配,增加_score,否則沒有影響,為每個文件相關性評分.

filter: 必須匹配,是non_scoring的過濾模式,只是簡單的包含或不包含.

因為這是我們看到的第一個包含其他查詢的查詢語句,我們需要談論相關性評分是怎麼計算的.

每個子句分別計算文件的相關性評分,一旦這些結果被計算出來,bool語句將這些分數合併到一起,並且返回一個單個分數值,代表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" }}}
        ]
    }
}
加上過濾查詢:

如果我們不想文件的日期對評分產生影響,我們可以使用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查詢放入filter子句,我們轉化它為non-scoring查詢,它不再對文件的相關性評分產生影響,並且因為是non-scoring查詢,可以使用過濾器的優化來提升效能.

任何一個查詢都可以使用這種方式,簡單的將查詢放到bool語句的filter子句中,會自動轉化為non-scoring過濾.

如果需要一個基於多標準的過濾,bool查詢本身可以作為non-scoring查詢

{
    "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" }}
              ]
          }
        }
    }
}
constant_score查詢

儘管不如bool查詢經常使用,constant_score查詢也依然是有用的,該查詢為匹配的文件應用靜態的,常數的分數.它主要是在執行過濾查詢時使用.

只有過濾子句時,你可以使用該語句代替bool語句.效能是相同的,但是有利於查詢的簡單性和清晰度

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

6. 驗證查詢

查詢可以是非常複雜,尤其是組合了不同的分析器和欄位對映的時候,validate-query API可以檢查一個請求是否有效.

在請求URL後加/_validate/query

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"
      }
   }
}
顯然,我們混淆了查詢語句的類別和欄位的名字
{
  "valid" :     false,
  "_shards" :   { ... },
  "explanations" : [ {
    "index" :   "gb",
    "valid" :   false,
    "error" :   "org.elasticsearch.index.query.QueryParsingException:
                 [gb] No query registered for [tweet]"
  } ]
}
我們也可以利於expalin引數理解ES是如何解釋查詢的:
GET /us,gb/_validate/query?explain
{
  "query": {
    "match": {
      "tweet": "really powerful"
    }
  }
}
為我們查詢的每個索引返回一個explanation,因為每個索引有不同的對映和分析器:
{
  "valid": true,
  "_shards": {
    "total": 2,
    "successful": 2,
    "failed": 0
  },
  "explanations": [
    {
      "index": "gb",
      "valid": true,
      "explanation": "tweet:realli tweet:power"
    },
    {
      "index": "us",
      "valid": true,
      "explanation": "tweet:really tweet:powerful"
    }
  ]
}
從explanation中,我們可以看出針對tweet欄位,match語句是如何將查詢字串really powerful 重寫為兩個單個詞term的.

兩個索引的重寫詞不一樣,原因是因為索引gb中的tweet欄位使用的是english分析器.