⌘+k ctrl+k
1.3 (稳定版)
搜索快捷键 cmd + k | ctrl + k
结构体数据类型

从概念上讲,STRUCT(结构体)列包含一个名为“entries”(条目)的有序列列表。条目通过字符串名称引用。本文档将这些条目名称称为键(keys)。STRUCT 列中的每一行必须具有相同的键。结构体条目的名称是模式的一部分。STRUCT 列中的每一行必须具有相同的布局。结构体条目的名称不区分大小写。

STRUCTs 通常用于将多个列嵌套到单个列中,并且嵌套列可以是任何类型,包括其他 STRUCTs 和 LISTs。

STRUCTs 类似于 PostgreSQL 的 ROW 类型。关键区别在于 DuckDB STRUCTs 要求 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;
  1. part1 是一列

一个点

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

两个(或更多)点

SELECT part1.part2.part3
FROM tbl;
  1. part1 是一个模式,part2 是一张表,part3 是一列
  2. part1 是一张表,part2 是一列,part3 是该列的一个属性
  3. 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 类型可以使用所有比较运算符进行比较。这些比较可以用于逻辑表达式,例如 WHEREHAVING 子句,并返回 BOOLEAN

比较以字典顺序进行,单个条目照常进行比较,但 NULL 值被视为大于所有其他值。

具体来说:

  • 如果 s1s2 的所有值比较相等,则 s1s2 比较相等。
  • 否则,对于第一个索引 i,如果 s1.value[i] < s2.value[i] OR s2.value[i] is NULLs1.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}          │
└───────────────────────────────┘

函数

请参阅结构体函数