从概念上讲,STRUCT 列包含一个有序的列列表,称为“条目”(entries)。这些条目通过字符串名称进行引用。本文档将这些条目名称称为键(keys)。STRUCT 列中的每一行必须具有相同的键。结构条目的名称是模式(schema)的一部分。STRUCT 列中的每一行必须具有相同的布局。结构条目的名称不区分大小写。
STRUCT 通常用于将多个列嵌套到单个列中,嵌套列可以是任何类型,包括其他 STRUCT 和 LIST。
STRUCT 类似于 PostgreSQL 的 ROW 类型。主要区别在于 DuckDB 的 STRUCT 要求 STRUCT 列的每一行都必须包含相同的键。这使得 DuckDB 能够通过充分利用其向量化执行引擎来显著提升性能,并强制执行类型一致性以提高准确性。DuckDB 包含一个 row 函数作为生成 STRUCT 的特殊方式,但没有 ROW 数据类型。有关详细信息,请参阅下方的示例以及 STRUCT 函数文档。
请参阅 数据类型概述 以获取嵌套数据类型之间的比较。
创建 Struct
可以使用 struct_pack(name := expr, ...) 函数、等效的数组符号 {'name': expr, ...}、使用行变量或使用 row 函数来创建 Struct。
使用 struct_pack 函数创建 Struct。注意键周围没有单引号,并使用了 := 运算符
SELECT struct_pack(key1 := 'value1', key2 := 42) AS s;
使用数组符号创建 Struct
SELECT {'key1': 'value1', 'key2': 42} AS s;
使用行变量创建 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;
为每个键创建不同类型的 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 中检索值。
使用点标记法检索键位置的值。在以下查询中,子查询生成一个 Struct 列 a,然后我们使用 a.x 进行查询。
SELECT a.x FROM (SELECT {'x': 1, 'y': 2, 'z': 3} AS a);
如果键包含空格,只需用双引号(")将其括起来即可。
SELECT a."x space" FROM (SELECT {'x space': 1, 'y': 2, 'z': 3} AS a);
也可以使用方括号标记法。注意这里使用的是单引号('),因为目的是指定特定的字符串键,且方括号内只能使用常量表达式(不能使用表达式)
SELECT a['x space'] FROM (SELECT {'x space': 1, 'y': 2, 'z': 3} AS a);
struct_extract 函数也是等效的。该函数返回 1
SELECT struct_extract({'x space': 1, 'y': 2, 'z': 3}, 'x space');
unnest / STRUCT.*
比起从 Struct 中检索单个键,可以使用 unnest 特殊函数将 Struct 中的所有键作为单独的列检索出来。当之前的操作创建了一个形状未知的 Struct,或者查询需要处理任何潜在的 Struct 键时,这尤其有用。
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 |
警告:星号标记法目前仅限于顶层 Struct 列和非聚合表达式。
点标记法的操作顺序
用点标记法引用 Struct 可能会与引用模式(schema)和表产生歧义。通常,DuckDB 会先查找列,然后再查找列中的 Struct 键。DuckDB 按此顺序解析引用,并使用首次匹配的结果。
无点
SELECT part1
FROM tbl;
part1是一个列
一点
SELECT part1.part2
FROM tbl;
part1是一个表,part2是一个列part1是一个列,part2是该列的一个属性
两点(或更多)
SELECT part1.part2.part3
FROM tbl;
part1是一个模式,part2是一个表,part3是一个列part1是一个表,part2是一个列,part3是该列的一个属性part1是一个列,part2是该列的一个属性,part3是该列的一个属性
任何额外的部分(例如 .part4.part5 等)始终被视为属性
使用 row 函数创建 Struct
row 函数可用于自动将多个列转换为单个 Struct 列。使用 row 时,键将为空字符串,从而可以轻松插入到具有 Struct 列的表中。但是,列不能使用 row 函数进行初始化,必须明确命名。例如,使用 row 函数将值插入到 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 函数初始化 Struct 列将会失败
CREATE TABLE t2 AS SELECT row('a');
Invalid Input Error:
A table cannot be created from an unnamed struct
在 Struct 之间进行转换时,至少一个字段的名称必须匹配。因此,以下查询将会失败
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 函数可用于返回未命名的 Struct。例如
SELECT row(x, x + 1, y) FROM (SELECT 1 AS x, 'a' AS y) AS s;
这会产生 (1, 2, a)。
如果创建 Struct 时使用多个表达式,row 函数是可选的。以下查询返回的结果与前一个相同
SELECT (x, x + 1, y) AS s FROM (SELECT 1 AS x, 'a' AS y);
比较与排序
STRUCT 类型可以使用所有 比较运算符 进行比较。这些比较可用于 逻辑表达式(如 WHERE 和 HAVING 子句中),并返回 BOOLEAN 值。
比较按字典顺序进行,单个条目的比较方式与平常相同,但 NULL 值被视为大于所有其他值。
具体而言
- 如果
s1和s2的所有值比较结果相等,则s1和s2相等。 - 否则,如果在第一个
s1.value[i] != s2.value[i]的索引i处,有s1.value[i] < s2.value[i] 或 s2.value[i] 为 NULL,则s1小于s2,反之亦然。
不同类型的 Struct 会按照 组合转换 的规则隐式转换为包含所有相关键的并集的 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
从 DuckDB v1.3.0 开始,可以使用 ALTER TABLE 子句 更新 Struct 的子模式。
按照以下示例,初始化 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 函数。