本篇介绍Query DSL的语法案例,查询语句的调试,以及排序的相关内容。java
最简单的搜索命令,不指定索引和类型的空搜索,它将返回集群下全部索引的全部文档(默认显示10条):mysql
GET /_search {}
GET /index1,index2/_doc/_search {}
GET /_search { "from": 0, "size": 10 }
HTTP协议,GET请求带body是不规范的作法,但因为ES搜索的复杂性,加上HTTP协议GET/POST方法表述的语义,GET更适合用来表述查询的动做,虽然不规范,但仍是这么用了。如今大多数浏览器也支持GET+request body,若是遇到不支持的,换成POST便可。了解一下就行,不用太慌张。sql
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,... } } }
再复杂的查询语句,也是由一个一个的查询条件叠加而成的,查询语句有两种形式:架构
举个例子:并发
{ "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
咱们仍是以英文儿歌的索引为案例,看一个搜索需求:歌词内容包含friend,同时歌长大于30秒的记录分布式
GET /music/children/_search { "query": { "bool": { "must": [ { "match": { "content": "friend" } } ], "filter": { "range": { "length": { "gte": 30 } } } } } }
仅按照搜索条件把须要的数据筛选出来,不计算相关度分数。
匹配条件的数据,会根据搜索条件的相关度,计算每一个document的分数,而后按照分数进行排序,这个才是全文搜索的状况。
filter只作过滤,不做排序,而且会缓存结果到内存中,性能很是高。
query匹配条件,要作评分,没有缓存,性能要低一些。
filter一个很是重要的做用就是减小不相关数据对query的影响,提高query的性能,两者经常搭配在一块儿使用。
组合使用的时候,把指望符合条件的document的搜索条件放在query里,把要滤掉的条件放在filter里。
若是一个查询只有filter过滤条件,能够用constant_score来替代bool查询,这样的查询语句更简洁、更清晰,只是没有评分,示例以下:
GET /music/children/_search { "query": { "constant_score": { "filter": { "term": { "content": "gymbo"} } } } }
filter内不支持terms语法,注意一下。
再复杂的查询语句,也是由最基础的查询变化而来的,而最经常使用的查询其实也就那么几个。
查询简单的匹配全部文档
GET /_search { "query": { "match_all": {} } }
不管是全文搜索仍是精确查询,match查询是最基本的标准
# 全文搜索例子 { "match": { "content": "loves smile" }} # 精确搜索 { "match": { "likes": 15 }} { "match": { "date": "2019-12-05" }} { "match": { "isOwner": true }} { "match": { "keyword": "love you" }}
对于精确值的查询,咱们可使用filter来替代,filter有缓存的效果。
能够在多个字段上执行相同的match查询
{ "multi_match": { "query": "my sunshine", "fields": [ "name", "content" ] } }
查询指定区间内的数字或时间,query和filter都支持,通常是filter用得多,容许的操做符以下:
{ "range": { "length": { "gte": 45, "lt": 60 } } }
用于精确值匹配,精确值能够是数字,日期,boolean或keyword类型的字符串
{ "term": { "likes": 15 }} { "term": { "date": "2019-12-05" }} { "term": { "isOwner": true }} { "term": { "keyword": "love you" }}
创建索引时mapping设置为not_analyzed时,match等同于term,用得多的是match和range。
跟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的用法与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架构社区