分段分组聚合
时间区间分段聚合
分段聚合是一种时序数据典型的查询方式,数据以高频进行采集,需要按照一定的时间间隔进行聚合计算,如计算每天的平均气温,需要将气温的序列按天进行分段,然后计算平均值。
在 IoTDB 中,聚合查询可以通过 GROUP BY 子句指定按照时间区间分段聚合。用户可以指定聚合的时间间隔和滑动步长,相关参数如下:
- 参数 1:时间轴显示时间窗口大小
- 参数 2:聚合窗口的大小(必须为正数)
- 参数 3:聚合窗口的滑动步长(可选,默认与聚合窗口大小相同)
下图中指出了这三个参数的含义:

接下来,我们给出几个典型例子:
未指定滑动步长的时间区间分组聚合查询
对应的 SQL 语句是:
1. 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 执行后的结果集如下所示:
1. +-----------------------------+-------------------------------+----------------------------------------+
2. | Time|count(root.ln.wf01.wt01.status)|max_value(root.ln.wf01.wt01.temperature)|
3. +-----------------------------+-------------------------------+----------------------------------------+
4. |2017-11-01T00:00:00.000+08:00| 1440| 26.0|
5. |2017-11-02T00:00:00.000+08:00| 1440| 26.0|
6. |2017-11-03T00:00:00.000+08:00| 1440| 25.99|
7. |2017-11-04T00:00:00.000+08:00| 1440| 26.0|
8. |2017-11-05T00:00:00.000+08:00| 1440| 26.0|
9. |2017-11-06T00:00:00.000+08:00| 1440| 25.99|
10. |2017-11-07T00:00:00.000+08:00| 1380| 26.0|
11. +-----------------------------+-------------------------------+----------------------------------------+
12. Total line number = 7
13. It costs 0.024s
指定滑动步长的时间区间分组聚合查询
对应的 SQL 语句是:
1. select count(status), max_value(temperature) from root.ln.wf01.wt01 group by ([2017-11-01 00:00:00, 2017-11-07 23: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 执行后的结果集如下所示:
1. +-----------------------------+-------------------------------+----------------------------------------+
2. | Time|count(root.ln.wf01.wt01.status)|max_value(root.ln.wf01.wt01.temperature)|
3. +-----------------------------+-------------------------------+----------------------------------------+
4. |2017-11-01T00:00:00.000+08:00| 180| 25.98|
5. |2017-11-02T00:00:00.000+08:00| 180| 25.98|
6. |2017-11-03T00:00:00.000+08:00| 180| 25.96|
7. |2017-11-04T00:00:00.000+08:00| 180| 25.96|
8. |2017-11-05T00:00:00.000+08:00| 180| 26.0|
9. |2017-11-06T00:00:00.000+08:00| 180| 25.85|
10. |2017-11-07T00:00:00.000+08:00| 180| 25.99|
11. +-----------------------------+-------------------------------+----------------------------------------+
12. Total line number = 7
13. It costs 0.006s
滑动步长可以小于聚合窗口,此时聚合窗口之间有重叠时间(类似于一个滑动窗口)。
例如 SQL:
1. select count(status), max_value(temperature) from root.ln.wf01.wt01 group by ([2017-11-01 00:00:00, 2017-11-01 10:00:00), 4h, 2h);
SQL 执行后的结果集如下所示:
1. +-----------------------------+-------------------------------+----------------------------------------+
2. | Time|count(root.ln.wf01.wt01.status)|max_value(root.ln.wf01.wt01.temperature)|
3. +-----------------------------+-------------------------------+----------------------------------------+
4. |2017-11-01T00:00:00.000+08:00| 180| 25.98|
5. |2017-11-01T02:00:00.000+08:00| 180| 25.98|
6. |2017-11-01T04:00:00.000+08:00| 180| 25.96|
7. |2017-11-01T06:00:00.000+08:00| 180| 25.96|
8. |2017-11-01T08:00:00.000+08:00| 180| 26.0|
9. +-----------------------------+-------------------------------+----------------------------------------+
10. Total line number = 5
11. It costs 0.006s
按照自然月份的时间区间分组聚合查询
对应的 SQL 语句是:
1. 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 执行后的结果集如下所示:
1. +-----------------------------+-------------------------------+
2. | Time|count(root.ln.wf01.wt01.status)|
3. +-----------------------------+-------------------------------+
4. |2017-11-01T00:00:00.000+08:00| 259|
5. |2018-01-01T00:00:00.000+08:00| 250|
6. |2018-03-01T00:00:00.000+08:00| 259|
7. |2018-05-01T00:00:00.000+08:00| 251|
8. |2018-07-01T00:00:00.000+08:00| 242|
9. |2018-09-01T00:00:00.000+08:00| 225|
10. |2018-11-01T00:00:00.000+08:00| 216|
11. |2019-01-01T00:00:00.000+08:00| 207|
12. |2019-03-01T00:00:00.000+08:00| 216|
13. |2019-05-01T00:00:00.000+08:00| 207|
14. |2019-07-01T00:00:00.000+08:00| 199|
15. |2019-09-01T00:00:00.000+08:00| 181|
16. |2019-11-01T00:00:00.000+08:00| 60|
17. +-----------------------------+-------------------------------+
对应的 SQL 语句是:
1. 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 执行后的结果集如下所示:
1. +-----------------------------+-------------------------------+
2. | Time|count(root.ln.wf01.wt01.status)|
3. +-----------------------------+-------------------------------+
4. |2017-10-31T00:00:00.000+08:00| 251|
5. |2017-12-31T00:00:00.000+08:00| 250|
6. |2018-02-28T00:00:00.000+08:00| 259|
7. |2018-04-30T00:00:00.000+08:00| 250|
8. |2018-06-30T00:00:00.000+08:00| 242|
9. |2018-08-31T00:00:00.000+08:00| 225|
10. |2018-10-31T00:00:00.000+08:00| 216|
11. |2018-12-31T00:00:00.000+08:00| 208|
12. |2019-02-28T00:00:00.000+08:00| 216|
13. |2019-04-30T00:00:00.000+08:00| 208|
14. |2019-06-30T00:00:00.000+08:00| 199|
15. |2019-08-31T00:00:00.000+08:00| 181|
16. |2019-10-31T00:00:00.000+08:00| 69|
17. +-----------------------------+-------------------------------+
左开右闭区间
每个区间的结果时间戳为区间右端点,对应的 SQL 语句是:
1. 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 的数据。
SQL 执行后的结果集如下所示:
1. +-----------------------------+-------------------------------+
2. | Time|count(root.ln.wf01.wt01.status)|
3. +-----------------------------+-------------------------------+
4. |2017-11-02T00:00:00.000+08:00| 1440|
5. |2017-11-03T00:00:00.000+08:00| 1440|
6. |2017-11-04T00:00:00.000+08:00| 1440|
7. |2017-11-05T00:00:00.000+08:00| 1440|
8. |2017-11-06T00:00:00.000+08:00| 1440|
9. |2017-11-07T00:00:00.000+08:00| 1440|
10. |2017-11-07T23:00:00.000+08:00| 1380|
11. +-----------------------------+-------------------------------+
12. Total line number = 7
13. It costs 0.004s
与分组聚合混合使用
通过定义 LEVEL 来统计指定层级下的数据点个数。
例如:
统计降采样后的数据点个数
1. select count(status) from root.ln.wf01.wt01 group by ((2017-11-01T00:00:00, 2017-11-07T23:00:00],1d), level=1;
结果:
1. +-----------------------------+-------------------------+
2. | Time|COUNT(root.ln.*.*.status)|
3. +-----------------------------+-------------------------+
4. |2017-11-02T00:00:00.000+08:00| 1440|
5. |2017-11-03T00:00:00.000+08:00| 1440|
6. |2017-11-04T00:00:00.000+08:00| 1440|
7. |2017-11-05T00:00:00.000+08:00| 1440|
8. |2017-11-06T00:00:00.000+08:00| 1440|
9. |2017-11-07T00:00:00.000+08:00| 1440|
10. |2017-11-07T23:00:00.000+08:00| 1380|
11. +-----------------------------+-------------------------+
12. Total line number = 7
13. It costs 0.006s
加上滑动 Step 的降采样后的结果也可以汇总
1. 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. +-----------------------------+-------------------------+
2. | Time|COUNT(root.ln.*.*.status)|
3. +-----------------------------+-------------------------+
4. |2017-11-01T00:00:00.000+08:00| 180|
5. |2017-11-02T00:00:00.000+08:00| 180|
6. |2017-11-03T00:00:00.000+08:00| 180|
7. |2017-11-04T00:00:00.000+08:00| 180|
8. |2017-11-05T00:00:00.000+08:00| 180|
9. |2017-11-06T00:00:00.000+08:00| 180|
10. |2017-11-07T00:00:00.000+08:00| 180|
11. +-----------------------------+-------------------------+
12. Total line number = 7
13. It costs 0.004s
路径层级分组聚合
在时间序列层级结构中,分层聚合查询用于对某一层级下同名的序列进行聚合查询。
- 使用
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 序列的数据点个数,使用以下查询:
1. select count(status) from root.** group by level = 1
运行结果为:
1. +-------------------------+---------------------------+
2. |count(root.ln.*.*.status)|count(root.sgcc.*.*.status)|
3. +-------------------------+---------------------------+
4. | 20160| 10080|
5. +-------------------------+---------------------------+
6. Total line number = 1
7. It costs 0.003s
示例2: 统计不同设备下 status 序列的数据点个数,可以规定 level = 3,
1. select count(status) from root.** group by level = 3
运行结果为:
1. +---------------------------+---------------------------+
2. |count(root.*.*.wt01.status)|count(root.*.*.wt02.status)|
3. +---------------------------+---------------------------+
4. | 20160| 10080|
5. +---------------------------+---------------------------+
6. Total line number = 1
7. It costs 0.003s
注意,这时会将 database ln 和 sgcc 下名为 wt01 的设备视为同名设备聚合在一起。
示例3: 统计不同 database 下的不同设备中 status 序列的数据点个数,可以使用以下查询:
1. select count(status) from root.** group by level = 1, 3
运行结果为:
1. +----------------------------+----------------------------+------------------------------+
2. |count(root.ln.*.wt01.status)|count(root.ln.*.wt02.status)|count(root.sgcc.*.wt01.status)|
3. +----------------------------+----------------------------+------------------------------+
4. | 10080| 10080| 10080|
5. +----------------------------+----------------------------+------------------------------+
6. Total line number = 1
7. It costs 0.003s
示例4: 查询所有序列下温度传感器 temperature 的最大值,可以使用下列查询语句:
1. select max_value(temperature) from root.** group by level = 0
运行结果:
1. +---------------------------------+
2. |max_value(root.*.*.*.temperature)|
3. +---------------------------------+
4. | 26.0|
5. +---------------------------------+
6. Total line number = 1
7. It costs 0.013s
示例5: 上面的查询都是针对某一个传感器,特别地,如果想要查询某一层级下所有传感器拥有的总数据点数,则需要显式规定测点为 *
1. select count(*) from root.ln.** group by level = 2
运行结果:
1. +----------------------+----------------------+
2. |count(root.*.wf01.*.*)|count(root.*.wf02.*.*)|
3. +----------------------+----------------------+
4. | 20160| 20160|
5. +----------------------+----------------------+
6. Total line number = 1
7. It costs 0.013s
标签分组聚合
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 设备目前处于调试阶段,还未被分配到具体的城市和车间,所以其相应的标签值为空值。
1. create database root.factory1;
2. create timeseries root.factory1.d1.temperature with datatype=FLOAT tags(city=Beijing, workshop=w1);
3. create timeseries root.factory1.d2.temperature with datatype=FLOAT tags(city=Beijing, workshop=w1);
4. create timeseries root.factory1.d3.temperature with datatype=FLOAT tags(city=Beijing, workshop=w2);
5. create timeseries root.factory1.d4.temperature with datatype=FLOAT tags(city=Beijing, workshop=w2);
6. create timeseries root.factory1.d5.temperature with datatype=FLOAT tags(city=Shanghai, workshop=w1);
7. create timeseries root.factory1.d6.temperature with datatype=FLOAT tags(city=Shanghai, workshop=w1);
8. create timeseries root.factory1.d7.temperature with datatype=FLOAT tags(city=Shanghai, workshop=w2);
9. create timeseries root.factory1.d8.temperature with datatype=FLOAT;
10. create timeseries root.factory1.d9.temperature with datatype=FLOAT;
12. insert into root.factory1.d1(time, temperature) values(1000, 104.0);
13. insert into root.factory1.d1(time, temperature) values(3000, 104.2);
14. insert into root.factory1.d1(time, temperature) values(5000, 103.3);
15. insert into root.factory1.d1(time, temperature) values(7000, 104.1);
17. insert into root.factory1.d2(time, temperature) values(1000, 104.4);
18. insert into root.factory1.d2(time, temperature) values(3000, 103.7);
19. insert into root.factory1.d2(time, temperature) values(5000, 103.3);
20. insert into root.factory1.d2(time, temperature) values(7000, 102.9);
22. insert into root.factory1.d3(time, temperature) values(1000, 103.9);
23. insert into root.factory1.d3(time, temperature) values(3000, 103.8);
24. insert into root.factory1.d3(time, temperature) values(5000, 102.7);
25. insert into root.factory1.d3(time, temperature) values(7000, 106.9);
27. insert into root.factory1.d4(time, temperature) values(1000, 103.9);
28. insert into root.factory1.d4(time, temperature) values(5000, 102.7);
29. insert into root.factory1.d4(time, temperature) values(7000, 106.9);
31. insert into root.factory1.d5(time, temperature) values(1000, 112.9);
32. insert into root.factory1.d5(time, temperature) values(7000, 113.0);
34. insert into root.factory1.d6(time, temperature) values(1000, 113.9);
35. insert into root.factory1.d6(time, temperature) values(3000, 113.3);
36. insert into root.factory1.d6(time, temperature) values(5000, 112.7);
37. insert into root.factory1.d6(time, temperature) values(7000, 112.3);
39. insert into root.factory1.d7(time, temperature) values(1000, 101.2);
40. insert into root.factory1.d7(time, temperature) values(3000, 99.3);
41. insert into root.factory1.d7(time, temperature) values(5000, 100.1);
42. insert into root.factory1.d7(time, temperature) values(7000, 99.8);
44. insert into root.factory1.d8(time, temperature) values(1000, 50.0);
45. insert into root.factory1.d8(time, temperature) values(3000, 52.1);
46. insert into root.factory1.d8(time, temperature) values(5000, 50.1);
47. insert into root.factory1.d8(time, temperature) values(7000, 50.5);
49. insert into root.factory1.d9(time, temperature) values(1000, 50.3);
50. insert into root.factory1.d9(time, temperature) values(3000, 52.1);
单标签聚合查询
用户想统计该工厂每个地区的设备的温度的平均值,可以使用如下查询语句
1. SELECT AVG(temperature) FROM root.factory1.** GROUP BY TAGS(city);
该查询会将具有同一个 city 标签值的时间序列的所有满足查询条件的点做平均值计算,计算结果如下
1. +--------+------------------+
2. | city| avg(temperature)|
3. +--------+------------------+
4. | Beijing|104.04666697184244|
5. |Shanghai|107.85000076293946|
6. | NULL| 50.84999910990397|
7. +--------+------------------+
8. Total line number = 3
9. It costs 0.231s
从结果集中可以看到,和时间区间聚合、按层次聚合相比,标签聚合的查询结果的不同点是:
- 标签聚合查询的聚合结果不会再做去星号展开,而是将多个时间序列的数据作为一个整体进行聚合计算。
- 标签聚合查询除了输出聚合结果列,还会输出聚合标签的键值列。该列的列名为聚合指定的标签键,列的值则为所有查询的时间序列中出现的该标签的值。 如果某些时间序列未设置该标签,则在键值列中有一行单独的
NULL,代表未设置标签的所有时间序列数据的聚合结果。
多标签聚合查询
除了基本的单标签聚合查询外,还可以按顺序指定多个标签进行聚合计算。
例如,用户想统计每个城市的每个车间内设备的平均温度。但因为各个城市的车间名称有可能相同,所以不能直接按照 workshop 做标签聚合。必须要先按照城市,再按照车间处理。
SQL 语句如下
1. SELECT avg(temperature) FROM root.factory1.** GROUP BY TAGS(city, workshop);
查询结果如下
1. +--------+--------+------------------+
2. | city|workshop| avg(temperature)|
3. +--------+--------+------------------+
4. | NULL| NULL| 50.84999910990397|
5. |Shanghai| w1|113.01666768391927|
6. | Beijing| w2| 104.4000004359654|
7. |Shanghai| w2|100.10000038146973|
8. | Beijing| w1|103.73750019073486|
9. +--------+--------+------------------+
10. Total line number = 5
11. It costs 0.027s
从结果集中可以看到,和单标签聚合相比,多标签聚合的查询结果会根据指定的标签顺序,输出相应标签的键值列。
基于时间区间的标签聚合查询
按照时间区间聚合是时序数据库中最常用的查询需求之一。IoTDB 在基于时间区间的聚合基础上,支持进一步按照标签进行聚合查询。
例如,用户想统计时间 [1000, 10000) 范围内,每个城市每个车间中的设备每 5 秒内的平均温度。
SQL 语句如下
1. SELECT AVG(temperature) FROM root.factory1.** GROUP BY ([1000, 10000), 5s), TAGS(city, workshop);
查询结果如下
1. +-----------------------------+--------+--------+------------------+
2. | Time| city|workshop| avg(temperature)|
3. +-----------------------------+--------+--------+------------------+
4. |1970-01-01T08:00:01.000+08:00| NULL| NULL| 50.91999893188476|
5. |1970-01-01T08:00:01.000+08:00|Shanghai| w1|113.20000076293945|
6. |1970-01-01T08:00:01.000+08:00| Beijing| w2| 103.4|
7. |1970-01-01T08:00:01.000+08:00|Shanghai| w2| 100.1999994913737|
8. |1970-01-01T08:00:01.000+08:00| Beijing| w1|103.81666692097981|
9. |1970-01-01T08:00:06.000+08:00| NULL| NULL| 50.5|
10. |1970-01-01T08:00:06.000+08:00|Shanghai| w1| 112.6500015258789|
11. |1970-01-01T08:00:06.000+08:00| Beijing| w2| 106.9000015258789|
12. |1970-01-01T08:00:06.000+08:00|Shanghai| w2| 99.80000305175781|
13. |1970-01-01T08:00:06.000+08:00| Beijing| w1| 103.5|
14. +-----------------------------+--------+--------+------------------+
和标签聚合相比,基于时间区间的标签聚合的查询会首先按照时间区间划定聚合范围,在时间区间内部再根据指定的标签顺序,进行相应数据的聚合计算。在输出的结果集中,会包含一列时间列,该时间列值的含义和时间区间聚合查询的相同。
标签聚合查询的限制
由于标签聚合功能仍然处于开发阶段,目前有如下未实现功能。
- 暂不支持
HAVING子句过滤查询结果。- 暂不支持结果按照标签值排序。
- 暂不支持
LIMIT,OFFSET,SLIMIT,SOFFSET。- 暂不支持
ALIGN BY DEVICE。- 暂不支持聚合函数内部包含表达式,例如
count(s+1)。- 不支持值过滤条件聚合,和分层聚合查询行为保持一致。
差值分段聚合
IoTDB支持通过GROUP BY VARIATION语句来根据差值进行分组。GROUP BY VARIATION会将第一个点作为一个组的基准点,每个新的数据在按照给定规则与基准点进行差值运算后, 如果差值小于给定的阈值则将该新点归于同一组,否则结束当前分组,以这个新的数据为新的基准点开启新的分组。 该分组方式不会重叠,且没有固定的开始结束时间。其子句语法如下:
1. 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支持的返回类型 | ignoreNull=false时对于Null值的处理 |
|---|---|---|
| delta!=0 | INT32、INT64、FLOAT、DOUBLE | 若正在维护分组的值不为null,null视为无穷大/无穷小,结束当前分组。连续的null视为差值相等的值,会被分配在同一个分组 |
| delta=0 | TEXT、BINARY、INT32、INT64、FLOAT、DOUBLE | null被视为新分组中的新值,连续的null属于相同的分组 |
下图为差值分段的一个分段方式示意图,与组中第一个数据的控制列值的差值在delta内的控制列对应的点属于相同的分组。

使用注意事项
controlExpression的结果应该为唯一值,如果使用通配符拼接后出现多列,则报错。- 对于一个分组,默认Time列输出分组的开始时间,查询时可以使用select
__endTime的方式来使得结果输出分组的结束时间。 - 与
ALIGN BY DEVICE搭配使用时会对每个device进行单独的分组操作。 - 当没有指定
delta和ignoreNull时,delta默认为0,ignoreNull默认为true。 - 当前暂不支持与
GROUP BY LEVEL搭配使用。
使用如下的原始数据,接下来会给出几个事件分段查询的使用样例
1. +-----------------------------+-------+-------+-------+--------+-------+-------+
2. | Time| s1| s2| s3| s4| s5| s6|
3. +-----------------------------+-------+-------+-------+--------+-------+-------+
4. |1970-01-01T08:00:00.000+08:00| 4.5| 9.0| 0.0| 45.0| 9.0| 8.25|
5. |1970-01-01T08:00:00.010+08:00| null| 19.0| 10.0| 145.0| 19.0| 8.25|
6. |1970-01-01T08:00:00.020+08:00| 24.5| 29.0| null| 245.0| 29.0| null|
7. |1970-01-01T08:00:00.030+08:00| 34.5| null| 30.0| 345.0| null| null|
8. |1970-01-01T08:00:00.040+08:00| 44.5| 49.0| 40.0| 445.0| 49.0| 8.25|
9. |1970-01-01T08:00:00.050+08:00| null| 59.0| 50.0| 545.0| 59.0| 6.25|
10. |1970-01-01T08:00:00.060+08:00| 64.5| 69.0| 60.0| 645.0| 69.0| null|
11. |1970-01-01T08:00:00.070+08:00| 74.5| 79.0| null| null| 79.0| 3.25|
12. |1970-01-01T08:00:00.080+08:00| 84.5| 89.0| 80.0| 845.0| 89.0| 3.25|
13. |1970-01-01T08:00:00.090+08:00| 94.5| 99.0| 90.0| 945.0| 99.0| 3.25|
14. |1970-01-01T08:00:00.150+08:00| 66.5| 77.0| 90.0| 945.0| 99.0| 9.25|
15. +-----------------------------+-------+-------+-------+--------+-------+-------+
delta=0时的等值事件分段
使用如下sql语句
1. select __endTime, avg(s1), count(s2), sum(s3) from root.sg.d group by variation(s6)
得到如下的查询结果,这里忽略了s6为null的行
1. +-----------------------------+-----------------------------+-----------------+-------------------+-----------------+
2. | Time| __endTime|avg(root.sg.d.s1)|count(root.sg.d.s2)|sum(root.sg.d.s3)|
3. +-----------------------------+-----------------------------+-----------------+-------------------+-----------------+
4. |1970-01-01T08:00:00.000+08:00|1970-01-01T08:00:00.040+08:00| 24.5| 3| 50.0|
5. |1970-01-01T08:00:00.050+08:00|1970-01-01T08:00:00.050+08:00| null| 1| 50.0|
6. |1970-01-01T08:00:00.070+08:00|1970-01-01T08:00:00.090+08:00| 84.5| 3| 170.0|
7. |1970-01-01T08:00:00.150+08:00|1970-01-01T08:00:00.150+08:00| 66.5| 1| 90.0|
8. +-----------------------------+-----------------------------+-----------------+-------------------+-----------------+
当指定ignoreNull为false时,会将s6为null的数据也考虑进来
1. select __endTime, avg(s1), count(s2), sum(s3) from root.sg.d group by variation(s6, ignoreNull=false)
得到如下的结果
1. +-----------------------------+-----------------------------+-----------------+-------------------+-----------------+
2. | Time| __endTime|avg(root.sg.d.s1)|count(root.sg.d.s2)|sum(root.sg.d.s3)|
3. +-----------------------------+-----------------------------+-----------------+-------------------+-----------------+
4. |1970-01-01T08:00:00.000+08:00|1970-01-01T08:00:00.010+08:00| 4.5| 2| 10.0|
5. |1970-01-01T08:00:00.020+08:00|1970-01-01T08:00:00.030+08:00| 29.5| 1| 30.0|
6. |1970-01-01T08:00:00.040+08:00|1970-01-01T08:00:00.040+08:00| 44.5| 1| 40.0|
7. |1970-01-01T08:00:00.050+08:00|1970-01-01T08:00:00.050+08:00| null| 1| 50.0|
8. |1970-01-01T08:00:00.060+08:00|1970-01-01T08:00:00.060+08:00| 64.5| 1| 60.0|
9. |1970-01-01T08:00:00.070+08:00|1970-01-01T08:00:00.090+08:00| 84.5| 3| 170.0|
10. |1970-01-01T08:00:00.150+08:00|1970-01-01T08:00:00.150+08:00| 66.5| 1| 90.0|
11. +-----------------------------+-----------------------------+-----------------+-------------------+-----------------+
delta!=0时的差值事件分段
使用如下sql语句
1. select __endTime, avg(s1), count(s2), sum(s3) from root.sg.d group by variation(s6, 4)
得到如下的查询结果
1. +-----------------------------+-----------------------------+-----------------+-------------------+-----------------+
2. | Time| __endTime|avg(root.sg.d.s1)|count(root.sg.d.s2)|sum(root.sg.d.s3)|
3. +-----------------------------+-----------------------------+-----------------+-------------------+-----------------+
4. |1970-01-01T08:00:00.000+08:00|1970-01-01T08:00:00.050+08:00| 24.5| 4| 100.0|
5. |1970-01-01T08:00:00.070+08:00|1970-01-01T08:00:00.090+08:00| 84.5| 3| 170.0|
6. |1970-01-01T08:00:00.150+08:00|1970-01-01T08:00:00.150+08:00| 66.5| 1| 90.0|
7. +-----------------------------+-----------------------------+-----------------+-------------------+-----------------+
group by子句中的controlExpression同样支持列的表达式
1. select __endTime, avg(s1), count(s2), sum(s3) from root.sg.d group by variation(s6+s5, 10)
得到如下的查询结果
1. +-----------------------------+-----------------------------+-----------------+-------------------+-----------------+
2. | Time| __endTime|avg(root.sg.d.s1)|count(root.sg.d.s2)|sum(root.sg.d.s3)|
3. +-----------------------------+-----------------------------+-----------------+-------------------+-----------------+
4. |1970-01-01T08:00:00.000+08:00|1970-01-01T08:00:00.010+08:00| 4.5| 2| 10.0|
5. |1970-01-01T08:00:00.040+08:00|1970-01-01T08:00:00.050+08:00| 44.5| 2| 90.0|
6. |1970-01-01T08:00:00.070+08:00|1970-01-01T08:00:00.080+08:00| 79.5| 2| 80.0|
7. |1970-01-01T08:00:00.090+08:00|1970-01-01T08:00:00.150+08:00| 80.5| 2| 180.0|
8. +-----------------------------+-----------------------------+-----------------+-------------------+-----------------+
条件分段聚合
当需要根据指定条件对数据进行筛选,并将连续的符合条件的行分为一组进行聚合运算时,可以使用GROUP BY CONDITION的分段方式;不满足给定条件的行因为不属于任何分组会被直接简单忽略。 其语法定义如下:
1. 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搭配使用。
对于如下原始数据,下面会给出几个查询样例:
1. +-----------------------------+-------------------------+-------------------------------------+------------------------------------+
2. | Time|root.sg.beijing.car01.soc|root.sg.beijing.car01.charging_status|root.sg.beijing.car01.vehicle_status|
3. +-----------------------------+-------------------------+-------------------------------------+------------------------------------+
4. |1970-01-01T08:00:00.001+08:00| 14.0| 1| 1|
5. |1970-01-01T08:00:00.002+08:00| 16.0| 1| 1|
6. |1970-01-01T08:00:00.003+08:00| 16.0| 0| 1|
7. |1970-01-01T08:00:00.004+08:00| 16.0| 0| 1|
8. |1970-01-01T08:00:00.005+08:00| 18.0| 1| 1|
9. |1970-01-01T08:00:00.006+08:00| 24.0| 1| 1|
10. |1970-01-01T08:00:00.007+08:00| 36.0| 1| 1|
11. |1970-01-01T08:00:00.008+08:00| 36.0| null| 1|
12. |1970-01-01T08:00:00.009+08:00| 45.0| 1| 1|
13. |1970-01-01T08:00:00.010+08:00| 60.0| 1| 1|
14. +-----------------------------+-------------------------+-------------------------------------+------------------------------------+
查询至少连续两行以上的charging_status=1的数据,sql语句如下:
1. select max_time(charging_status),count(vehicle_status),last_value(soc) from root.** group by condition(charging_status=1,KEEP>=2,ignoreNull=true)
得到结果如下:
1. +-----------------------------+-----------------------------------------------+-------------------------------------------+-------------------------------------+
2. | Time|max_time(root.sg.beijing.car01.charging_status)|count(root.sg.beijing.car01.vehicle_status)|last_value(root.sg.beijing.car01.soc)|
3. +-----------------------------+-----------------------------------------------+-------------------------------------------+-------------------------------------+
4. |1970-01-01T08:00:00.001+08:00| 2| 2| 16.0|
5. |1970-01-01T08:00:00.005+08:00| 10| 5| 60.0|
6. +-----------------------------+-----------------------------------------------+-------------------------------------------+-------------------------------------+
当设置ignoreNull为false时,遇到null值为将其视为一个不满足条件的行,会结束正在计算的分组。
1. select max_time(charging_status),count(vehicle_status),last_value(soc) from root.** group by condition(charging_status=1,KEEP>=2,ignoreNull=false)
得到如下结果,原先的分组被含null的行拆分:
1. +-----------------------------+-----------------------------------------------+-------------------------------------------+-------------------------------------+
2. | Time|max_time(root.sg.beijing.car01.charging_status)|count(root.sg.beijing.car01.vehicle_status)|last_value(root.sg.beijing.car01.soc)|
3. +-----------------------------+-----------------------------------------------+-------------------------------------------+-------------------------------------+
4. |1970-01-01T08:00:00.001+08:00| 2| 2| 16.0|
5. |1970-01-01T08:00:00.005+08:00| 7| 3| 36.0|
6. |1970-01-01T08:00:00.009+08:00| 10| 2| 60.0|
7. +-----------------------------+-----------------------------------------------+-------------------------------------------+-------------------------------------+
会话分段聚合
GROUP BY SESSION可以根据时间列的间隔进行分组,在结果集的时间列中,时间间隔小于等于设定阈值的数据会被分为一组。例如在工业场景中,设备并不总是连续运行,GROUP BY SESSION会将设备每次接入会话所产生的数据分为一组。 其语法定义如下:
1. group by session(timeInterval)
- timeInterval
设定的时间差阈值,当两条数据时间列的差值大于该阈值,则会给数据创建一个新的分组。
下图为group by session下的一个分组示意图

使用注意事项
- 对于一个分组,默认Time列输出分组的开始时间,查询时可以使用select
__endTime的方式来使得结果输出分组的结束时间。 - 与
ALIGN BY DEVICE搭配使用时会对每个device进行单独的分组操作。 - 当前暂不支持与
GROUP BY LEVEL搭配使用。
对于下面的原始数据,给出几个查询样例。
1. +-----------------------------+-----------------+-----------+--------+------+
2. | Time| Device|temperature|hardware|status|
3. +-----------------------------+-----------------+-----------+--------+------+
4. |1970-01-01T08:00:01.000+08:00|root.ln.wf02.wt01| 35.7| 11| false|
5. |1970-01-01T08:00:02.000+08:00|root.ln.wf02.wt01| 35.8| 22| true|
6. |1970-01-01T08:00:03.000+08:00|root.ln.wf02.wt01| 35.4| 33| false|
7. |1970-01-01T08:00:04.000+08:00|root.ln.wf02.wt01| 36.4| 44| false|
8. |1970-01-01T08:00:05.000+08:00|root.ln.wf02.wt01| 36.8| 55| false|
9. |1970-01-01T08:00:10.000+08:00|root.ln.wf02.wt01| 36.8| 110| false|
10. |1970-01-01T08:00:20.000+08:00|root.ln.wf02.wt01| 37.8| 220| true|
11. |1970-01-01T08:00:30.000+08:00|root.ln.wf02.wt01| 37.5| 330| false|
12. |1970-01-01T08:00:40.000+08:00|root.ln.wf02.wt01| 37.4| 440| false|
13. |1970-01-01T08:00:50.000+08:00|root.ln.wf02.wt01| 37.9| 550| false|
14. |1970-01-01T08:01:40.000+08:00|root.ln.wf02.wt01| 38.0| 110| false|
15. |1970-01-01T08:02:30.000+08:00|root.ln.wf02.wt01| 38.8| 220| true|
16. |1970-01-01T08:03:20.000+08:00|root.ln.wf02.wt01| 38.6| 330| false|
17. |1970-01-01T08:04:20.000+08:00|root.ln.wf02.wt01| 38.4| 440| false|
18. |1970-01-01T08:05:20.000+08:00|root.ln.wf02.wt01| 38.3| 550| false|
19. |1970-01-01T08:06:40.000+08:00|root.ln.wf02.wt01| null| 0| null|
20. |1970-01-01T08:07:50.000+08:00|root.ln.wf02.wt01| null| 0| null|
21. |1970-01-01T08:08:00.000+08:00|root.ln.wf02.wt01| null| 0| null|
22. |1970-01-02T08:08:01.000+08:00|root.ln.wf02.wt01| 38.2| 110| false|
23. |1970-01-02T08:08:02.000+08:00|root.ln.wf02.wt01| 37.5| 220| true|
24. |1970-01-02T08:08:03.000+08:00|root.ln.wf02.wt01| 37.4| 330| false|
25. |1970-01-02T08:08:04.000+08:00|root.ln.wf02.wt01| 36.8| 440| false|
26. |1970-01-02T08:08:05.000+08:00|root.ln.wf02.wt01| 37.4| 550| false|
27. +-----------------------------+-----------------+-----------+--------+------+
可以按照不同的时间单位设定时间间隔,sql语句如下:
1. select __endTime,count(*) from root.** group by session(1d)
得到如下结果:
1. +-----------------------------+-----------------------------+------------------------------------+---------------------------------+-------------------------------+
2. | Time| __endTime|count(root.ln.wf02.wt01.temperature)|count(root.ln.wf02.wt01.hardware)|count(root.ln.wf02.wt01.status)|
3. +-----------------------------+-----------------------------+------------------------------------+---------------------------------+-------------------------------+
4. |1970-01-01T08:00:01.000+08:00|1970-01-01T08:08:00.000+08:00| 15| 18| 15|
5. |1970-01-02T08:08:01.000+08:00|1970-01-02T08:08:05.000+08:00| 5| 5| 5|
6. +-----------------------------+-----------------------------+------------------------------------+---------------------------------+-------------------------------+
也可以和HAVING、ALIGN BY DEVICE共同使用
1. select __endTime,sum(hardware) from root.ln.wf02.wt01 group by session(50s) having sum(hardware)>0 align by device
得到如下结果,其中排除了sum(hardware)为0的部分
1. +-----------------------------+-----------------+-----------------------------+-------------+
2. | Time| Device| __endTime|sum(hardware)|
3. +-----------------------------+-----------------+-----------------------------+-------------+
4. |1970-01-01T08:00:01.000+08:00|root.ln.wf02.wt01|1970-01-01T08:03:20.000+08:00| 2475.0|
5. |1970-01-01T08:04:20.000+08:00|root.ln.wf02.wt01|1970-01-01T08:04:20.000+08:00| 440.0|
6. |1970-01-01T08:05:20.000+08:00|root.ln.wf02.wt01|1970-01-01T08:05:20.000+08:00| 550.0|
7. |1970-01-02T08:08:01.000+08:00|root.ln.wf02.wt01|1970-01-02T08:08:05.000+08:00| 1650.0|
8. +-----------------------------+-----------------+-----------------------------+-------------+
