跳至主要内容

ARRAY JOIN 子句

对于包含数组列的表,一个常见的操作是生成一个新表,该表包含一个列,其中包含初始列的每个单独的数组元素,而其他列的值则被复制。 这是 ARRAY JOIN 子句的基本作用。

它的名称来源于这样一个事实,即它可以被视为对数组或嵌套数据结构执行 JOIN 操作。 其意图类似于 arrayJoin 函数,但子句的功能更广泛。

语法

SELECT <expr_list>
FROM <left_subquery>
[LEFT] ARRAY JOIN <array>
[WHERE|PREWHERE <expr>]
...

SELECT 查询中,只能指定一个 ARRAY JOIN 子句。

下面列出了支持的 ARRAY JOIN 类型

  • ARRAY JOIN - 在基本情况下,空数组不包含在 JOIN 的结果中。
  • LEFT ARRAY JOIN - JOIN 的结果包含带有空数组的行。 空数组的值设置为数组元素类型的默认值(通常为 0、空字符串或 NULL)。

基本 ARRAY JOIN 示例

以下示例演示了 ARRAY JOINLEFT ARRAY JOIN 子句的使用。 让我们创建一个具有 数组 类型列的表,并将值插入其中

CREATE TABLE arrays_test
(
s String,
arr Array(UInt8)
) ENGINE = Memory;

INSERT INTO arrays_test
VALUES ('Hello', [1,2]), ('World', [3,4,5]), ('Goodbye', []);
┌─s───────────┬─arr─────┐
│ Hello │ [1,2] │
│ World │ [3,4,5] │
│ Goodbye │ [] │
└─────────────┴─────────┘

以下示例使用了 ARRAY JOIN 子句

SELECT s, arr
FROM arrays_test
ARRAY JOIN arr;
┌─s─────┬─arr─┐
│ Hello │ 1 │
│ Hello │ 2 │
│ World │ 3 │
│ World │ 4 │
│ World │ 5 │
└───────┴─────┘

下一个示例使用了 LEFT ARRAY JOIN 子句

SELECT s, arr
FROM arrays_test
LEFT ARRAY JOIN arr;
┌─s───────────┬─arr─┐
│ Hello │ 1 │
│ Hello │ 2 │
│ World │ 3 │
│ World │ 4 │
│ World │ 5 │
│ Goodbye │ 0 │
└─────────────┴─────┘

使用别名

可以在 ARRAY JOIN 子句中为数组指定一个别名。 在这种情况下,可以通过此别名访问数组项,但数组本身可以通过原始名称访问。 例如

SELECT s, arr, a
FROM arrays_test
ARRAY JOIN arr AS a;
┌─s─────┬─arr─────┬─a─┐
│ Hello │ [1,2] │ 1 │
│ Hello │ [1,2] │ 2 │
│ World │ [3,4,5] │ 3 │
│ World │ [3,4,5] │ 4 │
│ World │ [3,4,5] │ 5 │
└───────┴─────────┴───┘

使用别名,您可以对外部数组执行 ARRAY JOIN。 例如

SELECT s, arr_external
FROM arrays_test
ARRAY JOIN [1, 2, 3] AS arr_external;
┌─s───────────┬─arr_external─┐
│ Hello │ 1 │
│ Hello │ 2 │
│ Hello │ 3 │
│ World │ 1 │
│ World │ 2 │
│ World │ 3 │
│ Goodbye │ 1 │
│ Goodbye │ 2 │
│ Goodbye │ 3 │
└─────────────┴──────────────┘

多个数组可以在 ARRAY JOIN 子句中用逗号分隔。 在这种情况下,会同时对它们执行 JOIN(直接求和,而不是笛卡尔积)。 请注意,默认情况下,所有数组的大小必须相同。 例如

SELECT s, arr, a, num, mapped
FROM arrays_test
ARRAY JOIN arr AS a, arrayEnumerate(arr) AS num, arrayMap(x -> x + 1, arr) AS mapped;
┌─s─────┬─arr─────┬─a─┬─num─┬─mapped─┐
│ Hello │ [1,2] │ 1 │ 1 │ 2 │
│ Hello │ [1,2] │ 2 │ 2 │ 3 │
│ World │ [3,4,5] │ 3 │ 1 │ 4 │
│ World │ [3,4,5] │ 4 │ 2 │ 5 │
│ World │ [3,4,5] │ 5 │ 3 │ 6 │
└───────┴─────────┴───┴─────┴────────┘

以下示例使用了 arrayEnumerate 函数

SELECT s, arr, a, num, arrayEnumerate(arr)
FROM arrays_test
ARRAY JOIN arr AS a, arrayEnumerate(arr) AS num;
┌─s─────┬─arr─────┬─a─┬─num─┬─arrayEnumerate(arr)─┐
│ Hello │ [1,2] │ 1 │ 1 │ [1,2] │
│ Hello │ [1,2] │ 2 │ 2 │ [1,2] │
│ World │ [3,4,5] │ 3 │ 1 │ [1,2,3] │
│ World │ [3,4,5] │ 4 │ 2 │ [1,2,3] │
│ World │ [3,4,5] │ 5 │ 3 │ [1,2,3] │
└───────┴─────────┴───┴─────┴─────────────────────┘

可以通过使用 SETTINGS enable_unaligned_array_join = 1 来连接大小不同的多个数组。 例如

SELECT s, arr, a, b
FROM arrays_test ARRAY JOIN arr as a, [['a','b'],['c']] as b
SETTINGS enable_unaligned_array_join = 1;
┌─s───────┬─arr─────┬─a─┬─b─────────┐
│ Hello │ [1,2] │ 1 │ ['a','b'] │
│ Hello │ [1,2] │ 2 │ ['c'] │
│ World │ [3,4,5] │ 3 │ ['a','b'] │
│ World │ [3,4,5] │ 4 │ ['c'] │
│ World │ [3,4,5] │ 5 │ [] │
│ Goodbye │ [] │ 0 │ ['a','b'] │
│ Goodbye │ [] │ 0 │ ['c'] │
└─────────┴─────────┴───┴───────────┘

嵌套数据结构的 ARRAY JOIN

ARRAY JOIN 也适用于 嵌套数据结构

CREATE TABLE nested_test
(
s String,
nest Nested(
x UInt8,
y UInt32)
) ENGINE = Memory;

INSERT INTO nested_test
VALUES ('Hello', [1,2], [10,20]), ('World', [3,4,5], [30,40,50]), ('Goodbye', [], []);
┌─s───────┬─nest.x──┬─nest.y─────┐
│ Hello │ [1,2] │ [10,20] │
│ World │ [3,4,5] │ [30,40,50] │
│ Goodbye │ [] │ [] │
└─────────┴─────────┴────────────┘
SELECT s, `nest.x`, `nest.y`
FROM nested_test
ARRAY JOIN nest;
┌─s─────┬─nest.x─┬─nest.y─┐
│ Hello │ 1 │ 10 │
│ Hello │ 2 │ 20 │
│ World │ 3 │ 30 │
│ World │ 4 │ 40 │
│ World │ 5 │ 50 │
└───────┴────────┴────────┘

ARRAY JOIN 中指定嵌套数据结构的名称时,其含义与对所有包含的数组元素执行 ARRAY JOIN 相同。 以下列出了示例

SELECT s, `nest.x`, `nest.y`
FROM nested_test
ARRAY JOIN `nest.x`, `nest.y`;
┌─s─────┬─nest.x─┬─nest.y─┐
│ Hello │ 1 │ 10 │
│ Hello │ 2 │ 20 │
│ World │ 3 │ 30 │
│ World │ 4 │ 40 │
│ World │ 5 │ 50 │
└───────┴────────┴────────┘

这种变化也有意义

SELECT s, `nest.x`, `nest.y`
FROM nested_test
ARRAY JOIN `nest.x`;
┌─s─────┬─nest.x─┬─nest.y─────┐
│ Hello │ 1 │ [10,20] │
│ Hello │ 2 │ [10,20] │
│ World │ 3 │ [30,40,50] │
│ World │ 4 │ [30,40,50] │
│ World │ 5 │ [30,40,50] │
└───────┴────────┴────────────┘

可以使用别名为嵌套数据结构,以便选择 JOIN 结果或源数组。 例如

SELECT s, `n.x`, `n.y`, `nest.x`, `nest.y`
FROM nested_test
ARRAY JOIN nest AS n;
┌─s─────┬─n.x─┬─n.y─┬─nest.x──┬─nest.y─────┐
│ Hello │ 1 │ 10 │ [1,2] │ [10,20] │
│ Hello │ 2 │ 20 │ [1,2] │ [10,20] │
│ World │ 3 │ 30 │ [3,4,5] │ [30,40,50] │
│ World │ 4 │ 40 │ [3,4,5] │ [30,40,50] │
│ World │ 5 │ 50 │ [3,4,5] │ [30,40,50] │
└───────┴─────┴─────┴─────────┴────────────┘

使用 arrayEnumerate 函数的示例

SELECT s, `n.x`, `n.y`, `nest.x`, `nest.y`, num
FROM nested_test
ARRAY JOIN nest AS n, arrayEnumerate(`nest.x`) AS num;
┌─s─────┬─n.x─┬─n.y─┬─nest.x──┬─nest.y─────┬─num─┐
│ Hello │ 1 │ 10 │ [1,2] │ [10,20] │ 1 │
│ Hello │ 2 │ 20 │ [1,2] │ [10,20] │ 2 │
│ World │ 3 │ 30 │ [3,4,5] │ [30,40,50] │ 1 │
│ World │ 4 │ 40 │ [3,4,5] │ [30,40,50] │ 2 │
│ World │ 5 │ 50 │ [3,4,5] │ [30,40,50] │ 3 │
└───────┴─────┴─────┴─────────┴────────────┴─────┘

实现细节

运行 ARRAY JOIN 时,查询执行顺序会得到优化。 尽管 ARRAY JOIN 必须始终在查询中的 WHERE/PREWHERE 子句之前指定,但在技术上,它们可以以任何顺序执行,除非 ARRAY JOIN 的结果用于过滤。 处理顺序由查询优化器控制。

与短路函数评估的不兼容性

短路函数评估 是一项功能,可优化在 ifmultiIfandor 等特定函数中复杂表达式的执行。 它可以防止在这些函数的执行过程中出现潜在的异常,例如除以零。

arrayJoin 始终执行,并且不支持短路函数评估。 这是因为它是一个在查询分析和执行期间与所有其他函数分开处理的唯一函数,并且需要额外的逻辑,而这些逻辑不适用于短路函数执行。 原因是结果中的行数取决于 arrayJoin 的结果,并且实现 arrayJoin 的延迟执行过于复杂且代价高昂。