在工业物联网与后端架构中,时序数据处理常面临数据孤岛与处理链冗长的挑战。Apache IoTDB的SELECT INTO语句,通过将查询结果直接写入新序列,实现了数据库内部的轻量级ETL与物化视图功能,极大地简化了数据处理流程,提升了微服务数据层的自治能力。

一、SELECT INTO:时序数据的“查询即存储”引擎

传统数据处理流程中,原始数据往往需要经过外部ETL工具清洗、转换后才能存入分析库,链路长且复杂。Apache IoTDB的INTO子句创新性地将这一过程内化。其核心价值在于:在数据库内部完成“查询-转换-存储”的闭环,无需数据导出,降低了系统复杂度与网络开销。这对于构建高性能、自治的后端数据服务至关重要,尤其适用于实时监控、设备管理等场景。

主要应用场景包括:

  • 内部ETL:对原始时序数据进行过滤、计算、聚合后,将结果持久化到新序列。
  • 查询结果物化:将复杂或高频查询的结果固化存储,起到类似物化视图的作用,加速后续查询。
  • 序列结构转换:将非对齐序列的数据迁移到对齐序列中,优化存储与查询性能。
在这里插入图片描述

上图直观展示了SELECT INTO在数据处理链路中的核心作用。

二、语法精讲:从基础到对齐模式

SELECT INTO语句的基本语法结构如下,其核心在于INTO子句与查询结果集的映射关系:

selectIntoStatement
: SELECT
resultColumn [, resultColumn] ...
INTO intoItem [, intoItem] ...
FROM prefixPath [, prefixPath] ...
[WHERE whereCondition]
[GROUP BY groupByTimeClause, groupByLevelClause]
[FILL {PREVIOUS | LINEAR | constant}]
[LIMIT rowLimit OFFSET rowOffset]
[ALIGN BY DEVICE]
;
intoItem
: [ALIGNED] intoDevicePath '(' intoMeasurementName [',' intoMeasurementName]* ')'
;

INTO子句由多个intoItem构成,每个intoItem指定一个目标设备及其下的多个物理量(测点)。关键在于,目标序列必须与查询结果集的列一一对应。根据查询是否使用ALIGN BY DEVICE,对应规则分为两种模式:

1. 按时间对齐(默认模式)

此模式下,查询结果按时间戳横向排列。所有intoItem中定义的目标序列总数,必须等于查询结果的列数(排除时间列),并按从左到右的顺序严格对应。

示例:将原始数据库root.sg中四个序列的数据,写入到备份库root.sg_copy的指定序列中。

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

source column 列表示查询结果的列名
target timeseries 表示对应列写入的目标序列
written 表示预期写入的数据量

聚合查询的结果同样可以写回,这为构建预聚合指标层提供了便利:

IoTDB> select count(s1 + s2), last_value(s2) into root.agg.count(s1_add_s2), root.agg.last_value(s2) from root.sg.d1 group by ([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

2. 按设备对齐(ALIGN BY DEVICE)

当查询使用ALIGN BY DEVICE时,结果集会按设备纵向组织。此时,intoItem的数量必须与查询匹配的设备数一致,每个设备对应一个intoItem,且每个intoItem内的目标物理量数需与结果列数(排除时间、设备列)对应。

示例:按设备进行数据备份。

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

对于包含表达式计算的查询,按设备对齐的写回同样适用:

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

三、变量占位符:大幅简化批量操作

当需要处理大量序列时,手动编写每个目标路径极其繁琐。IoTDB提供了变量占位符功能,能根据源序列路径动态生成目标路径,极大提升了语句的简洁性与可维护性。

  • 后缀复制符 :::复制查询设备或物理量的后缀。例如,root.backup::表示目标设备从root.backup开始,后续层级与源设备相同。
  • 单层节点匹配符 ${i}:表示目标路径当前层节点名与查询序列路径的第i层节点名相同。例如,对于路径root.sg.d1.s1${2}代表d1

⚠️ 注意:占位符的使用模式(目标设备/物理量是否使用)与查询是否包含聚合、表达式计算紧密相关,需遵循特定规则以避免歧义。

按时间对齐下的占位符用例

用例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:目标设备根据占位符动态生成,物理量名固定指定。

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:设备与物理量均使用占位符,实现完整路径的复制与迁移。

select * into root.sg_bk.::(::) from root.sg.**;

按设备对齐下的占位符用例

在按设备对齐模式下,占位符的使用规则略有不同,需特别注意。

用例:为每个源设备下的物理量备份,并在物理量名前增加前缀。

select * into ::(backup_${4}) from root.sg.** align by device;

四、高级特性与实战应用场景

1. 指定对齐写入

可以通过ALIGNED关键词,精确控制目标设备的写入方式(对齐或非对齐),这对于优化存储和查询性能至关重要。

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;

2. 实现数据库内部ETL

结合IoTDB的内置函数或用户自定义函数(UDF),可以在写回过程中完成复杂的数据清洗与转换,实现真正的库内ETL。[AFFILIATE_SLOT_1]

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|
+--------------+-------------------+---------------------------+--------+

3. 构建物化视图

将耗时较长的聚合查询(如按小时、天聚合)结果持久化存储,后续查询可直接读取结果,极大提升报表和分析性能。

IOTDB > SELECT count(s1), last_value(s1) INTO root.sg.agg_${2}(count_s1, last_value_s1) FROM root.sg1.d1 GROUP BY ([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

4. 非对齐序列转对齐序列

这是数据模型优化的关键步骤。将历史非对齐数据迁移到对齐序列,可以充分利用对齐序列的存储和查询优势。⚠️ 注意:对于大数据量迁移,建议使用WHERE时间过滤或LIMIT/OFFSET进行分批操作。

IOTDB > SELECT s1, s2 INTO ALIGNED root.sg1.aligned_d(s1, s2) FROM root.sg1.non_aligned_d WHERE time >= 0 and time < 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

对于一般的聚合查询,时间戳是无意义的,约定使用 0 来存储
当目标序列存在时,需要保证源序列和目标时间序列的数据类型兼容
当目标序列不存在时,系统将自动创建目标序列(包括 database)
当查询的序列不存在或查询的序列不存在数据,则不会自动创建目标序列

五、权限、配置与性能考量

执行SELECT INTO语句的用户需要具备相应的权限:

所有 SELECT 子句中源序列的 WRITE_SCHEMA 权限
所有 INTO 子句中目标序列 WRITE_DATA 权限

此外,关键的配置参数select_into_insert_tablet_plan_row_limit控制着单次写入的数据块大小,合理调整此参数有助于平衡内存使用与写入性能。

在这里插入图片描述

No.文章地址(点击进入)
1Apache IoTDB(1):时序数据库介绍与单机版安装部署指南
2Apache IoTDB(2):时序数据库 IoTDB 集群安装部署的技术优势与适用场景分析
3Apache IoTDB(3):时序数据库 IoTDB Docker部署从单机到集群的全场景部署与实践指南
4Apache IoTDB(4):深度解析时序数据库 IoTDB 在Kubernetes 集群中的部署与实践指南
5Apache IoTDB(5):深度解析时序数据库 IoTDB 中 AINode 工具的部署与实践
6Apache IoTDB(6):深入解析数据库管理操作——增删改查与异构数据库实战指南
7Apache IoTDB(7):设备模板管理——工业物联网元数据标准化的破局之道
8Apache IoTDB(8):时间序列管理——从创建到分析的实战指南
9Apache IoTDB(9):数据库操作——数据写入从CLI到集群部署的六种实战
10Apache IoTDB(10):数据库操作——从查询到优化的全链路实践指南
11Apache IoTDB(11):分段聚合深度解析——从原理到实战的完整指南
12Apache IoTDB(12):深度解析时序数据聚合的GROUP BY与HAVING子句
13Apache IoTDB(13):数据处理的双刃剑——FILL空值填充与LIMIT/SLIMIT分页查询实战指南
14Apache IoTDB(14):IoTDB结果集排序与查询对齐模式——ORDER BY与ALIGN BY DEVICE使用

总结

Apache IoTDB的SELECT INTO子句是一项强大而灵活的特性,它将数据处理逻辑从应用层下沉到数据库层。通过“查询即存储”的模式,它不仅简化了后端架构中数据管道的复杂度,还通过物化视图、内部ETL等能力,显著提升了数据服务的响应速度与自治性。掌握其语法细节、占位符技巧以及应用场景,对于构建高效、可靠的物联网后端微服务与数据中间件具有重要意义。[AFFILIATE_SLOT_2]