引言
在工业物联网时代,时序数据的高效处理成为企业数字化转型的核心挑战。Apache IoTDB 作为专为物联网设计的时序数据库,其分段分组聚合能力是数据处理的关键工具。
Apache IoTDB 分段聚合包含时间区间、差值、条件、会话及点数五种模式。时间区间支持固定间隔与滑动窗口;差值分段依据控制表达式与阈值分组;条件分段筛选连续满足条件的行;会话分段按时间间隔阈值分组;点数分段按固定数据点数量分组。支持 GROUP BY 子句,可结合 HAVING、ALIGN BY DEVICE 使用,适用于工业物联网时序数据的高效处理与分析。

在工业物联网时代,时序数据的高效处理成为企业数字化转型的核心挑战。Apache IoTDB 作为专为物联网设计的时序数据库,其分段分组聚合能力是数据处理的关键工具。
时间区间分段聚合是一种时序数据典型的查询方式,数据以高频进行采集,需要按照一定的时间间隔进行聚合计算,如计算每天的平均气温,需要将气温的序列按天进行分段,然后计算平均值。
在 IoTDB 中,聚合查询可以通过 GROUP BY 子句指定按照时间区间分段聚合。用户可以指定聚合的时间间隔和滑动步长,相关参数如下:
SELECT COUNT(status), MAX_VALUE(temperature)
FROM root.ln.wf01.wt01
GROUP BY ([2017-11-01T00:00:00, 2017-11-07T23:00:00), 1d);
这条查询的含义是:由于用户没有指定滑动步长,滑动步长将会被默认设置为跟时间间隔参数相同,也就是 1d。
上面这个例子的第一个参数是显示窗口参数,决定了最终的显示范围是 [2017-11-01T00:00:00, 2017-11-07T23:00:00)。
上面这个例子的第二个参数是划分时间轴的时间间隔参数,将 1d 当作划分间隔,显示窗口参数的起始时间当作分割原点,时间轴即被划分为连续的时间间隔:[0,1d), [1d, 2d), [2d, 3d) 等等。
然后系统将会用 WHERE 子句中的时间和值过滤条件以及 GROUP BY 语句中的第一个参数作为数据的联合过滤条件,获得满足所有过滤条件的数据(在这个例子里是在 [2017-11-01T00:00:00, 2017-11-07 T23:00:00) 这个时间范围的数据),并把这些数据映射到之前分割好的时间轴中(这个例子里是从 2017-11-01T00:00:00 到 2017-11-07T23:00:00:00 的每一天)。
每个时间间隔窗口内都有数据,SQL 执行后的结果会返回对应分组的聚合值。
SELECT COUNT(status), MAX_VALUE(temperature)
FROM root.ln.wf01.wt01
GROUP BY ([2017-11-01T00:00:00, 2017-11-07T23:00:00), 3h, 1d);
这条查询的含义是:由于用户指定了滑动步长为 1d,GROUP BY 语句执行时将会每次把时间间隔往后移动一天的步长,而不是默认的 3 小时。
也就意味着,我们想要取从 2017-11-01 到 2017-11-07 每一天的凌晨 0 点到凌晨 3 点的数据。
上面这个例子的第一个参数是显示窗口参数,决定了最终的显示范围是 [2017-11-01T00:00:00, 2017-11-07T23:00:00)。
上面这个例子的第二个参数是划分时间轴的时间间隔参数,将 3h 当作划分间隔,显示窗口参数的起始时间当作分割原点,时间轴即被划分为连续的时间间隔:[2017-11-01T00:00:00, 2017-11-01T03:00:00), [2017-11-02T00:00:00, 2017-11-02T03:00:00), [2017-11-03T00:00:00, 2017-11-03T03:00:00) 等等。
上面这个例子的第三个参数是每次时间间隔的滑动步长。
然后系统将会用 WHERE 子句中的时间和值过滤条件以及 GROUP BY 语句中的第一个参数作为数据的联合过滤条件,获得满足所有过滤条件的数据(在这个例子里是在 [2017-11-01T00:00:00, 2017-11-07 T23:00:00) 这个时间范围的数据),并把这些数据映射到之前分割好的时间轴中(这个例子里是从 2017-11-01T00:00:00 到 2017-11-07T23:00:00:00 的每一天的凌晨 0 点到凌晨 3 点)。
每个时间间隔窗口内都有数据,SQL 执行后的结果会返回对应分组的聚合值。
滑动步长可以小于聚合窗口,此时聚合窗口之间有重叠时间。
SELECT COUNT(status), MAX_VALUE(temperature)
FROM root.ln.wf01.wt01
GROUP BY ([2017-11-01T00:00:00, 2017-11-01T10:00:00), 4h, 2h);
SELECT COUNT(status)
FROM root.ln.wf01.wt01
WHERE time > 2017-11-01T01:00:00
GROUP BY ([2017-11-01T00:00:00, 2019-11-07T23:00:00), 1mo, 2mo);
这条查询的含义是:由于用户指定了滑动步长为 2mo,GROUP BY 语句执行时将会每次把时间间隔往后移动 2 个自然月的步长,而不是默认的 1 个自然月。
也就意味着,我们想要取从 2017-11-01 到 2019-11-07 每 2 个自然月的第一个月的数据。
上面这个例子的第一个参数是显示窗口参数,决定了最终的显示范围是 [2017-11-01T00:00:00, 2019-11-07T23:00:00)。
起始时间为 2017-11-01T00:00:00,滑动步长将会以起始时间作为标准按月递增,取当月的 1 号作为时间间隔的起始时间。
上面这个例子的第二个参数是划分时间轴的时间间隔参数,将 1mo 当作划分间隔,显示窗口参数的起始时间当作分割原点,时间轴即被划分为连续的时间间隔:[2017-11-01T00:00:00, 2017-12-01T00:00:00), [2018-02-01T00:00:00, 2018-03-01T00:00:00), [2018-05-03T00:00:00, 2018-06-01T00:00:00) 等等。
上面这个例子的第三个参数是每次时间间隔的滑动步长。
然后系统将会用 WHERE 子句中的时间和值过滤条件以及 GROUP BY 语句中的第一个参数作为数据的联合过滤条件,获得满足所有过滤条件的数据(在这个例子里是在 [2017-11-01T00:00:00, 2019-11-07T23:00:00) 这个时间范围的数据),并把这些数据映射到之前分割好的时间轴中(这个例子里是从 2017-11-01T00:00:00 到 2019-11-07T23:00:00:00 的每两个自然月的第一个月)。
每个时间间隔窗口内都有数据,SQL 执行后的结果会返回对应分组的聚合值。
SELECT COUNT(status)
FROM root.ln.wf01.wt01
GROUP BY ([2017-10-31T00:00:00, 2019-11-07T23:00:00), 1mo, 2mo);
这条查询的含义是:由于用户指定了滑动步长为 2mo,GROUP BY 语句执行时将会每次把时间间隔往后移动 2 个自然月的步长,而不是默认的 1 个自然月。
也就意味着,我们想要取从 2017-10-31 到 2019-11-07 每 2 个自然月的第一个月的数据。
与上述示例不同的是起始时间为 2017-10-31T00:00:00,滑动步长将会以起始时间作为标准按月递增,取当月的 31 号(即最后一天)作为时间间隔的起始时间。若起始时间设置为 30 号,滑动步长会将时间间隔的起始时间设置为当月 30 号,若不存在则为最后一天。
上面这个例子的第一个参数是显示窗口参数,决定了最终的显示范围是 [2017-10-31T00:00:00, 2019-11-07T23:00:00)。
上面这个例子的第二个参数是划分时间轴的时间间隔参数,将 1mo 当作划分间隔,显示窗口参数的起始时间当作分割原点,时间轴即被划分为连续的时间间隔:[2017-10-31T00:00:00, 2017-11-31T00:00:00), [2018-02-31T00:00:00, 2018-03-31T00:00:00), [2018-05-31T00:00:00, 2018-06-31T00:00:00) 等等。
上面这个例子的第三个参数是每次时间间隔的滑动步长。
然后系统将会用 WHERE 子句中的时间和值过滤条件以及 GROUP BY 语句中的第一个参数作为数据的联合过滤条件,获得满足所有过滤条件的数据(在这个例子里是在 [2017-10-31T00:00:00, 2019-11-07T23:00:00) 这个时间范围的数据),并把这些数据映射到之前分割好的时间轴中(这个例子里是从 2017-10-31T00:00:00 到 2019-11-07T23:00:00:00 的每两个自然月的第一个月)。
每个时间间隔窗口内都有数据,SQL 执行后的结果会返回对应分组的聚合值。
每个区间的结果时间戳为区间右端点。
SELECT COUNT(status)
FROM root.ln.wf01.wt01
GROUP BY ((2017-11-01T00:00:00, 2017-11-07T23:00:00], 1d);
这条查询语句的时间区间是左开右闭的,结果中不会包含时间点 2017-11-01 的数据,但是会包含时间点 2017-11-07 的数据。
IoTDB 支持通过 GROUP BY VARIATION 语句来根据差值进行分组。GROUP BY VARIATION 会将第一个点作为一个组的基准点,每个新的数据在按照给定规则与基准点进行差值运算后,如果差值小于给定的阈值则将该新点归于同一组,否则结束当前分组,以这个新的数据为新的基准点开启新的分组。
该分组方式不会重叠,且没有固定的开始结束时间。其子句语法如下:
GROUP BY VARIATION(controlExpression[,delta][,ignoreNull=true/false])
不同的参数含义如下:
controlExpression:分组所参照的值,可以是查询数据中的某一列或是多列的表达式(多列表达式计算后仍为一个值,使用多列表达式时指定的列必须都为数值列),差值便是根据数据的 controlExpression 的差值运算。delta:分组所使用的阈值,同一分组中每个点的 controlExpression 对应的值与该组中基准点对应值的差值都小于 delta。当 delta=0 时,相当于一个等值分组,所有连续且 expression 值相同的数据将被分到一组。ignoreNull:用于指定 controlExpression 的值为 null 时对数据的处理方式,当 ignoreNull 为 false 时,该 null 值会被视为新的值,ignoreNull 为 true 时,则直接跳过对应的点。在 delta 取不同值时,controlExpression 支持的返回数据类型以及当 ignoreNull 为 false 时对于 null 值的处理方式可以参考官方文档说明。
下图为差值分段的一个分段方式示意图,与组中第一个数据的控制列值的差值在 delta 内的控制列对应的点属于相同的分组。
- controlExpression 的结果应该为唯一值,如果使用通配符拼接后出现多列,则报错。
- 对于一个分组,默认 Time 列输出分组的开始时间,查询时可以使用 SELECT __endTime 的方式来使得结果输出分组的结束时间。
- 与 ALIGN BY DEVICE 搭配使用时会对每个 device 进行单独的分组操作。
- 当没有指定 delta 和 ignoreNull 时,delta 默认为 0,ignoreNull 默认为 true。
- 当前暂不支持与 GROUP BY LEVEL 搭配使用。
使用如下的原始数据,后面会给出几个事件分段查询的使用样例。
SELECT __endTime, AVG(s1), COUNT(s2), SUM(s3)
FROM root.sg.d
GROUP BY VARIATION(s6);
得到如下的查询结果,这里忽略了 s6 为 null 的行。
当指定 ignoreNull 为 false 时,会将 s6 为 null 的数据也考虑进来。
SELECT __endTime, AVG(s1), COUNT(s2), SUM(s3)
FROM root.sg.d
GROUP BY VARIATION(s6, ignoreNull=false);
SELECT __endTime, AVG(s1), COUNT(s2), SUM(s3)
FROM root.sg.d
GROUP BY VARIATION(s6, 4);
group by 子句中的 controlExpression 同样支持列的表达式。
SELECT __endTime, AVG(s1), COUNT(s2), SUM(s3)
FROM root.sg.d
GROUP BY VARIATION(s6+s5, 10);
当需要根据指定条件对数据进行筛选,并将连续的符合条件的行分为一组进行聚合运算时,可以使用 GROUP BY CONDITION 的分段方式;不满足给定条件的行因为不属于任何分组会被直接简单忽略。
其语法定义如下:
GROUP BY CONDITION(predict,[keep>/>=/=/<=/<]threshold,[,ignoreNull=true/false])
predict:返回 boolean 数据类型的合法表达式,用于分组的筛选。keep[>/>=/=/<=/<]threshold:keep 表达式用来指定形成分组所需要连续满足 predict 条件的数据行数,只有行数满足 keep 表达式的分组才会被输出。keep 表达式由一个'keep'字符串和 long 类型的 threshold 组合或者是单独的 long 类型数据构成。ignoreNull=true/false:用于指定遇到 predict 为 null 的数据行时的处理方式,为 true 则跳过该行,为 false 则结束当前分组。
- keep 条件在查询中是必需的,但可以省略掉 keep 字符串给出一个 long 类型常数,默认为 keep=该 long 型常数的等于条件。
- ignoreNull 默认为 true。
- 对于一个分组,默认 Time 列输出分组的开始时间,查询时可以使用 SELECT __endTime 的方式来使得结果输出分组的结束时间。
- 与 ALIGN BY DEVICE 搭配使用时会对每个 device 进行单独的分组操作。
- 当前暂不支持与 GROUP BY LEVEL 搭配使用。
对于如下原始数据,下面会给出几个查询样例:
| Time | root.sg.beijing.car01.soc | root.sg.beijing.car01.charging_status | root.sg.beijing.car01.vehicle_status |
|---|---|---|---|
| 1970-01-01T08:00:00.001+08:00 | 14.0 | 1 | 1 |
| 1970-01-01T08:00:00.002+08:00 | 16.0 | 1 | 1 |
| 1970-01-01T08:00:00.003+08:00 | 16.0 | 0 | 1 |
| 1970-01-01T08:00:00.004+08:00 | 16.0 | 0 | 1 |
| 1970-01-01T08:00:00.005+08:00 | 18.0 | 1 | 1 |
| 1970-01-01T08:00:00.006+08:00 | 24.0 | 1 | 1 |
| 1970-01-01T08:00:00.007+08:00 | 36.0 | 1 | 1 |
| 1970-01-01T08:00:00.008+08:00 | 36.0 | null | 1 |
| 1970-01-01T08:00:00.009+08:00 | 45.0 | 1 | 1 |
| 1970-01-01T08:00:00.010+08:00 | 60.0 | 1 | 1 |
查询至少连续两行以上的 charging_status=1 的数据,sql 语句如下:
SELECT MAX_TIME(charging_status), COUNT(vehicle_status), LAST_VALUE(soc)
FROM root.**
GROUP BY CONDITION(charging_status=1, KEEP>=2, ignoreNull=true);
结果:
| Time | MAX_TIME(root.sg.beijing.car01.charging_status) | COUNT(root.sg.beijing.car01.vehicle_status) | LAST_VALUE(root.sg.beijing.car01.soc) |
|---|---|---|---|
| 1970-01-01T08:00:00.001+08:00 | 2 | 2 | 16.0 |
| 1970-01-01T08:00:00.005+08:00 | 10 | 5 | 60.0 |
当设置 ignoreNull 为 false 时,遇到 null 值为将其视为一个不满足条件的行,会结束正在计算的分组。
SELECT MAX_TIME(charging_status), COUNT(vehicle_status), LAST_VALUE(soc)
FROM root.**
GROUP BY CONDITION(charging_status=1, KEEP>=2, ignoreNull=false);
结果:
| Time | MAX_TIME(root.sg.beijing.car01.charging_status) | COUNT(root.sg.beijing.car01.vehicle_status) | LAST_VALUE(root.sg.beijing.car01.soc) |
|---|---|---|---|
| 1970-01-01T08:00:00.001+08:00 | 2 | 2 | 16.0 |
| 1970-01-01T08:00:00.005+08:00 | 7 | 3 | 36.0 |
| 1970-01-01T08:00:00.009+08:00 | 10 | 2 | 60.0 |
GROUP BY SESSION 可以根据时间列的间隔进行分组,在结果集的时间列中,时间间隔小于等于设定阈值的数据会被分为一组。例如在工业场景中,设备并不总是连续运行,GROUP BY SESSION 会将设备每次接入会话所产生的数据分为一组。
其语法定义如下:
GROUP BY SESSION(timeInterval)
timeInterval:设定的时间差阈值,当两条数据时间列的差值大于该阈值,则会给数据创建一个新的分组。下图为 group by session 下的一个分组示意图。
- 对于一个分组,默认 Time 列输出分组的开始时间,查询时可以使用 SELECT __endTime 的方式来使得结果输出分组的结束时间。
- 与 ALIGN BY DEVICE 搭配使用时会对每个 device 进行单独的分组操作。
- 当前暂不支持与 GROUP BY LEVEL 搭配使用。
对于下面的原始数据,给出几个查询样例。
可以按照不同的时间单位设定时间间隔,sql 语句如下:
SELECT __endTime, COUNT(*)
FROM root.**
GROUP BY SESSION(1d);
结果:
| Time | __endTime | COUNT(root.ln.wf02.wt01.temperature) | COUNT(root.ln.wf02.wt01.hardware) | COUNT(root.ln.wf02.wt01.status) |
|---|---|---|---|---|
| 1970-01-01T08:00:01.000+08:00 | 1970-01-01T08:08:00.000+08:00 | 15 | 18 | 15 |
| 1970-01-02T08:08:01.000+08:00 | 1970-01-02T08:08:05.000+08:00 | 5 | 5 | 5 |
也可以和 HAVING、ALIGN BY DEVICE 共同使用。
SELECT __endTime, SUM(hardware)
FROM root.ln.wf02.wt01
GROUP BY SESSION(50s)
HAVING SUM(hardware)>0
ALIGN BY DEVICE;
得到如下结果,其中排除了 sum(hardware) 为 0 的部分。
GROUP BY COUNT 可以根据点数分组进行聚合运算,将连续的指定数量数据点分为一组,即按照固定的点数进行分组。
其语法定义如下:
GROUP BY COUNT(controlExpression, size[,ignoreNull=true/false])
controlExpression:计数参照的对象,可以是结果集的任意列或是列的表达式。size:一个组中数据点的数量,每 size 个数据点会被分到同一个组。ignoreNull=true/false:是否忽略 controlExpression 为 null 的数据点,当 ignoreNull 为 true 时,在计数时会跳过 controlExpression 结果为 null 的数据点。
- 对于一个分组,默认 Time 列输出分组的开始时间,查询时可以使用 SELECT __endTime 的方式来使得结果输出分组的结束时间。
- 与 ALIGN BY DEVICE 搭配使用时会对每个 device 进行单独的分组操作。
- 当前暂不支持与 GROUP BY LEVEL 搭配使用。
- 当一个分组内最终的点数不满足 size 的数量时,不会输出该分组的结果。
对于下面的原始数据,给出几个查询样例。
SELECT COUNT(charging_status), FIRST_VALUE(soc)
FROM root.sg
GROUP BY COUNT(charging_status, 5);
得到如下结果,其中由于第二个 1970-01-01T08:00:00.006+08:00 到 1970-01-01T08:00:00.010+08:00 的窗口中包含四个点,不符合 size = 5 的条件,因此不被输出。
而当使用 ignoreNull 将 null 值也考虑进来时,可以得到两个点计数为 5 的窗口,sql 如下。
SELECT COUNT(charging_status), FIRST_VALUE(soc)
FROM root.sg
GROUP BY COUNT(charging_status, 5, ignoreNull=false);
结果:
Apache IoTDB 的分段聚合能力构建了从边缘到云端的完整数据价值链。通过五大分段策略实现时间维度的智能切片实现业务维度的深度挖掘,形成了高吞吐、低延迟的时序数据处理闭环。掌握这些核心技术,企业可构建自主可控的工业物联网数据中台。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
在线格式化和美化您的 SQL 查询(它支持各种 SQL 方言)。 在线工具,SQL 美化和格式化在线工具,online
解析 INSERT 等受限 SQL,导出为 CSV、JSON、XML、YAML、HTML 表格(见页内语法说明)。 在线工具,SQL转CSV/JSON/XML在线工具,online
CSV 与 JSON/XML/HTML/TSV/SQL 等互转,单页多 Tab。 在线工具,CSV 工具包在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online