欢迎光临
我们一直在努力

Apache IoTDB(10):数据库操作——从查询到优化的全链路实践指南

引言

在工业物联网(IIoT)场景中,时序数据的高效查询是核心诉求。Apache IoTDB作为专为物联网设计的时序数据库,凭借其“端-边-云”协同架构、树形元数据管理、高压缩存储等特性,成为能源、交通、制造等领域的关键基础设施。

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到集群部署的六种实战

本文将深度拆解IoTDB的数据查询机制,结合代码详细描述SELECT FROM与WHERE子句的底层逻辑与性能优化技巧。

在这里插入图片描述

一、概述

在 IoTDB 中,使用 SELECT 语句从一条或多条时间序列中查询数据,IoTDB 不区分历史数据和实时数据,用户可以用统一的sql语法进行查询,通过 WHERE 子句中的时间过滤谓词决定查询的时间范围

1.1 语法定义

SELECT [LAST] selectExpr [, selectExpr] ...
[INTO intoItem [, intoItem] ...]
FROM prefixPath [, prefixPath] ...
[WHERE whereCondition]
[GROUP BY {
([startTime, endTime), interval [, slidingStep]) |
LEVEL = levelNum [, levelNum] ... |
TAGS(tagKey [, tagKey] ... |
VARIATION(expression[,delta][,ignoreNull=true/false]) |
CONDITION(expression,[keep>/>=/=/</<=]threshold[,ignoreNull=true/false]) |
SESSION(timeInterval) |
COUNT(expression, size[,ignoreNull=true/false])
}]
[HAVING havingCondition]
[ORDER BY sortKey {ASC | DESC}]
[FILL ({PREVIOUS | LINEAR | constant}) (, interval=DURATION_LITERAL)?)]
[SLIMIT seriesLimit] [SOFFSET seriesOffset]
[LIMIT rowLimit] [OFFSET rowOffset]
[ALIGN BY {TIME | DEVICE}]

1.2 语法说明

SELECT 子句

  • SELECT子句指定查询的输出,由若干个 selectExpr 组成
  • 每个 selectExpr 定义查询结果中的一列或多列,它是一个由时间序列路径后缀、常量、函数和运算符组成的表达式
  • 支持使用AS为查询结果集中的列指定别名
  • 在 SELECT 子句中使用 LAST 关键词可以指定查询为最新点查询

INTO 子句

  • SELECT INTO 用于将查询结果写入一系列指定的时间序列中。INTO 子句指定了查询结果写入的目标时间序列

FROM 子句

  • FROM 子句包含要查询的一个或多个时间序列的路径前缀,支持使用通配符
  • 在执行查询时,会将 FROM 子句中的路径前缀和 SELECT 子句中的后缀进行拼接得到完整的查询目标序列

WHERE 子句

  • WHERE 子句指定了对数据行的筛选条件,由一个 whereCondition 组成
  • whereCondition 是一个逻辑表达式,对于要选择的每一行,其计算结果为真。如果没有 WHERE 子句,将选择所有行
  • 在 whereCondition 中,可以使用除聚合函数之外的任何 IOTDB 支持的函数和运算符

GROUP BY 子句

  • GROUP BY 子句指定对序列进行分段或分组聚合的方式。
  • 分段聚合是指按照时间维度,针对同时间序列中不同数据点之间的时间关系,对数据在行的方向进行分段,每个段得到一个聚合值。目前支持时间区间分段、差值分段、条件分段、会话分段和点数分段,未来将支持更多分段方式
  • 分组聚合是指针对不同时间序列,在时间序列的潜在业务属性上分组,每个组包含若干条时间序列,每个组得到一个聚合值。支持按路径层级分组和按序列标签分组两种分组方式
  • 分段聚合和分组聚合可以混合使用

HAVING 子句

  • HAVING 子句指定了对聚合结果的筛选条件,由一个 havingCondition 组成
  • havingCondition 是一个逻辑表达式,对于要选择的聚合结果,其计算结果为真。如果没有 HAVING 子句,将选择所有聚合结果
  • HAVING 要和聚合函数以及 GROUP BY 子句一起使用

ORDER BY 子句

  • ORDER BY 子句用于指定结果集的排序方式
  • 按时间对齐模式下:默认按照时间戳大小升序排列,可以通过 ORDER BY TIME DESC 指定结果集按照时间戳大小降序排列
  • 按设备对齐模式下:默认按照设备名的字典序升序排列,每个设备内部按照时间戳大小升序排列,可以通过 ORDER BY 子句调整设备列和时间列的排序优先级

FILL 子句

  • FILL 子句用于指定数据缺失情况下的填充模式,允许用户按照特定的方法对任何查询的结果集填充空值

SLIMIT 和 SOFFSET 子句

  • SLIMIT 指定查询结果的列数,SOFFSET 指定查询结果显示的起始列位置。SLIMIT 和 SOFFSET 仅用于控制值列,对时间列和设备列无效

LIMIT 和 OFFSET 子句

  • LIMIT 指定查询结果的行数,OFFSET 指定查询结果显示的起始行位置

ALIGN BY 子句

  • 查询结果集默认按时间对齐,包含一列时间列和若干个值列,每一行数据各列的时间戳相同
  • 除按时间对齐之外,还支持按设备对齐,查询结果集包含一列时间列、一列设备列和若干个值列

1.3 SQL 示例

IoTDB 支持即席(Ad_hoc)查询,即支持用户在使用系统时,自定义查询条件,根据自己当时的需求写出查询sql并执行。用户可以通过上述介绍的子句,进行组合,指定任意合法的过滤条件来满足当时的查询需求,下面介绍了一些查询的示例:

示例1:根据一个时间区间选择一列数据 SQL 语句为:

select temperature from root.ln.wf01.wt01 where time < 20171101T00:08:00.000

其含义为:

被选择的设备为 ln 集团 wf01 子站 wt01 设备;被选择的时间序列为温度传感器(temperature);该语句要求选择出该设备在 “2017-11-01T00:08:00.000” 时间点以前的所有温度传感器的值

该 SQL 语句的执行结果如下: 在这里插入图片描述

示例2:根据一个时间区间选择多列数据 SQL 语句为:

select status, temperature from root.ln.wf01.wt01 where time > 20171101T00:05:00.000 and time < 20171101T00:12:00.000;

其含义为:

被选择的设备为 ln 集团 wf01 子站 wt01 设备;被选择的时间序列为供电状态(status)和温度传感器(temperature);该语句要求选择出 “2017-11-01T00:05:00.000” 至 “2017-11-01T00:12:00.000” 之间的所选时间序列的值

该 SQL 语句的执行结果如下: 在这里插入图片描述

示例3:按照多个时间区间选择同一设备的多列数据 IoTDB 支持在一次查询中指定多个时间区间条件,用户可以根据需求随意组合时间区间条件。例如,

SQL 语句为:

select status, temperature from root.ln.wf01.wt01 where (time > 20171101T00:05:00.000 and time < 20171101T00:12:00.000) or (time >= 20171101T16:35:00.000 and time <= 20171101T16:37:00.000);

其含义为:

被选择的设备为 ln 集团 wf01 子站 wt01 设备;被选择的时间序列为“供电状态(status)”和“温度传感器(temperature)”;该语句指定了两个不同的时间区间,分别为“2017-11-01T00:05:00.000 至 2017-11-01T00:12:00.000”和“2017-11-01T16:35:00.000 至 2017-11-01T16:37:00.000”;该语句要求选择出满足任一时间区间的被选时间序列的值

该 SQL 语句的执行结果如下: 在这里插入图片描述

示例4:按照多个时间区间选择不同设备的多列数据 该系统支持在一次查询中选择任意列的数据,也就是说,被选择的列可以来源于不同的设备。例如,SQL 语句为:

select wf01.wt01.status, wf02.wt02.hardware from root.ln where (time > 20171101T00:05:00.000 and time < 20171101T00:12:00.000) or (time >= 20171101T16:35:00.000 and time <= 20171101T16:37:00.000);

其含义为:

被选择的时间序列为 “ln 集团 wf01 子站 wt01 设备的供电状态” 以及 “ln 集团 wf02 子站 wt02 设备的硬件版本”;该语句指定了两个时间区间,分别为 “2017-11-01T00:05:00.000 至 2017-11-01T00:12:00.000” 和 “2017-11-01T16:35:00.000 至 2017-11-01T16:37:00.000”;该语句要求选择出满足任意时间区间的被选时间序列的值

该 SQL 语句的执行结果如下: 在这里插入图片描述

示例5:根据时间降序返回结果集 IoTDB 支持 order by time 语句,用于对结果按照时间进行降序展示。例如,SQL 语句为:

select * from root.ln.** where time > 1 order by time desc limit 10;

语句执行的结果为: 在这里插入图片描述

1.4 查询执行接口

在 IoTDB 中,提供两种方式执行数据查询操作:

  • 使用 IoTDB-SQL 执行查询
  • 常用查询的高效执行接口,包括时间序列原始数据范围查询、最新点查询、简单聚合查询
  • 使用 IoTDB-SQL 执行查询
  • 数据查询语句支持在 SQL 命令行终端、JDBC、JAVA / C++ / Python / Go 等编程语言 API、RESTful API 中使用

    • 在 SQL 命令行终端中执行查询语句:启动 SQL 命令行终端,直接输入查询语句执行即可

    • 在 JDBC 中执行查询语句

    • 在 JAVA / C++ / Python / Go 等编程语言 API 中执行查询语句,详见应用编程接口一章相应文档。接口原型如下:

    SessionDataSet executeQueryStatement(String sql);

  • 常用查询的高效执行接口
  • 各编程语言的 API 为常用的查询提供了高效执行接口,可以省去 SQL 解析等操作的耗时。包括:

    • 时间序列原始数据范围查询: 指定的查询时间范围为左闭右开区间,包含开始时间但不包含结束时间

    SessionDataSet executeRawDataQuery(List<String> paths, long startTime, long endTime);

    • 最新点查询: 查询最后一条时间戳大于等于某个时间点的数据

    SessionDataSet executeLastDataQuery(List<String> paths, long lastTime);

    • 聚合查询: 支持指定查询时间范围。指定的查询时间范围为左闭右开区间,包含开始时间但不包含结束时间

      支持按照时间区间分段查询

    SessionDataSet executeAggregationQuery(List<String> paths, List<Aggregation> aggregations);

    SessionDataSet executeAggregationQuery(
    List<String> paths, List<Aggregation> aggregations, long startTime, long endTime);

    SessionDataSet executeAggregationQuery(
    List<String> paths,
    List<Aggregation> aggregations,
    long startTime,
    long endTime,
    long interval);

    SessionDataSet executeAggregationQuery(
    List<String> paths,
    List<Aggregation> aggregations,
    long startTime,
    long endTime,
    long interval,
    long slidingStep);

    二、选择表达式(SELECT FROM 子句)

    SELECT 子句指定查询的输出,由若干个 selectExpr 组成。 每个 selectExpr 定义了查询结果中的一列或多列

    selectExpr 是一个由时间序列路径后缀、常量、函数和运算符组成的表达式。即 selectExpr 中可以包含:

    • 时间序列路径后缀(支持使用通配符)
    • 运算符 算数运算符 比较运算符 逻辑运算符
    • 函数 聚合函数 时间序列生成函数(包括内置函数和用户自定义函数)
    • 常量

    2.1 使用别名

    由于 IoTDB 独特的数据模型,在每个传感器前都附带有设备等诸多额外信息。有时,我们只针对某个具体设备查询,而这些前缀信息频繁显示造成了冗余,影响了结果集的显示与分析。

    IoTDB 支持使用AS为查询结果集中的列指定别名。

    示例:

    select s1 as temperature, s2 as speed from root.ln.wf01.wt01;

    结果集将显示为: 在这里插入图片描述

    2.2 运算符

    IoTDB 中支持的运算符列表见文档 运算符和函数

    2.3 函数

  • 聚合函数 聚合函数是多对一函数。它们对一组值进行聚合计算,得到单个聚合结果
  • 包含聚合函数的查询称为聚合查询,否则称为时间序列查询。

    注意:聚合查询和时间序列查询不能混合使用。 下列语句是不支持的:

    select s1, count(s1) from root.sg.d1;
    select sin(s1), count(s1) from root.sg.d1;
    select s1, count(s1) from root.sg.d1 group by ([10,100),10ms);

  • 时间序列生成函数
  • 时间序列生成函数接受若干原始时间序列作为输入,产生一列时间序列输出。与聚合函数不同的是,时间序列生成函数的结果集带有时间戳列

    所有的时间序列生成函数都可以接受 * 作为输入,都可以与原始时间序列查询混合进行

    自定义时间序列生成函数 IoTDB 支持通过用户自定义函数能力进行函数功能扩展

    2.4 嵌套表达式举例

    IoTDB 支持嵌套表达式,由于聚合查询和时间序列查询不能在一条查询语句中同时出现,我们将支持的嵌套表达式分为时间序列查询嵌套表达式和聚合查询嵌套表达式两类

    时间序列查询嵌套表达式 IoTDB 支持在 SELECT 子句中计算由时间序列、常量、时间序列生成函数(包括用户自定义函数)和运算符组成的任意嵌套表达式

    说明:

    • 当某个时间戳下左操作数和右操作数都不为空(null)时,表达式才会有结果,否则表达式值为null,且默认不出现在结果集中。
    • 如果表达式中某个操作数对应多条时间序列(如通配符 *),那么每条时间序列对应的结果都会出现在结果集中(按照笛卡尔积形式)。 示例 1:

    select a,
    b,
    ((a + 1) * 2 1) % 2 + 1.5,
    sin(a + sin(a + sin(b))),
    (a + b) * (sin(a + b) * sin(a + b) + cos(a + b) * cos(a + b)) + 1
    from root.sg1;

    运行结果:图片显示不全 在这里插入图片描述 示例 2:

    select (a + b) * 2 + sin(a) from root.sg

    运行结果: 在这里插入图片描述 示例 3:

    select (a + *) / 2 from root.sg1

    运行结果: 在这里插入图片描述

    2.5 最新点查询

    最新点查询是时序数据库 Apache IoTDB 中提供的一种特殊查询。它返回指定时间序列中时间戳最大的数据点,即一条序列的最新状态。

    在物联网数据分析场景中,此功能尤为重要。为了满足了用户对设备实时监控的需求,Apache IoTDB 对最新点查询进行了缓存优化,能够提供毫秒级的返回速度。

    SQL 语法:

    select last <Path> [COMMA <Path>]* from < PrefixPath > [COMMA < PrefixPath >]* <whereClause> [ORDER BY TIMESERIES (DESC | ASC)?]

    三、查询过滤条件(WHERE 子句)

    WHERE 子句指定了对数据行的筛选条件,由一个 whereCondition 组成。

    whereCondition 是一个逻辑表达式,对于要选择的每一行,其计算结果为真。如果没有 WHERE 子句,将选择所有行。 在 whereCondition 中,可以使用除聚合函数之外的任何 IOTDB 支持的函数和运算符。

    根据过滤条件的不同,可以分为时间过滤条件和值过滤条件。时间过滤条件和值过滤条件可以混合使用。

    3.1 时间过滤条件

    使用时间过滤条件可以筛选特定时间范围的数据。对于时间戳支持的格式,请参考 时间戳类型 。

    示例如下:

    选择时间戳大于 2022-01-01T00:05:00.000 的数据:

    select s1 from root.sg1.d1 where time > 20220101T00:05:00.000;

    选择时间戳等于 2022-01-01T00:05:00.000 的数据:

    select s1 from root.sg1.d1 where time = 20220101T00:05:00.000;

    选择时间区间 [2017-11-01T00:05:00.000, 2017-11-01T00:12:00.000) 内的数据:

    select s1 from root.sg1.d1 where time >= 20220101T00:05:00.000 and time < 20171101T00:12:00.000;

    3.2 值过滤条件

    使用值过滤条件可以筛选数据值满足特定条件的数据。 允许使用 select 子句中未选择的时间序列作为值过滤条件。

    示例如下:

    选择值大于 36.5 的数据:

    select temperature from root.sg1.d1 where temperature > 36.5;

    选择值等于 true 的数据:

    select status from root.sg1.d1 where status = true;

    选择区间 [36.5,40] 内或之外的数据:

    select temperature from root.sg1.d1 where temperature between 36.5 and 40;

    select temperature from root.sg1.d1 where temperature not between 36.5 and 40;

    选择值在特定范围内的数据:

    select code from root.sg1.d1 where code in ('200', '300', '400', '500');

    选择值在特定范围外的数据:

    select code from root.sg1.d1 where code not in ('200', '300', '400', '500');

    选择值为空的数据:

    select code from root.sg1.d1 where temperature is null;

    选择值为非空的数据:

    select code from root.sg1.d1 where temperature is not null;

    3.3 模糊查询

    对于 TEXT 类型的数据,支持使用 Like 和 Regexp 运算符对数据进行模糊匹配

    使用 Like 进行模糊匹配

    匹配规则:

    % 表示任意0个或多个字符。 _ 表示任意单个字符。 示例 1: 查询 root.sg.d1 下 value 含有’cc’的数据。

    IoTDB> select * from root.sg.d1 where value like '%cc%'
    +—————————–+—————-+
    | Time|root.sg.d1.value|
    +—————————–+—————-+
    |2017-11-01T00:00:00.000+08:00| aabbccdd|
    |2017-11-01T00:00:01.000+08:00| cc|
    +—————————–+—————-+
    Total line number = 2
    It costs 0.002s

    示例 2: 查询 root.sg.d1 下 value 中间为 ‘b’、前后为任意单个字符的数据。

    IoTDB> select * from root.sg.device where value like '_b_'
    +—————————–+—————-+
    | Time|root.sg.d1.value|
    +—————————–+—————-+
    |2017-11-01T00:00:02.000+08:00| abc|
    +—————————–+—————-+
    Total line number = 1
    It costs 0.002s

    使用 Regexp 进行模糊匹配 需要传入的过滤条件为 Java 标准库风格的正则表达式。

    常见的正则匹配举例:

    长度为3-20的所有字符:^.{3,20}$
    大写英文字符:^[A-Z]+$
    数字和英文字符:^[A-Za-z0-9]+$
    以a开头的:^a.*

    示例 1: 查询 root.sg.d1 下 value 值为26个英文字符组成的字符串。

    IoTDB> select * from root.sg.d1 where value regexp '^[A-Za-z]+$'
    +—————————–+—————-+
    | Time|root.sg.d1.value|
    +—————————–+—————-+
    |2017-11-01T00:00:00.000+08:00| aabbccdd|
    |2017-11-01T00:00:01.000+08:00| cc|
    +—————————–+—————-+
    Total line number = 2
    It costs 0.002s

    示例 2: 查询 root.sg.d1 下 value 值为26个小写英文字符组成的字符串且时间大于100的。

    IoTDB> select * from root.sg.d1 where value regexp '^[a-z]+$' and time > 100
    +—————————–+—————-+
    | Time|root.sg.d1.value|
    +—————————–+—————-+
    |2017-11-01T00:00:00.000+08:00| aabbccdd|
    |2017-11-01T00:00:01.000+08:00| cc|
    +—————————–+—————-+
    Total line number = 2
    It costs 0.002s

    结语

    Apache IoTDB通过其独特的树形元数据模型、高压缩存储格式、丰富的查询语义,成为工业物联网时序数据管理的标杆。掌握IoTDB的数据查询艺术,不仅是技术能力的提升,更是对工业物联网业务价值的深度挖掘。本文从SELECT FROM与WHERE子句的底层逻辑揭示了高效查询的设计原则与优化技巧,因篇幅有限,后续博主会带来更多关于 IoTDB 数据库的操作。

    赞(0)
    未经允许不得转载:171主机测评 » Apache IoTDB(10):数据库操作——从查询到优化的全链路实践指南
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址