Apache IoTDB(15):IoTDB查询写回(INTO子句)深度解析——从语法到实战的ETL全链路指南
引言
在工业物联网场景中,时序数据的存储与处理常面临“数据孤岛”困境——生产设备产生的原始数据需经过清洗、聚合、转换等多步处理,才能转化为可分析的业务指标。Apache IoTDB的查询写回(INTO子句)正是破解这一痛点的“数据炼金术”。通过SELECT INTO语句,能将查询结果直接写入新序列,实现“查询-转换-存储”的闭环,相当于在数据库内部构建轻量级ETL管道。

Apache IoTDB 时序数据库【系列篇章】:
一、语法定义
SELECT INTO 语句用于将查询结果写入一系列指定的时间序列中。
使用场景
实现 IoTDB 内部 ETL:对原始数据进行 ETL 处理后写入新序列。
查询结果存储:将查询结果进行持久化存储,起到类似物化视图的作用。
非对齐序列转对齐序列:对齐序列从0.13版本开始支持,可以通过该功能将非对齐序列的数据写入新的对齐序列中。
二、使用示例
2.1 语法
selectIntoStatement : SELECT resultColumn [, resultColumn]...INTO intoItem [, intoItem]...FROM prefixPath [, prefixPath]...[WHERE whereCondition][GROUPBY groupByTimeClause, groupByLevelClause][FILL {PREVIOUS | LINEAR | constant}][LIMIT rowLimit OFFSET rowOffset][ALIGN BY DEVICE]; intoItem : [ALIGNED] intoDevicePath '(' intoMeasurementName [',' intoMeasurementName]*')';2.2 INTO 子句
INTO 子句由若干个 intoItem 构成。
每个 intoItem 由一个目标设备路径和一个包含若干目标物理量名的列表组成(与 INSERT 语句中的 INTO 子句写法类似)。
其中每个目标物理量名与目标设备路径组成一个目标序列,一个 intoItem 包含若干目标序列。例如:root.sg_copy.d1(s1, s2) 指定了两条目标序列 root.sg_copy.d1.s1 和 root.sg_copy.d1.s2。
INTO 子句指定的目标序列要能够与查询结果集的列一一对应。
规则:
按时间对齐(默认):全部 intoItem 包含的目标序列数量要与查询结果集的列数(除时间列外)一致,且按照表头从左到右的顺序一一对应。
按设备对齐(使用 ALIGN BY DEVICE):全部 intoItem 中指定的目标设备数和查询的设备数(即 FROM 子句中路径模式匹配的设备数)一致,且按照结果集设备的输出顺序一一对应。
为每个目标设备指定的目标物理量数量要与查询结果集的列数(除时间和设备列外)一致,且按照表头从左到右的顺序一一对应。
2.3 例子
1.按时间对齐
IoTDB>select s1, s2 into root.sg_copy.d1(t1), root.sg_copy.d2(t1, t2), root.sg_copy.d1(t2)from root.sg.d1, root.sg.d2;+--------------+-------------------+--------+| source column| target timeseries| written|+--------------+-------------------+--------+| root.sg.d1.s1| root.sg_copy.d1.t1|8000|+--------------+-------------------+--------+| root.sg.d2.s1| root.sg_copy.d2.t1|10000|+--------------+-------------------+--------+| root.sg.d1.s2| root.sg_copy.d2.t2|12000|+--------------+-------------------+--------+| root.sg.d2.s2| root.sg_copy.d1.t2|10000|+--------------+-------------------+--------+ Total line number =4 It costs 0.725s 该语句将 root.sg database 下四条序列的查询结果写入到 root.sg_copy database 下指定的四条序列中。
其中的 root.sg_copy.d2(t1, t2) 也可以写做 root.sg_copy.d2(t1), root.sg_copy.d2(t2)。
INTO 子句的写法非常灵活,只要满足组合出的目标序列没有重复,且与查询结果列一一对应即可。
source column 列表示查询结果的列名
target timeseries 表示对应列写入的目标序列
written 表示预期写入的数据量
2.按时间对齐
IoTDB>selectcount(s1 + s2), last_value(s2)into root.agg.count(s1_add_s2), root.agg.last_value(s2)from root.sg.d1 groupby([0,100),10ms);+--------------------------------------+-------------------------+--------+| source column| target timeseries| written|+--------------------------------------+-------------------------+--------+|count(root.sg.d1.s1 + root.sg.d1.s2)| root.agg.count.s1_add_s2|10|+--------------------------------------+-------------------------+--------+| last_value(root.sg.d1.s2)| root.agg.last_value.s2|10|+--------------------------------------+-------------------------+--------+ Total line number =2 It costs 0.375s 该语句将聚合查询的结果存储到指定序列中
3.按设备对齐
IoTDB>select s1, s2 into root.sg_copy.d1(t1, t2), root.sg_copy.d2(t1, t2)from root.sg.d1, root.sg.d2 align by device;+--------------+--------------+-------------------+--------+| source device| source column| target timeseries| written|+--------------+--------------+-------------------+--------+| root.sg.d1| s1| root.sg_copy.d1.t1|8000|+--------------+--------------+-------------------+--------+| root.sg.d1| s2| root.sg_copy.d1.t2|11000|+--------------+--------------+-------------------+--------+| root.sg.d2| s1| root.sg_copy.d2.t1|12000|+--------------+--------------+-------------------+--------+| root.sg.d2| s2| root.sg_copy.d2.t2|9000|+--------------+--------------+-------------------+--------+ Total line number =4 It costs 0.625s 该语句同样是将 root.sg database 下四条序列的查询结果写入到 root.sg_copy database 下指定的四条序列中。但在按设备对齐中,intoItem 的数量必须和查询的设备数量一致,每个查询设备对应一个 intoItem。
按设备对齐查询时,CLI 展示的结果集多出一列 source device 列表示查询的设备。
4.按设备对齐
IoTDB>select s1 + s2 into root.expr.add(d1s1_d1s2), root.expr.add(d2s1_d2s2)from root.sg.d1, root.sg.d2 align by device;+--------------+--------------+------------------------+--------+| source device| source column| target timeseries| written|+--------------+--------------+------------------------+--------+| root.sg.d1| s1 + s2| root.expr.add.d1s1_d1s2|10000|+--------------+--------------+------------------------+--------+| root.sg.d2| s1 + s2| root.expr.add.d2s1_d2s2|10000|+--------------+--------------+------------------------+--------+ Total line number =2 It costs 0.532s 该语句将表达式计算的结果存储到指定序列中
三、变量占位符
特别的可以使用变量占位符描述目标序列与查询序列之间的对应规律,简化语句书写。目前支持两种变量占位符。
- 后缀复制符 :::复制查询设备后缀(或物理量),表示从该层开始一直到设备的最后一层(或物理量),目标设备的节点名(或物理量名)与查询的设备对应的节点名(或物理量名)相同。
- 单层节点匹配符 i :表示目标序列当前层节点名与查询序列的第 i 层节点名相同。比如,对于路径 r o o t . s g 1. d 1. s 1 而言, {i}:表示目标序列当前层节点名与查询序列的第i层节点名相同。比如,对于路径root.sg1.d1.s1而言, i:表示目标序列当前层节点名与查询序列的第i层节点名相同。比如,对于路径root.sg1.d1.s1而言,{1}表示sg1, 2 表示 d 1 , {2}表示d1, 2表示d1,{3}表示s1。
在使用变量占位符时,intoItem与查询结果集列的对应关系不能存在歧义
按时间对齐(默认):变量占位符只能描述序列与序列之间的对应关系,如果查询中包含聚合、表达式计算,此时查询结果中的列无法与某个序列对应,因此目标设备和目标物理量都不能使用变量占位符。
(1)目标设备不使用变量占位符 & 目标物理量列表使用变量占位符
限制:
1.每个 intoItem 中,物理量列表的长度必须为 1。
(如果长度可以大于1,例如 root.sg1.d1(::, s1),无法确定具体哪些列与::匹配)
2.intoItem 数量为 1,或与查询结果集列数一致。
(在每个目标物理量列表长度均为 1 的情况下,若 intoItem 只有 1 个,此时表示全部查询序列写入相同设备;若 intoItem 数量与查询序列一致,则表示为每个查询序列指定一个目标设备;若 intoItem 大于 1 小于查询序列数,此时无法与查询序列一一对应)
匹配方法: 每个查询序列指定目标设备,而目标物理量根据变量占位符生成。
例:
select s1, s2 into root.sg_copy.d1(::), root.sg_copy.d2(s1), root.sg_copy.d1(${3}), root.sg_copy.d2(::)from root.sg.d1, root.sg.d2;等于下面的语句
select s1, s2 into root.sg_copy.d1(s1), root.sg_copy.d2(s1), root.sg_copy.d1(s2), root.sg_copy.d2(s2)from root.sg.d1, root.sg.d2;(2)目标设备使用变量占位符 & 目标物理量列表不使用变量占位符
限制: 全部 intoItem 中目标物理量的数量与查询结果集列数一致。
匹配方式: 为每个查询序列指定了目标物理量,目标设备根据对应目标物理量所在 intoItem 的目标设备占位符生成。
例:
select d1.s1, d1.s2, d2.s3, d3.s4 into ::(s1_1, s2_2), root.sg.d2_2(s3_3), root.${2}_copy.::(s4)from root.sg;(3)目标设备使用变量占位符 & 目标物理量列表使用变量占位符
限制: intoItem 只有一个且物理量列表的长度为 1。
匹配方式: 每个查询序列根据变量占位符可以得到一个目标序列。
例:
select*into root.sg_bk.::(::)from root.sg.**;将 root.sg 下全部序列的查询结果写到 root.sg_bk,设备名后缀和物理量名保持不变
按设备对齐(使用 ALIGN BY DEVICE)
注:变量占位符只能描述序列与序列之间的对应关系,如果查询中包含聚合、表达式计算,此时查询结果中的列无法与某个物理量对应,因此目标物理量不能使用变量占位符。
(1)目标设备不使用变量占位符 & 目标物理量列表使用变量占位符
限制: 每个 intoItem 中,如果物理量列表使用了变量占位符,则列表的长度必须为 1。
匹配方法: 每个查询序列指定目标设备,而目标物理量根据变量占位符生成。
例:
select s1, s2, s3, s4 into root.backup_sg.d1(s1, s2, s3, s4), root.backup_sg.d2(::), root.sg.d3(backup_${4})from root.sg.d1, root.sg.d2, root.sg.d3 align by device;(2)目标设备使用变量占位符 & 目标物理量列表不使用变量占位符
限制: intoItem 只有一个。(如果出现多个带占位符的 intoItem,我们将无法得知每个 intoItem 需要匹配哪几个源设备)
匹配方式: 每个查询设备根据变量占位符得到一个目标设备,每个设备下结果集各列写入的目标物理量由目标物理量列表指定。
例:
selectavg(s1),sum(s2)+sum(s3),count(s4)into root.agg_${2}.::(avg_s1, sum_s2_add_s3, count_s4)from root.** align by device;(3)目标设备使用变量占位符 & 目标物理量列表使用变量占位符
限制: intoItem 只有一个且物理量列表的长度为 1。
匹配方式: 每个查询序列根据变量占位符可以得到一个目标序列。
例:
select*into ::(backup_${4})from root.sg.** align by device;将 root.sg 下每条序列的查询结果写到相同设备下,物理量名前加backup_。
指定目标序列为对齐序列
通过 ALIGNED 关键词可以指定写入的目标设备为对齐写入,每个 intoItem 可以独立设置。
例:
select s1, s2 into root.sg_copy.d1(t1, t2), aligned root.sg_copy.d2(t1, t2)from root.sg.d1, root.sg.d2 align by device;该语句指定了 root.sg_copy.d1 是非对齐设备,root.sg_copy.d2是对齐设备。
不支持使用的查询子句
SLIMIT、SOFFSET:查询出来的列不确定,功能不清晰,因此不支持
LAST查询、GROUP BY TAGS、DISABLE ALIGN:表结构和写入结构不一致,因此不支持
需注意:
对于一般的聚合查询,时间戳是无意义的,约定使用 0 来存储
当目标序列存在时,需要保证源序列和目标时间序列的数据类型兼容
当目标序列不存在时,系统将自动创建目标序列(包括 database)
当查询的序列不存在或查询的序列不存在数据,则不会自动创建目标序列
四、内部ETL实现
对原始数据进行 ETL 处理后写入新序列。
IOTDB >SELECT preprocess_udf(s1, s2)INTO ::(preprocessed_s1, preprocessed_s2)FROM root.sg.* ALIGN BY DEIVCE;+--------------+-------------------+---------------------------+--------+| source device| source column| target timeseries| written|+--------------+-------------------+---------------------------+--------+| root.sg.d1| preprocess_udf(s1)| root.sg.d1.preprocessed_s1|8000|+--------------+-------------------+---------------------------+--------+| root.sg.d1| preprocess_udf(s2)| root.sg.d1.preprocessed_s2|10000|+--------------+-------------------+---------------------------+--------+| root.sg.d2| preprocess_udf(s1)| root.sg.d2.preprocessed_s1|11000|+--------------+-------------------+---------------------------+--------+| root.sg.d2| preprocess_udf(s2)| root.sg.d2.preprocessed_s2|9000|+--------------+-------------------+---------------------------+--------+以上语句使用自定义函数对数据进行预处理,将预处理后的结果持久化存储到新序列中。
查询结果存储
将查询结果进行持久化存储,起到类似物化视图的作用
IOTDB >SELECTcount(s1), last_value(s1)INTO root.sg.agg_${2}(count_s1, last_value_s1)FROM root.sg1.d1 GROUPBY([0,10000),10ms);+--------------------------+-----------------------------+--------+| source column| target timeseries| written|+--------------------------+-----------------------------+--------+|count(root.sg.d1.s1)| root.sg.agg_d1.count_s1|1000|+--------------------------+-----------------------------+--------+| last_value(root.sg.d1.s2)| root.sg.agg_d1.last_value_s2|1000|+--------------------------+-----------------------------+--------+ Total line number =2 It costs 0.115s 以上语句将降采样查询的结果持久化存储到新序列中。
非对齐序列转对齐序列
对齐序列从 0.13 版本开始支持,可以通过该功能将非对齐序列的数据写入新的对齐序列中。
注意: 建议配合使用 LIMIT & OFFSET 子句或 WHERE 子句(时间过滤条件)对数据进行分批,防止单次操作的数据量过大。
IOTDB >SELECT s1, s2 INTO ALIGNED root.sg1.aligned_d(s1, s2)FROM root.sg1.non_aligned_d WHEREtime>=0andtime<10000;+--------------------------+----------------------+--------+| source column| target timeseries| written|+--------------------------+----------------------+--------+| root.sg1.non_aligned_d.s1| root.sg1.aligned_d.s1|10000|+--------------------------+----------------------+--------+| root.sg1.non_aligned_d.s2| root.sg1.aligned_d.s2|10000|+--------------------------+----------------------+--------+ Total line number =2 It costs 0.375s 以上语句将一组非对齐的序列的数据迁移到一组对齐序列
五、用户权限与配置参数
相关用户权限
用户必须有下列权限才能正常执行查询写回语句:
所有 SELECT 子句中源序列的 WRITE_SCHEMA 权限
所有 INTO 子句中目标序列 WRITE_DATA 权限
相关配置参数
select_into_insert_tablet_plan_row_limit

总结
Apache IoTDB的INTO子句通过“查询即存储”的创新设计,实现了时序数据处理的性能突破、功能突破、易用性突破:通过类SQL语法降低使用门槛,配合变量占位符实现动态查询在工业物联网、智能电网、车联网等场景中,INTO子句已成为时序数据处理的核心之一。通过本文的深度解析,不仅能掌握INTO子句的语法细节,更能理解其背后的设计哲学——让数据库自身成为数据处理的核心引擎。