从概念上讲,STRUCT
(结构体)列包含一个名为“entries”(条目)的有序列列表。条目通过字符串名称引用。本文档将这些条目名称称为键(keys)。STRUCT
列中的每一行必须具有相同的键。结构体条目的名称是模式的一部分。STRUCT
列中的每一行必须具有相同的布局。结构体条目的名称不区分大小写。
STRUCT
s 通常用于将多个列嵌套到单个列中,并且嵌套列可以是任何类型,包括其他 STRUCT
s 和 LIST
s。
STRUCT
s 类似于 PostgreSQL 的 ROW
类型。关键区别在于 DuckDB STRUCT
s 要求 STRUCT
列的每一行具有相同的键。这使得 DuckDB 可以通过充分利用其向量化执行引擎来显著提高性能,并且还强制类型一致性以提高正确性。DuckDB 包含一个 row
函数,作为生成 STRUCT
的特殊方式,但没有 ROW
数据类型。有关详细信息,请参见下面的示例和 STRUCT
函数文档。
有关嵌套数据类型之间的比较,请参见数据类型概述。
创建结构体
可以使用 struct_pack(name := expr, ...)
函数、等效的数组表示法 {'name': expr, ...}
、使用行变量或使用 row
函数来创建结构体。
使用 struct_pack
函数创建结构体。请注意键周围没有单引号,并使用了 :=
运算符。
SELECT struct_pack(key1 := 'value1', key2 := 42) AS s;
使用数组表示法创建结构体
SELECT {'key1': 'value1', 'key2': 42} AS s;
使用行变量创建结构体
SELECT d AS s FROM (SELECT 'value1' AS key1, 42 AS key2) d;
创建整数结构体
SELECT {'x': 1, 'y': 2, 'z': 3} AS s;
创建包含 NULL
值的字符串结构体
SELECT {'yes': 'duck', 'maybe': 'goose', 'huh': NULL, 'no': 'heron'} AS s;
为每个键创建具有不同类型的结构体
SELECT {'key1': 'string', 'key2': 1, 'key3': 12.345} AS s;
创建包含 NULL
值的结构体嵌套结构体
SELECT {
'birds': {'yes': 'duck', 'maybe': 'goose', 'huh': NULL, 'no': 'heron'},
'aliens': NULL,
'amphibians': {'yes': 'frog', 'maybe': 'salamander', 'huh': 'dragon', 'no': 'toad'}
} AS s;
向结构体添加字段/值
向整数结构体中添加
SELECT struct_insert({'a': 1, 'b': 2, 'c': 3}, d := 4) AS s;
从结构体中检索
可以使用点表示法、方括号表示法或通过 结构体函数(如 struct_extract
)从结构体中检索值。
使用点表示法检索键位置处的值。在以下查询中,子查询生成一个结构体列 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.*
除了从结构体中检索单个键外,还可以使用 unnest
特殊函数将结构体中的所有键作为单独的列检索。当之前的操作创建了一个未知形状的结构体,或者查询必须处理任何潜在的结构体键时,这特别有用。
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 |
警告:星号表示法目前仅限于顶级结构体列和非聚合表达式。
点表示法操作顺序
使用点表示法引用结构体可能会与引用模式和表产生歧义。一般来说,DuckDB 首先查找列,然后在列中查找结构体键。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
函数创建结构体
row
函数可用于自动将多列转换为单个结构体列。使用 row
时,键将为空字符串,便于插入到具有结构体列的表中。但是,列不能使用 row
函数进行初始化,必须明确命名。例如,使用 row
函数将值插入到结构体列中:
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
函数初始化结构体列将失败
CREATE TABLE t2 AS SELECT row('a');
Invalid Input Error:
A table cannot be created from an unnamed 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
函数可用于返回未命名的结构体。例如:
SELECT row(x, x + 1, y) FROM (SELECT 1 AS x, 'a' AS y) AS s;
这将产生 (1, 2, a)
。
在创建结构体时使用多个表达式时,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
比较相等。 - 否则,对于第一个索引
i
,如果s1.value[i] < s2.value[i] OR s2.value[i] is NULL
且s1.value[i] != s2.value[i]
,则s1
小于s2
,反之亦然。
不同类型的结构体将根据组合类型转换规则隐式转换为包含所有相关键的联合结构体类型。
以下查询返回 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};
更新模式
从 DuckDB v1.3.0 开始,可以使用 ALTER TABLE
子句更新结构体的子模式。
要遵循示例,请按如下方式初始化 test
表:
CREATE TABLE test(s STRUCT(i INTEGER, j INTEGER));
INSERT INTO test VALUES (ROW(1, 1)), (ROW(2, 2));
添加字段
向表 test
中结构体 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
中结构体 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
中结构体 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} │
└───────────────────────────────┘
函数
请参阅结构体函数。