概要
本篇介紹Query DSL的語法案例,查詢語句的調(diào)試,以及排序的相關(guān)內(nèi)容砂豌。
基本語法
空查詢
最簡單的搜索命令厢岂,不指定索引和類型的空搜索,它將返回集群下所有索引的所有文檔(默認(rèn)顯示10條):
GET /_search
{}
搜索多個索引
GET /index1,index2/_doc/_search
{}
指定分頁搜索
GET /_search
{
"from": 0,
"size": 10
}
get帶request body
HTTP協(xié)議阳距,GET請求帶body是不規(guī)范的做法塔粒,但由于ES搜索的復(fù)雜性焕济,加上HTTP協(xié)議GET/POST方法表述的語義泻仙,GET更適合用來表述查詢的動作榔幸,雖然不規(guī)范燕侠,但還是這么用了。現(xiàn)在大多數(shù)瀏覽器也支持GET+request body嘲玫,如果遇到不支持的击纬,換成POST即可嘿歌。了解一下就行馍管,不用太慌張郭赐。
查詢表達(dá)式Query DSL
Query DSL是一種非常靈活、可讀性高的查詢語言确沸,body為JSON格式捌锭,絕大部分功能都可以用它來展現(xiàn),并且這種查詢語句更純粹张惹,讓學(xué)習(xí)者更專注于本身的功能舀锨,避免Client API的干擾岭洲。
上一節(jié)的空查詢宛逗,等價于這個:
GET /_search
{
"query": {
"match_all": {}
}
}
基本語法
# 查詢語句結(jié)構(gòu)
{
QUERY_NAME: {
ARGUMENT: VALUE,
ARGUMENT: VALUE,...
}
}
# 針對某個字段的查詢
{
QUERY_NAME: {
FIELD_NAME: {
ARGUMENT: VALUE,
ARGUMENT: VALUE,...
}
}
}
合并查詢語句
再復(fù)雜的查詢語句,也是由一個一個的查詢條件疊加而成的盾剩,查詢語句有兩種形式:
- 葉子語句:單個條件組成的語句雷激,如match語句,類似mysql的"id = 1"這種告私。
- 復(fù)合語句:有多個條件屎暇,需要合并在一起才能組成一個完整的語句,需要使用bool進(jìn)行組合驻粟,里面的條件可以用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 }} }
}
}
復(fù)合語句可以嵌套酷麦,來實(shí)現(xiàn)更復(fù)雜的查詢需求矿卑,在上面的例子上簡單延伸一下:
"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 }} }
}
復(fù)合語句相關(guān)性分?jǐn)?shù)計算
每一個子查詢都獨(dú)自地計算文檔的相關(guān)性得分。一旦他們的得分被計算出來沃饶,bool 查詢就將這些得分進(jìn)行合并并且返回一個代表整個布爾操作的得分母廷,得分高的顯示在前面轻黑,filter內(nèi)的條件不參與分?jǐn)?shù)計算。
過濾器filter
我們還是以英文兒歌的索引為案例琴昆,看一個搜索需求:歌詞內(nèi)容包含friend氓鄙,同時歌長大于30秒的記錄
GET /music/children/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"content": "friend"
}
}
],
"filter": {
"range": {
"length": {
"gte": 30
}
}
}
}
}
}
filter與query
- 過濾情況filtering context
僅按照搜索條件把需要的數(shù)據(jù)篩選出來,不計算相關(guān)度分?jǐn)?shù)业舍。
- 查詢情況query context
匹配條件的數(shù)據(jù)玖详,會根據(jù)搜索條件的相關(guān)度,計算每個document的分?jǐn)?shù)勤讽,然后按照分?jǐn)?shù)進(jìn)行排序蟋座,這個才是全文搜索的情況。
性能差異
filter只做過濾脚牍,不作排序向臀,并且會緩存結(jié)果到內(nèi)存中,性能非常高诸狭。
query匹配條件券膀,要做評分,沒有緩存驯遇,性能要低一些芹彬。
應(yīng)用場景
filter一個非常重要的作用就是減少不相關(guān)數(shù)據(jù)對query的影響,提升query的性能叉庐,二者常常搭配在一起使用舒帮。
組合使用的時候,把期望符合條件的document的搜索條件放在query里陡叠,把要濾掉的條件放在filter里玩郊。
constant_score查詢
如果一個查詢只有filter過濾條件,可以用constant_score來替代bool查詢枉阵,這樣的查詢語句更簡潔译红、更清晰,只是沒有評分兴溜,示例如下:
GET /music/children/_search
{
"query": {
"constant_score": {
"filter": {
"term": { "content": "gymbo"}
}
}
}
}
filter內(nèi)不支持terms語法侦厚,注意一下。
最常用的查詢
再復(fù)雜的查詢語句拙徽,也是由最基礎(chǔ)的查詢變化而來的刨沦,而最常用的查詢其實(shí)也就那么幾個。
- match_all查詢
查詢簡單的匹配所有文檔
GET /_search
{
"query": {
"match_all": {}
}
}
- match查詢
無論是全文搜索還是精確查詢斋攀,match查詢是最基本的標(biāo)準(zhǔn)
# 全文搜索例子
{ "match": { "content": "loves smile" }}
# 精確搜索
{ "match": { "likes": 15 }}
{ "match": { "date": "2019-12-05" }}
{ "match": { "isOwner": true }}
{ "match": { "keyword": "love you" }}
對于精確值的查詢已卷,我們可以使用filter來替代,filter有緩存的效果淳蔼。
- multi_match查詢
可以在多個字段上執(zhí)行相同的match查詢
{
"multi_match": {
"query": "my sunshine",
"fields": [ "name", "content" ]
}
}
- range查詢
查詢指定區(qū)間內(nèi)的數(shù)字或時間侧蘸,query和filter都支持裁眯,一般是filter用得多,允許的操作符如下:
- gt 大于
- gte 大于或等于
- lt 小于
- lte 小于或等于
{
"range": {
"length": {
"gte": 45,
"lt": 60
}
}
}
- term查詢
用于精確值匹配讳癌,精確值可以是數(shù)字穿稳,日期,boolean或keyword類型的字符串
{ "term": { "likes": 15 }}
{ "term": { "date": "2019-12-05" }}
{ "term": { "isOwner": true }}
{ "term": { "keyword": "love you" }}
建立索引時mapping設(shè)置為not_analyzed時晌坤,match等同于term逢艘,用得多的是match和range。
- terms查詢
跟term類似骤菠,只是允許一次指定多個值進(jìn)行匹配它改,只要有任何一個匹配上,都滿足條件
{ "terms": { "content": [ "love", "gymbo", "sunshine" ] }}
查詢語句調(diào)試
復(fù)雜的查詢語句商乎,可能會有幾百行央拖,可以先使用調(diào)試工具檢測一下查詢語句,定位不合法的搜索及原因鹉戚,完整語法如下:
GET /index/type/_validate/query?explain
{
"query": {
...
}
}
explain參數(shù)可以提供更詳細(xì)的查詢不合法的信息鲜戒,便于問題定位。寫一個錯誤的例子抹凳,比如使用中文標(biāo)點(diǎn)符號:
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關(guān)鍵字遏餐,true為驗(yàn)證通過,false為不通過赢底,如上提示信息失都,會指明3行33列錯誤,原因是使用了中文的引號颖系。將語法修正后嗅剖,得到的正確響應(yīng)如下:
{
"_shards": {
"total": 1,
"successful": 1,
"failed": 0
},
"valid": true,
"explanations": [
{
"index": "music",
"valid": true,
"explanation": "+content:(gymbo love sunshine) #*:*"
}
]
}
排序
查詢請求得到的結(jié)果,默認(rèn)排序是相關(guān)性得分降序嘁扼。如果我們只使用filter過濾,符合filter條件的文檔黔攒,評分都是一樣的(bool的filter得分是null趁啸,constant_score得分是1),結(jié)果文檔還是隨機(jī)返回督惰,顯然這樣的排序不符合我們的預(yù)期不傅。
sort排序規(guī)則
為此,我們可以使用sort屬性赏胚,對文檔進(jìn)行排序访娶,sort的用法與mysql如出一轍,示例如下:
GET /music/children/_search
{
"query": {
"bool": {
"filter": { "range": { "length" : { "gt" : 30 }} }
}
},
"sort": [
{
"length": {
"order": "desc"
}
}
]
}
sort內(nèi)可以同時指定多個排序字段觉阅,一旦使用sort排序后崖疤,_score得分將變成null秘车,因?yàn)槲覀冎付伺判蛞?guī)則,_score沒有實(shí)際意義了劫哼,就不用耗費(fèi)精力再去計算它叮趴。
字符串排序問題
我們知道text類型的字段,會有關(guān)鍵詞分詞處理权烧,對這樣的字段進(jìn)行排序眯亦,結(jié)果往往都不準(zhǔn)確,6.x版本以后的text類型般码,會再自動建立一個keyword類型的字段妻率,這個字段是不分詞的,所以這樣就有了分工板祝,text類型的負(fù)責(zé)搜索舌涨,keyword類型則負(fù)責(zé)排序。
我們回顧一下music索引的mapping信息(節(jié)選):
{
"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的字段革为,所以在排序的場景中扭粱,我們應(yīng)該使用name.keyword,示例如下:
GET /music/children/_search
{
"sort": [
{
"name.keyword": {
"order": "asc"
}
}
]
}
小結(jié)
本篇介紹Query DSL的語法及基礎(chǔ)實(shí)戰(zhàn)內(nèi)容震檩,順帶點(diǎn)了一下filter與query的區(qū)別琢蛤,面對復(fù)雜查詢語句時,建議先用驗(yàn)證工具進(jìn)行排查抛虏,最后介紹了一下排序方面的知識博其,基礎(chǔ)語法、上機(jī)案例多實(shí)踐即可迂猴。
專注Java高并發(fā)慕淡、分布式架構(gòu),更多技術(shù)干貨分享與心得沸毁,請關(guān)注公眾號:Java架構(gòu)社區(qū)