Elasticsearch 聚合查询详解:分组、直方图与指标统计

本文介绍了Elasticsearch的聚合查询功能,包括分组、桶的概念、指标聚合(如SUM、COUNT等)、以及Terms、Histogram、Datehistogram和Range聚合的使用示例,展示了如何利用这些功能进行数据分析和可视化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、前言

Elasticsearch 不仅是一个强大的搜索引擎,它还提供了丰富的聚合(Aggregation)功能,支持对数据进行多维度分析和统计。通过聚合查询,我们可以实现类似 SQL 中 GROUP BY 的分组操作,并结合各种指标函数(如 SUM、AVG、COUNT 等)生成报表、折线图、饼图等可视化数据展示。

本文将详细介绍 Elasticsearch 中常见的几种聚合方式,包括:

  • Terms 聚合(按字段值分组)
  • Histogram 聚合(按数值间隔分组)
  • Date Histogram 聚合(按时间间隔分组)
  • Range 聚合(自定义范围分组)
  • 指标聚合(如 AVG、SUM、COUNT)

同时也会讲解 Java 客户端中如何使用这些聚合查询,并提供完整的示例代码。


二、基本概念

1. 聚合查询流程

Elasticsearch 的聚合查询流程分为两个主要步骤:

(1)分桶(Bucketing)

将满足特定条件的文档划分到不同的“桶”中。每个桶代表一个数据分组。

类比 SQL:相当于 GROUP BY 操作

(2)指标计算(Metrics)

在每个桶内部进行统计计算,例如求和、求平均值、计数等。

类比 SQL:相当于 SUM()AVG()COUNT() 等函数


三、常用聚合类型详解

1. Terms 聚合 —— 类似SQL的group by,根据字段唯一值分组

Terms 聚合类似于 SQL 中的 GROUP BY,根据字段的唯一值对数据进行分组。

示例 DSL 查询:

GET person_info/_search
{
  "size": 0,
  "aggs": {
    "source_buckets": {
      "terms": {
        "field": "source.keyword"
      }
    }
  }
}

返回结果示例:

"buckets": [
  { "key": "填表", "doc_count": 5340 },
  { "key": "普查", "doc_count": 56 },
  { "key": "网上下载", "doc_count": 39 }
]

Java 代码示例:

	TermsAggregationBuilder termAggBuilder = AggregationBuilders.terms("buket_name").field("analysis.sensitive_words").size(100).minDocCount(0);
	
	searchSourceBuilder.aggregation(termAggBuilder);

		try {
            searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        Aggregations aggregations = searchResponse.getAggregations();
        if(aggregations!= null){
            Terms terms = aggregations.get("buket_name");
            List<? extends Terms.Bucket> buckets = terms.getBuckets();
            JSONObject reslut = new JSONObject(true);
            for (Terms.Bucket bucket : buckets) {
                //分组的key
                String key = bucket.getKeyAsString();
                long docCount = bucket.getDocCount();
                reslut.put(key,docCount);
            }
            return AjaxResult.success(reslut);
        }else {
            return AjaxResult.error("查询无数据");
        }

解析聚合结果时,遍历 Terms.Bucket 即可获取每个分组的 key 和 count。


2. Histogram 聚合 —— 根据数值间隔分组,可做直方图

Histogram 聚合适用于数值型字段,按照指定的数值间隔进行分组,常用于绘制直方图或条形图。

示例 DSL 查询:

GET person_info/_search
{
  "size": 0,
  "aggs": {
    "age_buckets": {
      "histogram": {
        "field": "age",
        "interval": 5
      }
    }
  }
}

返回结果示例:

"buckets": [
  { "key": 10, "doc_count": 814 },
  { "key": 15, "doc_count": 1612 },
  { "key": 20, "doc_count": 1290 }
]

Java 代码示例:

HistogramAggregationBuilder histogramAgg = AggregationBuilders.histogram("age_buckets")
    .field("age")
    .interval(5);
searchSourceBuilder.aggregation(histogramAgg);

3. Date Histogram 聚合 —— 根据时间间隔分组,可做时间折线图

Date Histogram 是专门处理时间字段的聚合方式,支持按天、月、小时等时间单位进行分组,非常适合做趋势图分析。

示例 DSL 查询:

GET person_info/_search
{
  "size": 0,
  "aggs": {
    "buket_name": { //这是为了不返回数据,只返回聚合结果
      "date_histogram": {  // 聚合类型为: date_histogram
        "field": "data",	// 根据date字段分组
        "calendar_interval": "month",		// 分组间隔,详解在下边
        "format" : "yyyy-MM-dd",	// 设置返回结果中桶key的时间格式
        "time_zone": "+08:00", 		//**设置时区,如果存入的时候没设置就不用填**
        "min_doc_count": 0,			// 没有数据的月份返回0
        "extended_bounds": {		//强制返回的日期区间,既需要填充0的范围
          "min": "2000-01-01",
          "max": "2003-01-01"
        }
      }
    }
  }
}
GET person_info/_search
{
  "size": 0,
  "aggs": {
    "date_buckets": {
      "date_histogram": {
        "field": "data",
        "calendar_interval": "month",
        "format": "yyyy-MM-dd",
        "time_zone": "+08:00",
        "min_doc_count": 0,
        "extended_bounds": {
          "min": "2000-01-01",
          "max": "2003-01-01"
        }
      }
    }
  }
}

参数说明:

参数说明
field要聚合的时间字段名
calendar_interval使用日历感知的时间间隔(如 day, month 等)
fixed_interval固定时间间隔(如 2h、5m)
format返回桶 key 的时间格式
time_zone设置时区,解决时间偏移问题
min_doc_count最小文档数,设置为 0 表示返回空桶
extended_bounds强制返回指定时间范围内的所有桶(包括无数据的)

fixed_intervalcalendar_interval 区别

对比项calendar_intervalfixed_interval
是否支持复合单位❌ 不支持(只能用 1d、1M)✅ 支持任意倍数(如 2h、5m)
是否考虑日历逻辑✅ 按自然日历切分(如每月第一天)❌ 固定时间长度,不考虑日历
支持单位minute、hour、day、week、month、quarter、yearms、s、m、h、d
推荐场景按自然月、自然天聚合按固定时间间隔(如每 2 小时)聚合

⚠️ 注意:interval 参数在 ES 7.2+ 已被弃用,推荐使用 fixed_intervalcalendar_interval

Java 代码示例:

DateHistogramAggregationBuilder dateHisAgg = AggregationBuilders.dateHistogram("date_buckets")
    .field("data_time")
    .fixedInterval(DateHistogramInterval.hours(2))
    //.calendarInterval(DateHistogramInterval.HOUR);
    .timeZone(ZoneOffset.of("+08:00"))
    .format("yyyy-MM-dd HH:mm:ss");

searchSourceBuilder.aggregation(dateHisAgg);

4. Range 聚合 —— 自定义数值范围分组

Range 聚合允许我们自定义多个区间,将数据划分到不同的区间中。

示例 DSL 查询:

GET /xxx/_search
{
  "size": 0,
  "aggs": {
    "warn_range": {
      "range": {
        "field": "alarmNum",
        "ranges": [
          { "to": 24 },
          { "from": 24, "to": 32 },
          { "from": 32 }
        ]
      }
    }
  }
}

Java 代码示例:

RangeAggregationBuilder rangeAgg = AggregationBuilders.range("warn_range")
    .field("alarmNum")
    .addUnboundedTo(24)
    .addRange(24, 32)
    .addUnboundedFrom(32);

searchSourceBuilder.aggregation(rangeAgg);

四、指标聚合(Metrics)

指标聚合是对桶内数据进行统计计算的操作,常见类型如下:

指标类型说明
avg计算平均值
sum计算总和
min获取最小值
max获取最大值
value_count统计总数(类似 SQL 的 COUNT)
cardinality统计去重后的数量(类似 SQL 的 COUNT(DISTINCT))

示例 DSL 查询:

POST /exams/_search?size=0
{
  "aggs": {
    "avg_grade": {
      "avg": {
        "field": "grade"
      }
    }
  }
}

Java 代码示例:

AvgAggregationBuilder avgAgg = AggregationBuilders.avg("avg_grade").field("grade");
searchSourceBuilder.aggregation(avgAgg);

五、多层嵌套聚合与排序

Elasticsearch 支持多层嵌套聚合,即在一个桶的基础上再进行细分,形成树状结构的聚合。

示例 DSL 查询:

GET /project_zcy/_search
{
  "size": 0,
  "query": {
    "term": {
      "project_status.keyword": {
        "value": "执行中"
      }
    }
  },
  "aggs": {
    "institute_agg": {
      "terms": {
        "field": "institute_code.keyword"
      },
      "aggs": {
        "name_agg": {
          "terms": {
            "field": "institute_name.keyword"
          },
          "aggs": {
            "total_alarm": {
              "sum": {
                "field": "alarmNum"
              }
            },
            "count": {
              "value_count": {
                "field": "institute_code.keyword"
              }
            }
          }
        }
      }
    }
  }
}

排序方式:

  • _count:按文档数量排序
  • _term:按字段值排序(仅 terms)
  • _key:按键值排序(仅 histogram)
"order": {
  "_count": "desc"
}

六、限制返回桶的数量(size)

默认情况下,Elasticsearch 返回最多 10 个桶。可以通过 size 参数调整:

"terms": {
  "field": "product",
  "size": 5
}

七、总结

Elasticsearch 提供了非常灵活和强大的聚合功能,能够满足从基础分组统计到复杂数据分析的各种需求。掌握好聚合查询,不仅能帮助你快速构建数据看板,还能为后续的数据挖掘和业务决策提供有力支撑。


八、扩展建议

  • 使用 Kibana 可视化工具查看聚合结果,效果更直观。
  • 多层聚合配合指标统计可用于构建复杂的业务报表。
  • 结合 moving_avgderivative 等高级聚合,可实现时间序列预测和变化率分析。
### 回答1: 在 TURBO C 2.0 中,您可以使用 biosdisk() 函数来格式化软盘。 下面是一段代码示例: ``` #include <stdio.h> #include <dos.h> int main() { // 定义扇区的大小 unsigned int sector_size[3] = {512, 512, 1024}; // 定义填充字节 unsigned char fill_char[3] = {0xF6, 0xF6, 0x48}; // 格式化磁道 70 面 0 的前三个扇区 for (int i = 0; i < 3; i++) { int result = biosdisk(2, 0, 70, 0, i + 1, 1, sector_size[i], fill_char[i]); if (result != 0) { printf("格式化扇区 %d 失败,返回码: %d\n", i + 1, result); } } return 0; } ``` 请注意,在使用 biosdisk() 函数之前,需要包含 dos.h 头文件。 希望这能帮到您! ### 回答2: 使用TURBO C 2.0语言,可以通过以下代码格式化软盘的70磁道0面,并分别格式化3个扇区,大小分别为512字节、512字节和1024字节。其中,前两个扇区使用F6填充,第三个扇区使用48填充。 ```c #include<stdlib.h> #include<stdio.h> #include<dos.h> void formatFloppyDisk(){ union REGS regs; regs.h.ah = 0x0;// To format a floppy disk, we set AH=0 regs.h.dl = 0;// Drive number (0=A, 1=B, etc.) regs.x.cx = 0;// Track number to format regs.h.dh = 0;// Head number regs.h.al = 0;// Sector size (0=default, 1=512 bytes, 2=1024 bytes, 3=2048 bytes etc.) int FILL_BYTE = 0;// The byte value to fill the sectors with during formatting int NUM_SECTORS = 3;// Number of sectors to format // To format 70th track 0th head regs.x.ax = 0x1301; // 0x13 = Reset disk system, 01H = Reset only specified drive int86(0x13, &regs, &regs); // BIOS interrupt to reset disk system for (int i=0; i<NUM_SECTORS; i++){ regs.x.ax = 0x3101; // 0x31 = Write Format, 01H = Format only current track regs.x.bx = 0x0001; // 0x00 = Drive A:, 01H = Head 1, 0 = Generate ID Field depending on the disk in the drive 1 = Keep the ID Field all zeros regs.x.cx = 0x0170; // Track number=70(0-79 range) regs.h.dh = 0x00; // Head number=0 or 1 regs.h.al = 0x02; // Control byte=always zero regs.x.dx = i+1; // Sector number starting from 1 regs.x.si = 0x0000; // segment and offset of read/write buffer regs.x.di = 0x0000; // segment and offset of result if(i == 2){ FILL_BYTE = 0x48; // Fill the third sector with 48 regs.x.ax = 0x3102; // 0x31 = Write Format, 02H = Format sequential tracks immediately following the one being formatted }else{ FILL_BYTE = 0xF6; // Fill the first two sectors with F6 } regs.h.ah = FILL_BYTE; // Fill the sector with specified byte int86(0x13, &regs, &regs); // BIOS interrupt to format the specified sector } } int main(){ formatFloppyDisk(); return 0; } ``` 上述代码使用了INT 0x13,即BIOS中断服务例程,来执行软盘格式化操作。通过设置寄存器的不同参数,可以指定要格式化的磁道、面、扇区大小和填充字节。在这个例子中,我们格式化了软盘70磁道0面的3个扇区,前两个扇区使用F6填充,第三个扇区使用48填充。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

苍煜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值