概要
本篇介绍 Query DSL 的语法案例, 查询语句的调试, 以及排序的相关内容.
基本语法
空查询
最简单的搜索命令, 不指定索引和类型的空搜索, 它将返回集群下所有索引的所有文档 (默认显示 10 条):
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 即可. 了解一下就行, 不用太慌张.
查询表达式 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 }} }
- }
- }
复合语句可以嵌套, 来实现更复杂的查询需求, 在上面的例子上简单延伸一下:
- "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 内的条件不参与分数计算.
过滤器 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 语法, 注意一下.
最常用的查询
再复杂的查询语句, 也是由最基础的查询变化而来的, 而最常用的查询其实也就那么几个.
match_all 查询
查询简单的匹配所有文档
- GET /_search
- {
- "query": {
- "match_all": {}
- }
- }
match 查询
无论是全文搜索还是精确查询, match 查询是最基本的标准
- # 全文搜索例子
- {
- "match": {
- "content": "loves smile"
- }
- }
- # 精确搜索
- {
- "match": {
- "likes": 15
- }
- }
- {
- "match": {
- "date": "2019-12-05"
- }
- }
- {
- "match": {
- "isOwner": true
- }
- }
- {
- "match": {
- "keyword": "love you"
- }
- }
对于精确值的查询, 我们可以使用 filter 来替代, filter 有缓存的效果.
multi_match 查询
可以在多个字段上执行相同的 match 查询
- {
- "multi_match": {
- "query": "my sunshine",
- "fields": [ "name", "content" ]
- }
- }
range 查询
查询指定区间内的数字或时间, query 和 filter 都支持, 一般是 filter 用得多, 允许的操作符如下:
gt 大于
gte 大于或等于
lt 小于
lte 小于或等于
- {
- "range": {
- "length": {
- "gte": 45,
- "lt": 60
- }
- }
- }
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.
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 架构社区
来源: https://www.cnblogs.com/huangying2124/p/12129049.html