跳到主要内容

Struct 数据类型

从概念上讲,STRUCT 列包含一个有序的字段列表(称为 “entries”)。这些 entry 通过字符串名称引用。本文将这些 entry 名称称为 key。STRUCT 列中的每一行都必须具有相同 key。struct entry 名称是 schema 的一部分,因此 STRUCT 列中的每一行都必须具有相同布局。struct entry 名称不区分大小写。

STRUCT 通常用于将多个列嵌套到单个列中,且嵌套列可以是任意类型,包括其他 STRUCTLIST

STRUCT 与 PostgreSQL 的 ROW 类型相似。关键区别在于 Goose STRUCT 要求同一 STRUCT 列的每一行都拥有相同 key。这样 Goose 就能充分利用向量化执行引擎以显著提升性能,同时也通过强制类型一致性提升正确性。Goose 提供 row function 作为生成 STRUCT 的特殊方式,但并不提供 ROW 数据类型。详见下文示例和 STRUCT functions documentation

关于嵌套数据类型的对比,请参见 data types overview

创建 Struct

可以通过 struct_pack(name := expr, ...) function、等价的数组写法 {'name': expr, ...}、row 变量或 row function 来创建 struct。

使用 struct_pack function 创建 struct。注意 key 周围没有单引号,并使用 := 运算符:

SELECT struct_pack(key1 := 'value1', key2 := 42) AS s;

使用数组写法创建 struct:

SELECT {'key1': 'value1', 'key2': 42} AS s;

使用 row 变量创建 struct:

SELECT d AS s FROM (SELECT 'value1' AS key1, 42 AS key2) d;

创建整数 struct:

SELECT {'x': 1, 'y': 2, 'z': 3} AS s;

创建包含 NULL 的字符串 struct:

SELECT {'yes': 'duck', 'maybe': 'goose', 'huh': NULL, 'no': 'heron'} AS s;

创建每个 key 类型不同的 struct:

SELECT {'key1': 'string', 'key2': 1, 'key3': 12.345} AS s;

创建包含 NULL 的嵌套 struct:

SELECT {
'birds': {'yes': 'duck', 'maybe': 'goose', 'huh': NULL, 'no': 'heron'},
'aliens': NULL,
'amphibians': {'yes': 'frog', 'maybe': 'salamander', 'huh': 'dragon', 'no': 'toad'}
} AS s;

新增或更新 Struct 字段

要新增字段或更新已有字段,可使用 struct_update

SELECT struct_update({'a': 1, 'b': 2}, b := 3, c := 4) AS s;

另外,struct_insert 也可新增字段,但不能更新已存在字段。

从 Struct 中取值

从 struct 中取值可以使用点号语法、方括号语法,或使用 struct_extractstruct functions

使用点号语法可读取某个 key 对应的值。下例中,子查询生成 struct 列 a,然后通过 a.x 访问。

SELECT a.x FROM (SELECT {'x': 1, 'y': 2, 'z': 3} AS a);

如果 key 包含空格,只需用双引号(")包裹。

SELECT a."x space" FROM (SELECT {'x space': 1, 'y': 2, 'z': 3} AS a);

也可使用方括号语法。注意这里使用单引号('),因为目标是指定某个字符串 key,且方括号内只能使用常量表达式(不能使用表达式):

SELECT a['x space'] FROM (SELECT {'x space': 1, 'y': 2, 'z': 3} AS a);

struct_extract function 也与上述写法等价。如下会返回 1:

SELECT struct_extract({'x space': 1, 'y': 2, 'z': 3}, 'x space');

unnest / STRUCT.*

除了提取单个 key,还可以使用 unnest special function 将 struct 中所有 key 展开为独立列。 这在前序操作生成了未知结构的 struct,或查询需要处理任意潜在 key 时尤其有用:

SELECT unnest(a)
FROM (SELECT {'x': 1, 'y': 2, 'z': 3} AS a);
xyz
123

同样也可通过星号语法(*)实现,并且还能对返回列进行修改

SELECT a.* EXCLUDE ('y')
FROM (SELECT {'x': 1, 'y': 2, 'z': 3} AS a);
xz
13

Warning 星号语法当前仅支持顶层 struct 列与非聚合表达式。

点号语法的解析顺序

用点号引用 struct 时,可能与 schema/table 的引用产生歧义。通常 Goose 先查找列,再查找列内的 struct key。解析顺序如下,命中第一个匹配即停止:

无点号

SELECT part1
FROM tbl;
  1. part1 是列

一个点号

SELECT part1.part2
FROM tbl;
  1. part1 是表,part2 是列
  2. part1 是列,part2 是该列的属性

两个(或更多)点号

SELECT part1.part2.part3
FROM tbl;
  1. part1 是 schema,part2 是表,part3 是列
  2. part1 是表,part2 是列,part3 是该列的属性
  3. part1 是列,part2 是该列的属性,part3 是该列的属性

其余部分(如 .part4.part5 等)始终视为属性。

使用 row Function 创建 Struct

row function 可将多个列自动转换为一个 struct 列。 使用 row 时,key 会是空字符串,从而便于插入到包含 struct 列的表中。 但列本身不能直接通过 row function 初始化,必须显式命名。 例如,使用 row function 向 struct 列插入值:

CREATE TABLE t1 (s STRUCT(v VARCHAR, i INTEGER));
INSERT INTO t1 VALUES (row('a', 42));
SELECT * FROM t1;

表中将包含一条记录:

{'v': a, 'i': 42}

下面的写法会产生与上面相同的结果:

CREATE TABLE t1 AS (
SELECT row('a', 42)::STRUCT(v VARCHAR, i INTEGER)
);

使用 row function 初始化 struct 列会失败:

CREATE TABLE t2 AS SELECT row('a');
Invalid Input Error:
A table cannot be created from an unnamed struct

在 struct 之间进行 cast 时,至少需要一个字段名匹配。因此下面查询会失败:

SELECT a::STRUCT(y INTEGER) AS b
FROM
(SELECT {'x': 42} AS a);
Binder Error:
STRUCT to STRUCT cast must have at least one matching member

一种替代方案是改用 struct_pack

SELECT struct_pack(y := a.x) AS b
FROM
(SELECT {'x': 42} AS a);

row function 也可用于返回无名 struct。例如:

SELECT row(x, x + 1, y) FROM (SELECT 1 AS x, 'a' AS y) AS s;

结果为 (1, 2, a)

创建 struct 时若使用多个表达式,row function 是可选的。下列查询与前一个返回相同结果:

SELECT (x, x + 1, y) AS s FROM (SELECT 1 AS x, 'a' AS y);

比较与排序

STRUCT 类型可使用所有比较运算符进行比较。 这些比较可用于 WHEREHAVING逻辑表达式,并返回 BOOLEAN

比较采用字典序。各 entry 按常规比较,但 NULL 被视为大于所有其他值。

具体规则:

  • s1s2 的所有值都相等,则 s1s2 相等。
  • 否则,在首个满足 s1.value[i] != s2.value[i] 的位置 i 上,若 s1.value[i] < s2.value[i]s2.value[i] is NULL,则 s1 < s2,反之亦然。

不同类型的 struct 会按组合转换规则,隐式 cast 到包含相关 key 并集的 struct 类型。

以下查询返回 true

SELECT {'k1': 0, 'k2': 0} < {'k1': 1, 'k2': 0};
SELECT {'k1': 'hello'} < {'k1': 'world'};
SELECT {'k1': 0, 'k2': 0} < {'k1': 0, 'k2': NULL};
SELECT {'k1': 0} < {'k2': 0};
SELECT  {'k1': 0, 'k2': 0} < {'k2': 0, 'k3': 0};
SELECT {'k1': 1, 'k2': 0} > {'k3': 0, 'k1': 0};

以下查询返回 false

SELECT {'k1': 1, 'k2': 0} < {'k1': 0, 'k2': 1};
SELECT {'k1': [0]} < {'k1': [0, 0]};
SELECT {'k1': 1} > {'k2': 0};
SELECT {'k1': 0, 'k2': 0} < {'k3': 0, 'k1': 1};
SELECT  {'k1': 1, 'k2': 0} > {'k2': 0, 'k3': 0};

更新 Schema

从 Goose v1.3.0 开始,可以通过 ALTER TABLE clause 更新 struct 的子 schema。

为复现下面示例,先按如下方式初始化 test 表:

CREATE TABLE test (s STRUCT(i INTEGER, j INTEGER));
INSERT INTO test VALUES (ROW(1, 1)), (ROW(2, 2));

添加字段

test 表中 struct s 添加字段 k INTEGER

ALTER TABLE test ADD COLUMN s.k INTEGER;
FROM test;
┌─────────────────────────────────────────┐
│ s │
│ struct(i integer, j integer, k integer) │
├─────────────────────────────────────────┤
│ {'i': 1, 'j': 1, 'k': NULL} │
│ {'i': 2, 'j': 2, 'k': NULL} │
└─────────────────────────────────────────┘

删除字段

test 表中 struct s 删除字段 i

ALTER TABLE test DROP COLUMN s.i;
FROM test;
┌──────────────────────────────┐
│ s │
│ struct(j integer, k integer) │
├──────────────────────────────┤
│ {'j': 1, 'k': NULL} │
│ {'j': 2, 'k': NULL} │
└──────────────────────────────┘

重命名字段

test 表中 struct s 的字段 j 重命名为 v1

ALTER TABLE test RENAME s.j TO v1;
FROM test;
┌───────────────────────────────┐
│ s │
│ struct(v1 integer, k integer) │
├───────────────────────────────┤
│ {'v1': 1, 'k': NULL} │
│ {'v1': 2, 'k': NULL} │
└───────────────────────────────┘

函数

参见 Struct Functions