聚合查询
ES聚合查询是一种以结构化的方式提取和展示数据的机制。可以视它为SQL中的GROUP BY语句,但是它更加强大与灵活。
ES聚合查询类型
Elasticsearch的聚合操作支持嵌套,即一个聚合内部可以包含别的子聚合,从而实现非常复杂的数据挖掘和统计需求。
- Metric Aggregations
这类聚合查询基于文档字段的数值进行计算并且返回一个单一的数值结果。例如最大值(max)、最小值(min)、平均值(average)、总和(sum)、统计信息(stats,包含了上述几种操作),以及其他复杂的聚合如百分数(percentiles)、基数(cardinality)等。
- Bucket Aggregations
这类聚合会创建一组buckets,每个bucket对应一个特定的条件或范围,然后文档会根据这些条件或范围被分类到相应的bucket中。常见的包括区间(range)、日期区间(date range)、直方图(histogram)、日期直方图(date histogram)、地理哈希网格(geohash grid)等。
- Pipeline Aggregations
这类聚合可以基于其他聚合的结果进行二次计算。比如计算差异、比例、移动平均等。
聚合初步探索
ES查询种类, 因此在代码中的第一层嵌套由"query"变为了"aggs"
1,query
2 ,聚合 (aggs)
以下是一个即带query又带聚合的查询语句实例,可以看到query和聚合aggs都是第一层级。
{
"aggs": {
"group_by_keys": {
"aggs": {
"latest_record": {
"top_hits": {
"_source": {
"includes": ["module","event","host", "timestamp", "@timestamp","data"]
},
"size": 1,
"sort": [
{
"timestamp": {
"order": "desc"
} } ] } }
},
"terms": {
"script": {
"lang": "painless",
"source": "doc['module.keyword'].value + '|' + doc['event.keyword'].value + '|' + doc['host.keyword'].value"
},
"size": 10000
}}
},
"query": {
"bool": {
"must": [
{
"match": {
"event": "EventFileUpdateLog"
}
},
{
"range": {
"@timestamp": {
"gte": "2024-08-11T23:55:44.000Z",
"lte": "2024-08-12T15:55:44.000Z"
}
}
},
{
"match": {
"module": "admerge"
}
}
]
}
},
"size": 0
}
只能对keyword字段使用聚合,不能对text字段使用聚合,如果对text使用聚合,会报错。
聚合写法
聚合语法:
"aggregations" : {
"<aggregation_name>" : { <!--聚合的名字 -->
"<aggregation_type>" : { <!--聚合的类型 -->
<aggregation_body> <!--聚合体:对哪些字段进行聚合 -->
}
[,"meta" : { [<meta_data_body>] } ]? <!--元 -->
[,"aggregations" : { [<sub_aggregation>]+ } ]? <!--在聚合里面在定义子聚合 -->
}
[,"<aggregation_name_2>" : { ... } ]* <!--聚合的名字 -->
}
aggregations可以简写为aggs
分桶聚合
分桶聚合用例:
{
"size": 0, // 不显示query具体的内容
"query": {
"bool": {
"must": [{
"range": {
"@timestamp": {
"gte": 1533556800000,
"lte": 1533806520000
} } }]
}
}, //query结束
// 聚合
"aggs": {
// 自己取的名称
"group_by_status": {
// es提供
"terms": {
// 聚合字段名
"field": "LowStatusOfPrice"
}
}
}
}
- 因为设置了size:0,表示我们只对聚合结果感兴趣,不需要返回任何具体的搜索结果。
- "aggs" (或者 "aggregations") 块定义了我们的聚合。接下来需要为聚合起一个名字上例子中是my_aggs_name
- 定义聚合字段 "terms": { "field": "LowStatusOfPrice" // 聚合字段名 }
使用terms聚合,并且使用LowStatusOfPrice作为分桶依据
- 聚合size
aggs下不设置size的话默认返回10个桶
Histogram 聚合
类型的桶聚合。它可以按照指定的间隔将数字字段的值划分为一系列桶。每个桶代表了这个区间内的所有文档。
{ "size": 0, // 不显示query具体的内容
"aggs": { // 聚合
"price_histogram": { // 自己取的名称
"histogram": {
// 聚合字段名
"field": "price",
"interval":50
}
}
}
}
price_histogram 是一个histogram聚合,它以50为间隔将产品的价格划分为一系列的桶
指标聚合
在 Elasticsearch 中,指标聚合是对数据进行统计计算的一种方式,例如求和、平均值、最小值、最大值等。以下是一些常用的指标聚合类型:
avg:计算字段的平均值。
sum:计算字段的总和。
min:查找字段的最小值。
max:查找字段的最大值。
count:计算匹配文档的数量。
stats:提供了 count、sum、min、max 和 avg 的基本统计。
extended_stats 统计更多的信息,比如平⽅和、⽅差、标准差
cardinality 去重计数
#对全样数据进行聚合,eg. 统计nba球员的平均年龄
GET /nba/_search
{
"aggs": {
"avg_age": { #自定义的聚合名称
"avg": { #要使用的聚合函数
"field": "age" #要统计的字段
}
}
},
"size": 0}
"avg": { "field": "age" } 定义了我们执行的聚合类型avg 以及对字段age进行聚合。在这里,我们告诉 Elasticsearch 使用 avg 聚合,并且对 age 字段的值进行计算。Elasticsearch 将返回一个包含所有age平均的结果。
{
"query": {
"term": {
"activity_id": 123
}
},
"aggs": {
"coun_user": {
"cardinality": {
"field": "user_id"
}
}
},
"size": 0}
cardinality去重统计
嵌套聚合
嵌套聚合就是在聚合内部使用聚合。 在 Elasticsearch 中,嵌套聚合通常用于处理 nested 类型的字段。nested 类型允许你将一个文档中的一组对象作为独立的文档进行索引和查询,这对于拥有复杂数据结构(例如数组或列表中的对象)的场景非常有用。
先分班级统计,再统计每班平均学生的年龄,再按平均年龄对桶进行排序
GET /nba/_search
{
"aggs": {
"class_avg_age": {
"terms": {
"field": "class_name",
"order": {
"avg_age": "asc" #avg_age是年龄聚合的名称
}
},
"aggs": {
"avg_age": {
"avg": {
"field": "age"
}
}
}
}
},
"size": 0 //只展示聚合内容
}
#桶嵌套桶。先按省份划分用户,再在每个桶中按城市划分
GET /user/_search
{
"aggs": {
"count_province": {
"terms": {
"field": "province"
},
"aggs": {
"count_province_city": {
"terms": {
"field": "city"
}
}
}
}
},
"size": 0}
基于查询结果聚合
我们首先执行一个查询,然后对查询结果进行聚合
"query": {
"bool": {
"must": [{
"range": {
"@timestamp": {
"gte": 1533556800000,
"lte": 1533806520000
} } }] }},
"size": 0, // 不显示query具体的内容
// 聚合
"aggs": {
// 自己取的聚合名字
"my_aggs_name": {
// es提供的时间处理函数
"date_histogram": {
// 需要聚合分组的字段名称, 类型需要为date, 格式没有要求
"field": "@timestamp",
// 按什么时间段聚合, 这里是5分钟, 可用的interval在上面给出
"interval": "5m",
// 设置时区, 这样就相当于东八区的时间
"time_zone":"+08:00",
// 返回值格式化,HH大写,不然不能区分上午、下午
"format": "yyyy-MM-dd HH",
// 为空的话则填充0
"min_doc_count": 0,
// 需要填充0的范围
"extended_bounds": {
"min": 1533556800000,
"max": 1533806520000
}
},
// 聚合
"aggs": {
// 自己取的名称
"group_by_status": {
// es提供
"terms": {
// 聚合字段名
"field": "LowStatusOfPrice"
}
}
}
}
}
基于聚合结果的查询 (Post-Filter)
我们先执行聚合,然后基于聚合的结果执行过滤操作。这通常用于在聚合结果中应用一些额外的过滤条件。例如,如果我们想对所有产品进行销售数量聚合,然后从结果中过滤出销售数量大于10的产品,可以这样做:
GET /sales/search
{
"size":0;
"aggs":{
"sales_per_product":{
"terms":{
"field":"product id'
}
}
},
"post filter":{
"bucket selector":
"buckets path":{
"salesCount":"sales_per_product._count"
"script":{
"source":"params.salesCount >10"
}
}
}
}
首先执行了一个 terms 聚合,按产品ID汇总销售记录。然后我们使用 bucket_selector post-filter 进一步筛选出销售数量大于10的桶(每个桶对应一个产品)。
聚合排序 _count 使用
分桶聚合类型:terms按照关键字划分。 _count 是一个内置的排序键,代表桶中文档的数量。
#对全样数据进行聚合,eg. 统计每个球队的球员(人数)
GET /nba/_search
{
"aggs": {
"team_buckets": { #自定义的聚合名称
"terms": { #按关键字划分。不能换成term、match
"field": "team_name", #指定要聚合的字段。按队名划分桶,一个队一个桶,桶内是该队所有球员的文档,字段的值即桶名
# "include": ["湖人队","公牛队","勇士队"], #指定参与聚合的值,只聚合这些值
# "exclude": ["湖人队"], #指定不参与聚合的值,除了这些值其它都参与聚合
"size": 10, #返回的桶数,默认返回所有的桶
"order": { #桶的排序方式
"_count": "desc" #按桶中的文档数降序排列,默认也是按桶中的文档数降序排列
}
}
}
},
"size": 0 #返回的文档数,默认返回每个桶中的所有文档,设置为0即不返回文档,可提高查询速度。如果不需要获取具体文档,可设置为0}
_term排序
_term 在 Elasticsearch 的聚合排序中用来指定按照词条(即桶的键)来排序。
GET /sales/_search
{
"size": 0,
"aggs": {
"products": {
"terms": {
"field": "product_id",
"order": { "_term": "asc" }
}
}
}
}
指定了按照 product_id 的值升序排序这些桶。返回的结果将包含按照 product_id 升序排列的产品 ID 列表,每个产品 ID 对应一个桶,并且每个桶内包含对应产品的销售记录。
在新版本的 Elasticsearch 中(7.0 以后), _term 已经被 key 替代用于排序
GET /sales/_search
{
"size": 0,
"aggs" : {
"products" : {
"terms" : {
"field" : "product_id",
"order" : { "_key" : "asc" }
}
}
}
}
聚合结果结构
size:0所以hits中数据为空。aggregations中buckets数据就是聚会机构数据
{
"took" : 32,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 6720,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"group_by_keys" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "module-1|sssss|dmsssst",
"doc_count" : 960,
"latest_record" : {
"hits" : {
"total" : {
"value" : 960,
"relation" : "eq"
},
"max_score" : null,
"hits" : [
{
"_index" : "engine_manager_upstream_2024-08-12",
"_type" : "_doc",
"_id" : "_X5MR5EBH1BoCOoLzHVn",
"_score" : null,
"_ignored" : [
"data.keyword"
],
"_source" : {
"@timestamp" : "2024-08-12T15:55:01.094Z",
"module" : "module-1",
"host" : "dmsssst",
"event" : "sssss",
"timestamp" : sdsd
},
"sort" : [
1723478100
]
}
]
}
}
},
{
"key" : "ttt|EventFilelogs|tthth",
"doc_count" : 960,
"latest_record" : {
"hits" : {
"total" : {
"value" : 960,
"relation" : "eq"
},
"max_score" : null,
"hits" : [
{
"_index" : "engine_manager_upstream_2024-08-12",
"_type" : "_doc",
"_id" : "C2hMR5EBzddARe60y6b-",
"_score" : null,
"_ignored" : [
"data.keyword"
],
"_source" : {
"@timestamp" : "2024-08-12T15:55:00.989Z",
"module" : "ttt",
"host" : "tthth",
"event" : "EventFilelogs",
"timestamp" : 1723478100
},
"sort" : [
1723478100
]
}
]
}
}
}
]
}
}
}