Apache IoTDB(15):IoTDB查询写回(INTO子句)深度解析——从语法到实战的ETL全链路指南

引言

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

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到集群部署的六种实战
10 Apache IoTDB(10):数据库操作------从查询到优化的全链路实践指南
11 Apache IoTDB(11):分段聚合深度解析------从原理到实战的完整指南
12 Apache IoTDB(12):深度解析时序数据聚合的GROUP BY与HAVING子句
13 Apache IoTDB(13):数据处理的双刃剑------FILL空值填充与LIMIT/SLIMIT分页查询实战指南
14 Apache IoTDB(14):IoTDB结果集排序与查询对齐模式------ORDER BY与ALIGN BY DEVICE使用

一、语法定义

SELECT INTO 语句用于将查询结果写入一系列指定的时间序列中。

使用场景

实现 IoTDB 内部 ETL:对原始数据进行 ETL 处理后写入新序列。

查询结果存储:将查询结果进行持久化存储,起到类似物化视图的作用。

非对齐序列转对齐序列:对齐序列从0.13版本开始支持,可以通过该功能将非对齐序列的数据写入新的对齐序列中。

二、使用示例

2.1 语法

sql 复制代码
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]* ')'
    ;

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.按时间对齐

sql 复制代码
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.按时间对齐

sql 复制代码
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

该语句将聚合查询的结果存储到指定序列中

3.按设备对齐

sql 复制代码
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.按设备对齐

sql 复制代码
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

该语句将表达式计算的结果存储到指定序列中

三、变量占位符

特别的可以使用变量占位符描述目标序列与查询序列之间的对应规律,简化语句书写。目前支持两种变量占位符。

  1. 后缀复制符 :::复制查询设备后缀(或物理量),表示从该层开始一直到设备的最后一层(或物理量),目标设备的节点名(或物理量名)与查询的设备对应的节点名(或物理量名)相同。
  2. 单层节点匹配符 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 小于查询序列数,此时无法与查询序列一一对应)

匹配方法: 每个查询序列指定目标设备,而目标物理量根据变量占位符生成。

例:

sql 复制代码
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;

等于下面的语句

sql 复制代码
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 的目标设备占位符生成。

例:

sql 复制代码
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。

匹配方式: 每个查询序列根据变量占位符可以得到一个目标序列。

例:

sql 复制代码
select * into root.sg_bk.::(::) from root.sg.**;

root.sg 下全部序列的查询结果写到 root.sg_bk,设备名后缀和物理量名保持不变

按设备对齐(使用 ALIGN BY DEVICE)

注:变量占位符只能描述序列与序列之间的对应关系,如果查询中包含聚合、表达式计算,此时查询结果中的列无法与某个物理量对应,因此目标物理量不能使用变量占位符。

(1)目标设备不使用变量占位符 & 目标物理量列表使用变量占位符

限制: 每个 intoItem 中,如果物理量列表使用了变量占位符,则列表的长度必须为 1。

匹配方法: 每个查询序列指定目标设备,而目标物理量根据变量占位符生成。

例:

sql 复制代码
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 需要匹配哪几个源设备)

匹配方式: 每个查询设备根据变量占位符得到一个目标设备,每个设备下结果集各列写入的目标物理量由目标物理量列表指定。

例:

sql 复制代码
select avg(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。

匹配方式: 每个查询序列根据变量占位符可以得到一个目标序列。

例:

sql 复制代码
select * into ::(backup_${4}) from root.sg.** align by device;

root.sg 下每条序列的查询结果写到相同设备下,物理量名前加backup_。

指定目标序列为对齐序列

通过 ALIGNED 关键词可以指定写入的目标设备为对齐写入,每个 intoItem 可以独立设置。

例:

sql 复制代码
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 处理后写入新序列。

sql 复制代码
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|
+--------------+-------------------+---------------------------+--------+

以上语句使用自定义函数对数据进行预处理,将预处理后的结果持久化存储到新序列中。

查询结果存储

将查询结果进行持久化存储,起到类似物化视图的作用

sql 复制代码
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

以上语句将降采样查询的结果持久化存储到新序列中。

非对齐序列转对齐序列

对齐序列从 0.13 版本开始支持,可以通过该功能将非对齐序列的数据写入新的对齐序列中。

注意: 建议配合使用 LIMIT & OFFSET 子句或 WHERE 子句(时间过滤条件)对数据进行分批,防止单次操作的数据量过大。

sql 复制代码
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

以上语句将一组非对齐的序列的数据迁移到一组对齐序列

五、用户权限与配置参数

相关用户权限

用户必须有下列权限才能正常执行查询写回语句:

所有 SELECT 子句中源序列的 WRITE_SCHEMA 权限

所有 INTO 子句中目标序列 WRITE_DATA 权限

相关配置参数

select_into_insert_tablet_plan_row_limit

总结

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

相关推荐
不知名的老吴2 小时前
Redis的延迟瓶颈:TCP栈开销无法避免
数据库·redis·缓存
YOU OU2 小时前
三大范式和E-R图
数据库
一江寒逸2 小时前
零基础从入门到精通MySQL(上篇):筑基篇——吃透核心概念与基础操作,打通SQL入门第一关
数据库·sql·mysql
@土豆2 小时前
Ubuntu 22.04 运行 Filebeat 7.11.2 崩溃问题分析及解决文档
linux·数据库·ubuntu
专注API从业者2 小时前
淘宝商品详情 API 与爬虫技术的边界:合法接入与反爬策略的技术博弈
大数据·数据结构·数据库·爬虫
爱码小白2 小时前
MySQL 单表查询练习题汇总
数据库·python·算法
WangJunXiang62 小时前
第09章:PostgreSQL日常维护
数据库·postgresql
三道渊3 小时前
进程通信与网络协议
开发语言·数据库·php
徒 花3 小时前
数据库知识复习05
android·数据库