ElasticSearch 5學習(10)——結構化查詢(包括新特性)


之前我們所有的查詢都屬於命令行查詢,但是不利於復雜的查詢,而且一般在項目開發中不使用命令行查詢方式,只有在調試測試時使用簡單命令行查詢,但是,如果想要善用搜索,我們必須使用請求體查詢(request body search)API。之所以這么稱呼,是因為大多數的參數以JSON格式所容納而非查詢字符串。請求體查詢,並不僅僅用來處理查詢,而且還可以高亮返回結果中的片段,並且給出幫助你的用戶找尋最好結果的相關數據建議。

空查詢

我們以最簡單的search API開始,空查詢將會返回索引中所有的文檔。

GET /_search
{}

同字符串查詢一樣,可以查詢一個,多個或_all索引(indices)或類型(types):

也可以使用from及size參數進行分頁:

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

需要注意的是:攜帶內容的GET請求?

任何一種語言(特別是js)的HTTP庫都不允許GET請求中攜帶交互數據。 事實上,有些用戶很驚訝GET請求中居然會允許攜帶交互數據。

真實情況是,http://tools.ietf.org/html/rfc7231#page-24[RFC 7231], 一份規定HTTP語義及內容的RFC中並未規定GET請求中允許攜帶交互數據! 所以,有些HTTP服務允許這種行為,而另一些(特別是緩存代理),則不允許這種行為。

Elasticsearch的作者們傾向於使用GET提交查詢請求,因為他們覺得這個詞相比POST來說,能更好的描述這種行為。然而,因為攜帶交互數據的GET請求並不被廣泛支持,所以searchAPI同樣支持POST請求,類似於這樣:

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

這個原理同樣應用於其他攜帶交互數據的GET API請求中。

結構化查詢 Query DSL

結構化查詢是一種靈活的,多表現形式的查詢語言。 Elasticsearch在一個簡單的JSON接口中用結構化查詢來展現Lucene絕大多數能力。 你應當在你的產品中采用這種方式進行查詢。它使得你的查詢更加靈活,精准,易於閱讀並且易於debug。

使用結構化查詢,你需要傳遞query參數:

GET /_search
{
    "query": 發查詢體放置於此即可
}

空查詢 - {} - 在功能上等同於使用match_all查詢子句,正如其名字一樣,匹配所有的文檔:

GET /_search
{
    "query": {
        "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"
        }
    }
}

合並多子句

查詢子句就像是搭積木一樣,可以合並簡單的子句為一個復雜的查詢語句,比如:

  • 葉子子句(leaf clauses)(比如match子句)用以在將查詢字符串與一個字段(或多字段)進行比較
  • 復合子句(compound)用以合並其他的子句。例如,bool子句允許你合並其他的合法子句,mustmust_not或者should,如果可能的話:
{
    "bool": {
        "must":     { "match": { "tweet": "elasticsearch" }},
        "must_not": { "match": { "name":  "mary" }},
        "should":   { "match": { "tweet": "full text" }}
    }
}

復合子句能合並任意其他查詢子句,包括其他的復合子句。這就意味着復合子句可以相互嵌套,從而實現非常復雜的邏輯。

以下實例查詢的是郵件正文中含有“business opportunity”字樣的星標郵件或收件箱中正文中含有“business opportunity”字樣的非垃圾郵件:

#整個屬於查詢體
{
    "bool": {
        "must": { "match":      { "email": "business opportunity" }},
        "should": [
             { "match":         { "starred": true }},
             { "bool": {
                   "must":      { "folder": "inbox" },
                   "must_not":  { "spam": true }
             }}
        ],
        "minimum_should_match": 1
    }
}

查詢與過濾

Elasticsearch使用的DSL具有一組稱為查詢的組件,它們可以混合並以無窮組合進行匹配。這一組組件可以在兩個上下文中使用:過濾上下文和查詢上下文。

當用於過濾上下文時,該查詢被稱為“非評分”或“過濾”查詢。也就是說,查詢只詢問問題:“此文檔是否匹配?”。答案總是一個簡單的二進制yes|no。

  • created的日期范圍是否在 2013 到 2014 ?
  • status字段中是否包含單詞 "published" ?
  • lat_lon字段中的地理位置與目標點相距是否不超過10km ?

當在查詢上下文中使用時,查詢變為“評分”查詢。類似於其非評分兄弟,這確定文檔是否匹配以及文檔匹配的程度。

查詢的典型用法:

  • 查找與 full text search 這個詞語最佳匹配的文檔
  • 查找包含單詞 run ,但是也包含runs, running, jogsprint的文檔
  • 同時包含着 quick, brownfox --- 單詞間離得越近,該文檔的相關性越高
  • 標識着 lucene, searchjava --- 標識詞越多,該文檔的相關性越高

評分查詢計算每個文檔與查詢的相關程度,並為其分配相關性_score,稍后用於按相關性對匹配文檔進行排序。這種相關性的概念非常適合於全文搜索,其中很少有完全“正確”的答案。

新特性:歷史上,查詢和過濾器是Elasticsearch中的單獨組件。從Elasticsearch 2.0開始,過濾器在技術上被消除,並且所有查詢都獲得了成為非評分的能力。

然而,為了清楚和簡單,將使用term“過濾器”來表示在非評分過濾上下文中使用的查詢。可以將term“過濾器”,“過濾查詢”和“非評分查詢”視為相同。
類似地,如果單獨使用term“查詢”而不使用限定符,指的是“評分查詢”。

關於具體的Query DSL變化可以查看Query DSL changes

性能差異

過濾查詢是對集合包含/排除的簡單檢查,這使得計算非常快。當您的過濾查詢中至少有一個是“稀疏”(匹配文檔較少)時,可以利用各種優化,並且可以將經常使用的非評分查詢緩存在內存中以便更快地訪問。

相比之下,評分查詢不僅必須找到匹配的文檔,而且還要計算每個文檔的相關程度,這通常使得他們比他們的非評分對手更重。此外,查詢結果不可緩存。

由於倒排索引,只匹配幾個文檔的簡單評分查詢可能與跨越數百萬個文檔的過濾器一樣好或更好。然而,一般來說,過濾器將勝過評分查詢。

過濾的目的是減少必須由評分查詢檢查的文檔的數量。

什么情況下使用

作為一般規則,對全文搜索或任何會影響相關性分數的條件使用查詢子句,並對其他所有條件使用過濾器。

最重要的查詢過濾語句

match_all 查詢

match_all查詢只匹配所有文檔。如果未指定任何查詢,則是使用的默認查詢:

{“match_all”:{}}

此查詢經常與過濾器結合使用,例如,用於檢索收件箱文件夾中的所有電子郵件。所有文件被認為是同等相關的,所以他們都獲得1的中性分數。

match 查詢

match查詢是一個標准查詢,不管你需要全文本查詢還是精確查詢基本上都要用到它。

如果你使用match查詢一個全文本字段,它會在真正查詢之前用分析器先分析match一下查詢字符:

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

如果用match下指定了一個確切值,在遇到數字,日期,布爾值或者not_analyzed的字符串時,它將為你搜索你給定的值:

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

提示: 做精確匹配搜索時,你最好用過濾語句,因為過濾語句可以緩存數據。

不像我們在ElasticSearch 5學習(4)——簡單搜索筆記中介紹的字符查詢,match查詢不可以用類似"+usid: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用於按照精確值進行搜索,無論是數字,日期,布爾值還是未分析的精確值字符串字段:

{ "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查詢類似,不對輸入文本執行分析。它正在尋找精確匹配(包括大小寫,重音,空格等)。

exists and missing 查詢

existsmissing查詢用於查找指定字段具有一個或多個值(exists)或沒有任何值(missing)的文檔。
它在本質上類似於SQL中的IS_NULL(缺失)和NOT IS_NULL(存在):

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

這些查詢經常用於僅在存在字段時應用條件,以及在缺少條件時應用不同的條件。

查詢與過濾條件的合並

現實世界的搜索請求從來不簡單;他們使用各種輸入文本搜索多個字段,並根據條件數組進行過濾。要構建復雜的搜索,您需要一種將多個查詢組合到一個搜索請求中的方法。

要做到這一點,你可以使用bool詢。此查詢在用戶定義的布爾組合中將多個查詢組合在一起。此查詢接受以下參數:

bool 過濾

bool 過濾可以用來合並多個過濾條件查詢結果的布爾邏輯,它包含一下操作符:

must :: 多個查詢條件的完全匹配,相當於 and。

must_not :: 多個查詢條件的相反匹配,相當於 not。

should :: 至少有一個查詢條件匹配, 相當於 or。

這些參數可以分別繼承一個過濾條件或者一個過濾條件的數組:

{
    "bool": {
        "must":     { "term": { "folder": "inbox" }},
        "must_not": { "term": { "tag":    "spam"  }},
        "should": [
                    { "term": { "starred": true   }},
                    { "term": { "unread":  true   }}
        ]
    }
}

因為這是我們見過的第一個包含其他查詢的查詢,所以我們需要談論分數是如何組合的。每個子查詢子句將單獨計算文檔的相關性分數。一旦計算了這些分數,bool查詢將將分數合並在一起,並返回表示布爾運算的總分數的單個分數。

以下查詢將會找到 title 字段中包含 "how to make millions",並且 tag 字段沒有被標為 "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" }}}
        ]
    }
}

提示: 如果bool查詢下沒有must子句,那至少應該有一個should子句。但是如果有must子句,那么沒有should子句也可以進行查詢。

添加過濾查詢

如果我們不希望文檔的日期影響評分,我們可以重新排列前面的示例以使用過濾子句:

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

范圍查詢已從should子句中移出並進入過濾器子句。

通過將范圍查詢移動到過濾子句中,我們將其轉換為非評分查詢。它將不再為文檔的相關性排名貢獻分數。並且因為它現在是一個非評分查詢,它可以使用可用於過濾器的各種優化,這應該提高性能。

任何查詢都可以以這種方式使用。只需將查詢移動到bool查詢的過濾器子句中,它就會自動轉換為非評分過濾器。

如果你需要過濾許多不同的標准,bool查詢本身可以用作非評分查詢。只需將它放在過濾器子句中,並繼續構建布爾邏輯:

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

通過在filter子句中嵌入bool查詢,我們可以為我們的過濾條件添加布爾邏輯。

constant_score 查詢

盡管不像bool查詢那樣經常使用,但是constant_score查詢在你的工具箱中仍然有用。查詢對所有匹配的文檔應用靜態,常數得分。它主要用於當你想執行一個過濾器,沒有別的(例如沒有評分查詢)。你可以使用它而不是一個只有過濾器子句的bool。性能將是相同的,但它可以幫助查詢簡單/清晰。

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

驗證查詢

查詢語句可以變得非常復雜,特別是與不同的分析器和字段映射相結合后,就會有些難度。

validate API 可以驗證一條查詢語句是否合法。

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

以上請求的返回值告訴我們這條語句是非法的:

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

理解錯誤信息

要找出為什么它無效,請將explain參數添加到查詢字符串:

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

顯然,我們已經將查詢(match)類型與字段名稱(tweet)混淆:

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

理解查詢語句

使用explain參數具有返回(有效)查詢的可讀描述的附加優點,這對理解Elasticsearch如何解釋查詢是有用的:

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

為每個我們查詢的索引返回一個解釋,因為每個索引可以有不同的映射和分析器:

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

從解釋中,您可以看到查詢字符串的match查詢really powerful已被重寫為對tweet字段的兩個單項查詢,每個term一個。

此外,對於我們的索引,這兩個termreallypowerful的,而對於gb索引,termreallipower。原因是我們改變了gb索引中的tweet字段以使用english分析器。

轉載請注明出處。
作者:wuxiwei
出處:http://www.cnblogs.com/wxw16/p/6204644.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM