JOIN子句
JOIN
子句通过使用各表中共同的值结合一个或多个表的列来生成新表。这是一种在支持SQL的数据库中常见的操作,对应于关系代数的连接。单表连接的特例通常称为“自连接”。
语法
ON
子句中的表达式和USING
子句中的列称为“连接键”。除非另有说明,JOIN
会从具有匹配“连接键”的行中产生一个笛卡尔积,这可能导致结果的行数远远超过源表的行数。
支持的JOIN类型
支持所有标准的SQL JOIN类型:
类型 | 描述 |
---|---|
INNER JOIN | 仅返回匹配的行。 |
LEFT OUTER JOIN | 返回左表中非匹配的行以及匹配的行。 |
RIGHT OUTER JOIN | 返回右表中非匹配的行以及匹配的行。 |
FULL OUTER JOIN | 返回两个表中非匹配的行以及匹配的行。 |
CROSS JOIN | 产生两个表的笛卡尔积,不指定“连接键”。 |
- 未指定类型的
JOIN
默认为INNER
。 - 关键字
OUTER
可以安全省略。 CROSS JOIN
的替代语法是使用逗号分隔的多个表在FROM
子句中指定。
ClickHouse中可用的其他连接类型有:
类型 | 描述 |
---|---|
LEFT SEMI JOIN ,RIGHT SEMI JOIN | 仅允许“连接键”的白名单,而不产生笛卡尔积。 |
LEFT ANTI JOIN ,RIGHT ANTI JOIN | 针对“连接键”的黑名单,而不产生笛卡尔积。 |
LEFT ANY JOIN ,RIGHT ANY JOIN ,INNER ANY JOIN | 部分(对于LEFT 和RIGHT 的对立面)或完全(对于INNER 和FULL )禁用标准JOIN 类型的笛卡尔积。 |
ASOF JOIN ,LEFT ASOF JOIN | 使用非精确匹配连接序列。ASOF JOIN 的用法如下所述。 |
PASTE JOIN | 对两个表执行水平连接。 |
当join_algorithm设置为partial_merge
时,仅支持RIGHT JOIN
和FULL JOIN
,并且仅在ALL
严格性下(SEMI
、ANTI
、ANY
和ASOF
不受支持)。
设置
可以使用join_default_strictness
设置覆盖默认的连接类型。
ClickHouse服务器对于ANY JOIN
操作的行为取决于any_join_distinct_right_table_keys
设置。
另见
join_algorithm
join_any_take_last_row
join_use_nulls
partial_merge_join_rows_in_right_blocks
join_on_disk_max_files_to_merge
any_join_distinct_right_table_keys
使用cross_to_inner_join_rewrite
设置定义当ClickHouse无法将CROSS JOIN
重写为INNER JOIN
时的行为。默认值为1
,允许连接继续,但会更慢。如果希望抛出错误,请将cross_to_inner_join_rewrite
设置为0
,将其设置为2
将不运行交叉连接,而是强制重写所有逗号/交叉连接。如果在值为2
时重写失败,您将收到一条错误消息,说明“请尝试简化WHERE
部分”。
ON部分条件
ON
部分可以包含多个通过AND
和OR
运算符组合的条件。指定连接键的条件必须:
- 引用左表和右表
- 使用等式运算符
其他条件可以使用其他逻辑运算符,但必须引用查询的左表或右表。
如果满足整个复杂条件,则行被连接。如果条件不满足,则根据JOIN
类型,行仍可能包含在结果中。请注意,如果相同的条件放置在WHERE
部分且没有满足,则行将始终从结果中过滤掉。
ON
子句中的OR
运算符使用哈希连接算法——对于每个带有JOIN
的连接键的OR
参数,会创建一个单独的哈希表,因此内存消耗和查询执行时间随着ON
子句中表达式OR
数量的增加而线性增长。
如果条件引用来自不同表的列,则目前仅支持等值运算符(=
)。
示例
考虑table_1
和table_2
:
包含一个连接键条件和一个对table_2
的附加条件的查询:
请注意,结果中包含名字为C
且文本列为空的行。由于使用了OUTER
类型的连接,因此它被包含在结果中。
使用INNER
类型连接和多个条件的查询:
结果:
使用INNER
类型连接和带有OR
条件的查询:
结果:
使用INNER
类型连接和带有OR
和AND
条件的查询:
默认情况下,非相等条件是支持的,只要它们使用同一表中的列。
例如,t1.a = t2.key AND t1.b > 0 AND t2.b > t2.c
,因为t1.b > 0
仅使用t1
中的列,而t2.b > t2.c
仅使用t2
中的列。
但是,您可以尝试实验性支持条件,例如t1.a = t2.key AND t1.b > t2.key
,请查看下面的部分以获取更多详细信息。
结果:
带有不同表的列的不等式条件的JOIN
Clickhouse当前支持ALL/ANY/SEMI/ANTI INNER/LEFT/RIGHT/FULL JOIN
,并使用不等式条件以补充等式条件。不等式条件仅支持hash
和grace_hash
连接算法。使用join_use_nulls
时不支持不等式条件。
示例
表t1
:
表t2
JOIN键中的NULL值
NULL
不等于任何值,包括它自己。这意味着如果一个JOIN
键在一个表中有NULL
值,它将与另一个表中的NULL
值不匹配。
示例
表A
:
表B
:
请注意,表A
中的Charlie
行和表B
中的得分为88的行未出现在结果中,因为JOIN
键中有NULL
值。
如果您想匹配NULL
值,可以使用isNotDistinctFrom
函数比较JOIN
键。
ASOF JOIN用法
ASOF JOIN
在您需要连接没有精确匹配的记录时非常有用。
此JOIN算法在表中需要一个特殊列。该列:
语法ASOF JOIN ... ON
:
您可以使用任意数量的等式条件和恰好一个最近匹配条件。例如,SELECT count() FROM table_1 ASOF LEFT JOIN table_2 ON table_1.a == table_2.b AND table_2.t <= table_1.t
。
最近匹配支持的条件:>
、>=
、<
、<=
。
语法ASOF JOIN ... USING
:
ASOF JOIN
使用equi_columnX
进行等式连接,使用asof_column
进行最近匹配连接,条件为table_1.asof_column >= table_2.asof_column
。asof_column
列始终是在USING
子句中的最后一个列。
例如,考虑下面的表:
ASOF JOIN
可以从table_1
获取用户事件的时间戳,并找到table_2
中时间戳最接近于来自table_1
的事件时间戳的事件,满足最近匹配条件。如果可用,相等的时间戳值就是最近的。在我们的例子中,event_1_1
可以与event_2_1
连接,event_1_2
可以与event_2_3
连接,但event_2_2
无法连接。
ASOF JOIN
仅支持hash
和full_sorting_merge
连接算法。
不支持Join表引擎。
PASTE JOIN用法
PASTE JOIN
的结果是一个包含左侧子查询所有列的表,随后是右侧子查询的所有列。
根据原始表中的位置匹配行(行的顺序应该被定义)。
如果子查询返回不同数量的行,则多余的行会被截断。
示例:
注意:在这种情况下,如果读取是并行的,结果可能是不确定的。例如:
分布式JOIN
涉及分布式表的JOIN有两种执行方式:
- 当使用普通的
JOIN
时,查询被发送到远程服务器。在每个服务器上运行子查询以生成右表,连接在此表上执行。换句话说,右表在每个服务器上单独生成。 - 当使用
GLOBAL ... JOIN
时,请求服务器首先运行一个子查询来计算右表。此临时表被传递到每个远程服务器,在它们上使用传输的临时数据运行查询。
使用GLOBAL
时要小心。有关更多信息,请参见分布式子查询部分。
隐式类型转换
INNER JOIN
、LEFT JOIN
、RIGHT JOIN
和FULL JOIN
查询支持“连接键”的隐式类型转换。但是,如果左表和右表中的连接键无法转换为单一类型,则无法执行查询(例如,不能有数据类型可以同时容纳所有来自UInt64
和Int64
的值,或 String
和Int32
)。
示例
考虑表t_1
:
以及表t_2
:
查询
返回集合:
使用建议
处理空或NULL单元格
在连接表时,可能会出现空单元格。join_use_nulls设置定义了ClickHouse如何填充这些单元格。
如果JOIN
键是Nullable字段,则至少有一个键的值为NULL的行不会连接。
语法
在USING
中指定的列在两个子查询中必须具有相同的名称,其他列必须命名不同。您可以使用别名更改子查询中的列名。
USING
子句指定一个或多个要连接的列,从而建立这些列的相等性。列列表未用括号括起来。不支持更复杂的连接条件。
语法限制
对于单个SELECT
查询中的多个JOIN
子句:
- 只有在连接表时才能通过
*
获取所有列,而不是子查询。 PREWHERE
子句不可用。USING
子句不可用。
对于ON
、WHERE
和GROUP BY
子句:
- 在
ON
、WHERE
和GROUP BY
子句中不能使用任意表达式,但是您可以在SELECT
子句中定义表达式,然后通过别名在这些子句中使用它。
性能
在执行JOIN
时,查询执行的顺序不会相对于查询的其他阶段进行优化。连接(在右表中搜索)在WHERE
中的过滤之前执行,并且在聚合之前执行。
每次使用相同JOIN
运行查询时,子查询会再次运行,因为结果不会被缓存。为避免这种情况,使用特殊的Join表引擎,这是一个始终在RAM中的准备好的用于连接的数组。
在某些情况下,使用IN可能比JOIN
更有效。
如果您需要一个用于与维度表连接的JOIN
(这些表相对较小,包含维度属性,如广告活动的名称),由于右表在每个查询中都被重新访问,JOIN
可能不太方便。对于这种情况,您应该使用“字典”功能,而不是JOIN
。有关更多信息,请参见字典部分。
内存限制
默认情况下,ClickHouse使用哈希连接算法。ClickHouse取右表并在RAM中为其创建哈希表。如果启用join_algorithm = 'auto'
,那么在某些内存消耗阈值后,ClickHouse将回退到归并连接算法。有关JOIN
算法的描述,请参见join_algorithm设置。
如果您需要限制JOIN
操作的内存消耗,请使用以下设置:
- max_rows_in_join — 限制哈希表中的行数。
- max_bytes_in_join — 限制哈希表的大小。
达到这些限制中的任何一个时,ClickHouse将按照join_overflow_mode设置指示的方式进行处理。
示例
示例: