数据查询
相较于许多时序数据库和实时数据库,TDengine 自首个版本起就支持标准 SQL 查询。这一能力降低了时序数据查询和分析的学习成本。
本章以智能电表数据模型为例,使用快速体验中 taosBenchmark -y 写入的 test 库数据,在 shell 中快速体验常用查询:按条件过滤、排序、限制返回行数,按标签或子表聚合,以及按时间窗口统计数据。每类查询都会给出 SQL 和代表性返回结果,帮助你理解查询输出的形态。完整语法和高级查询能力请参见文末“继续阅读”。
前提条件
请先确认已经完成以下准备:
- TDengine 服务已经启动,可以通过 shell 连接。
- 已经在下载与安装章节的快速体验中执行过
taosBenchmark -y,生成了test数据库和meters超级表。若尚未生成,可先在终端执行taosBenchmark -y。
该命令默认写入约 1 亿条记录:10,000 张子表(d0–d9999),每张 10,000 条,时间戳范围为 2017-07-14 10:40:00.000 到 2017-07-14 10:40:09.999(约 10 秒,采集间隔 1 ms)。下文窗口示例因此使用秒级窗口,而不是分钟级窗口。
进入 shell 后,切换到 test 数据库。
USE test;
基本查询
执行下面的 SQL,从超级表 meters 中查询电压大于 250V 的数据,并按时间倒序返回前 5 行。
SELECT tbname, ts, current, voltage
FROM meters
WHERE voltage > 250 and tbname = 'd1'
ORDER BY ts DESC
LIMIT 5;
其中:
WHERE voltage > 250用于过滤电压大于 250V 的记录。ORDER BY ts DESC按时间戳倒序返回结果。LIMIT 5只返回前 5 行。
tbname 是伪列,表示数据来自哪张子表。
返回结果类似如下,具体子表名和数值可能因 taosBenchmark 版本或随机数据不同而略有差异。
tbname | ts | current | voltage |
========================================================
d1 | 2017-07-14 10:40:09.998 | 11.7984 | 253 |
d1 | 2017-07-14 10:40:09.998 | 11.7984 | 253 |
d1 | 2017-07-14 10:40:09.998 | 11.7984 | 253 |
d1 | 2017-07-14 10:40:09.998 | 11.7984 | 253 |
d1 | 2017-07-14 10:40:09.998 | 11.7984 | 253 |
Query OK, 5 row(s) in set
按标签过滤
标签适合用来描述设备的静态属性,例如位置和分组。下面的 SQL 查询位于 California.SanFrancisco 的电表数据。
SELECT tbname, ts, current, voltage, phase
FROM meters
WHERE location = "California.SanFrancisco"
ORDER BY ts DESC
LIMIT 5;
也可以同时使用标签条件和普通列条件。
SELECT tbname, ts, current, voltage, phase
FROM meters
WHERE location = "California.SanFrancisco" AND voltage > 250
ORDER BY ts DESC
LIMIT 5;
返回结果类似如下。
tbname | ts | current | voltage | phase |
===============================================================
d3737 | 2017-07-14 10:40:09.998 | 11.7984 | 253 | 147 |
d8742 | 2017-07-14 10:40:09.998 | 11.7984 | 253 | 147 |
d8745 | 2017-07-14 10:40:09.998 | 11.7984 | 253 | 147 |
d6259 | 2017-07-14 10:40:09.998 | 11.7984 | 253 | 147 |
d6252 | 2017-07-14 10:40:09.998 | 11.7984 | 253 | 147 |
Query OK, 5 row(s) in set
聚合查询
聚合函数可以帮助你快速计算统计值。下面的 SQL 统计所有电表的平均电压、最大电压和总记录数。
SELECT AVG(voltage), MAX(voltage), COUNT(*)
FROM meters;
返回结果中只有一行,表示全量数据的汇总结果。
avg(voltage) | max(voltage) | count(*) |
===========================================
243.9314 | 258 | 100000000 |
Query OK, 1 row(s) in set
如果需要按分组统计,可以使用 GROUP BY。快速体验数据中的分组标签列为 groupId。
SELECT groupId, AVG(voltage), COUNT(*)
FROM meters
GROUP BY groupId
ORDER BY groupId;
返回结果会按 groupId 分组,每个分组一行。
groupId | avg(voltage) | count(*) |
====================================
1 | 243.9314 | 9800000 |
2 | 243.9314 | 9940000 |
3 | 243.9314 | 9800000 |
4 | 243.9314 | 10040000 |
5 | 243.9314 | 10310000 |
...
Query OK, 10 row(s) in set
GROUP BY 的结果在未排序时不保证固定顺序。如需按统计值排序,可以继续使用 ORDER BY。
SELECT groupId, AVG(voltage) AS avg_voltage
FROM meters
GROUP BY groupId
ORDER BY avg_voltage DESC;
按子表聚合
如果需要分别统计每个电表,可以使用 PARTITION BY tbname。下面的 SQL 按子表统计平均电压,并用 SLIMIT 只取前几个分片,避免一次返回 10,000 行。
SELECT tbname, AVG(voltage), COUNT(*)
FROM meters
PARTITION BY tbname
SLIMIT 3;
返回结果会按子表切分。分片出现顺序可能因环境略有不同,示意如下。
tbname | avg(voltage) | count(*) |
===================================
d0 | 243.9314 | 10000 |
d1 | 243.9314 | 10000 |
d2 | 243.9314 | 10000 |
Query OK, 3 row(s) in set
PARTITION BY 会先把超级表数据按指定维度切分,再在每个分片中执行计算。它常用于“每台设备分别统计”的场景。
窗口查询
窗口查询用于把时序数据按时间、状态、事件或行数切分,再在每个窗口内计算。快速上手阶段可以先理解下面几类窗口:

- 时间窗口:按固定时间间隔切分,使用
INTERVAL。 - 滑动窗口:在时间窗口基础上设置滑动步长,使用
SLIDING。 - 状态窗口:按状态值变化切分,使用
STATE_WINDOW。 - 会话窗口:按相邻记录时间间隔切分,使用
SESSION。 - 事件窗口:按开始条件和结束条件切分,使用
EVENT_WINDOW。 - 计数窗口:按固定行数切分,使用
COUNT_WINDOW。 - 外部窗口:由子查询显式给出窗口范围,使用
EXTERNAL_WINDOW。
下面先体验最常用的几种窗口。示例时间范围覆盖 test.meters 的全部数据区间。
时间窗口
下面的 SQL 按 1 秒窗口计算每个电表的平均电压。
SELECT tbname, _wstart, _wend, AVG(voltage)
FROM meters
WHERE ts >= "2017-07-14 10:40:00" AND ts < "2017-07-14 10:40:10"
PARTITION BY tbname
INTERVAL(1s)
SLIMIT 2;
其中:
INTERVAL(1s)表示按 1 秒切分时间窗口。_wstart和_wend是窗口开始时间和结束时间。PARTITION BY tbname表示每个子表独立做窗口聚合。SLIMIT 2只返回前 2 个分片,避免结果过长。
返回结果中每一行对应一个时间窗口,节选如下。
tbname | _wstart | _wend | avg(voltage) |
==========================================================================
d0 | 2017-07-14 10:40:00.000 | 2017-07-14 10:40:01.000 | 244.003 |
d0 | 2017-07-14 10:40:01.000 | 2017-07-14 10:40:02.000 | 243.872 |
d0 | 2017-07-14 10:40:02.000 | 2017-07-14 10:40:03.000 | 244.261 |
d1 | 2017-07-14 10:40:00.000 | 2017-07-14 10:40:01.000 | 244.003 |
d1 | 2017-07-14 10:40:01.000 | 2017-07-14 10:40:02.000 | 243.872 |
...
滑动窗口
如果希望窗口按更短步长滑动,可以加上 SLIDING。下面的 SQL 使用 1 秒窗口,并每 500 毫秒滑动一次。
SELECT tbname, _wstart, AVG(voltage)
FROM meters
WHERE ts >= "2017-07-14 10:40:00" AND ts < "2017-07-14 10:40:10"
PARTITION BY tbname
INTERVAL(1s)
SLIDING(500a)
SLIMIT 1;
返回结果中,_wstart 每次前进 500 毫秒,说明 1 秒窗口正在按 500 毫秒步长滑动。
tbname | _wstart | avg(voltage) |
==================================================
d0 | 2017-07-14 10:39:59.500 | 243.808 |
d0 | 2017-07-14 10:40:00.000 | 244.003 |
d0 | 2017-07-14 10:40:00.500 | 244.089 |
d0 | 2017-07-14 10:40:01.000 | 243.872 |
d0 | 2017-07-14 10:40:01.500 | 244.019 |
...
填充缺失窗口
窗口中没有数据时,可以使用 FILL 指定填充方式。下面的 SQL 使用前一个非空值填充缺失窗口。
SELECT _wstart, _wend, AVG(voltage)
FROM d0
WHERE ts >= "2017-07-14 10:40:00" AND ts < "2017-07-14 10:40:10"
INTERVAL(1s)
FILL(prev);
本章示例数据较连续,下面结果主要展示 FILL 查询的输出结构。若某个窗口没有数据,FILL(prev) 会使用前一个非空窗口的结果填充。
_wstart | _wend | avg(voltage) |
================================================================
2017-07-14 10:40:00.000 | 2017-07-14 10:40:01.000 | 244.003 |
2017-07-14 10:40:01.000 | 2017-07-14 10:40:02.000 | 243.872 |
2017-07-14 10:40:02.000 | 2017-07-14 10:40:03.000 | 244.261 |
2017-07-14 10:40:03.000 | 2017-07-14 10:40:04.000 | 243.479 |
2017-07-14 10:40:04.000 | 2017-07-14 10:40:05.000 | 243.972 |
...
状态窗口
状态窗口适合按状态变化切分数据。下面的 SQL 根据电压是否处于 240V 到 250V 的范围划分窗口。
SELECT _wstart, _wend, COUNT(*),
CASE WHEN voltage >= 240 AND voltage <= 250 THEN 1 ELSE 0 END AS status
FROM d0
WHERE ts >= "2017-07-14 10:40:00" AND ts < "2017-07-14 10:40:03"
STATE_WINDOW(
CASE WHEN voltage >= 240 AND voltage <= 250 THEN 1 ELSE 0 END
)
LIMIT 4;
返回结果中,相邻窗口的 status 不同,表示状态发生了变化。
_wstart | _wend | count(*) | status |
=====================================================================
2017-07-14 10:40:00.000 | 2017-07-14 10:40:00.001 | 2 | 0 |
2017-07-14 10:40:00.002 | 2017-07-14 10:40:00.002 | 1 | 1 |
2017-07-14 10:40:00.003 | 2017-07-14 10:40:00.006 | 4 | 0 |
2017-07-14 10:40:00.007 | 2017-07-14 10:40:00.014 | 8 | 1 |
Query OK, 4 row(s) in set
会话窗口
会话窗口适合按相邻记录的时间间隔切分数据。下面的 SQL 将间隔不超过 30 秒的数据归为同一个会话。由于 d0 中相邻点间隔为 1 ms,整段数据会落在同一个会话窗口中。
SELECT _wstart, _wend, COUNT(*)
FROM d0
WHERE ts >= "2017-07-14 10:40:00" AND ts < "2017-07-14 10:40:10"
SESSION(ts, 30s);
返回结果如下。
_wstart | _wend | count(*) |
=============================================================
2017-07-14 10:40:00.000 | 2017-07-14 10:40:09.999 | 10000 |
Query OK, 1 row(s) in set
事件窗口
事件窗口适合“满足开始条件后开窗,满足结束条件后关窗”的场景。例如电压升高到某个阈值后开始观察,降回另一个阈值后结束观察。
SELECT _wstart, _wend, COUNT(*)
FROM d0
WHERE ts >= "2017-07-14 10:40:00" AND ts < "2017-07-14 10:40:10"
EVENT_WINDOW START WITH voltage >= 250 END WITH voltage < 245
LIMIT 4;
返回结果中,每一行表示一次从开窗条件到关窗条件之间的事件区间。
_wstart | _wend | count(*) |
=============================================================
2017-07-14 10:40:00.000 | 2017-07-14 10:40:00.001 | 2 |
2017-07-14 10:40:00.004 | 2017-07-14 10:40:00.005 | 2 |
2017-07-14 10:40:00.006 | 2017-07-14 10:40:00.011 | 6 |
2017-07-14 10:40:00.016 | 2017-07-14 10:40:00.017 | 2 |
Query OK, 4 row(s) in set
计数窗口
计数窗口适合按固定行数分组。下面的 SQL 每 100 行切分一个窗口。
SELECT _wstart, _wend, COUNT(*)
FROM d0
WHERE ts >= "2017-07-14 10:40:00" AND ts < "2017-07-14 10:40:10"
COUNT_WINDOW(100)
LIMIT 5;
返回结果中,每个窗口最多包含 100 行数据。
_wstart | _wend | count(*) |
=============================================================
2017-07-14 10:40:00.000 | 2017-07-14 10:40:00.099 | 100 |
2017-07-14 10:40:00.100 | 2017-07-14 10:40:00.199 | 100 |
2017-07-14 10:40:00.200 | 2017-07-14 10:40:00.299 | 100 |
2017-07-14 10:40:00.300 | 2017-07-14 10:40:00.399 | 100 |
2017-07-14 10:40:00.400 | 2017-07-14 10:40:00.499 | 100 |
Query OK, 5 row(s) in set
外部窗口
外部窗口更适合用已有事件表、排班表或维护计划定义窗口范围。下面的 SQL 先用子查询显式给出两个窗口边界,再在 d0 表中分别计算每个窗口内的平均电压。
SELECT _wstart, _wend, AVG(voltage)
FROM d0
EXTERNAL_WINDOW (
(SELECT CAST("2017-07-14 10:40:00" AS TIMESTAMP) AS ws,
CAST("2017-07-14 10:40:01" AS TIMESTAMP) AS we
UNION ALL
SELECT CAST("2017-07-14 10:40:01" AS TIMESTAMP),
CAST("2017-07-14 10:40:02" AS TIMESTAMP)
ORDER BY ws) w
);
返回结果中,窗口边界来自子查询,而不是由 INTERVAL 自动切分。
_wstart | _wend | avg(voltage) |
=================================================================
2017-07-14 10:40:00.000 | 2017-07-14 10:40:01.000 | 244.206793 |
2017-07-14 10:40:01.000 | 2017-07-14 10:40:02.000 | 244.367632 |
Query OK, 2 row(s) in set
也可以用 INTERVAL 子查询生成有序窗口,效果类似:
SELECT _wstart, _wend, AVG(voltage)
FROM d0
EXTERNAL_WINDOW (
(SELECT _wstart, _wend
FROM d0
WHERE ts >= "2017-07-14 10:40:00" AND ts < "2017-07-14 10:40:02"
INTERVAL(1s)) w
);
常用查询模式
下面是几个快速上手阶段常用的查询模式。
查看某张子表的最新数据:
SELECT * FROM d0 ORDER BY ts DESC LIMIT 1;
查看某个位置的电表记录数:
SELECT location, COUNT(*)
FROM meters
GROUP BY location
ORDER BY location;
查看每个电表的最大电压(用 SLIMIT 限制返回分片数):
SELECT tbname, MAX(voltage)
FROM meters
PARTITION BY tbname
SLIMIT 3;
查看某段时间内的电流平均值:
SELECT AVG(current)
FROM meters
WHERE ts >= "2017-07-14 10:40:00" AND ts < "2017-07-14 10:40:10";
继续阅读
本章只覆盖快速上手阶段最常用的查询方式。更多高级查询能力,请继续阅读以下文档:








