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的强大功能,创造真正的业务价值。

相关推荐
abap帅哥2 小时前
SAP MIRO/MIR4付款条件消失 :设计逻辑、根本原因与终极解决方案
数据库·后端·sap·abap·erp
不穿格子的程序员2 小时前
Redis篇9——Redis深度剖析:
数据库·redis·多线程·事务回滚·ap·cp
今晚务必早点睡2 小时前
Redis——快速入门第五课:Redis 常见坑 & 面试高频问题
数据库·redis·面试
盒马coding2 小时前
Patroni + HAProxy + Keepalived + watchdog + ETCD 各组件原理
数据库·etcd
TG:@yunlaoda360 云老大2 小时前
如何通过华为云国际站代理商CSBS进行跨Region备份与容灾?
大数据·数据库·华为云
武昌库里写JAVA2 小时前
java设计模式 - 工厂方法模式
vue.js·spring boot·sql·layui·课程设计
bing.shao2 小时前
FerretDB 完美对接 MongoDB
数据库·mongodb
大学生资源网2 小时前
基于springboot的农村综合风貌展示平台设计与实现(源码+文档)
java·数据库·spring boot·后端·毕业设计·源码·springboot
严文文-Chris3 小时前
向量数据库选型完全指南
数据库