ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

TDengine Join 查询完全指南:从 INNER/OUTER 到 ASOF/WINDOW 的时序语义与源码剖析

TDengine Join 查询完全指南:从 INNER/OUTER 到 ASOF/WINDOW 的时序语义与源码剖析 TDengine Join 查询完全指南从 INNER/OUTER 到 ASOF/WINDOW 的时序语义与源码剖析【免费下载链接】TDengineHigh-performance, scalable time-series database designed for Industrial IoT (IIoT) scenarios项目地址: https://gitcode.com/GitHub_Trending/tde/TDengineTDengine 作为面向工业物联网IIoT的时序数据库其 Join 查询以主键时间戳列为核心展开支持 Inner、Outer、Semi、Anti-Semi、ASOF、Window 与 Full Join 七大类连接方式。本文以官方 SQL 文档 05-join.md 为骨架结合解析器、规划器与执行器源码系统讲解各类 Join 的语法、结果集语义、主键时间线规则与限制条件帮助你在智能电表、设备监测等真实场景中正确书写并调优 Join 语句。Join 基本概念驱动表Driving Table驱动表是发起 Join 查询的表。在 Left Join 系列中左表为驱动表在 Right Join 系列中右表为驱动表。驱动表的行将逐行与另一张表进行匹配其结果行的主键时间线也通常取自驱动表。连接条件Join Conditions连接条件指表连接时指定的条件。TDengine 支持的所有 Join 查询都要求指定连接条件其出现位置因 Join 类型而异除Inner Join 与 Window Join外连接条件一般写在ON之后Inner Join 中写在WHERE之后的条件在语义上也可视为连接条件Window Join 通过WINDOW_OFFSET指定连接条件。除 ASOF Join 外所有 Join 类型都必须显式指定连接条件。ASOF Join 具有隐式的默认连接条件若默认条件满足需求则无需显式写出。除 ASOF/Window Join 外连接条件除主连接条件外还可包含任意数量的附加条件附加条件必须通过AND与主连接条件关联附加条件之间则无此限制。这些附加条件可以包含主键列、Tag、普通列、常量以及它们的标量函数或运算的任意逻辑组合。以智能电表场景为例以下 SQL 都包含合法连接条件SELECT a.* FROM meters a LEFT JOIN meters b ON a.ts b.ts AND a.ts 2023-10-18 10:00:00.000; SELECT a.* FROM meters a LEFT JOIN meters b ON a.ts b.ts AND (a.ts 2023-10-18 10:00:00.000 OR a.ts 2023-10-17 10:00:00.000); SELECT a.* FROM meters a LEFT JOIN meters b ON timetruncate(a.ts, 1s) timetruncate(b.ts, 1s) AND (a.ts 1s 2023-10-18 10:00:00.000 OR a.groupId 0); SELECT a.* FROM meters a LEFT ASOF JOIN meters b ON timetruncate(a.ts, 1s) timetruncate(b.ts, 1s) AND a.groupId b.groupId;主连接条件Main Join Condition作为时序数据库TDengine 的所有 Join 查询都围绕主键时间戳列展开。因此除 ASOF/Window Join 外所有 Join 查询都必须包含主键列上的等值条件连接条件中出现的第一个主键列等值条件即被视为主连接条件。ASOF Join 的主连接条件可以包含非等值条件Window Join 的主连接条件通过WINDOW_OFFSET指定。从 3.3.6.0 版本开始TDengine 支持子查询中的常量时间戳包括返回时间戳的常量函数如today()、now()等常量时间戳及其加减运算作为等价主键列出现在主连接条件中例如SELECT * from d1001 a JOIN (SELECT today() as ts1, * from d1002 WHERE ts 2025-03-19 10:00:00.000) b ON timetruncate(a.ts, 1d) b.ts1;上述 SQL 会将表 d1001 今天的所有记录与表 d1002 中某一时间记录进行 Join。需要特别注意的是SQL 中出现的常量时间字符串默认不会被当作时间戳处理例如2025-03-19 10:00:00.000只会被当作普通字符串。若需将其作为常量时间戳可通过类型前缀timestamp显式声明例如SELECT * from d1001 a JOIN (SELECT timestamp 2025-03-19 10:00:00.000 as ts1, * from d1002 WHERE ts 2025-03-19 10:00:00.000) b ON timetruncate(a.ts, 1d) b.ts1;除 Window Join 外TDengine 支持在主连接条件中使用timetruncate函数运算如ON timetruncate(a.ts, 1s) timetruncate(b.ts, 1s)但不支持其他函数和标量运算。这一点在规划器源码中亦有印证planPhysiCreater.c 中要求主连接条件的操作符必须是等值比较OP_TYPE_EQUAL仅在JOIN_STYPE_ASOFASOF Join子类型下允许非等值操作符否则直接返回TSDB_CODE_PLAN_INTERNAL_ERROR。分组条件Grouping Conditions时序数据库特有的 ASOF/Window Join 支持将 Join 查询的输入数据分组然后在每个组内执行 Join。分组仅影响 Join 查询的输入输出结果不会包含分组信息。ASOF/Window Join 中ON之后出现的等值条件ASOF 的主连接条件除外会被视为分组条件。主键时间线Primary Key TimelineTDengine 要求每张表子表都有主键时间戳列它作为主键时间线服务于许多时间相关的操作。子查询结果或 Join 操作的结果也需要明确哪一列作为后续时间相关操作的主键时间线子查询中第一个出现的有序主键列或其运算或与主键列等价的伪列_wstart/_wend会被视为输出表的主键时间线从 3.3.6.0 版本开始TDengine 还支持子查询结果中的常量时间戳列作为输出表的主键时间线。Join 输出结果中主键时间线的选取遵循以下规则Left/Right Join 系列使用驱动表子查询的主键列作为后续查询的主键时间线在 Window Join 窗口内由于两表都有序任意表的主键列都可用作主键时间线且优先选用同表的主键列Inner Join可以使用任意表的主键列作为主键时间线但当存在与主连接条件以AND关联的类似 Tag 列等值条件等分组条件时将不产生主键时间线Full Join无法产生任何有效的主键序列因此没有主键时间线即 Full Join 中不能执行时间相关操作。语法说明left/right 记法下文将 Left/Right Join 系列合并介绍Outer、Semi、Anti-Semi、ASOF 与 Window 系列均采用类似的 left/right 记法同时介绍 Left Join 与 Right Join/ 前的内容适用于 Left Join/ 后的内容适用于 Right Joinleft/right table 表示对 Left Join 指 左表对 Right Join 指 右表right/left table 表示对 Left Join 指 右表对 Right Join 指 左表。各类 Join 详解Inner Join定义Inner Join 只返回左右两表中同时满足连接条件的数据可视为两表满足连接条件数据的交集。语法SELECT ... FROM table_name1 [INNER] JOIN table_name2 [ON ...] [WHERE ...] [...] or SELECT ... FROM table_name1, table_name2 WHERE ... [...]结果集左右两表满足连接条件的行的笛卡尔积。适用范围支持超级表、基础表、子表及子查询之间的 Inner Join。说明第一种语法中INNER关键字可选ON和/或WHERE可以指定主连接条件和其他连接条件WHERE还可以指定过滤条件ON/WHERE至少指定其一第二种语法中可在WHERE中指定主连接条件、其他连接条件及过滤条件对超级表执行 Inner Join 时与主连接条件以AND关联的 Tag 列等值条件会被用作分组条件因此输出结果无法保证有序。示例查询表 d1001 与表 d1002 电压都大于 220V 的时刻及各自电压值SELECT a.ts, a.voltage, b.voltage FROM d1001 a JOIN d1002 b ON a.ts b.ts and a.voltage 220 and b.voltage 220源码佐证解析器 sql.y 中为各类 Join 定义了独立文法规则。Inner Join 是唯一支持嵌套与多表 Join 的类型详见文末限制章节因此它是组合复杂查询的基础构件。Left/Right Outer Join定义Left/RightOuterJoin 包含两表满足连接条件的数据集以及左/右表中不满足连接条件的数据集。语法SELECT ... FROM table_name1 LEFT|RIGHT [OUTER] JOIN table_name2 ON ... [WHERE ...] [...]结果集Inner Join 的结果集 左/右表中不满足连接条件的行与右/左表空数据NULL组成的行。适用范围支持超级表、基础表、子表及子查询之间的 Left/Right Join。说明OUTER关键字可选。示例表 d1001 的全部电压值时刻以及两表电压都大于 220V 的时刻及各自电压值SELECT a.ts, a.voltage, b.voltage FROM d1001 a LEFT JOIN d1002 b ON a.ts b.ts and a.voltage 220 and b.voltage 220Left/Right Semi Join定义Left/Right Semi Join 通常表达IN/EXISTS的语义即对左/右表中的任意一行只有当右/左表中存在满足连接条件的任意一行时才返回该行。语法SELECT ... FROM table_name1 LEFT|RIGHT SEMI JOIN table_name2 ON ... [WHERE ...] [...]结果集由左/右表中满足连接条件的行组成的行数据集即只输出单边表的行不输出另一表的列。适用范围支持超级表、基础表、子表及子查询之间的 Left/Right Semi Join。示例表 d1001 中电压大于 220V、且同一时刻还有其他电表电压也大于 220V 的时刻SELECT a.ts FROM d1001 a LEFT SEMI JOIN meters b ON a.ts b.ts and a.voltage 220 and b.voltage 220 and b.tbname ! d1001Left/Right Anti-Semi Join定义Left/Right Anti Join 与 Semi Join 逻辑相反通常表达NOT IN/NOT EXISTS的语义。即对左/右表中的任意一行只有当右/左表中不存在满足连接条件的对应数据时才返回该行。语法SELECT ... FROM table_name1 LEFT|RIGHT ANTI JOIN table_name2 ON ... [WHERE ...] [...]结果集由左/右表中不满足连接条件的行与右/左表空数据NULL组成的行数据集。适用范围支持超级表、基础表、子表及子查询之间的 Left/Right Anti-Semi Join。示例表 d1001 中电压大于 220V、且同一时刻没有其他电表电压也大于 220V 的时刻SELECT a.ts FROM d1001 a LEFT ANTI JOIN meters b ON a.ts b.ts and b.voltage 220 and b.tbname ! d1001 WHERE a.voltage 220源码佐证Semi 与 Anti 的语法规则定义在 sql.y 中分别以JOIN_STYPE_SEMI、JOIN_STYPE_ANTI构造连接节点执行器在 mergejoinoperator.c 中根据子类型将 Anti Join 分发到mAntiJoinDo处理函数Semi Join 则走专门的半连接算子路径。Left/Right ASOF Join定义与传统 Join 要求精确匹配不同Left/Right ASOF Join 允许基于最近的主键时间戳进行近似匹配。这是时序数据库中常用的最近邻时间对齐连接典型场景如将两条频率不同的设备数据流按时间就近对齐。语法SELECT ... FROM table_name1 LEFT|RIGHT ASOF JOIN table_name2 [ON ...] [JLIMIT jlimit_num] [WHERE ...] [...]结果集左/右表中每一行与右/左表中满足连接条件且时间戳最近的至多jlimit_num行的笛卡尔积按主键列排序或无数据时以NULL填充。适用范围仅支持超级表、基础表与子表之间的 Left/Right ASOF Join。说明仅支持表与表之间的 ASOF Join不支持子查询ON子句支持基于主键列或其timetruncate函数指定单条匹配规则不支持其他标量运算和函数。支持的运算符及其在 Left ASOF 下的含义如下运算符Left ASOF 的含义匹配右表中主键时间戳小于且最接近左表主键时间戳的行匹配右表中主键时间戳小于或等于且最接近左表主键时间戳的行匹配右表中主键时间戳等于左表主键时间戳的行匹配右表中主键时间戳大于且最接近左表主键时间戳的行匹配右表中主键时间戳大于或等于且最接近左表主键时间戳的行对 Right ASOF上述运算符的含义相反。若ON子句缺省或未指定主键列匹配规则默认主键匹配规则运算符为即匹配右表中主键时间戳小于或等于左表主键时间戳的行不支持多个主连接条件ON子句还可以指定 Tag 或普通列之间的等值条件不支持标量函数和运算用于分组计算不支持其他类型的条件ON子句中的所有条件仅支持AND运算JLIMIT指定单行匹配的最大行数可选默认值为 1即左/右表中每一行最多可从右/左表获取一行匹配行。JLIMIT取值范围为 [0, 1024]。jlimit_num个匹配行不要求时间戳相同若右/左表中满足条件的行数少于jlimit_num结果行数可能少于jlimit_num若满足条件的行数多于jlimit_num当时间戳相同时会随机返回jlimit_num行。示例表 d1001 中电压大于 220V、且表 d1002 在同时刻或稍早时刻电压也大于 220V 的时刻及各自电压值SELECT a.ts, a.voltage, b.ts, b.voltage FROM d1001 a LEFT ASOF JOIN d1002 b ON a.ts b.ts where a.voltage 220 and b.voltage 220源码佐证ASOF 连接的运行时逻辑在 mergejoin.c 中实现。执行器根据asofOpType判定需要向前还是向后搜索mAsofBackwardJoinDo等处理函数jLimit默认取 1与文档所述JLIMIT默认值一致并依据ASOF_EQ_ROW_INCLUDED/ASOF_LOWER_ROW_INCLUDED/ASOF_GREATER_ROW_INCLUDED宏判断当前运算符是否包含等值行、小于行或大于行从而决定扫描方向。JLIMIT子句的文法定义于 sql.y通过createLimitNode生成限额节点。Left/Right Window Join定义Left/Right Window Join 基于左/右表中每一行的主键时间戳与窗口边界构建窗口并执行窗口连接支持窗口内的投影、标量与聚合操作。语法SELECT ... FROM table_name1 LEFT|RIGHT WINDOW JOIN table_name2 [ON ...] WINDOW_OFFSET(start_offset, end_offset) [JLIMIT jlimit_num] [WHERE ...] [...]结果集左/右表中每一行数据与右/左表中位于该行主键时间戳与WINDOW_OFFSET定义的窗口内、至多jlimit_num行数据的笛卡尔积或无数据时以NULL填充也可以是上述窗口内每行数据的聚合结果行数据集。适用范围仅支持超级表、基础表与子表之间的 Left/Right Window Join。说明仅支持表与表之间的 Window Join不支持子查询ON子句可选仅支持指定除主键列外的 Tag 与普通列之间的等值条件不含标量函数和运算用于分组计算且所有条件之间仅支持AND运算WINDOW_OFFSET用于指定相对左/右表主键时间戳的窗口左右边界支持带内置时间单位的格式。例如 Left Window Join 的WINDOW_OFFSET(-1a, 1a)表示每个窗口为 [左表主键时间戳 - 1 毫秒, 左表主键时间戳 1 毫秒]两侧边界均为闭区间。数字后的时间单位可为b纳秒、u微秒、a毫秒、s秒、m分钟、h小时、d天、w周不支持自然月n、自然年y最小支持的时间单位为数据库精度且左右两表数据库精度必须一致JLIMIT用于指定单个窗口内的最大匹配行数可选默认获取每个窗口内全部匹配行。取值范围为 [0, 1024]当右表中满足条件的行不足jlimit_num时结果行数可能少于jlimit_num当满足条件的行多于jlimit_num时优先返回窗口内主键时间戳最小的jlimit_num行SQL 语句中不能包含其他GROUP BY/PARTITION BY/窗口查询WHERE子句支持标量过滤HAVING子句支持对每个窗口的聚合函数过滤不支持标量过滤不支持SLIMIT不支持各种窗口伪列。示例 1表 d1001 中电压大于 220V且表 d1002 在其前后 1 秒区间内的电压值SELECT a.ts, a.voltage, b.voltage FROM d1001 a LEFT WINDOW JOIN d1002 b WINDOW_OFFSET (-1s, 1s) where a.voltage 220示例 2表 d1001 中电压大于 220V 的时刻与电压值以及表 d1002 在其前后 1 秒区间内电压平均值也大于 220V 的平均电压值SELECT a.ts, a.voltage, avg(b.voltage) FROM d1001 a LEFT WINDOW JOIN d1002 b WINDOW_OFFSET (-1s, 1s) where a.voltage 220 HAVING(avg(b.voltage) 220)源码佐证Window Join 的语法规则定义在 sql.y通过window_offset_clause与jlimit_clause_opt构建连接节点。执行器在 mergejoinoperator.c 中为 ASOF/Window Join 初始化独立的窗口上下文mJoinInitWindowCtx并将 Window Join 分发到mWinJoinDo与mWinJoinGroupReset处理函数L1966-L1968窗口内聚合正是在这套窗口执行上下文中完成的。Full Outer Join定义FullOuterJoin 包含左右两表满足连接条件的数据集以及任意一表不满足连接条件的数据集。语法SELECT ... FROM table_name1 FULL [OUTER] JOIN table_name2 ON ... [WHERE ...] [...]结果集Inner Join 的结果集 左表中不满足连接条件的行与右表空数据NULL组合的行 右表中不满足连接条件的行与左表空数据NULL组合的行。适用范围支持超级表、基础表、子表及子查询之间的 Full Outer Join。说明OUTER关键字可选。示例表 d1001 与 d1002 中所有时刻与电压值记录SELECT a.ts, a.voltage, b.ts, b.voltage FROM d1001 a FULL JOIN d1002 b on a.ts b.ts约束与限制输入时间线限制目前所有 Join 都要求输入数据包含有效的主键时间线。所有表查询均可满足该要求但子查询需要确保输出数据包含有效的主键时间线。连接条件限制除 ASOF 和 Window Join 外其他 Join 的主连接条件中必须包含主键列主连接条件与其他条件之间仅支持AND运算用作主连接条件的主键列仅支持timetruncate函数不支持其他函数和标量运算用作其他连接条件时无限制。分组条件限制仅支持除主键列外的 Tag 与普通列的等值条件不支持标量运算支持多个分组条件条件之间仅支持AND运算。查询结果顺序限制在基础表、子表、无分组条件且无排序的子查询场景中查询结果按驱动表主键列顺序输出在超级表查询、Full Join、或有分组条件但未排序的场景中输出没有固定顺序因此在需要排序而输出顺序不固定的场景中需要显式执行排序操作。部分依赖时间线的函数可能因缺少有效时间线而无法执行。嵌套 Join 与多表 Join 限制目前除 Inner Join 支持嵌套与多表 Join 外其他类型的 Join 均不支持嵌套与多表 Join。实战建议与测试参考仓库测试目录 test/cases/14-JoinQueries 提供了丰富的 Join 用例可供参考与验证包括但不限于test_join.py各类 Join 的基础功能用例test_join_full.py 与 test_join_full2.pyFull Join 行为验证test_join_const.py 与 joinConst常量时间戳作为主连接条件的场景test_join_pk.py主键时间线相关行为test_left_asof_join_diff_64bit.py不同精度64 位时间戳下的 ASOF Jointest_join_tbname.pytbname伪列参与连接条件的场景。结合上述用例与实际业务可以总结出几条选型建议数据精确对齐两张表时间戳完全一致需要求交集时用Inner Join需要保留左表全量数据时用Left Join存在性判断用Semi Join替代IN/EXISTS用Anti Join替代NOT IN/NOT EXISTS可避免子查询额外开销不同采样频率的数据流对齐使用ASOF Join按时间就近匹配配合JLIMIT控制单行匹配数量滑动时间窗内的关联与聚合使用Window Join配合WINDOW_OFFSET与HAVING完成窗口内聚合过滤需要合并两侧全部数据使用Full Join但注意其结果没有主键时间线无法直接进行时间相关操作。需要提醒的是ASOF/Window Join 只支持表与表之间的连接不支持子查询且所有 Join 都要求左右表具备一致的时间精度与有效的主键时间线非 Inner Join 均不支持嵌套与多表连接复杂场景需通过子查询或分步查询组合实现。【免费下载链接】TDengineHigh-performance, scalable time-series database designed for Industrial IoT (IIoT) scenarios项目地址: https://gitcode.com/GitHub_Trending/tde/TDengine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表