Elasticsearch系列---實戰搜索語法

概要

本篇介紹Query DSL的語法案例,查詢語句的調試,以及排序的相關內容。java

基本語法

空查詢

最簡單的搜索命令,不指定索引和類型的空搜索,它將返回集羣下全部索引的全部文檔(默認顯示10條):mysql

GET /_search
{}
搜索多個索引
GET /index1,index2/_doc/_search
{}
指定分頁搜索
GET /_search
{
  "from": 0,
  "size": 10
}
get帶request body

HTTP協議,GET請求帶body是不規範的作法,但因爲ES搜索的複雜性,加上HTTP協議GET/POST方法表述的語義,GET更適合用來表述查詢的動做,雖然不規範,但仍是這麼用了。如今大多數瀏覽器也支持GET+request body,若是遇到不支持的,換成POST便可。瞭解一下就行,不用太慌張。sql

查詢表達式Query DSL

Query DSL是一種很是靈活、可讀性高的查詢語言,body爲JSON格式,絕大部分功能均可以用它來展示,而且這種查詢語句更純粹,讓學習者更專一於自己的功能,避免Client API的干擾。瀏覽器

上一節的空查詢,等價於這個:緩存

GET /_search
{
    "query": {
        "match_all": {}
    }
}
基本語法
# 查詢語句結構
{
    QUERY_NAME: {
        ARGUMENT: VALUE,
        ARGUMENT: VALUE,...
    }
}

# 針對某個字段的查詢
{
    QUERY_NAME: {
        FIELD_NAME: {
            ARGUMENT: VALUE,
            ARGUMENT: VALUE,...
        }
    }
}
合併查詢語句

再複雜的查詢語句,也是由一個一個的查詢條件疊加而成的,查詢語句有兩種形式:架構

  • 葉子語句:單個條件組成的語句,如match語句,相似mysql的"id = 1"這種。
  • 複合語句:有多個條件,須要合併在一塊兒才能組成一個完整的語句,須要使用bool進行組合,裏面的條件能夠用must必須匹配、must not必須不匹配、should能夠匹配修飾,也能夠包含過濾器filter。相似mysql的"(status = 1 && language != 'french' && (author = 'John' || author = 'Tom'))"這種。

舉個例子:併發

{
    "bool": {
        "must":     { "match": { "status": 1 }},
        "must_not": { "match": { "language":  "french" }},
        "should":   { "match": { "author": "John Tom" }},
        "filter":   { "range": { "length" : { "gt" : 30 }} }
    }
}

複合語句能夠嵌套,來實現更復雜的查詢需求,在上面的例子上簡單延伸一下:app

"bool": {
        "must":     { "match": { "status": 1 }},
        "must_not": { "match": { "language":  "french" }},
        "should":   [
            {"match": { "author": "John Tom" }},
            {"bool": {
                "must":     { "match": { "name": "friend" }},
                "must_not": { "match": { "content":  "star" }}
            }}
        ],
        "filter":   { "range": { "length" : { "gt" : 30 }} }
    }
複合語句相關性分數計算

每個子查詢都獨自地計算文檔的相關性得分。一旦他們的得分被計算出來,bool 查詢就將這些得分進行合併而且返回一個表明整個布爾操做的得分,得分高的顯示在前面,filter內的條件不參與分數計算。elasticsearch

過濾器filter

咱們仍是以英文兒歌的索引爲案例,看一個搜索需求:歌詞內容包含friend,同時歌長大於30秒的記錄分佈式

GET /music/children/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "content": "friend"
          }
        }
      ],
      "filter": {
        "range": {
          "length": {
            "gte": 30
          }
        }
      }
    }
  }
}

filter與query

  • 過濾狀況filtering context

僅按照搜索條件把須要的數據篩選出來,不計算相關度分數。

  • 查詢狀況query context

匹配條件的數據,會根據搜索條件的相關度,計算每一個document的分數,而後按照分數進行排序,這個纔是全文搜索的狀況。

性能差別

filter只作過濾,不做排序,而且會緩存結果到內存中,性能很是高。
query匹配條件,要作評分,沒有緩存,性能要低一些。

應用場景

filter一個很是重要的做用就是減小不相關數據對query的影響,提高query的性能,兩者經常搭配在一塊兒使用。
組合使用的時候,把指望符合條件的document的搜索條件放在query裏,把要濾掉的條件放在filter裏。

constant_score查詢

若是一個查詢只有filter過濾條件,能夠用constant_score來替代bool查詢,這樣的查詢語句更簡潔、更清晰,只是沒有評分,示例以下:

GET /music/children/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "term": { "content": "gymbo"}
      }
    }
  }
}

filter內不支持terms語法,注意一下。

最經常使用的查詢

再複雜的查詢語句,也是由最基礎的查詢變化而來的,而最經常使用的查詢其實也就那麼幾個。

  1. match_all查詢

查詢簡單的匹配全部文檔

GET /_search
{
    "query": {
        "match_all": {}
    }
}
  1. match查詢

不管是全文搜索仍是精確查詢,match查詢是最基本的標準

# 全文搜索例子
{ "match": { "content": "loves smile" }}

# 精確搜索
{ "match": { "likes":    15           }}
{ "match": { "date":   "2019-12-05" }}
{ "match": { "isOwner": true         }}
{ "match": { "keyword":    "love you"  }}

對於精確值的查詢,咱們可使用filter來替代,filter有緩存的效果。

  1. multi_match查詢

能夠在多個字段上執行相同的match查詢

{
    "multi_match": {
        "query":    "my sunshine",
        "fields":   [ "name", "content" ]
    }
}
  1. range查詢

查詢指定區間內的數字或時間,query和filter都支持,通常是filter用得多,容許的操做符以下:

  • gt 大於
  • gte 大於或等於
  • lt 小於
  • lte 小於或等於
{
    "range": {
        "length": {
            "gte":  45,
            "lt":   60
        }
    }
}
  1. term查詢

用於精確值匹配,精確值能夠是數字,日期,boolean或keyword類型的字符串

{ "term": { "likes":    15           }}
{ "term": { "date":   "2019-12-05" }}
{ "term": { "isOwner": true         }}
{ "term": { "keyword":    "love you"  }}

創建索引時mapping設置爲not_analyzed時,match等同於term,用得多的是match和range。

  1. terms查詢

跟term相似,只是容許一次指定多個值進行匹配,只要有任何一個匹配上,都知足條件

{ "terms": { "content": [ "love", "gymbo", "sunshine" ] }}

查詢語句調試

複雜的查詢語句,可能會有幾百行,能夠先使用調試工具檢測一下查詢語句,定位不合法的搜索及緣由,完整語法以下:

GET /index/type/_validate/query?explain
{
  "query": {
    ...
  }
}

explain參數能夠提供更詳細的查詢不合法的信息,便於問題定位。寫一個錯誤的例子,好比使用中文標點符號:

GET /music/children/_validate/query?explain
{
  "query": {
    "terms": { "content「: [ "love", "gymbo", "sunshine" ] }
  }
}

錯誤提示以下:

{
  "valid": false,
  "error": """
ParsingException[Failed to parse]; nested: JsonParseException[Unexpected character ('l' (code 108)): was expecting a colon to separate field name and value
 at [Source: org.elasticsearch.transport.netty4.ByteBufStreamInput@5e57280e; line: 3, column: 33]];; com.fasterxml.jackson.core.JsonParseException: Unexpected character ('l' (code 108)): was expecting a colon to separate field name and value
 at [Source: org.elasticsearch.transport.netty4.ByteBufStreamInput@5e57280e; line: 3, column: 33]
"""
}

valid關鍵字,true爲驗證經過,false爲不經過,如上提示信息,會指明3行33列錯誤,緣由是使用了中文的引號。將語法修正後,獲得的正確響應以下:

{
  "_shards": {
    "total": 1,
    "successful": 1,
    "failed": 0
  },
  "valid": true,
  "explanations": [
    {
      "index": "music",
      "valid": true,
      "explanation": "+content:(gymbo love sunshine) #*:*"
    }
  ]
}

排序

查詢請求獲得的結果,默認排序是相關性得分降序。若是咱們只使用filter過濾,符合filter條件的文檔,評分都是同樣的(bool的filter得分是null,constant_score得分是1),結果文檔仍是隨機返回,顯然這樣的排序不符合咱們的預期。

sort排序規則

爲此,咱們可使用sort屬性,對文檔進行排序,sort的用法與mysql一模一樣,示例以下:

GET /music/children/_search
{
  "query": {
    "bool": {
        "filter":   { "range": { "length" : { "gt" : 30 }} }
    }
  },
  "sort": [
    {
      "length": {
        "order": "desc"
      }
    }
  ]
}

sort內能夠同時指定多個排序字段,一旦使用sort排序後,_score得分將變成null,由於咱們指定了排序規則,_score沒有實際意義了,就不用耗費精力再去計算它。

字符串排序問題

咱們知道text類型的字段,會有關鍵詞分詞處理,對這樣的字段進行排序,結果每每都不許確,6.x版本之後的text類型,會再自動創建一個keyword類型的字段,這個字段是不分詞的,因此這樣就有了分工,text類型的負責搜索,keyword類型則負責排序。
咱們回顧一下music索引的mapping信息(節選):

{
  "music": {
    "mappings": {
      "children": {
        "properties": {
          "content": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          },
          "name": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          }
        }
      }
    }
  }
}

例如name字段,有一個text類型的,裏面fields還有一個類型爲keyword,名稱也爲keyword的字段,因此在排序的場景中,咱們應該使用name.keyword,示例以下:

GET /music/children/_search
{
  "sort": [
    {
      "name.keyword": {
        "order": "asc"
      }
    }
  ]
}

小結

本篇介紹Query DSL的語法及基礎實戰內容,順帶點了一下filter與query的區別,面對複雜查詢語句時,建議先用驗證工具進行排查,最後介紹了一下排序方面的知識,基礎語法、上機案例多實踐便可。

專一Java高併發、分佈式架構,更多技術乾貨分享與心得,請關注公衆號:Java架構社區
Java架構社區.jpg

相關文章
相關標籤/搜索