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

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

引言

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

在这里插入图片描述

Apache IoTDB 时序数据库【系列篇章】

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使用

一、语法定义

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 

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

三、变量占位符

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

  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 小于查询序列数,此时无法与查询序列一一对应)

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

例:

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子句的语法细节,更能理解其背后的设计哲学——让数据库自身成为数据处理的核心引擎。

Read more

【C++】string类

【C++】string类

C++ string 类全面解析 1. 为什么学习 string 类? 1.1 C语言中的字符串局限性 在C语言中,字符串是以\0结尾的字符数组,这种表示方式存在几个明显的缺陷: C语言字符串的主要问题: * 安全性问题:容易发生缓冲区溢出,导致程序崩溃或安全漏洞 * 内存管理复杂:需要手动管理内存分配和释放,容易造成内存泄漏 * 功能有限:标准库函数功能相对基础,复杂的字符串操作需要自行实现 * 不符合面向对象思想:数据与操作分离,不符合现代编程范式 // C语言字符串操作的典型问题char str[10];strcpy(str,"这个字符串太长了会导致溢出");// 潜在的安全风险 1.2 实际应用需求 在现代编程中,字符串处理占据了极大的比重。无论是Web开发、数据处理还是系统编程,都离不开高效的字符串操作。string类的出现正是为了解决C语言字符串的种种痛点。 面试题示例(后续详解): * 字符串转整型数字 * 大数相加(字符串形式)

By Ne0inhk

深入解析C++轻量级WebServer实现

目录 写在前面 概述:这是基于bs模型并且使用epoll实现的高并发服务器,使用线程池+连接池+使用同步或异步的日志系统+定时器等; 线程池使用整体架构图 线程池详细工作流程图 数据库连接池和数据库服务器与线程池的关系 数据库与线程池共同使用流程图 Main.cpp Webserver.h+Webserver.cpp Webserver类的init的函数参数讲解: Webserver.cpp WebServer类的WebServer实现: WebServer::~WebServer()的实现 WebServer::init()的实现: WebServer::trig_mode()的实现: WebServer::log_write() WebServer::sql_pool() WebServer::thread_pool() Timer lst_timer.h lst_timer.cpp Threadpool CGImysql

By Ne0inhk
【C++ Qt】网络编程(QUdpSocket、QTcpSocket、Http)

【C++ Qt】网络编程(QUdpSocket、QTcpSocket、Http)

每日激励:“不设限和自我肯定的心态:I can do all things。 — Stephen Curry” 绪论 : 本章将提到Qt中的网络部分,在看这篇文章之前需要有一定的网络基础也就是TCP/HTTP、本篇文章主要讲到的是Qt中基础的Udp、Tcp、Http的使用方法,并附有了多个小demo方便实操练习,并且其中还在每章最后进行了小总结回顾重要接口和函数方便回顾。 ———————— 早关注不迷路,话不多说安全带系好,发车啦(建议电脑观看)。 网络编程主要依赖于操作系统提供的Socket API。需要注意的是,C++标准库本身并未封装网络编程相关的API。 关于Qt网络编程的几个要点: 1. 网络应用开发本质上是编写应用层代码,需要传输层协议(如TCP/UDP)的支持 2. 为此,Qt提供了两套专门的网络编程API(QUDPSocket和QTcpSocket) 3. 使用Qt网络编程API时,需先在.pro文件中添加network模块 4. 之前学习的Qt控件和核心功能都属于QtCore模块(默认已包含) 为什么Qt要划分出这些模块呢? Qt 本身是一个非常庞

By Ne0inhk
深入解剖STL RB-tree(红黑树):用图解带入相关复杂操作实现

深入解剖STL RB-tree(红黑树):用图解带入相关复杂操作实现

👇点击进入作者专栏: 《算法画解》 ✅ 《linux系统编程》✅ 《C++》 ✅ 文章目录 * 一、红黑树介绍 * 1. 什么是红黑树? * 2. 红黑树的规则 * 3. 为什么最长路径不超过最短路径的两倍? * 4. 红黑树的效率 * 二、红黑树的实现 * 2.1 红黑树的节点结构 * 2.2 红黑树整体结构 * 三、红黑树的插入操作 * 3.1 插入的大致流程 * 3.2 插入后的三种情况 * 情况1:叔叔节点存在且为红色(变色处理) * 情况2:叔叔节点不存在或为黑色 + cur和p在同一侧(单旋+变色) * 情况3:叔叔节点不存在或为黑色 + cur和p在不同侧(双旋+变色) * 3.3 插入完整代码 * 3.4 旋转操作的实现

By Ne0inhk