🙃ES高级查询Query DSL
ES中提供了一种强大的检索数据方式,这种检索方式称之为Query DSL(Domain Specified Language 领域专用语言) , Query DSL是利用Rest API传递JSON格式的请求体(RequestBody)数据与ES进行交互,这种方式的丰富查询语法让ES检索变得更强大,更简洁。
语法
GET /es_db/_search {json请求体数据}
💡match_all
使用match_all,匹配所有文档,默认只会返回10条数据。
原因:_search查询默认采用的是分页查询,每页记录数size的默认值为10。如果想显示更多数据,指定size
#返回es_db前10条数据
GET /es_db/_search
等同于
GET /es_db/_search
{
"query":{
"match_all":{}
}
}
返回指定条数size
# size指定返回条数
GET /es_db/_search
{
"query": {
"match_all": {}
},
"size": 100
}
_source返回指定字段
# _source返回指定字段
GET /es_db/_search
{
"query": {
"match_all": {}
},
"_source": ["name","address"]
}
#在查询中过滤
#不查看源数据,仅查看元字段
GET /es_db/_search
{
"_source": false,
"query": {
"match_all":{}
}
}
#只看以addr开头的字段
GET /es_db/_search
{
"_source": "addr*",
"query": {
"match_all":{}
}
}
分页查询from&size
#size:显示应该返回的结果数量,默认是 10
#from:显示应该跳过的初始结果数量,默认是 0
#from 关键字用来指定起始返回位置,和size关键字连用可实现分页效果
GET /es_db/_search
{
"query": {
"match_all": {}
},
"from": 0,
"size": 5
}
指定字段排序sort
#sort 排序
GET /es_db/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"age": "desc"
}
]
}
#排序,分页
GET /es_db/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"age": "desc"
}
],
"from": 6,
"size": 5
}
💡术语级别查询
术语查询相当于mysql中等于操作,不进行文本分析,直接进行匹配操作
Term query术语查询
⚠️最好不要在term查询的字段中使用text字段,因为text字段会被分词,这样做既没有意义,还很有可能什么也查不到。
# term 精确匹配
GET /es_db/_search
{
"query": {
"term": {
"age": {
"value": 28
}
}
}
}
# 采用term精确查询, 查询字段映射类型为keyword
GET /es_db/_search
{
"query":{
"term": {
"address.keyword": {
"value": "广州白云山公园"
}
}
}
}
Terms Query多术语查询
❤️Terms query用于查询多个值,类似mysql 中 IN
GET /es_db/_search
{
"query": {
"terms": {
"age": [
"25",
"28"
]
}
}
}
exists query
使用exists进行字段名称查询,如果存在就返回。
#查询索引库中存在name字段的文档数据
GET /es_db/_search
{
"query": {
"exists":
{
"field": "name"
}
}
}
ids query
ids 关键字 : 值为数组类型,用来根据id获取多个对应的数据
GET /es_db/_search
{
"query": {
"ids": {
"values": [1,2]
}
}
}
range query 范围查询
范围查询
- gte 大于等于
- lte 小于等于
- gt 大于
- lt 小于
- now 当前时间
GET /es_db/_search
{
"query": {
"range": {
"age": {
"gte": 10,
"lte": 20
}
}
}
}
prefix query前缀查询
它会对每个分词进行前缀匹配。
GET /es_db/_search
{
"query": {
"prefix": {
"address": {
"value": "大厦"
}
}
}
}
wildcard query通配符查询
与prefix类似,但是它不仅可以支持前缀匹配,还支持后缀匹配方式
GET /es_db/_search
{
"query": {
"wildcard": {
"address": {
"value": "*大*"
}1
}
}
}
fuzzy query模糊查询
fuzzy 搜索进行模糊查询,可以使用fuzziness属性指定运行有几处错别字的情形,范围是0-2
GET /es_db/_search
{
"query": {
"fuzzy": {
"address": {
"value": "白x云山",
"fuzziness": 1
}
}
}
}
全文检索
全文检索查询(查询条件进行分词)旨在基于相关性搜索和匹配文本数据
match query匹配查询
match在匹配时会对所查找的关键词进行分词,然后按分词匹配查找。
match支持以下参数:
- query : 指定匹配的值
- operator : 匹配条件类型
- and : 条件分词后都要匹配
- or : 条件分词后有一个匹配即可(默认)
- minmum_should_match : 最低匹配度,即条件在倒排索引中最低的匹配度
#match 分词后or的效果
GET /es_db/_search
{
"query": {
"match": {
"address": "广州白云山公园"
}
}
}
# 分词后 and的效果
GET /es_db/_search
{
"query": {
"match": {
"address": {
"query": "广州白云山公园",
"operator": "and"
}
}
}
}
在match中的应用: 当operator参数设置为or时,minnum_should_match参数用来控制匹配的分词的最少数量。
# 最少匹配广州,公园两个词
GET /es_db/_search
{
"query": {
"match": {
"address": {
"query": "广州公园",
"minimum_should_match": 2
}
}
}
}
multi_match query 多字段查询
指定查询匹配的字段,将查询条件分词之后进行查询,如果该字段不分词就会将查询条件作为整体进行查询,得分最高的在前面。
GET /es_db/_search
{
"query": {
"multi_match": {
"query": "长沙张龙",
"fields": [
"address",
"name"
]
}
}
}
match_phrase query短语查询
短语搜索会对搜索文本进行文本分词,到索引中寻找搜索的每个分词并要求分词相邻,可以通过调整slop参数设置分词出现的最大间隔距离。
#广州云山分词后相隔为2,可以匹配到结果
GET /es_db/_search
{
"query": {
"match_phrase": {
"address": {
"query": "广州云山",
"slop": 2
}
}
}
}
query_string query
允许我们在单个查询字符串中指定AND | OR | NOT条件,同时也和 multi_match query 一样,支持多字段搜索。和match类似,但是match需要指定字段名,query_string是在所有字段中搜索,范围更广泛。
#未指定字段查询
# AND 要求大写
GET /es_db/_search
{
"query": {
"query_string": {
"query": "赵六 AND 橘子洲"
}
}
}
# 指定单个字段查询
#Query String
GET /es_db/_search
{
"query": {
"query_string": {
"default_field": "address",
"query": "白云山 OR 橘子洲"
}
}
}
#指定多个字段查询
GET /es_db/_search
{
"query": {
"query_string": {
"fields": ["name","address"],
"query": "张三 OR (广州 AND 王五)"
}
}
}
simple_query_string
类似Query String,但是会忽略错误的语法,同时只支持部分查询语法,不支持AND OR NOT,会当作字符串处理。支持部分逻辑:
- +替代AND
- |替代OR
- -替代NOT
#simple_query_string 默认的operator是OR
GET /es_db/_search
{
"query": {
"simple_query_string": {
"fields": ["name","address"],
"query": "广州公园",
"default_operator": "AND"
}
}
}
GET /es_db/_search
{
"query": {
"simple_query_string": {
"fields": ["name","address"],
"query": "广州 + 公园"
}
}
}
bool query布尔查询
布尔查询可以按照布尔逻辑条件组织多条查询语句,只有符合整个布尔条件的文档才会被搜索出来。
布尔查询一共支持4种组合类型:
类型 | 说明 |
---|---|
must | 可包含多个查询条件,每个条件均满足的文档才能被搜索到,每次查询需要计算相关度得分,属于搜索上下文 |
should | 可包含多个查询条件,不存在must和fiter条件时,至少要满足多个查询条件中的一个,文档才能被搜索到,否则需满足的条件数量不受限制,匹配到的查询越多相关度越高,也属于搜索上下文 |
filter | 可包含多个过滤条件,每个条件均满足的文档才能被搜索到,每个过滤条件不计算相关度得分,结果在一定条件下会被缓存, 属于过滤上下文 |
must_not | 可包含多个过滤条件,每个条件均不满足的文档才能被搜索到,每个过滤条件不计算相关度得分,结果在一定条件下会被缓存, 属于过滤上下文 |
PUT /books
{
"settings": {
"number_of_replicas": 1,
"number_of_shards": 1
},
"mappings": {
"properties": {
"id": {
"type": "long"
},
"title": {
"type": "text",
"analyzer": "ik_max_word"
},
"language": {
"type": "keyword"
},
"author": {
"type": "keyword"
},
"price": {
"type": "double"
},
"publish_time": {
"type": "date",
"format": "yyy-MM-dd"
},
"description": {
"type": "text",
"analyzer": "ik_max_word"
}
}
}
}
POST /_bulk
{"index":{"_index":"books","_id":"1"}}
{"id":"1", "title":"Java编程思想", "language":"java", "author":"Bruce Eckel", "price":70.20, "publish_time":"2007-10-01", "description":"Java学习必读经典,殿堂级著作!赢得了全球程序员的广泛赞誉。"}
{"index":{"_index":"books","_id":"2"}}
{"id":"2","title":"Java程序性能优化","language":"java","author":"葛一鸣","price":46.5,"publish_time":"2012-08-01","description":"让你的Java程序更快、更稳定。深入剖析软件设计层面、代码层面、JVM虚拟机层面的优化方法"}
{"index":{"_index":"books","_id":"3"}}
{"id":"3","title":"Python科学计算","language":"python","author":"张若愚","price":81.4,"publish_time":"2016-05-01","description":"零基础学python,光盘中作者独家整合开发winPython运行环境,涵盖了Python各个扩展库"}
{"index":{"_index":"books","_id":"4"}}
{"id":"4", "title":"Python基础教程", "language":"python", "author":"Helant", "price":54.50, "publish_time":"2014-03-01", "description":"经典的Python入门教程,层次鲜明,结构严谨,内容翔实"}
{"index":{"_index":"books","_id":"5"}}
{"id":"5","title":"JavaScript高级程序设计","language":"javascript","author":"Nicholas C. Zakas","price":66.4,"publish_time":"2012-10-01","description":"JavaScript技术经典名著"}
GET /books/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"title": "java编程"
}
},{
"match": {
"description": "性能优化"
}
}
]
}
}
}
GET /books/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"title": "java编程"
}
},{
"match": {
"description": "性能优化"
}
}
],
"minimum_should_match": 1
}
}
}
GET /books/_search
{
"query": {
"bool": {
"filter": [
{
"term": {
"language": "java"
}
},
{
"range": {
"publish_time": {
"gte": "2010-08-01"
}
}
}
]
}
}
}
highlight高亮
highlight 关键字: 可以让符合条件的文档中的关键词高亮。
highlight相关属性:
- pre_tags 前缀标签
- post_tags 后缀标签
- tags_schema 设置为styled可以使用内置高亮样式
- require_field_match 多字段高亮需要设置为false
测试数据
#指定ik分词器
PUT /products
{
"settings" : {
"index" : {
"analysis.analyzer.default.type": "ik_max_word"
}
}
}
PUT /products/_doc/1
{
"proId" : "2",
"name" : "牛仔男外套",
"desc" : "牛仔外套男装春季衣服男春装夹克修身休闲男生潮牌工装潮流头号青年春秋棒球服男 7705浅蓝常规 XL",
"timestamp" : 1576313264451,
"createTime" : "2019-12-13 12:56:56"
}
PUT /products/_doc/2
{
"proId" : "6",
"name" : "HLA海澜之家牛仔裤男",
"desc" : "HLA海澜之家牛仔裤男2019时尚有型舒适HKNAD3E109A 牛仔蓝(A9)175/82A(32)",
"timestamp" : 1576314265571,
"createTime" : "2019-12-18 15:56:56"
}
测试
GET /products/_search
{
"query": {
"term": {
"name": {
"value": "牛仔"
}
}
},
"highlight": {
"fields": {
"*":{}
}
}
}
#自定义高亮html标签,可以在highlight中使用pre_tags和post_tags
GET /products/_search
{
"query": {
"multi_match": {
"fields": ["name","desc"],
"query": "牛仔"
}
},
"highlight": {
"post_tags": ["</span>"],
"pre_tags": ["<span style='color:red'>"],
"fields": {
"*":{}
}
}
}
#多字段高亮
GET /products/_search
{
"query": {
"term": {
"name": {
"value": "牛仔"
}
}
},
"highlight": {
"pre_tags": ["<font color='red'>"],
"post_tags": ["<font/>"],
"require_field_match": "false",
"fields": {
"name": {},
"desc": {}
}
}
}
ES 深度分页
什么是深度分页
查询的数据页数特别大,当from + size大于10000的时候,就会出现问题,如下图报错信息所示:
ES通过参数index.max_result_window用来限制单次查询满足查询条件的结果窗口的大小,其默认值为10000。
深度分页会带来什么问题
在分布式系统中,对结果排序的成本随分页的深度成指数上升
深度分页问题的常见解决方案
- 避免使用深度分页
- 滚动查询:Scroll Search
#查询命令中新增scroll=1m,说明采用游标查询,保持游标查询窗口1分钟,也就是本次快照的结果缓存起来的有效时间是1分钟。
#首次查询
GET /es_db/_search?scroll=1m
{
"query": { "match_all": {}},
"size": 2
}
#下一次查询
# scroll_id 的值就是上一个请求中返回的 _scroll_id 的值
GET /_search/scroll
{
"scroll": "1m",
"scroll_id" : "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFmNwcVdjblRxUzVhZXlicG9HeU02bWcAAAAAAABmzRY2YlV3Z0o5VVNTdWJobkE5Z3MtXzJB"
}
#删除游标
DELETE /_search/scroll
{
"scroll_id" : "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFmNwcVdjblRxUzVhZXlicG9HeU02bWcAAAAAAABmzRY2YlV3Z0o5VVNTdWJobkE5Z3MtXzJB"
}
- search_after:search_after适用于高效的深度滚动,是 Elasticsearch 7.10 版本之后才有的新特性。
# 创建一个时间点(PIT)来保存搜索期间的当前索引状态
POST /es_db/_pit?keep_alive=1m
#根据pit首次查询
GET /_search
{
"query": {
"match_all": {}
},
"pit": {
"id": "39K1AwEFZXNfZGIWZTN2N2Nrdk5RRjY3QjBma1h5aFRodwAWdkhjbE9YNVRTMUNDcWNQQVR2ZXYzdwAAAAAAAAA9jhZvaGpLSDlzVVMxbW5idG5DZ0xEUHFRAAEWZTN2N2Nrdk5RRjY3QjBma1h5aFRodwAA",
"keep_alive": "1m"
},
"size": 2,
"sort": [
{"_id": "asc"}
]
}
#根据search_after和pit进行翻页查询
#search_after指定为上一次查询返回的sort值。
GET /_search
{
"query": {
"match_all": {}
},
"pit": {
"id": "39K1AwEFZXNfZGIWZTN2N2Nrdk5RRjY3QjBma1h5aFRodwAWdkhjbE9YNVRTMUNDcWNQQVR2ZXYzdwAAAAAAAAA9jhZvaGpLSDlzVVMxbW5idG5DZ0xEUHFRAAEWZTN2N2Nrdk5RRjY3QjBma1h5aFRodwAA",
"keep_alive": "1m"
},
"size": 2,
"sort": [
{"_id": "asc"}
],
"search_after": [
3
]
}
总结
分页方式 | 性能 | 优点 | 缺点 | 适用场景 |
---|---|---|---|---|
from + size | 低 | 灵活性好,实现简单,支持随机翻页 | 受制于max_result_window设置,不能无限制翻页;存在深度翻译问题,越往后翻译越慢。 | 数据量比较小,能容忍深度分页问题 |
scroll | 中 | 解决了深度分页问题 | scroll查询的相应数据是非实时的,如果遍历过程中插入新的数据,是查询不到的;保留上下文需要足够的堆内存空间。 | 海量数据的导出,需要查询海量结果集的数据 |
search_after | 高 | 性能最好,不存在深度分页问题,能够反映数据的实时变更 | 实现复杂,需要有一个全局唯一的字段连续分页的实现会比较复杂,因为每一次查询都需要上次查询的结果,它不适用于大幅度跳页查询 | 海量数据的分页 |