Apache IoTDB(12):深度解析时序数据聚合的GROUP BY与HAVING子句

引言

在工业物联网场景中,某设备监控系统每秒产生超过2万条包含温度、压力、振动幅度的多维时序数据。若直接存储原始数据,单日存储量将突破200GB。通过IoTDB的分组聚合(GROUP BY)与聚合结果过滤(HAVING)子句的协同使用,保证分析结果的精准性。

Apache IoTDB 时序数据库【系列篇章】

No. 文章地址(点击进入)
1 Apache IoTDB(1):时序数据库介绍与单机版安装部署指南
2 Apache IoTDB(2):时序数据库 IoTDB 集群安装部署的技术优势与适用场景分析
3 Apache IoTDB(3):时序数据库 IoTDB Docker部署从单机到集群的全场景部署与实践指南
4 Apache IoTDB(4):深度解析时序数据库 IoTDB 在Kubernetes 集群中的部署与实践指南
5 Apache IoTDB(5):深度解析时序数据库 IoTDB 中 AINode 工具的部署与实践
6 Apache IoTDB(6):深入解析数据库管理操作------增删改查与异构数据库实战指南
7 Apache IoTDB(7):设备模板管理------工业物联网元数据标准化的破局之道
8 Apache IoTDB(8):时间序列管理------从创建到分析的实战指南
9 Apache IoTDB(9):数据库操作------数据写入从CLI到集群部署的六种实战
10 Apache IoTDB(10):数据库操作------从查询到优化的全链路实践指南
11 Apache IoTDB(11):分段聚合深度解析------从原理到实战的完整指南

本文将从案例,系统剖析这两个子句的协同工作机制。不同于传统数据库的聚合操作,IoTDB针对时序数据的特性进行了深度优化,支持时间窗口、设备层级、标签等多维分组方式,并可通过HAVING子句实现聚合结果的精准过滤。

一、分组聚合(GROUP BY)

1.1 路径层级分组聚合

在时间序列层级结构中,路径层级分组聚合查询用于对某一层级下同名的序列进行聚合查询。

使用 GROUP BY LEVEL = INT 来指定需要聚合的层级,并约定 ROOT 为第 0 层。若统计 "root.ln" 下所有序列则需指定 level 为 1。

路径层次分组聚合查询支持使用所有内置聚合函数。对于 sum,avg,min_value, max_value, extreme 五种聚合函数,需保证所有聚合的时间序列数据类型相同。其他聚合函数没有此限制。

练习1: 不同 database 下均存在名为 status 的序列, 如 "root.ln.wf01.wt01.status", "root.ln.wf02.wt02.status", 以及 "root.sgcc.wf03.wt01.status", 如果需要统计不同 database 下 status 序列的数据点个数,使用以下查询:

sql 复制代码
select count(status) from root.** group by level = 1

结果:

练习2: 统计不同设备下 status 序列的数据点个数,可以规定 level = 3

sql 复制代码
select count(status) from root.** group by level = 3

结果:

注意:这时会将 database ln 和 sgcc 下名为 wt01 的设备视为同名设备聚合在一起。

练习3: 统计不同 database 下的不同设备中 status 序列的数据点个数

sql 复制代码
select count(status) from root.** group by level = 1, 3

结果:

练习4: 查询所有序列下温度传感器 temperature 的最大值,可以使用下列查询语句:

sql 复制代码
select max_value(temperature) from root.** group by level = 0

结果:

练习5: 上面的查询都是针对某一个传感器,特别地,如果想要查询某一层级下所有传感器拥有的总数据点数,则需要显式规定测点为 *

sql 复制代码
select count(*) from root.ln.** group by level = 2

结果:

1.2 与时间区间分段聚合混合使用

通过定义 LEVEL 来统计指定层级下的数据点个数。

练习1:统计降采样后的数据点个数

sql 复制代码
select count(status) from root.ln.wf01.wt01 group by ((2017-11-01T00:00:00, 2017-11-07T23:00:00],1d), level=1;

结果:

练习2:加上滑动 Step 的降采样后的结果也可以汇总

sql 复制代码
select count(status) from root.ln.wf01.wt01 group by ([2017-11-01 00:00:00, 2017-11-07 23:00:00), 3h, 1d), level=1;

结果:

1.3 标签分组聚合

IoTDB 支持通过 GROUP BY TAGS 语句根据时间序列中定义的标签的键值做分组聚合查询。

我们先在 IoTDB 中写入如下示例数据,稍后会以这些数据为例介绍标签聚合查询。

这些是某工厂 factory1 在多个城市的多个车间的设备温度数据, 时间范围为 [1000, 10000)。

时间序列路径中的设备一级是设备唯一标识。城市信息 city 和车间信息 workshop 则被建模在该设备时间序列的标签中。

其中,设备 d1、d2 在 Beijing 的 w1 车间, d3、d4 在 Beijing 的 w2 车间,d5、d6 在 Shanghai 的 w1 车间,d7 在 Shanghai 的 w2 车间。

d8 和 d9 设备目前处于调试阶段,还未被分配到具体的城市和车间,所以其相应的标签值为空值。

sql 复制代码
create database root.factory1;
create timeseries root.factory1.d1.temperature with datatype=FLOAT tags(city=Beijing, workshop=w1);
create timeseries root.factory1.d2.temperature with datatype=FLOAT tags(city=Beijing, workshop=w1);
create timeseries root.factory1.d3.temperature with datatype=FLOAT tags(city=Beijing, workshop=w2);
create timeseries root.factory1.d4.temperature with datatype=FLOAT tags(city=Beijing, workshop=w2);
create timeseries root.factory1.d5.temperature with datatype=FLOAT tags(city=Shanghai, workshop=w1);
create timeseries root.factory1.d6.temperature with datatype=FLOAT tags(city=Shanghai, workshop=w1);
create timeseries root.factory1.d7.temperature with datatype=FLOAT tags(city=Shanghai, workshop=w2);
create timeseries root.factory1.d8.temperature with datatype=FLOAT;
create timeseries root.factory1.d9.temperature with datatype=FLOAT;

insert into root.factory1.d1(time, temperature) values(1000, 104.0);
insert into root.factory1.d1(time, temperature) values(3000, 104.2);
insert into root.factory1.d1(time, temperature) values(5000, 103.3);
insert into root.factory1.d1(time, temperature) values(7000, 104.1);

insert into root.factory1.d2(time, temperature) values(1000, 104.4);
insert into root.factory1.d2(time, temperature) values(3000, 103.7);
insert into root.factory1.d2(time, temperature) values(5000, 103.3);
insert into root.factory1.d2(time, temperature) values(7000, 102.9);

insert into root.factory1.d3(time, temperature) values(1000, 103.9);
insert into root.factory1.d3(time, temperature) values(3000, 103.8);
insert into root.factory1.d3(time, temperature) values(5000, 102.7);
insert into root.factory1.d3(time, temperature) values(7000, 106.9);

insert into root.factory1.d4(time, temperature) values(1000, 103.9);
insert into root.factory1.d4(time, temperature) values(5000, 102.7);
insert into root.factory1.d4(time, temperature) values(7000, 106.9);

insert into root.factory1.d5(time, temperature) values(1000, 112.9);
insert into root.factory1.d5(time, temperature) values(7000, 113.0);

insert into root.factory1.d6(time, temperature) values(1000, 113.9);
insert into root.factory1.d6(time, temperature) values(3000, 113.3);
insert into root.factory1.d6(time, temperature) values(5000, 112.7);
insert into root.factory1.d6(time, temperature) values(7000, 112.3);

insert into root.factory1.d7(time, temperature) values(1000, 101.2);
insert into root.factory1.d7(time, temperature) values(3000, 99.3);
insert into root.factory1.d7(time, temperature) values(5000, 100.1);
insert into root.factory1.d7(time, temperature) values(7000, 99.8);

insert into root.factory1.d8(time, temperature) values(1000, 50.0);
insert into root.factory1.d8(time, temperature) values(3000, 52.1);
insert into root.factory1.d8(time, temperature) values(5000, 50.1);
insert into root.factory1.d8(time, temperature) values(7000, 50.5);

insert into root.factory1.d9(time, temperature) values(1000, 50.3);
insert into root.factory1.d9(time, temperature) values(3000, 52.1);

1.4 单标签聚合查询

统计该工厂每个地区的设备的温度的平均值,可以使用如下查询语句

sql 复制代码
SELECT AVG(temperature) FROM root.factory1.** GROUP BY TAGS(city);

该查询会将具有同一个 city 标签值的时间序列的所有满足查询条件的点做平均值计算

结果:

从结果集中可以看到,和分段聚合、按层次分组聚合相比,标签聚合的查询结果的不同点是:

  1. 标签聚合查询的聚合结果不会再做去星号展开,而是将多个时间序列的数据作为一个整体进行聚合计算
  2. 标签聚合查询除了输出聚合结果列,还会输出聚合标签的键值列。该列的列名为聚合指定的标签键,列的值则为所有查询的时间序列中出现的该标签的值。
    如果某些时间序列未设置该标签,则在键值列中有一行单独的 NULL ,代表未设置标签的所有时间序列数据的聚合结果

1.5 多标签分组聚合查询

除了基本的单标签聚合查询外,还可以按顺序指定多个标签进行聚合计算。

例如,统计每个城市的每个车间内设备的平均温度。但因为各个城市的车间名称有可能相同,所以不能直接按照 workshop 做标签聚合。必须要先按照城市,再按照车间处理。

sql 复制代码
SELECT avg(temperature) FROM root.factory1.** GROUP BY TAGS(city, workshop);

结果:

从结果集中可以看到,和单标签聚合相比,多标签聚合的查询结果会根据指定的标签顺序,输出相应标签的键值列。

1.6 基于时间区间的标签聚合查询

按照时间区间聚合是时序数据库中最常用的查询需求之一。IoTDB 在基于时间区间的聚合基础上,支持进一步按照标签进行聚合查询。

例如,统计时间 [1000, 10000) 范围内,每个城市每个车间中的设备每 5 秒内的平均温度。

sql 复制代码
SELECT AVG(temperature) FROM root.factory1.** GROUP BY ([1000, 10000), 5s), TAGS(city, workshop);

结果:

和标签聚合相比,基于时间区间的标签聚合的查询会首先按照时间区间划定聚合范围,在时间区间内部再根据指定的标签顺序,进行相应数据的聚合计算。在输出的结果集中,会包含一列时间列,该时间列值的含义和时间区间聚合查询的相同。

二、聚合结果过滤(HAVING)

如果想对聚合查询的结果进行过滤,可以在 GROUP BY 子句之后使用 HAVING 子句。

2.1 HAVING与WHERE的本质区别

对比项 WHERE子句 HAVING子句
执行阶段 分组前执行(行级过滤) 分组后执行(组级过滤)
引用对象 原始列或常量 聚合函数结果
索引适用性 可利用B+树索引加速 无法直接使用索引
典型场景 数据预筛选 聚合结果二次筛选

在工业质检场景中,通过SELECT device_id, count(*) FROM production GROUP BY device_id HAVING count(*) > 1000可快速定位产量异常设备。配合WHERE子句的AND quality_status='failed'实现缺陷设备的精准定位,使质检效率提升50%。

2.2 注意事项

  1. HAVING子句中的过滤条件必须由聚合值构成,原始序列不能单独出现。

下列使用方式是不正确的:

sql 复制代码
select count(s1) from root.** group by ([1,3),1ms) having sum(s1) > s1
select count(s1) from root.** group by ([1,3),1ms) having s1 > 1
  1. 对GROUP BY LEVEL结果进行过滤时,SELECT和HAVING中出现的PATH只能有一级。

下列使用方式是不正确的:

sql 复制代码
select count(s1) from root.** group by ([1,3),1ms), level=1 having sum(d1.s1) > 1
select count(d1.s1) from root.** group by ([1,3),1ms), level=1 having sum(s1) > 1

2.3 正确使用案例

  1. 对于以下聚合结果进行过滤:
sql 复制代码
 select count(s1) from root.** group by ([1,11),2ms), level=1 having count(s2) > 2;

结果:

  1. 对于以下聚合结果进行过滤:
sql 复制代码
 select count(s1), count(s2) from root.** group by ([1,11),2ms) having count(s2) > 1 align by device;

结果:

三、智慧城市交通流量分析实战案例

3.1 需求分析

某智慧城市项目需要实现:

  1. 每5分钟统计各路口交通流量
  2. 筛选出平均车速低于30km/h且拥堵指数超过1.5的路口
  3. 补全缺失的车流量数据以保证可视化效果

3.2 GROUP BY 与 HAVING 协同查询实现

sql 复制代码
SELECT
  intersection_id,
  avg(speed) AS avg_speed,
  count(*) FILTER (WHERE status='congestion') AS congestion_count
FROM root.city.traffic
WHERE time >= '2023-12-01 00:00:00' AND time <= '2023-12-07 23:59:59'
GROUP BY time(5m), intersection_id
HAVING avg_speed < 30 AND congestion_count > 1.5;

3.3 结果可视化方案

通过Grafana集成IoTDB数据源

实现:

  • 交通流量热力图,实时显示各路口拥堵情况
  • 车速趋势图,展示平均车速变化趋势
  • 拥堵指数排行榜,快速定位高拥堵路口
  • 实时告警看板,自动推送拥堵预警信息

五、总结

Apache IoTDB的GROUP BY和HAVING子句构成了时序数据分析的完整工具链。通过合理配置和优化,实现了查询效率提升、存储空间减少、数据完整率提高、业务洞察能力的提升,本文详细讲述了GROUP BY和HAVING子句的具体使用和案例,能够帮助小伙伴们在实际项目中充分发挥IoTDB的强大功能,创造真正的业务价值。

相关推荐
z123456789868 分钟前
2026最新两款AI编程工具深度对比实测
java·数据库·ai编程
程序猿DD10 分钟前
一个 API Key,统一调用大模型、生图和联网搜索
数据库·网关
TlSfoward1 小时前
抓包代理链路下的 TLS 指纹变化分析 TLSFOWARD抓包工具
数据库·爬虫·网络协议·搜索引擎·php
数智化管理手记1 小时前
账龄分析手工统计易遗漏?自动账龄分析工具怎么搭建
大数据·网络·数据库·人工智能·数据挖掘
LabVIEW开发2 小时前
NI Package Manager安装LabVIEW时InternalServerError错误
网络·数据库·labview·labview知识·labview功能·labview程序
SelectDB2 小时前
PostgreSQL 实时同步到 Apache Doris:一站式 CDC 解决方案
数据库
Database_Cool_2 小时前
AI Agent 长期记忆怎么存?阿里云 PolarDB-X 对接 Mem0 记忆框架实践
数据库·阿里云
会编程的土豆3 小时前
数据库范式
数据库·oracle
L1624763 小时前
MySQL 全环境生产快速安装 + 完整配置手册(整合国产银河麒麟适配 + 主从补充版)
数据库·mysql
yio_yin3 小时前
Spring事务管理(数据一致性)
数据库·sql·spring