From f23fc3328ea3c46b97d0defafe72565e691cab2a Mon Sep 17 00:00:00 2001 From: Shengliang Guan Date: Tue, 18 Feb 2025 14:34:30 +0800 Subject: [PATCH] doc: minor changes --- docs/zh/05-basic/01-model.md | 30 ++++----- docs/zh/05-basic/02-insert.md | 8 +-- docs/zh/05-basic/03-query.md | 113 +++++++++++++++++----------------- 3 files changed, 76 insertions(+), 75 deletions(-) diff --git a/docs/zh/05-basic/01-model.md b/docs/zh/05-basic/01-model.md index f49db17892..fc5c3a0a2e 100644 --- a/docs/zh/05-basic/01-model.md +++ b/docs/zh/05-basic/01-model.md @@ -25,11 +25,11 @@ toc_max_heading_level: 4 ### 采集量 采集量是指通过各种传感器、设备或其他类型的采集点所获取的物理量,如电流、电压、温度、压力、GPS 等。由于这些物理量随时间不断变化,因此采集的数据类型多 -样,包括整型、浮点型、布尔型以及字符串等。随着时间的积累,存储的数据将持续增长。以智能电表为例,其中的 current(电流)、voltage(电压)和 phase(相位)便是典型的采集量。 +样,包括整型、浮点型、布尔型以及字符串等。随着时间的积累,存储的数据将持续增长。以智能电表为例,其中的 current、voltage 和 phase 便是典型的采集量。 ### 标签 -标签是指附着在传感器、设备或其他类型采集点上的静态属性,这些属性不会随时间发生变化,例如设备型号、颜色、设备所在地等。标签的数据类型可以是任意类型。尽管标签本身是静态的,但在实际应用中,用户可能需要对标签进行修改、删除或添加。与采集量不同,随着时间的推移,存储的标签数据量保持相对稳定,不会呈现明显的增长趋势。在智能电表的示例中,location(位置)和 Group ID(分组 ID)就是典型的标签。 +标签是指附着在传感器、设备或其他类型采集点上的静态属性,这些属性不会随时间发生变化,例如设备型号、颜色、设备所在地等。标签的数据类型可以是任意类型。尽管标签本身是静态的,但在实际应用中,用户可能需要对标签进行修改、删除或添加。与采集量不同,随着时间的推移,存储的标签数据量保持相对稳定,不会呈现明显的增长趋势。在智能电表的示例中,location 和 Group ID 就是典型的标签。 ### 数据采集点 @@ -49,9 +49,9 @@ toc_max_heading_level: 4 4. 一个数据块内部,采用列式存储,对于不同的数据类型,可以采用不同压缩算法来提高压缩率。并且,由于采集量的变化通常是缓慢的,压缩率会更高。 -如果采用传统的方式,将多个数据采集点的数据写入一张表,由于网络延时不可控,不同数据采集点的数据到达服务器的时序是无法保证的,写入操作是要有锁保护的,而且一个数据采集点的数据是难以保证连续存储在一起的。采用一个数据采集点一张表的方式,能最大程度的保证单个数据采集点的插入和查询的性能是最优的,,而且数据压缩率最高。 +如果采用传统的方式,将多个数据采集点的数据写入一张表,由于网络延时不可控,不同数据采集点的数据到达服务器的时序是无法保证的,写入操作是要有锁保护的,而且一个数据采集点的数据是难以保证连续存储在一起的。采用一个数据采集点一张表的方式,能最大程度的保证单个数据采集点的插入和查询的性能是最优的,而且数据压缩率最高。 -在 TDengine 中,通常使用数据采集点的名称(如:d1001)来做表名,每个数据采集点可以有多个采集量(如:current、voltage、phase 等),每个采集量对应一张表的一列。采集量的数据类型可以是整型、浮点型、字符串等。 +在 TDengine 中,通常使用数据采集点的名称(如 d1001)来做表名,每个数据采集点可以有多个采集量(如 current、voltage、phase 等),每个采集量对应一张表的一列。采集量的数据类型可以是整型、浮点型、字符串等。 此外,表的第一列必须是时间戳,即数据类型为 Timestamp。对于每个采集量,TDengine 将使用第一列时间戳建立索引,采用列式存储。对于复杂的设备,比如汽车,它有多个数据采集点,则需要为一辆汽车建立多张表。 @@ -86,12 +86,12 @@ toc_max_heading_level: 4 ### 时间戳 时间戳在时序数据处理中扮演着至关重要的角色,特别是在应用程序需要从多个不同时区访问数据库时,这一问题变得更加复杂。在深入了解 TDengine 如何处理时间戳与时区之前,我们先介绍以下几个基本概念。 -- 本地日期时间:指特定地区的当地时间,通常表示为 yyyy-MM-dd hh:mm:ss.SSS 格 式 的 字 符 串。 这 种 时 间 表 示 不 包 含 任 何 时 区 信 息, 如“2021-07-21 12:00:00.000”。 -- 时区:地球上不同地理位置的标准时间。协调世界时(Universal Time Coordinated,UTC)或格林尼治时间是国际时间标准,其他时区通常表示为相对于 UTC 的偏移量,如“UTC+8”代表东八区时间。 UTC 时间戳:表示自 UNIX 纪 元(即 UTC 时 间 1970 年 1 月 1 日 0 点) 起 经 过的毫秒数。例如,“1700000000000”对应的日期时间是“2023-11-14 22:13:20(UTC+0)”。 在 TDengine 中保存时序数据时,实际上保存的是 UTC 时间戳。TDengine 在写入数据时,时间戳的处理分为如下两种情况。 -- RFC-3339 格式:当使用这种格式时,TDengine 能够正确解析带有时区信息的时间字符串为 UTC 时间戳。例如,“2018-10-03T14:38:05.000+08:00”会被转换为UTC 时间戳。 +- 本地日期时间:指特定地区的当地时间,通常表示为 yyyy-MM-dd hh:mm:ss.SSS 格式的字符串。这种时间表示不包含任何时区信息,如 “2021-07-21 12:00:00.000”。 +- 时区:地球上不同地理位置的标准时间。协调世界时(Universal Time Coordinated,UTC)或格林尼治时间是国际时间标准,其他时区通常表示为相对于 UTC 的偏移量,如 “UTC+8” 代表东八区时间。 UTC 时间戳:表示自 UNIX 纪元(即 UTC 时间 1970 年 1 月 1 日 0 点)起经过的毫秒数。例如,“1700000000000” 对应的日期时间是 “2023-11-14 22:13:20(UTC+0)”。 在 TDengine 中保存时序数据时,实际上保存的是 UTC 时间戳。TDengine 在写入数据时,时间戳的处理分为如下两种情况。 +- RFC-3339 格式:当使用这种格式时,TDengine 能够正确解析带有时区信息的时间字符串为 UTC 时间戳。例如,“2018-10-03T14:38:05.000+08:00” 会被转换为 UTC 时间戳。 - 非 RFC-3339 格式:如果时间字符串不包含时区信息,TDengine 将使用应用程序所在的时区设置自动将时间转换为 UTC 时间戳。 -在查询数据时,TDengine 客户端会根据应用程序当前的时区设置,自动将保存的UTC 时间戳转换成本地时间进行显示,确保用户在不同时区下都能看到正确的时间信息。 +在查询数据时,TDengine 客户端会根据应用程序当前的时区设置,自动将保存的 UTC 时间戳转换成本地时间进行显示,确保用户在不同时区下都能看到正确的时间信息。 ## 数据建模 @@ -110,7 +110,7 @@ CREATE DATABASE power PRECISION 'ms' KEEP 3650 DURATION 10 BUFFER 16; - `DURATION 10` :每 10 天的数据放在一个数据文件中 - `BUFFER 16` :写入使用大小为 16MB 的内存池。 -在创建power数据库后,可以执行 USE 语句来使用切换数据库。 +在创建 power 数据库后,可以执行 USE 语句来使用切换数据库。 ```sql use power; @@ -134,10 +134,10 @@ CREATE STABLE meters ( 在 TDengine 中,创建超级表的 SQL 语句与关系型数据库类似。例如,上面的 SQL 中,`CREATE STABLE` 为关键字,表示创建超级表;接着,`meters` 是超级表的名称;在表名后面的括号中,定义超级表的列(列名、数据类型等),规则如下: -1. 第 1 列必须为时间戳列。例如:`ts timestamp` 表示,时间戳列名是 `t`s,数据类型为 `timestamp`; -2. 从第 2 列开始是采集量列。采集量的数据类型可以为整型、浮点型、字符串等。例如:`current float` 表示,采集量电流 `current`,数据类型为 `float`; +1. 第 1 列必须为时间戳列。例如:`ts timestamp` 表示,时间戳列名是 `ts`,数据类型为 `timestamp`; +2. 第 2 列开始是采集量列。采集量的数据类型可以为整型、浮点型、字符串等。例如:`current float` 表示,采集量电流 `current`,数据类型为 `float`。 -最后,TAGS是关键字,表示标签,在 TAGS 后面的括号中,定义超级表的标签(标签名、数据类型等)。 +最后,TAGS 是关键字,表示标签,在 TAGS 后面的括号中,定义超级表的标签(标签名、数据类型等)。 1. 标签的数据类型可以为整型、浮点型、字符串等。例如:`location varchar(64)` 表示,标签地区 `location`,数据类型为 `varchar(64)`; 2. 标签的名称不能与采集量列的名称相同。 @@ -155,7 +155,7 @@ USING meters ( ); ``` -上面的 SQL 中,`CREATE TABLE` 为关键字,表示创建表;`d1001` 是子表的名称;`USING` 是关键字,表示要使用超级表作为模版;`meters` 是超级表的名称;在超级表名后的括号中,`location`, `group_id` 表示,是超级表的标签列名列表;`TAGS` 是关键字,在后面的括号中指定子表的标签列的值。`"California.SanFrancisco"` 和 `2` 表示子表 `d1001` 的位置为 `California.SanFrancisco`,分组 ID 为 `2` 。 +上面的 SQL 中,`CREATE TABLE` 为关键字,表示创建表;`d1001` 是子表的名称;`USING` 是关键字,表示要使用超级表作为模版;`meters` 是超级表的名称;在超级表名后的括号中,`location`、`group_id` 表示,是超级表的标签列名列表;`TAGS` 是关键字,在后面的括号中指定子表的标签列的值。`"California.SanFrancisco"` 和 `2` 表示子表 `d1001` 的位置为 `California.SanFrancisco`,分组 ID 为 `2`。 当对超级表进行写入或查询操作时,用户可以使用伪列 tbname 来指定或输出对应操作的子表名。 @@ -178,7 +178,7 @@ TAGS ( ); ``` -上面的 SQL 中,`INSERT INTO d1002` 表示,向子表 `d1002` 中写入数据;`USING meters` 表示,使用超级表 `meters` 作为模版;`TAGS ("California.SanFrancisco", 2)` 表示,子表 `d1002` 的标签值分别为 `California.SanFrancisco` 和 `2`;`VALUES (NOW, 10.2, 219, 0.32)` 表示,向子表 `d1002` 插入一行记录,值分别为NOW(当前时间戳)、10.2(电流)、219(电压)、0.32(相位)。在 TDengine 执行这条 SQL 时,如果子表 `d1002` 已经存在,则直接写入数据;当子表 `d1002` 不存在,会先自动创建子表,再写入数据。 +上面的 SQL 中,`INSERT INTO d1002` 表示,向子表 `d1002` 中写入数据;`USING meters` 表示,使用超级表 `meters` 作为模版;`TAGS ("California.SanFrancisco", 2)` 表示,子表 `d1002` 的标签值分别为 `California.SanFrancisco` 和 `2`;`VALUES (NOW, 10.2, 219, 0.32)` 表示,向子表 `d1002` 插入一行记录,值分别为 NOW(当前时间戳)、10.2(电流)、219(电压)、0.32(相位)。在 TDengine 执行这条 SQL 时,如果子表 `d1002` 已经存在,则直接写入数据;当子表 `d1002` 不存在,会先自动创建子表,再写入数据。 ### 创建普通表 @@ -204,7 +204,7 @@ CREATE TABLE d1003( ); ``` -上面的 SQL 表示,创建普通表 `d1003` ,表结构包括 `ts`、`current`、`voltage`、`phase`、`location`、`group_id`,共 6 个列。这样的数据模型,与关系型数据库完全一致。 +上面的 SQL 表示,创建普通表 `d1003`,表结构包括 `ts`、`current`、`voltage`、`phase`、`location`、`group_id`,共 6 个列。这样的数据模型,与关系型数据库完全一致。 采用普通表作为数据模型意味着静态标签数据(如 location 和 group_id)会重复存储在表的每一行中。这种做法不仅增加了存储空间的消耗,而且在进行查询时,由于无法直接利用标签数据进行过滤,查询性能会显著低于使用超级表的数据模型。 diff --git a/docs/zh/05-basic/02-insert.md b/docs/zh/05-basic/02-insert.md index 88d131e832..b129fdbff1 100644 --- a/docs/zh/05-basic/02-insert.md +++ b/docs/zh/05-basic/02-insert.md @@ -12,9 +12,9 @@ toc_max_heading_level: 4 ### 一次写入一条 -假设设备 ID 为 d1001 的智能电表在 2018 年 10 月 3 日 14:38:05 采集到数据:电流10.3A,电压 219V,相位 0.31。在第 3 章中,我们已经在 TDengine 的 power 数据库中创建了属于超级表 meters 的子表 d1001。接下来可以通过下面的 insert 语句在子表 d1001 中写入时序数据。 +假设设备 ID 为 d1001 的智能电表在 2018 年 10 月 3 日 14:38:05 采集到数据:电流 10.3A,电压 219V,相位 0.31。在第 3 章中,我们已经在 TDengine 的 power 数据库中创建了属于超级表 meters 的子表 d1001。接下来可以通过下面的 insert 语句在子表 d1001 中写入时序数据。 -1. 可以通过下面的 INSERT 语句向子表d1001中写入时序数据。 +1. 可以通过下面的 INSERT 语句向子表 d1001 中写入时序数据。 ```sql insert into d1001 (ts, current, voltage, phase) values ( "2018-10-03 14:38:05", 10.3, 219, 0.31) @@ -120,7 +120,7 @@ values( "d1001, "2018-10-03 14:38:05", 10.2, 220, 0.23, "California.SanFrancisco ## 更新 -可以通过写入重复时间戳的一条数据来更新时序数据,新写入的数据会替换旧值。 下面的 SQL,通过指定列的方式,向子表 `d1001` 中写入 1 行数据;当子表 `d1001` 中已经存在日期时间为 `2018-10-03 14:38:05` 的数据时,`current`(电流)的新值22,会替换旧值。 +可以通过写入重复时间戳的一条数据来更新时序数据,新写入的数据会替换旧值。下面的 SQL,通过指定列的方式,向子表 `d1001` 中写入 1 行数据;当子表 `d1001` 中已经存在日期时间为 `2018-10-03 14:38:05` 的数据时,`current`(电流)的新值 22,会替换旧值。 ```sql INSERT INTO d1001 (ts, current) VALUES ("2018-10-03 14:38:05", 22); @@ -128,7 +128,7 @@ INSERT INTO d1001 (ts, current) VALUES ("2018-10-03 14:38:05", 22); ## 删除 -为方便用户清理由于设备故障等原因产生的异常数据,TDengine 支持根据时间戳删除时序数据。 下面的 SQL,将超级表 `meters` 中所有时间戳早于 `2021-10-01 10:40:00.100` 的数据删除。数据删除后不可恢复,请慎重使用。为了确保删除的数据确实是自己要删除的,建议可以先使用 select 语句加 where 后的删除条件查看要删除的数据内容,确认无误后再执行 delete 。 +为方便用户清理由于设备故障等原因产生的异常数据,TDengine 支持根据时间戳删除时序数据。下面的 SQL,将超级表 `meters` 中所有时间戳早于 `2021-10-01 10:40:00.100` 的数据删除。数据删除后不可恢复,请慎重使用。为了确保删除的数据确实是自己要删除的,建议可以先使用 select 语句加 where 后的删除条件查看要删除的数据内容,确认无误后再执行 delete 。 ```sql delete from meters where ts < '2021-10-01 10:40:00.100' ; diff --git a/docs/zh/05-basic/03-query.md b/docs/zh/05-basic/03-query.md index f3c9eb099e..0b2f290667 100644 --- a/docs/zh/05-basic/03-query.md +++ b/docs/zh/05-basic/03-query.md @@ -14,7 +14,7 @@ toc_max_heading_level: 4 taosBenchmark --start-timestamp=1600000000000 --tables=100 --records=10000000 --time-step=10000 ``` -上面的命令,taosBenchmark 工具在 TDengine 中生成了一个用于测试的数据库,产生共 10 亿条时序数据。时序数据的时间戳从 `1600000000000`(2020-09-13T20:26:40+08:00)开始,包含 `100` 个设备(子表),每个设备有 `10000000` 条数据,时序数据的采集频率是 10 秒/ 条。 +上面的命令,taosBenchmark 工具在 TDengine 中生成了一个用于测试的数据库,产生共 10 亿条时序数据。时序数据的时间戳从 `1600000000000`(2020-09-13T20:26:40+08:00)开始,包含 `100` 个设备(子表),每个设备有 `10000000` 条数据,时序数据的采集频率是 10 秒/条。 在 TDengine 中,用户可以通过 WHERE 语句指定条件,查询时序数据。以智能电表的数据为例 @@ -74,22 +74,22 @@ GROUP BY groupid; Query OK, 10 row(s) in set (0.042446s) ``` -**注意**: group by 子句在聚合数据时,并不保证结果集按照特定顺序排列。为了获得有序的结果集,可以使用 order by 子句对结果进行排序。这样,可以根据需要调整输出结果的顺序,以满足特定的业务需求或报告要求。 +**注意**:group by 子句在聚合数据时,并不保证结果集按照特定顺序排列。为了获得有序的结果集,可以使用 order by 子句对结果进行排序。这样,可以根据需要调整输出结果的顺序,以满足特定的业务需求或报告要求。 TDengine 提供了多种内置的聚合函数。如下表所示: | 聚合函数 | 功能说明 | |:----------------------:|:--------------------------------------------------------------:| -|APERCENTILE | 统计表/超级表中指定列的值的近似百分比分位数,与 PERCENTILE 函数相似,但是返回近似结果。 | -|AVG | 统计指定字段的平均值 | -|COUNT | 统计指定字段的记录行数 | +|APERCENTILE | 统计表/超级表中指定列的值的近似百分比分位数,与 PERCENTILE 函数相似,但是返回近似结果。| +|AVG | 统计指定字段的平均值。| +|COUNT | 统计指定字段的记录行数。| |ELAPSED|elapsed 函数表达了统计周期内连续的时间长度,和 twa 函数配合使用可以计算统计曲线下的面积。在通过 INTERVAL 子句指定窗口的情况下,统计在给定时间范围内的每个窗口内有数据覆盖的时间范围;如果没有 INTERVAL 子句,则返回整个给定时间范围内的有数据覆盖的时间范围。注意,ELAPSED 返回的并不是时间范围的绝对值,而是绝对值除以 time_unit 所得到的单位个数。| -|LEASTSQUARES | 统计表中某列的值的拟合直线方程。start_val 是自变量初始值,step_val 是自变量的步长值。 | +|LEASTSQUARES | 统计表中某列的值的拟合直线方程。start_val 是自变量初始值,step_val 是自变量的步长值。| |SPREAD | 统计表中某列的最大值和最小值之差。| -|STDDEV | 统计表中某列的均方差。 | -|SUM | 统计表/超级表中某列的和。 | -|HYPERLOGLOG | 采用 hyperloglog 算法,返回某列的基数。该算法在数据量很大的情况下,可以明显降低内存的占用,求出来的基数是个估算值,标准误差(标准误差是多次实验,每次的平均数的标准差,不是与真实结果的误差)为 0.81%。在数据量较少的时候该算法不是很准确,可以使用 select count(data) from (select unique(col) as data from table) 的方法。 | -|HISTOGRAM | 统计数据按照用户指定区间的分布。 | +|STDDEV | 统计表中某列的均方差。| +|SUM | 统计表/超级表中某列的和。| +|HYPERLOGLOG | 采用 hyperloglog 算法,返回某列的基数。该算法在数据量很大的情况下,可以明显降低内存的占用,求出来的基数是个估算值,标准误差(标准误差是多次实验,每次的平均数的标准差,不是与真实结果的误差)为 0.81%。在数据量较少的时候该算法不是很准确,可以使用 select count(data) from (select unique(col) as data from table) 的方法。| +|HISTOGRAM | 统计数据按照用户指定区间的分布。| |PERCENTILE | 统计表中某列的值百分比分位数。| ## 数据切分查询 @@ -101,12 +101,12 @@ PARTITION BY part_list `part_list` 可以是任意的标量表达式,包括列、常量、标量函数和它们的组合。 -TDengine 按如下方式处理数据切分子句。 +TDengine 按如下方式处理数据切分子句: 1. 数据切分子句位于 WHERE 子句之后; 2. 数据切分子句将表数据按指定的维度进行切分,每个切分的分片进行指定的计算。计算由之后的子句定义(窗口子句、GROUP BY 子句或 SELECT 子句); 3. 数据切分子句可以和窗口切分子句(或 GROUP BY 子句)一起使用,此时后面的子句作用在每个切分的分片上。 -数据切分的 SQL 如下:s +数据切分的 SQL 如下: ```sql SELECT location, avg(voltage) @@ -141,6 +141,7 @@ Query OK, 10 row(s) in set (2.415961s) - 状态窗口(status window) - 会话窗口(session window) - 事件窗口(event window) +- 计数窗口(count window) 窗口划分逻辑如下图所示: @@ -152,14 +153,15 @@ Query OK, 10 row(s) in set (2.415961s) window_clause: { SESSION(ts_col, tol_val) | STATE_WINDOW(col) - | INTERVAL(interval_val [, interval_offset]) [SLIDING (sliding_val)] [FILL(fill_mod_and_val)] + | INTERVAL(interval_val [, interval_offset]) [SLIDING (sliding_val)] [WATERMARK(watermark_val)] [FILL(fill_mod_and_val)] | EVENT_WINDOW START WITH start_trigger_condition END WITH end_trigger_condition + | COUNT_WINDOW(count_val[, sliding_val]) } ``` **注意** 在使用窗口子句时应注意以下规则: 1. 窗口子句位于数据切分子句之后,不可以和 GROUP BY 子句一起使用。 -2. 窗口子句将数据按窗口进行切分,对每个窗口进行 SELECT 列表中的表达式的计算,SELECT 列表中的表达式只能包含:常量;伪列:_wstart 伪列、_wend 伪列和 _wduration 伪列;聚合函数(包括选择函数和可以由参数确定输出行数的时序特有函数) +2. 窗口子句将数据按窗口进行切分,对每个窗口进行 SELECT 列表中的表达式的计算,SELECT 列表中的表达式只能包含:常量;伪列:_wstart、_wend 和 _wduration;聚合函数:包括选择函数和可以由参数确定输出行数的时序特有函数。 3. WHERE 语句可以指定查询的起止时间和其他过滤条件。 ### 时间戳伪列 @@ -177,16 +179,15 @@ INTERVAL(interval_val [, interval_offset]) ``` 时间窗口子句包括 3 个子句: -- INTERVAL 子句:用于产生相等时间周期的窗口,interval_val 指定每个时间窗口的大小,interval_offset 指定; +- INTERVAL 子句:用于产生相等时间周期的窗口,interval_val 指定每个时间窗口的大小,interval_offset 指定窗口偏移量; - SLIDING 子句:用于指定窗口向前滑动的时间; - FILL:用于指定窗口区间数据缺失的情况下,数据的填充模式。 -对于时间窗口,interval_val 和 sliding_val 都表示时间段, 语法上支持三种方式。例如: -1. INTERVAL(1s, 500a) SLIDING(1s),带时间单位的形式,其中的时间单位是单字符表示, 分别为: a (毫秒), b (纳秒), d (天), h (小时), m (分钟), n (月), s (秒), u (微秒), w (周), y (年); +对于时间窗口,interval_val 和 sliding_val 都表示时间段,语法上支持三种方式。例如: +1. INTERVAL(1s, 500a) SLIDING(1s),带时间单位的形式,其中的时间单位是单字符表示,分别为:a(毫秒)、b(纳秒),d(天)、h(小时)、m(分钟)、n(月)、s(秒)、u(微秒)、w(周)、y(年); 2. INTERVAL(1000, 500) SLIDING(1000),不带时间单位的形式,将使用查询库的时间精度作为默认时间单位,当存在多个库时默认采用精度更高的库; 3. INTERVAL('1s', '500a') SLIDING('1s'),带时间单位的字符串形式,字符串内部不能有任何空格等其它字符。 - 示例 SQL 如下: ```sql SELECT tbname, _wstart, _wend, avg(voltage) @@ -220,7 +221,7 @@ Query OK, 12 row(s) in set (0.021265s) #### 滑动窗口 -每次执行的查询是一个时间窗口,时间窗口随着时间流动向前滑动。在定义连续查询的时候需要指定时间窗口(time window )大小和每次前向增量时间(forward sliding times)。如下图,[t0s, t0e] ,[t1s , t1e], [t2s, t2e] 是分别是执行三次连续查询的时间窗口范围,窗口的前向滑动的时间范围 sliding time 标识 。查询过滤、聚合等操作按照每个时间窗口为独立的单位执行。 +每次执行的查询是一个时间窗口,时间窗口随着时间流动向前滑动。在定义连续查询的时候需要指定时间窗口(time window )大小和每次前向增量时间(forward sliding times)。如下图,[t0s, t0e]、[t1s, t1e]、[t2s, t2e] 是分别是执行三次连续查询的时间窗口范围,窗口的前向滑动的时间范围 sliding time 标识。查询过滤、聚合等操作按照每个时间窗口为独立的单位执行。 ![时间窗口示意图](./sliding-window.png) @@ -238,7 +239,7 @@ SELECT COUNT(*) FROM temp_tb_1 INTERVAL(1m) SLIDING(2m); **使用时间窗口需要注意** 1. 聚合时间段的窗口宽度由关键词 INTERVAL 指定,最短时间间隔 10 毫秒(10a);并且支持偏移 offset(偏移必须小于间隔),也即时间窗口划分与“UTC 时刻 0”相比的偏移量。SLIDING 语句用于指定聚合时间段的前向增量,也即每次窗口向前滑动的时长。 -2. 使用 INTERVAL 语句时,除非极特殊的情况,都要求把客户端和服务端的 taos.cfg 配置文件中的 timezone 参数配置为相同的取值,以避免时间处理函数频繁进行跨时区转换而导致的严重性能影响。 +2. 使用 INTERVAL 语句时,除非极特殊的情况,都要求把客户端和服务端的 timezone 参数配置为相同的取值,以避免时间处理函数频繁进行跨时区转换而导致的严重性能影响。 3. 返回的结果中时间序列严格单调递增。 示例: @@ -274,7 +275,7 @@ Query OK, 11 row(s) in set (0.013153s) #### 翻转窗口 -当 SLIDING 与 INTERVAL 相等的时候,滑动窗口即为翻转窗口。翻转窗口和滑动窗口的区别在于,滑动窗口因为 interval_val 和 sliding_val 不同,不同时间窗口之间,会存在数据重叠,翻转窗口则没有数据重叠。本质上,翻转窗口就是按照 interval_val 进行了时间窗口划分,INTERVAL(1m)和INTERVAL(1m) SLIDING(1m)是等效的。 +当 SLIDING 与 INTERVAL 相等的时候,滑动窗口即为翻转窗口。翻转窗口和滑动窗口的区别在于,滑动窗口因为 interval_val 和 sliding_val 不同,不同时间窗口之间,会存在数据重叠,翻转窗口则没有数据重叠。本质上,翻转窗口就是按照 interval_val 进行了时间窗口划分,INTERVAL(1m) 和 INTERVAL(1m) SLIDING(1m) 是等效的。 示例: @@ -304,7 +305,7 @@ Query OK, 5 row(s) in set (0.016812s) #### FILL 子句 1. 不进行填充:NONE(默认填充模式)。 -2. VALUE 填充:固定值填充,此时需要指定填充的数值。例如:FILL(VALUE, 1.23)。这里需要注意,最终填充的值受由相应列的类型决定,如 FILL(VALUE, 1.23),相应列为 INT 类型,则填充值为 1, 若查询列表中有多列需要 FILL, 则需要给每一个 FILL 列指定 VALUE, 如 `SELECT _wstart, min(c1), max(c1) FROM ... FILL(VALUE, 0, 0)`, 注意, SELECT 表达式中只有包含普通列时才需要指定 FILL VALUE, 如 `_wstart`, `_wstart+1a`, `now`, `1+1` 以及使用 partition by 时的 partition key (如 tbname)都不需要指定 VALUE, 如 `timediff(last(ts), _wstart)` 则需要指定VALUE。 +2. VALUE 填充:固定值填充,此时需要指定填充的数值。例如:FILL(VALUE, 1.23)。这里需要注意,最终填充的值受由相应列的类型决定,如 FILL(VALUE, 1.23),相应列为 INT 类型,则填充值为 1,若查询列表中有多列需要 FILL,则需要给每一个 FILL 列指定 VALUE,如 `SELECT _wstart, min(c1), max(c1) FROM ... FILL(VALUE, 0, 0)`。注意,SELECT 表达式中只有包含普通列时才需要指定 FILL VALUE,如 `_wstart`、`_wstart+1a`、`now`、`1+1` 以及使用 partition by 时的 partition key (如 tbname)都不需要指定 VALUE,,如 `timediff(last(ts), _wstart)` 则需要指定VALUE。 3. PREV 填充:使用前一个非 NULL 值填充数据。例如:FILL(PREV)。 4. NULL 填充:使用 NULL 填充数据。例如:FILL(NULL)。 5. LINEAR 填充:根据前后距离最近的非 NULL 值做线性插值填充。例如:FILL(LINEAR)。 @@ -313,11 +314,11 @@ Query OK, 5 row(s) in set (0.016812s) 以上填充模式中,除了 NONE 模式默认不填充值之外,其他模式在查询的整个时间范围内如果没有数据 FILL 子句将被忽略,即不产生填充数据,查询结果为空。这种行为在部分模式(PREV、NEXT、LINEAR)下具有合理性,因为在这些模式下没有数据意味着无法产生填充数值。 对另外一些模式(NULL、VALUE)来说,理论上是可以产生填充数值的,至于需不需要输出填充数值,取决于应用的需求。所以为了满足这类需要强制填充数据或 NULL 的应用的需求,同时不破坏现有填充模式的行为兼容性,TDengine 还支持两种新的填充模式: -1. NULL_F: 强制填充 NULL 值 -2. VALUE_F: 强制填充 VALUE 值 +1. NULL_F:强制填充 NULL 值 +2. VALUE_F:强制填充 VALUE 值 -NULL、 NULL_F、 VALUE、 VALUE_F 这几种填充模式针对不同场景区别如下: -1. INTERVAL 子句: NULL_F, VALUE_F 为强制填充模式;NULL, VALUE 为非强制模式。在这种模式下下各自的语义与名称相符 +NULL、NULL_F、VALUE、VALUE_F 这几种填充模式针对不同场景区别如下: +1. INTERVAL 子句:NULL_F、VALUE_F 为强制填充模式;NULL、VALUE 为非强制模式。在这种模式下下各自的语义与名称相符 2. 流计算中的 INTERVAL 子句:NULL_F 与 NULL 行为相同,均为非强制模式;VALUE_F 与 VALUE 行为相同,均为非强制模式。即流计算中的 INTERVAL 没有强制模式 3. INTERP 子句:NULL 与 NULL_F 行为相同,均为强制模式;VALUE 与 VALUE_F 行为相同,均为强制模式。即 INTERP 中没有非强制模式。 @@ -405,7 +406,7 @@ Query OK, 22 row(s) in set (0.153403s) ### 会话窗口 -会话窗口根据记录的时间戳主键的值来确定是否属于同一个会话。如下图所示,如果设置时间戳的连续的间隔小于等于 12 秒,则以下 6 条记录构成 2 个会话窗口,分别是:[2019-04-28 14:22:10,2019-04-28 14:22:30]和[2019-04-28 14:23:10,2019-04-28 14:23:30]。因为 2019-04-28 14:22:30 与 2019-04-28 14:23:10 之间的时间间隔是 40 秒,超过了连续时间间隔(12 秒)。 +会话窗口根据记录的时间戳主键的值来确定是否属于同一个会话。如下图所示,如果设置时间戳的连续的间隔小于等于 12 秒,则以下 6 条记录构成 2 个会话窗口,分别是:[2019-04-28 14:22:10,2019-04-28 14:22:30] 和 [2019-04-28 14:23:10,2019-04-28 14:23:30]。因为 2019-04-28 14:22:30 与 2019-04-28 14:23:10 之间的时间间隔是 40 秒,超过了连续时间间隔(12 秒)。 ![会话窗口示意图](./session-window.png) @@ -452,7 +453,7 @@ Query OK, 10 row(s) in set (0.043489s) 事件窗口无法关闭时,不构成一个窗口,不会被输出。即有数据满足 start_trigger_condition,此时窗口打开,但后续数据都不能满足 end_trigger_condition,这个窗口无法被关闭,这部分数据不够成一个窗口,不会被输出。 -如果直接在超级表上进行事件窗口查询,TDengine 会将超级表的数据汇总成一条时间线,然后进行事件窗口的计算。 如果需要对子查询的结果集进行事件窗口查询,那么子查询的结果集需要满足按时间线输出的要求,且可以输出有效的时间戳列。 +如果直接在超级表上进行事件窗口查询,TDengine 会将超级表的数据汇总成一条时间线,然后进行事件窗口的计算。如果需要对子查询的结果集进行事件窗口查询,那么子查询的结果集需要满足按时间线输出的要求,且可以输出有效的时间戳列。 以下面的 SQL 语句为例,事件窗口切分如下图所示。 @@ -474,7 +475,7 @@ EVENT_WINDOW START WITH voltage >= 225 END WITH voltage < 235 LIMIT 5; ``` -上面的 SQL,查询超级表meters中,时间戳大于等于2022-01-01T00:00:00+08:00,且时间戳小于2022-01-01T00:10:00+08:00的数据;数据先按照子表名tbname进行数据切分,再根据事件窗口条件:电压大于等于 225V,且小于 235V 进行切分;最后,取每个分片的前 5 行的数据作为结果,返回子表名、窗口开始时间、窗口结束时间、窗口宽度、窗口内数据条数。查询结果如下: +上面的 SQL,查询超级表 meters 中,时间戳大于等于 2022-01-01T00:00:00+08:00,且时间戳小于 2022-01-01T00:10:00+08:00 的数据;数据先按照子表名 tbname 进行数据切分,再根据事件窗口条件:电压大于等于 225V,且小于 235V 进行切分;最后,取每个分片的前 5 行的数据作为结果,返回子表名、窗口开始时间、窗口结束时间、窗口宽度、窗口内数据条数。查询结果如下: ```text tbname | _wstart | _wend | _wduration | count(*) | @@ -529,25 +530,25 @@ Query OK, 10 row(s) in set (0.062794s) 时序数据特有函数是 TDengine 针对时序数据查询场景专门设计的一组函数。在通用数据库中,要实现类似的功能通常需要编写复杂的查询语句,而且效率较低。为了降低用户的使用成本和简化查询过程,TDengine 将这些功能以内置函数的形式提供,从而实现了高效且易于使用的时序数据处理能力。时序数据特有函数如下表所示。 -| 函数 | 功能说明 | -|:---------------:|:--------------------------------------------------------------------:| -|CSUM | 累加和(Cumulative sum),忽略 NULL 值。 | -|DERIVATIVE | 统计表中某列数值的单位变化率。其中单位时间区间的长度可以通过 time_interval 参数指定,最小可以是 1 秒(1s);ignore_negative 参数的值可以是 0 或 1,为 1 时表示忽略负值。 | -|DIFF | 统计表中某列的值与前一行对应值的差。 ignore_negative 取值为 0|1 , 可以不填,默认值为 0。 不忽略负值。ignore_negative 为 1 时表示忽略负数。| -|IRATE | 计算瞬时增长率。使用时间区间中最后两个样本数据来计算瞬时增长速率;如果这两个值呈递减关系,那么只取最后一个数用于计算,而不是使用二者差值。 | -|MAVG | 计算连续 k 个值的移动平均数(moving average)。如果输入行数小于 k,则无结果输出。参数 k 的合法输入范围是 1≤ k ≤ 1000。| -|STATECOUNT | 返回满足某个条件的连续记录的个数,结果作为新的一列追加在每行后面。条件根据参数计算,如果条件为 true 则加 1,条件为 false 则重置为 -1,如果数据为 NULL,跳过该条数据。 | +| 函数 | 功能说明 | +|:------------:|:--------------------------------------------------------------------:| +|CSUM | 累加和(Cumulative sum),忽略 NULL 值。| +|DERIVATIVE | 统计表中某列数值的单位变化率。其中单位时间区间的长度可以通过 time_interval 参数指定,最小可以是 1 秒(1s);ignore_negative 参数的值可以是 0 或 1,为 1 时表示忽略负值。| +|DIFF | 统计表中某列的值与前一行对应值的差。ignore_negative 取值为 0|1 ,可以不填,默认值为 0。不忽略负值。ignore_negative 为 1 时表示忽略负数。| +|IRATE | 计算瞬时增长率。使用时间区间中最后两个样本数据来计算瞬时增长速率;如果这两个值呈递减关系,那么只取最后一个数用于计算,而不是使用二者差值。| +|MAVG | 计算连续 k 个值的移动平均数(moving average)。如果输入行数小于 k,则无结果输出。参数 k 的合法输入范围是 1≤ k ≤ 1000。| +|STATECOUNT | 返回满足某个条件的连续记录的个数,结果作为新的一列追加在每行后面。条件根据参数计算,如果条件为 true 则加 1,条件为 false 则重置为 -1,如果数据为 NULL,跳过该条数据。| |STATEDURATION | 返回满足某个条件的连续记录的时间长度,结果作为新的一列追加在每行后面。条件根据参数计算,如果条件为 true 则加上两个记录之间的时间长度(第一个满足条件的记录时间长度记为 0),条件为 false 则重置为 -1,如果数据为 NULL,跳过该条数据| -|TWA | 时间加权平均函数。统计表中某列在一段时间内的时间加权平均。 | +|TWA | 时间加权平均函数。统计表中某列在一段时间内的时间加权平均。| ## 嵌套查询 嵌套查询,也称为 subquery(子查询),是指在一个 SQL 中,内层查询的计算结果可以作为外层查询的计算对象来使用。TDengine 支持在 from 子句中使用非关联 subquery。非关联是指 subquery 不会用到父查询中的参数。在 select 查询的 from 子句之后,可以接一个独立的 select 语句,这个 select 语句被包含在英文圆括号内。通过使用嵌套查询,你可以在一个查询中引用另一个查询的结果,从而实现更复杂的数据处理和分析。以智能电表为例进行说明,SQL 如下 ```sql -SELECT max(voltage),* +SELECT max(voltage), * FROM ( - SELECT tbname,last_row(ts),voltage,current,phase,groupid,location + SELECT tbname, last_row(ts), voltage, current, phase, groupid, location FROM meters PARTITION BY tbname ) @@ -559,12 +560,12 @@ GROUP BY groupid; TDengine 的嵌套查询遵循以下规则: 1. 内层查询的返回结果将作为“虚拟表”供外层查询使用,此虚拟表建议起别名,以便于外层查询中方便引用。 2. 外层查询支持直接通过列名或列名的形式引用内层查询的列或伪列。 -3. 在内层和外层查询中,都支持普通的表间/超级表间 JOIN。内层查询的计算结果也可以再参与数据子表的 JOIN 操作。 +3. 在内层和外层查询中,都支持普通表间/超级表间 JOIN。内层查询的计算结果也可以再参与数据子表的 JOIN 操作。 4. 内层查询支持的功能特性与非嵌套的查询语句能力是一致的。内层查询的 ORDER BY 子句一般没有意义,建议避免这样的写法以免无谓的资源消耗。 5. 与非嵌套的查询语句相比,外层查询所能支持的功能特性存在如下限制: -6. 如果内层查询的结果数据未提供时间戳,那么计算过程隐式依赖时间戳的函数在外层会无法正常工作。例如:INTERP, DERIVATIVE, IRATE, LAST_ROW, FIRST, LAST, TWA, STATEDURATION, TAIL, UNIQUE。 -7. 如果内层查询的结果数据不是按时间戳有序,那么计算过程依赖数据按时间有序的函数在外层会无法正常工作。例如:LEASTSQUARES, ELAPSED, INTERP, DERIVATIVE, IRATE, TWA, DIFF, STATECOUNT, STATEDURATION, CSUM, MAVG, TAIL, UNIQUE。 -8. 计算过程需要两遍扫描的函数,在外层查询中无法正常工作。例如:此类函数包括:PERCENTILE。 +6. 如果内层查询的结果数据未提供时间戳,那么计算过程隐式依赖时间戳的函数在外层会无法正常工作。例如:INTERP、DERIVATIVE、IRATE、LAST_ROW、FIRST、LAST、TWA、STATEDURATION、TAIL、UNIQUE。 +7. 如果内层查询的结果数据不是按时间戳有序,那么计算过程依赖数据按时间有序的函数在外层会无法正常工作。例如:LEASTSQUARES、ELAPSED、INTERP、DERIVATIVE、IRATE、TWA、DIFF、STATECOUNT、STATEDURATION、CSUM、MAVG、TAIL、UNIQUE。 +8. 计算过程需要两遍扫描的函数,在外层查询中无法正常工作。例如:PERCENTILE。 ## UNION 子句 @@ -573,11 +574,11 @@ TDengine 支持 UNION 操作符。也就是说,如果多个 SELECT 子句返 示例: ```sql -(SELECT tbname,* FROM d1 limit 1) +(SELECT tbname, * FROM d1 limit 1) UNION ALL -(SELECT tbname,* FROM d11 limit 2) +(SELECT tbname, * FROM d11 limit 2) UNION ALL -(SELECT tbname,* FROM d21 limit 3); +(SELECT tbname, * FROM d21 limit 3); ``` 上面的 SQL,分别查询:子表 d1 的 1 条数据,子表 d11 的 2 条数据,子表 d21 的 3 条数据,并将结果合并。返回的结果如下: @@ -594,7 +595,7 @@ UNION ALL Query OK, 6 row(s) in set (0.006438s) ``` -在同一个 sql 语句中,最多支持 100 个 UNION 子句。 +在同一个 SQL 语句中,最多支持 100 个 UNION 子句。 ## 关联查询 @@ -640,9 +641,9 @@ select a.* from meters a left asof join meters b on timetruncate(a.ts, 1s) < tim ### 语法说明 -在接下来的内容中,我们将通过统一的方式并行介绍 Left Join 和 Right Join 系列。因此,在后续关于 Outer、Semi、Anti-Semi、ASOF、Window 等系列内容的介绍中,我们采用了“ Left/Right”这种表述方式来同时涵盖 Left Join 和 Right Join 的相关知识。这里的“ /”符号前的描述专指应用于 Left Join,而“ /”符号后的描述则专指应用于 Right Join。通过这种表述方式,我们可以更加清晰地展示这两种 Join 操作的特点和用法。 +在接下来的内容中,我们将通过统一的方式并行介绍 Left Join 和 Right Join 系列。因此,在后续关于 Outer、Semi、Anti-Semi、ASOF、Window 等系列内容的介绍中,我们采用了“Left/Right”这种表述方式来同时涵盖 Left Join 和 Right Join 的相关知识。这里的“/”符号前的描述专指应用于 Left Join,而“/”符号后的描述则专指应用于 Right Join。通过这种表述方式,我们可以更加清晰地展示这两种 Join 操作的特点和用法。 -例如,当我们提及“左 / 右表”时,对于 Left Join,它特指左表,而对于 Right Join,它则特指右表。同理,当我们提及“右 / 左表”时,对于 Left Join,它特指右表,而对于 Right Join,它则特指左表。 +例如,当我们提及“左/右表”时,对于 Left Join,它特指左表,而对于 Right Join,它则特指右表。同理,当我们提及“右/左表”时,对于 Left Join,它特指右表,而对于 Right Join,它则特指左表。 ### Join 功能 @@ -650,13 +651,13 @@ select a.* from meters a left asof join meters b on timetruncate(a.ts, 1s) < tim | Join 类型 | 定义 | |:------------------------:|:--------------------------------------------------------:| -|Inner Join | 内连接,只有左右表中同时符合连接条件的数据才会被返回,可以视为两张表符合连接条件的数据的交集 | -|Left/Right Outer Join | 左 / 右(外)连接,既包含左右表中同时符合连接条件的数据集合,也包括左 / 右表中不符合连接条件的数据集合 | -|Left/Right Semi Join | 左 / 右半连接,通常表达的是 in、exists 的含义,即对左 / 右表任意一条数据来说,只有当右 / 左表中存在任一符合连接条件的数据时才返回左 / 右表行数据 | +|Inner Join | 内连接,只有左右表中同时符合连接条件的数据才会被返回,可以视为两张表符合连接条件的数据的交集 | +|Left/Right Outer Join | 左 / 右(外)连接,既包含左右表中同时符合连接条件的数据集合,也包括左 / 右表中不符合连接条件的数据集合 | +|Left/Right Semi Join | 左 / 右半连接,通常表达的是 in、exists 的含义,即对左 / 右表任意一条数据来说,只有当右 / 左表中存在任一符合连接条件的数据时才返回左 / 右表行数据 | |Left/Right Anti-Semi Join | 左 / 右反连接,同左 / 右半连接的逻辑正好相反,通常表达的是 not in、not exists 的含义,即对左 / 右表任意一条数据来说,只有当右 / 左表中不存在任何符合连接条件的数据时才返回左 / 右表行数据 | -|left/Right ASOF Join | 左 / 右不完全匹配连接,不同于其他传统 Join 操作的完全匹配模式,ASOF Join 允许以指定的匹配模式进行不完全匹配,即按照主键时间戳最接近的方式进行匹配 | -|Left/Right Window Join | 左 / 右窗口连接,根据左 / 右表中每一行的主键时间戳和窗口边界构造窗口并据此进行窗口连接,支持在窗口内进行投影、标量和聚合操作 | -|Full Outer Join | 全(外)连接,既包含左右表中同时符合连接条件的数据集合,也包括左右表中不符合连接条件的数据集合 | +|left/Right ASOF Join | 左 / 右不完全匹配连接,不同于其他传统 Join 操作的完全匹配模式,ASOF Join 允许以指定的匹配模式进行不完全匹配,即按照主键时间戳最接近的方式进行匹配 | +|Left/Right Window Join | 左 / 右窗口连接,根据左 / 右表中每一行的主键时间戳和窗口边界构造窗口并据此进行窗口连接,支持在窗口内进行投影、标量和聚合操作 | +|Full Outer Join | 全(外)连接,既包含左右表中同时符合连接条件的数据集合,也包括左右表中不符合连接条件的数据集合 | ### 约束和限制