⌘+k ctrl+k
1.4 (LTS)
搜索快捷键 cmd + k | ctrl + k
Struct 数据类型

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

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

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;
  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 函数创建 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 类型可以使用所有 比较运算符 进行比较。这些比较可用于 逻辑表达式(如 WHEREHAVING 子句中),并返回 BOOLEAN

比较按字典顺序进行,单个条目的比较方式与平常相同,但 NULL 值被视为大于所有其他值。

具体而言

  • 如果 s1s2 的所有值比较结果相等,则 s1s2 相等。
  • 否则,如果在第一个 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 函数

© 2025 DuckDB 基金会,阿姆斯特丹,荷兰
行为准则 商标使用指南