系列文章:
搜索有两种方式:一种是通过URL
参数进行搜索,另一种是通过DSL(Request Body)
进行搜索
DSL:Domain Specified Language,特定领域语言
使用请求体可以让你的JSON数据以一种更加可读和更加富有展现力的方式发送。
# 批量插入测试数据
POST /zpark/user/_bulk
{"index":{"_id":1}}
{"name":"zs","realname":"张三","age":18,"birthday":"2018-12-27","salary":1000.0,"address":"北京市昌平区沙阳路55号"}
{"index":{"_id":2}}
{"name":"ls","realname":"李四","age":20,"birthday":"2017-10-20","salary":5000.0,"address":"北京市朝阳区三里屯街道21号"}
{"index":{"_id":3}}
{"name":"ww","realname":"王五","age":25,"birthday":"2016-03-15","salary":4300.0,"address":"北京市海淀区中关村大街新中关商城2楼511室"}
{"index":{"_id":4}}
{"name":"zl","realname":"赵六","age":20,"birthday":"2003-04-19","salary":12300.0,"address":"北京市海淀区中关村软件园9号楼211室"}
{"index":{"_id":5}}
{"name":"tq","realname":"田七","age":35,"birthday":"2001-08-11","salary":1403.0,"address":"北京市海淀区西二旗地铁辉煌国际大厦负一楼"}
查询所有并排序
URL实现
GET /zpark/user/_search?q=*&sort=age:desc&pretty
DSL实现
GET /zpark/user/_search
{
"query":{
"match_all":{} # 查询所有
},
"sort":{
"age":"desc" # 按年龄倒序排列
}
}
分页查询
URL实现
GET /zpark/user/_search?q=*&sort=_id:asc&from=2&size=2
DSL实现
GET /zpark/user/_search
{
"query":{
"match_all":{} # 查询所有
},
"sort":{
"_id":"asc" # 按年龄倒序排列
},
"from":2, # 从(nowPage-1)*pageSize检索
"size":2 # 查 pageSize条
}
address
在海淀区的所有用户,并高亮
基于全文检索的查询(分析检索关键词 匹配索引库 返回结果)
DSL实现
GET /zpark/user/_search
{
"query": {
"match": { # 注意: match查询会分词 如:海淀区 会分词为 海 | 淀 | 区
"address":"海淀区"
}
},
"highlight": {
"fields": { # 需要高亮的字段列表
"address": {}
}
}
}
name
是zs
关键字的用户基于Term词元查询
URL实现
GET /zpark/user/_search?q=name:zs
DSL实现
GET /zpark/user/_search
{
"query":{
"term": {
"name": {
"value": "zs"
}
}
}
}
20~30
岁之间的用户基于范围查询
DSL实现
GET /zpark/user/_search
{
"query": {
"range": {
"age": {
"gte": 20, # 大于等于 大于用 gt
"lte": 30 # 小于等于 小于用 lt
}
}
}
}
张
开头的用户基于前缀(prefix)查询
DSL实现
GET /zpark/user/_search
{
"query": {
"prefix": {
"realname": {
"value": "李"
}
}
}
}
s
结尾的用户基于通配符(wildcard)的查询
?
匹配一个字符*
匹配0~n个字符
DSL实现
GET /zpark/user/_search
{
"query": {
"wildcard": {
"name": {
"value": "*s"
}
}
}
}
id
为1,2,3的用户基于Ids的查询
DSL实现
GET /zpark/user/_search
{
"query": {
"ids": {
"values": [1,2,3]
}
}
}
realname
中包含张
关键字的用户基于Fuzzy的查询
DSL实现
GET /zpark/user/_search
{
"query": {
"fuzzy": {
"realname": {"value": "张"}
}
}
}
age
在15-30岁之间并且name
必须通配z*基于Boolean的查询(多条件查询)
must
:查询结果必须符合该查询条件(列表)。should
:类似于or的查询条件。must_not
:查询结果必须不符合查询条件(列表)。
DSL实现
GET /zpark/user/_search
{
"query": {
"bool": {
"must": [ #年龄在15~30岁之间并且必须名字通配z*
{
"range": {
"age": {
"gte": 15,
"lte": 30
}
}
},
{
"wildcard": {
"name": {
"value": "z*"
}
}
}
],
"must_not": [ # 正则查询 name必须不能以s结尾
{
"regexp": {
"name": ".*s"
}
}
]
}
}
}
其实准确来说,ES中的查询操作分为2种:查询(query)和过滤(filter)。查询即是之前提到的query查询,它(查询)默认会计算每个返回文档的得分,然后根据得分排序。而过滤(filter)只会筛选出符合的文档,并不计算得分,且它可以缓存文档。所以,单从性能考虑,过滤比查询更快。
换句话说,过滤适合在大范围筛选数据,而查询则适合精确匹配数据。一般应用时,应先使用过滤操作过滤数据,然后使用查询匹配数据。
GET /zpark/user/_search
{
"query":{
"bool": {
"must": [
{"match_all": {}}
],
"filter": { # 过滤年龄大于等于25岁的用户
"range": {
"age": {
"gte": 25
}
}
}
}
}
}
注意: 过滤查询运行时先执行过滤语句,后执行普通查询
term
、terms
Filterterm、terms的含义与查询时一致。term用于精确匹配、terms用于多词条匹配
GET /zpark/user/_search
{
"query":{
"bool": {
"must": [
{"match_all": {}}
],
"filter": {
"terms": {
"name": [
"zs",
"ls"
]
}
}
}
}
}
ranage
filterexists
filterexists
过滤指定字段没有值的文档
GET /zpark/user/_search
{
"query": {
"bool": {
"must": [
{
"match_all": {}
}
],
"filter": { # 排除salary为null的结果
"exists": {
"field": "salary"
}
}
}
},
"sort": [
{
"_id": {
"order": "asc"
}
}
]
}
或(相反操作)
GET /zpark/user/_search
{
"query": {
"bool": {
"must_not": [
{
"exists": {
"field": "salary"
}
}
]
}
}
}
ids
filter需要过滤出若干指定_id的文档,可使用标识符过滤器(ids)
GET /zpark/user/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"address": "昌平区"
}
}
],
"filter": {
"ids": { # id 过滤器
"values": [
1,
2,
3
]
}
}
}
}
}
Note:
Query和Filter更详细的对比可参考:https://blog.csdn.net/laoyang360/article/details/80468757
https://www.elastic.co/guide/en/elasticsearch/reference/6.x/search-aggregations.html
聚合提供了功能可以分组并统计你的数据。理解聚合最简单的方式就是可以把它粗略的看做SQL的GROUP BY操作和SQL的聚合函数。
ES中常用的聚合:
ES中的聚合API如下:
"aggregations" : { // 表示聚合操作,可以使用aggs替代
"" : { // 聚合名,可以是任意的字符串。用做响应的key,便于快速取得正确的响应数据。
"" : { // 聚合类别,就是各种类型的聚合,如min等
<aggregation_body> // 聚合体,不同的聚合有不同的body
}
[,"aggregations" : { [<sub_aggregation>]+ } ]? // 嵌套的子聚合,可以有0或多个
}
[,"" : { ... } ]* // 另外的聚合,可以有0或多个
}
平均值查询,作用于number类型字段上。如:查询用户的平均年龄
POST /zpark/user/_search
{
"aggs": {
"age_avg": {
"avg": {"field": "age"}
}
}
}
-------------------------------------------------------------------
{
......
"aggregations": {
"age_avg": {
"value": 23.6
}
}
}
也可以先过滤,再进行统计,如:
POST /zpark/user/_search
{ "query": {
"ids": {
"values":[1,2,3]
}
},
"aggs": {
"age_avg": {
"avg": {"field": "age"}
}
}
}
最大值查询。如:查询员工的最高工资
POST /zpark/user/_search
{
"aggs": {
"max_salary": {
"max": {
"field": "salary"
}
}
}
}
统计查询,一次性统计出某个字段上的常用统计值
POST /zpark/user/_search
{
"aggs": {
"max_salary": {
"stats": {
"field": "salary"
}
}
}
}
--------------------------------------------
{
....
"aggregations": {
"max_salary": {
"count": 4,
"min": 1000,
"max": 12300,
"avg": 5650,
"sum": 22600
}
}
}
自定义区间范围的聚合,我们可以自己手动地划分区间,ES会根据划分出来的区间将数据分配不同的区间上去。
如: 统计0-20岁,20-35岁,35~60岁用户人数
POST /zpark/user/_search
{
"aggs": {
"age_ranges": {
"range": {
"field": "age",
"ranges": [
{
"from": 0,
"to": 20
},
{
"from": 20,
"to": 35
},
{
"from": 35,
"to": 60
}
]
}
}
}
}
----------------------------------------------------------
{
......
"aggregations": {
"age_ranges": {
"buckets": [
{
"key": "0.0-20.0",
"from": 0,
"to": 20,
"doc_count": 1 # 区间范围的文档数量
},
{
"key": "20.0-35.0",
"from": 20,
"to": 35,
"doc_count": 3
},
{
"key": "35.0-60.0",
"from": 35,
"to": 60,
"doc_count": 1
}
]
}
}
}
自定义分组依据Term,对分组后的数据进行统计
如:根据年龄分组,统计相同年龄的用户
POST /zpark/user/_search
{
"aggs": {
"age_counts":{
"terms": {
"field": "age",
"size": 2 // 保留2个统计结果
}
}
}
}
----------------------------------------------------------
{
......
"aggregations": {
"age_counts": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 2,
"buckets": [
{
"key": 20,
"doc_count": 2
},
{
"key": 18,
"doc_count": 1
}
]
}
}
}
时间区间聚合专门针对date类型的字段,它与Range Aggregation的主要区别是其可以使用时间运算表达式。
如: 统计生日在2018年、2017年、2016年的用户
POST /zpark/user/_search
{
"aggs": {
"date_counts": {
"date_range": {
"field": "birthday",
"format": "yyyy-MM-dd",
"ranges": [
{
"from": "now/y", # 当前年的1月1日
"to": "now" # 当前时间
},
{
"from": "now/y-1y", # 当前年上一年的1月1日
"to":"now/y" # 当前年的1月1日
},
{
"from": "now/y-2y",
"to":"now/y-1y"
}
]
}
}
}
}
-------------------------------------------------------------------------------
{
.....
"aggregations": {
"date_counts": {
"buckets": [
{
"key": "2016-01-01-2017-01-01",
"from": 1451606400000,
"from_as_string": "2016-01-01",
"to": 1483228800000,
"to_as_string": "2017-01-01",
"doc_count": 1
},
{
"key": "2017-01-01-2018-01-01",
"from": 1483228800000,
"from_as_string": "2017-01-01",
"to": 1514764800000,
"to_as_string": "2018-01-01",
"doc_count": 1
},
{
"key": "2018-01-01-2018-12-26",
"from": 1514764800000,
"from_as_string": "2018-01-01",
"to": 1545847233691,
"to_as_string": "2018-12-26",
"doc_count": 0
}
]
}
}
}
直方图聚合,它将某个number类型字段等分成n份,统计落在每一个区间内的记录数。它与前面介绍的Range聚合非常像,只不过Range可以任意划分区间,而Histogram做等间距划分。既然是等间距划分,那么参数里面必然有距离参数,就是interval参数。
如:根据年龄间隔(5岁)统计
POST /zpark/user/_search
{
"aggs": {
"histogram_age": {
"histogram": {
"field": "age",
"interval": 5
}
}
}
}
-------------------------------------------------------------------------------
{
......
"aggregations": {
"histogram_age": {
"buckets": [
{
"key": 15,
"doc_count": 1
},
{
"key": 20,
"doc_count": 2
},
{
"key": 25,
"doc_count": 1
},
{
"key": 30,
"doc_count": 0
},
{
"key": 35,
"doc_count": 1
}
]
}
}
}
日期直方图聚合,专门对时间类型的字段做直方图聚合。这种需求是比较常用见得的,我们在统计时,通常就会按照固定的时间断(1个月或1年等)来做统计。
如:按年统计用户
POST /zpark/user/_search
{
"aggs": {
"date_histogram": {
"date_histogram": {
"field": "birthday",
"interval": "year",
"format": "yyyy-MM-dd"
}
}
}
}
-------------------------------------------------------------------------------
{
......
"aggregations": {
"date_histogram": {
"buckets": [
{
"key_as_string": "2001-01-01",
"key": 978307200000,
"doc_count": 1
},
{
"key_as_string": "2002-01-01",
"key": 1009843200000,
"doc_count": 0
},
{
"key_as_string": "2003-01-01",
"key": 1041379200000,
"doc_count": 1
},
{
"key_as_string": "2004-01-01",
"key": 1072915200000,
"doc_count": 0
},
{
"key_as_string": "2005-01-01",
"key": 1104537600000,
"doc_count": 0
},
{
"key_as_string": "2006-01-01",
"key": 1136073600000,
"doc_count": 0
},
{
"key_as_string": "2007-01-01",
"key": 1167609600000,
"doc_count": 0
},
{
"key_as_string": "2008-01-01",
"key": 1199145600000,
"doc_count": 0
},
{
"key_as_string": "2009-01-01",
"key": 1230768000000,
"doc_count": 0
},
{
"key_as_string": "2010-01-01",
"key": 1262304000000,
"doc_count": 0
},
{
"key_as_string": "2011-01-01",
"key": 1293840000000,
"doc_count": 0
},
{
"key_as_string": "2012-01-01",
"key": 1325376000000,
"doc_count": 0
},
{
"key_as_string": "2013-01-01",
"key": 1356998400000,
"doc_count": 0
},
{
"key_as_string": "2014-01-01",
"key": 1388534400000,
"doc_count": 0
},
{
"key_as_string": "2015-01-01",
"key": 1420070400000,
"doc_count": 0
},
{
"key_as_string": "2016-01-01",
"key": 1451606400000,
"doc_count": 1
},
{
"key_as_string": "2017-01-01",
"key": 1483228800000,
"doc_count": 1
},
{
"key_as_string": "2018-01-01",
"key": 1514764800000,
"doc_count": 1
}
]
}
}
}
聚合操作是可以嵌套使用的。通过嵌套,可以使得metric类型的聚合操作作用在每一bucket上。我们可以使用ES的嵌套聚合操作来完成稍微复杂一点的统计功能。
如:统计每年中用户的最高工资
POST /zpark/user/_search
{
"aggs": {
"date_histogram": { # bucket聚合 按照年分区
"date_histogram": {
"field": "birthday",
"interval": "year",
"format": "yyyy-MM-dd"
},
"aggs": {
"salary_max": {
"max": { # metric聚合 求最大工资
"field": "salary"
}
}
}
}
}
}
--------------------------------------------------------------------------------------
{
......
"aggregations": {
"date_histogram": {
"buckets": [
......
{
"key_as_string": "2017-01-01",
"key": 1483228800000,
"doc_count": 1,
"salary_max": {
"value": 5000
}
},
{
"key_as_string": "2018-01-01",
"key": 1514764800000,
"doc_count": 1,
"salary_max": {
"value": 1000
}
}
]
}
}
}