Struct 数据类型
从概念上讲,STRUCT 列包含一个有序的字段列表(称为 “entries”)。这些 entry 通过字符串名称引用。本文将这些 entry 名称称为 key。STRUCT 列中的每一行都必须具有相同 key。struct entry 名称是 schema 的一部分,因此 STRUCT 列中的每一行都必须具有相同布局。struct entry 名称不区分大小写。
STRUCT 通常用于将多个列嵌套到单个列中,且嵌套列可以是任意类型,包括其他 STRUCT 和 LIST。
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_extract 等 struct 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);
| x | y | z |
|---|---|---|
| 1 | 2 | 3 |
同样也可通过星号语法(*)实现,并且还能对返回列进行修改:
SELECT a.* EXCLUDE ('y')
FROM (SELECT {'x': 1, 'y': 2, 'z': 3} AS a);
| x | z |
|---|---|
| 1 | 3 |
Warning 星号语法当前仅支持顶层 struct 列与非聚合表达式。
点号语法的解析顺序
用点号引用 struct 时,可能与 schema/table 的引用产生歧义。通常 Goose 先查找列,再查找列内的 struct key。解析顺序如下,命中第一个匹配即停止:
无点号
SELECT part1
FROM tbl;
part1是列
一个点号
SELECT part1.part2
FROM tbl;
part1是表,part2是列part1是列,part2是该列的属性
两个(或更多)点号
SELECT part1.part2.part3
FROM tbl;
part1是 schema,part2是表,part3是列part1是表,part2是列,part3是该列的属性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 类型可使用所有比较运算符进行比较。
这些比较可用于 WHERE、HAVING 等逻辑表达式,并返回 BOOLEAN 值。
比较采用字典序。各 entry 按常规比较,但 NULL 被视为大于所有其他值。
具体规则:
- 若
s1与s2的所有值都相等,则s1与s2相等。 - 否则,在首个满足
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。