ElasticSearch的DSL高级查询操作
一、ES的两种查询方式
1.1 查询字符串搜索
GET /user/_search?q=name:张三
1.2 DSL查询
Elasticsearch提供丰富且灵活的查询语言叫做DSL查询(Query DSL),它允许你构建更加复杂、强大的查询。DSL(Domain Specific Language特定领域语言)以JSON请求体的形式出现。
GET user/_search
{
"query": {
"match": {
"name": "张三"
}
}
}
平时更多采用这种方式,因为可操作性更强,处理复杂请求时更得心应手。
二、精确查询(term)
term查询不会分析查询条件,只有当词条和查询字符串完全匹配时才匹配,也就是精确查找,比如数字,日期,布尔值或 not_analyzed 的字符串(未经分析的文本数据类型):
测试准备数据:
PUT /test/_doc/1
{
"name":"张三",
"age":18,
"sex":"男",
"address":"上海市浦东区"
}
PUT /test/_doc/2
{
"name":"李四",
"age":29,
"sex":"男",
"address":"广东省广州市"
}
PUT /test/_doc/3
{
"name":"王五",
"age":30,
"sex":"男",
"address":"广东省深圳市"
}
示例:查询年龄为29岁的用户信息
POST /test/_search
{
"query": {
"term": {
"age": 29
}
}
}
terms查询:terms 跟 term 有点类似,但 terms 允许指定多个匹配条件。 如果某个字段指定了多个值,那么文档需要一起去 做匹配:
示例:查询年龄为29或者30岁的用户信息
POST /test/_search
{
"query": {
"terms": {
"age": [
29,
30
]
}
}
}
三、全文查询(match)
全文查询会分析查询条件,先将查询条件进行分词,然后查询,求并集。
term和match的区别是:match是经过analyer的,也就是说,文档首先被分析器给处理了。根据不同的分析器,分析的结果也稍显不同,然后再根据分词结果进行匹配。term则不经过分词,它是直接去倒排索引中查找了精确的值了。
match 查询语法汇总:
- match_all:查询全部。
- match:返回所有匹配的分词。
- match_phrase:短语查询,在match的基础上进一步查询词组,可以指定slop分词间隔。
- match_phrase_prefix:前缀查询,根据短语中最后一个词组做前缀匹配,可以应用于搜索提示,但注意和max_expanions搭配。其实默认是50.......
- multi_match:多字段查询,使用相当的灵活,可以完成match_phrase和match_phrase_prefix的工作。
测试准备数据:
PUT /test1/_doc/1
{
"name":"Jack",
"age":18,
"sex":"男",
"address":"美国纽约州",
"remark":"美国是世界上军事实力最强大的国家"
}
PUT /test1/_doc/2
{
"name":"李四",
"age":29,
"sex":"男",
"address":"中国广东省广州市",
"remark":"中国是世界上人口最多的国家"
}
PUT /test1/_doc/3
{
"name":"阿三",
"age":30,
"sex":"男",
"address":"印度新德里",
"remark":"印度的食品干净又卫生"
}
PUT /test1/_doc/4
{
"name":"王五",
"age":30,
"sex":"男",
"address":"中国北京市三里屯",
"remark":"中国的首都是北京市"
}
1、match系列之match_all (查询全部)
GET /test1/_search
{
"query": {
"match_all": {}
}
}
这个没啥好说的。
2、match系列之match查询(单字段条件查询)
POST /test1/_search
{
"query": {
"match": {
"address": "中国"
}
}
}
从结果我们可以看到,我查询的条件是中国,虽然如期的返回了中国的文档,但是为什么含有美国的地址信息也被查询出来了,这并不是我们想要的,是怎么回事呢?因为这是elasticsearch在内部对文档做分词的时候,对于中文来说,就是一个字一个字分的,例如:'I Love You',此时会被分为三个词分别是:I、Love、You,而中文也是一样的,例如:‘我爱中国’,会被分词为:我、爱、中、国。所以我们的查询条件也会被分为:中和国,所以中和国都符合条件,当去查询文档的时候,此时美国中的 国 字也符合,所以匹配上了并且将结果返回了。
而我们认为中国是个短语,是一个有具体含义的词。所以elasticsearch在处理中文分词方面比较弱势。后面会讲针对中文的分词插件。但目前我们还有办法解决,那就是使用短语查询 用match_phrase来处理。
3、match系列之match_phrase(短语查询)
GET /test1/_search
{
"query": {
"match_phrase": {
"address": {
"query": "中国"
}
}
}
}
此时可以发现返回的结果中只包含了中国的文档数据。
4、match系列之match_phrase_prefix(最左前缀查询)智能搜索--以什么开头
示例:查询地址以 印度 开头的文档信息
GET /test1/_search
{
"query": {
"match_phrase_prefix": {
"remark": "印度"
}
}
}
5、match系列之multi_match(多字段查询)
multi_match是要在多个字段中查询同一个关键字 除此之外,mulit_match甚至可以当做match_phrase和match_phrase_prefix使用,只需要指定type类型即可
GET /test1/_search
{
"query": {
"multi_match": {
"query": "中国",
"fields": ["address","remark"]
}
}
}
加上phrase属性,当设置属性 type:phrase 时 等同于 短语查询。
GET /test1/_search
{
"query": {
"multi_match": {
"query": "中国",
"fields": ["address","remark"],
"type": "phrase"
}
}
}
加上phrase_prefix属性,当设置属性 type:phrase_prefix时 等同于 最左前缀查询。
GET /test1/_search
{
"query": {
"multi_match": {
"query": "中国",
"fields": ["address","remark"],
"type": "phrase_prefix"
}
}
}
四、通配符查询(wildcard)
wildcard查询:会对查询条件进行分词。还可以使用通配符 ?(任意单个字符) 和 * (0个或多个字符)
# wildcard 查询。查询条件分词,模糊查询
GET /test1/_search
{
"query": {
"wildcard": {
"name": {
"value": "李*"
}
}
}
}
prefix查询:前缀查询
# 前缀查询
GET /test1/_search
{
"query": {
"wildcard": {
"name": {
"value": "三"
}
}
}
}
五、排序查询(sort)
注意:需要分词的字段不可以直接排序,比如:text类型,如果想要对这类字段进行排序,需要特别设置:对字段索引两次,一次索引分词(用于搜索)一次索引不分词(用于排序),es默认生成的text类型字段就是通过这样的方法实现可排序的。
GET /test1/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"age": {
"order": "desc"
}
}
]
}
六、分页查询
Elasticsearch
的分页查询和SQL
使用 LIMIT 关键字返回只有一页的结果一样,Elasticsearch
接受 from 和 size 参数:
- size : 结果数,默认10
- from : 跳过开始的结果数,即从哪一行开始获取数据,默认0
示例:从第一页开始获取两条数据
GET /test1/_search
{
"query": {
"match_all": {}
},
"from": 0,
"size": 2
}
示例:分页查询用户名为Jack的用户信息
GET /test1/_search
{
"query": {
"match_phrase_prefix": {
"name": "Jack"
}
},
"from": 0,
"size": 2
}
七、范围查询(range)
range 过滤允许我们按照指定范围查找一批数据,范围操作符包含:
- gt:大于,相当于关系型数据库中的 >。
- gte:大于等于,相当于关系型数据库中的 >=。
- lt:小于,相当于关系型数据库中的 <。
- lte:小于等于,相当于关系型数据库中的 <=。
示例:查询年龄在20-30岁之间的用户信息
POST /test1/_search
{
"query": {
"range": {
"age": {
"gte": 20,
"lte": 30
}
}
}
}
八、布尔查询(bool)
bool 查询可以用来合并多个条件查询结果的布尔逻辑,它包含一下操作符:
- must:多个查询条件必须完全匹配,相当于关系型数据库中的 and。
- should:至少有一个查询条件匹配,相当于关系型数据库中的 or。
- must_not: 多个查询条件的相反匹配,相当于关系型数据库中的 not。
- filter:过滤满足条件的数据。
- range:条件筛选范围。
- gt:大于,相当于关系型数据库中的 >。
- gte:大于等于,相当于关系型数据库中的 >=。
- lt:小于,相当于关系型数据库中的 <。
- lte:小于等于,相当于关系型数据库中的 <=。
- range:条件筛选范围。
1、must:多个查询条件必须完全匹配,相当于关系型数据库中的 and。如果有一个条件不满足则不返回数据。
实例:查询用户名称满足叫 李四 的用户信息
GET /test1/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"name": "李四"
}
}
]
}
}
}
示例:查询用户名称满足叫 李四 并且年龄为29 的用户信息
GET /test1/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"name": "李四"
}
},
{
"match": {
"age": 29
}
}
]
}
}
}
2、should:至少有一个查询条件匹配,相当于关系型数据库中的 or。只要符合其中一个条件就返回
查询用户名称叫 李四 或者 年龄为27岁的用户信息
GET /test1/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"name": "李四"
}
},
{
"match": {
"age": 27
}
}
]
}
}
}
3、must_not:多个查询条件的相反匹配,相当于关系型数据库中的 not。
示例:查询名称不包含李四并且年龄不包含30岁的用户信息
GET /test1/_search
{
"query": {
"bool": {
"must_not": [
{
"match": {
"name": "李四"
}
},
{
"match": {
"age": 30
}
}
]
}
}
}
4、filter:条件过滤查询,过滤满足条件的数据。过滤条件的范围用range表示gt表示大于、lt表示小于、gte表示大于等于、lte表示小于等于
示例:查询出用户年龄在20-30岁之间名称为 李四 的用户
GET /test1/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"name": "李四"
}
}
],
"filter": {
"range": {
"age": {
"gte": 20,
"lt": 30
}
}
}
}
}
}
九、查询结果过滤
我们在查询数据的时候,返回的结果中,所有字段都给我们返回了,但是有时候我们并不需要那么多,所以可以对结果进行过滤处理。
示例:只查看name和age两个属性的返回值,其他不需要。
GET /test1/_search
{
"query": {
"match_all": {}
},
"_source": [
"name",
"age"
]
}
十、高亮查询
我们平时在使用百度的时候,输入关键字查询内容后,关键字一般都是高亮显示的。
所以ES作为一个专业的搜索框架肯定也提供了这样的功能。
ES的默认高亮显示:
GET /test1/_search
{
"query": {
"match": {
"name": "李四"
}
},
"highlight": {
"fields": {
"name": {}
}
}
}
ES自定义高亮显示(在highlight中,pre_tags用来实现我们的自定义标签的前半部分,在这里,我们也可以为自定义的 标签添加属性和样式。post_tags实现标签的后半部分,组成一个完整的标签。至于标签中的内容,则还是交给fields来完成)
GET /test1/_search
{
"query": {
"match": {
"remark": "中国"
}
},
"highlight": {
"pre_tags": "<b class='key' style='color:red'>",
"post_tags": "</b>",
"fields": {
"remark": {}
}
}
}
# 或者
GET /test1/_search
{
"query": {
"match": {
"remark": "中国"
}
},
"highlight": {
"fields": {
"remark": {
"pre_tags": "<b class='key' style='color:red'>",
"post_tags": "</b>"
}
}
}
}
十一、聚合查询
我们平时在使用Elasticsearch时,更多会用到聚合操作,它类似SQL中的group by操作。ES的聚合查询一定是先查出结果,然后对结果使用聚合函数做处理,常用的操作有:avg:求平均、max:最大值、min:最小值、sum:求和等。
在ES中聚合分为指标聚合和分桶聚合:
- 指标聚合:指标聚合对一个数据集求最大、最小、和、平均值等
- 分桶聚合:除了有聚合函数外,还可以对查询出的数据进行分组group by,再在组上进行游标聚合。
测试准备数据:
PUT /test2/_doc/1
{
"name":"Jack",
"age":18,
"sex":"男",
"salary":6000,
"bithday":"1997-07-18",
"address":"美国纽约州",
"remark":"美国是世界上军事实力最强大的国家"
}
PUT /test2/_doc/2
{
"name":"李四",
"age":29,
"sex":"男",
"salary":12000,
"bithday":"1997-02-07",
"address":"中国广东省广州市",
"remark":"中国是世界上人口最多的国家"
}
PUT /test2/_doc/3
{
"name":"阿三",
"age":30,
"sex":"男",
"salary":3000,
"bithday":"1995-04-09",
"address":"印度新德里",
"remark":"印度的食品干净又卫生"
}
PUT /test2/_doc/4
{
"name":"王五",
"age":30,
"sex":"男",
"salary":16000,
"bithday":"1997-07-07",
"address":"中国北京市三里屯",
"remark":"中国的首都是北京市"
}
PUT /test2/_doc/5
{
"name":"老六",
"age":29,
"sex":"男",
"salary":15000,
"bithday":"1987-02-02",
"address":"中国上海市浦东新区",
"remark":"老六是中国某不知名小厂的程序员一枚"
}
PUT /test2/_doc/6
{
"name":"七七",
"age":20,
"sex":"女",
"salary":15000,
"bithday":"1999-11-17",
"address":"中国上海市浦东新区",
"remark":"七七是一个漂亮的Web前端程序猿"
}
11.1 Metric指标聚合分析
指标聚合:指标聚合对一个数据集求最大、最小、和、平均值等
- 单值分析,只输出一个分析结果——avg:求平均、max:最大值、min:最小值、sum:求和、cardinality:值去重计数。
- 多值分析,输出多个分析结果
- stats:统计了count max min avg sum5个值
- extended_stats:extended_stats比stats多很多更加高级的统计结果:如平方和、方差、标准差、平均值加/减两个标准差的区间等
- percentile:占比百分位对应的值统计,默认返回【1,5,25,50,75,95,99】分位上的值
avg示例:查询所有用户的平均年龄
GET /test2/_search
{
"query": {
"match_all": {}
},
"aggs": {
"avg_age": {
"avg": {
"field": "age"
}
}
},
"_source": [
"name",
"age"
]
}
上例中,首先匹配查询所有的数据。在此基础上做查询平均值的操作,这里就用到了聚合函数,其语法被封装在aggs中,而avg_age则是为查询结果起个别名,封装了计算出的平均值。
如果只想看输出的值,而不关心输出的文档的话可以通过size=0来控制
GET /test2/_search
{
"query": {
"match_all": {}
},
"aggs": {
"avg_age": {
"avg": {
"field": "age"
}
}
},
"size": 0,
"_source": [
"name",
"age"
]
}
avg示例:查询地址在中国用户的平均工资
GET /test2/_search
{
"query": {
"match_phrase": {
"address": "中国"
}
},
"aggs": {
"avg_salary": {
"avg": {
"field": "salary"
}
}
},
"size": 0
}
max示例:查询年龄的最大值
GET /test2/_search
{
"query": {
"match_all": {}
},
"aggs": {
"max_age": {
"max": {
"field": "age"
}
}
},
"size": 0
}
min示例:查询年龄的最小值
GET /test2/_search
{
"query": {
"match_all": {}
},
"aggs": {
"age_min": {
"min": {
"field": "age"
}
}
},
"size": 0
}
sum示例:查询符合条件的年龄之和
GET /test2/_search
{
"query": {
"match_all": {}
},
"aggs": {
"age_sum": {
"sum": {
"field": "age"
}
}
},
"size": 0
}
示例:查询所有用户不同年龄的数量
POST /test2/_search
{
"query": {
"match_all": {}
},
"aggs": {
"count_age": {
"cardinality": {
"field": "age"
}
}
},
"size": 0
}
示例:查出所有用户的年龄stats信息
POST /test2/_search
{
"query": {
"match_all": {}
},
"aggs": {
"stats_age": {
"stats": {
"field": "age"
}
}
},
"size": 0
}
示例:查出所有用户的年龄extended_stats信息
POST /test2/_search
{
"query": {
"match_all": {}
},
"aggs": {
"extended_stats_age": {
"extended_stats": {
"field": "age"
}
}
},
"size": 0
}
示例:查出所有用户的年龄占比
POST /test2/_search
{
"query": {
"match_all": {}
},
"aggs": {
"pecent_age": {
"percentiles": {
"field": "age"
}
}
},
"size": 0
}
11.2 Bucket分桶聚合分析
分桶聚合:除了有聚合函数外,还可以对查询出的数据进行分组group by,再在组上进行游标聚合。
分桶group by示例:根据年龄聚合查询
GET /test2/_search
{
"size": 0,
"query": {
"match_all": {}
},
"aggs": {
"age_group": {
"terms": {
"field": "age"
}
}
}
}
分桶group by示例:根据年龄段聚合查询
GET /test2/_search
{
"size": 0,
"query": {
"match_all": {}
},
"aggs": {
"age_group": {
"range": {
"field": "age",
"ranges": [
{
"from": 15,
"to": 20
},
{
"from": 20,
"to": 25
},
{
"from": 25,
"to": 30
}
]
}
}
}
}
分桶group by示例:根据用户出生日期的年月分组分段聚合查询
POST /test2/_search
{
"size": 0,
"query": {
"match_all": {}
},
"aggs": {
"bithday_range": {
"date_range": {
"field": "bithday",
"format": "yyy-MM",
"ranges": [
{
"to": "1989-01"
},
{
"from": "1989-01",
"to": "1999-01"
},
{
"from": "1999-01",
"to": "2005-01"
},
{
"from": "2005-01"
}
]
}
}
}
}
分桶group by 进阶示例:按照年龄聚合,并且查询出这些年龄段的这些人的平均薪资
#其实就是aggs里面又加了一个aggs,第二个aggs根据第一个aggs聚合后的结果在聚合
GET /test2/_search
{
"query": {
"match_all": {}
},
"aggs": {
"ageAgg": {
"terms": {
"field": "age",
"size": 100
},
"aggs": {
"ageAvg": {
"avg": {
"field": "salary"
}
}
}
}
},
"size": 0
}
分桶group by 进阶示例:查出所有年龄分布,并且这些年龄段中 性别为男 的平均薪资和 性别为女 的平均薪资以及这个年龄的用户信息
GET /test2/_search
{
"query": {
"match_all": {}
},
"size": 0,
"aggs": {
"age_agg": {
"terms": {
"field": "age",
"size": 1000
},
"aggs": {
"sex_agg": {
"terms": {
"field": "sex.keyword",
"size": 10
},
"aggs": {
"salary_avg": {
"avg": {
"field": "salary"
}
}
}
},
"salary_avg": {
"avg": {
"field": "salary"
}
}
}
}
}
}
十二、queryString查询
会对查询条件进行分词,然后将分词后的查询条件和词条进行等值匹配,默认取并集(OR),可以指定单个字段也可多个查询字段
POST /test2/_search
{
"query": {
"query_string": {
"default_field": "name",
"query": "张三 OR 七七"
}
},
"size": 100
}
GET /test2/_search
{
"query": {
"query_string": {
"fields": ["name","address"],
"query": "张三 or 中国"
}
}
}
十三、重建索引
随着业务需求的变更,索引的结构可能发生改变。ElasticSearch的索引一旦创建,只允许添加字段,不允许改变字段。因为改变字段,需要重建倒排索引,影响内部缓存结构,性能太低。那么此时,就需要重建一个新的索引,并将原有索引的数据导入到新索引中。
- 原索引库 :test_index_v1
- 新索引库 :test_index_v2
# 新建test_index_v1索引,索引名称必须全部小写
PUT test_index_v1
{
"mappings": {
"properties": {
"birthday":{
"type": "date"
}
}
}
}
# 查询索引
GET test_index_v1
# 添加数据
PUT test_index_v1/_doc/1
{
"birthday":"2020-11-11"
}
# 查询数据
GET test_index_v1/_search
# 随着业务的变更,换种数据类型进行添加数据,程序会直接报错
PUT test_index_v1/_doc/1
{
"birthday":"2020年11月11号"
}
# 业务变更,需要改变birthday数据类型为text
# 1:创建新的索引 test_index_v2
# 2:将test_index_v1 数据拷贝到 test_index_v2
# 创建新的索引
PUT test_index_v2
{
"mappings": {
"properties": {
"birthday":{
"type": "text"
}
}
}
}
# 2:将test_index_v1 数据拷贝到 test_index_v2
POST _reindex
{
"source": {
"index": "test_index_v1"
},
"dest": {
"index": "test_index_v2"
}
}
# 查询新索引库数据
GET test_index_v2/_search
# 在新的索引库里面添加数据
PUT test_index_v2/_doc/2
{
"birthday":"2020年11月13号"
}
DELETE test_index_v1
十四、别名的使用
1、查询别名
#获取指定索引的别名
GET /test2/_alias
#获取ES中所有索引的别名
GET /_alias
2、新增别名
POST /_aliases
{
"actions": [
{
"add": {
"index": "test2",
"alias": "test2_alias_1.0"
}
}
]
}
3、删除别名
#方式一
POST /_aliases
{
"actions": [
{
"remove": {
"index": "test2",
"alias": "test2_alias_1.0"
}
}
]
}
#方式二
DELETE /test2/_alias/test2_alias_1.0
4、重命名别名
POST /_aliases
{
"actions": [
{
"remove": {
"index": "test2",
"alias": "test2_alias_1.0"
}
},
{
"add": {
"index": "test2",
"alias": "test2_alias_1.0"
}
}
]
}
5、为多个索引指定一个别名
POST /_aliases
{
"actions": [
{
"add": {
"index": "test2",
"alias": "test2_alias_2.0"
}
},{
"add": {
"index": "test1",
"alias": "test1_alias_2.0"
}
}
]
}
6、为同个索引指定多个别名
POST /_aliases
{
"actions": [
{
"add": {
"index": "test2",
"alias": "test2_alias_2.1"
}
},{
"add": {
"index": "test2",
"alias": "test2_alias_2.2"
}
}
]
}
7、通过别名读索引
GET /test2_alias_2.1
8、通过别名写索引
POST /test2_alias_2.1.0/_doc/
{
"name": "Tom",
"age": "26"
}