- 安装
- 文档
- 入门
- 连接
- 数据导入与导出
- 湖仓格式
- 客户端 API
- 概览
- 第三方客户端
- ADBC
- C
- C++
- CLI
- Dart
- Go
- Java (JDBC)
- Julia
- Node.js (已弃用)
- Node.js (Neo)
- ODBC
- PHP
- Python
- R
- Rust
- Swift
- Wasm
- SQL
- 介绍
- 语句
- 概览
- ANALYZE
- ALTER TABLE
- ALTER VIEW
- ATTACH 和 DETACH
- CALL
- CHECKPOINT
- COMMENT ON
- COPY
- CREATE INDEX
- CREATE MACRO
- CREATE SCHEMA
- CREATE SECRET
- CREATE SEQUENCE
- CREATE TABLE
- CREATE VIEW
- CREATE TYPE
- DELETE
- DESCRIBE
- DROP
- EXPORT 和 IMPORT DATABASE
- INSERT
- LOAD / INSTALL
- MERGE INTO
- PIVOT
- 性能分析
- SELECT
- SET / RESET
- SET VARIABLE
- SHOW 与 SHOW DATABASES
- SUMMARIZE
- 事务管理
- UNPIVOT
- UPDATE
- USE
- VACUUM
- 查询语法
- SELECT
- FROM 和 JOIN
- WHERE
- GROUP BY
- GROUPING SETS
- HAVING
- ORDER BY
- LIMIT 和 OFFSET
- SAMPLE
- 展开嵌套
- WITH
- WINDOW
- QUALIFY
- VALUES
- FILTER
- 集合操作
- 预处理语句
- 数据类型
- 表达式
- 函数
- 概览
- 聚合函数
- 数组函数
- 位字符串函数
- Blob 函数
- 日期格式化函数
- 日期函数
- 日期部分函数
- 枚举函数
- 间隔函数
- Lambda 函数
- 列表函数
- 映射函数
- 嵌套函数
- 数值函数
- 模式匹配
- 正则表达式
- 结构体函数
- 文本函数
- 时间函数
- 时间戳函数
- 带时区时间戳函数
- 联合函数
- 实用函数
- 窗口函数
- 约束
- 索引
- 元查询
- DuckDB 的 SQL 方言
- 示例
- 配置
- 扩展
- 核心扩展
- 概览
- 自动补全
- Avro
- AWS
- Azure
- Delta
- DuckLake
- 编码
- Excel
- 全文搜索
- httpfs (HTTP 和 S3)
- Iceberg
- ICU
- inet
- jemalloc
- Lance
- MySQL
- PostgreSQL
- 空间
- SQLite
- TPC-DS
- TPC-H
- UI
- Unity Catalog
- Vortex
- VSS
- 指南
- 概览
- 数据查看器
- 数据库集成
- 文件格式
- 概览
- CSV 导入
- CSV 导出
- 直接读取文件
- Excel 导入
- Excel 导出
- JSON 导入
- JSON 导出
- Parquet 导入
- Parquet 导出
- 查询 Parquet 文件
- 使用 file: 协议访问文件
- 网络和云存储
- 概览
- HTTP Parquet 导入
- S3 Parquet 导入
- S3 Parquet 导出
- S3 Iceberg 导入
- S3 Express One
- GCS 导入
- Cloudflare R2 导入
- 通过 HTTPS / S3 使用 DuckDB
- Fastly 对象存储导入
- 元查询
- ODBC
- 性能
- Python
- 安装
- 执行 SQL
- Jupyter Notebooks
- marimo Notebooks
- Pandas 上的 SQL
- 从 Pandas 导入
- 导出到 Pandas
- 从 Numpy 导入
- 导出到 Numpy
- Arrow 上的 SQL
- 从 Arrow 导入
- 导出到 Arrow
- Pandas 上的关系型 API
- 多个 Python 线程
- 与 Ibis 集成
- 与 Polars 集成
- 使用 fsspec 文件系统
- SQL 编辑器
- SQL 功能
- 代码片段
- 故障排除
- 术语表
- 离线浏览
- 操作手册
- 概览
- DuckDB 的占用空间
- 安装 DuckDB
- 日志
- 保护 DuckDB 安全
- 非确定性行为
- 限制
- DuckDB Docker 容器
- 开发
- 内部结构
- 站点地图
- 在线演示
UNION 类型(不要与 SQL 的 UNION 运算符混淆)是一种嵌套类型,能够保存多个“备选”值中的一个,非常类似于 C 语言中的 union。其主要区别在于,这些 UNION 类型是带标签的联合(tagged unions),因此总是携带一个辨别“标签”,用于指示当前保存的是哪个备选值,即使内部值本身为空(null)也是如此。因此,UNION 类型更类似于 C++17 的 std::variant、Rust 的 Enum 或大多数函数式编程语言中的“求和类型(sum type)”。
UNION 类型必须至少包含一个成员,并且虽然它们可以包含多个相同类型的成员,但标签名称必须唯一。UNION 类型最多可以有 256 个成员。
在底层,UNION 类型是在 STRUCT 类型之上实现的,并简单地将“标签”作为第一个条目保存。
UNION 值可以使用 union_value(tag := expr) 函数创建,或者通过从成员类型转换来创建。
示例
创建带有 UNION 列的表
CREATE TABLE tbl1 (u UNION(num INTEGER, str VARCHAR));
INSERT INTO tbl1 VALUES (1), ('two'), (union_value(str := 'three'));
任何类型都可以隐式转换为包含该类型的 UNION。如果源 UNION 的成员是目标 UNION 成员的子集(且转换无歧义),则任何 UNION 也可以隐式转换为另一个 UNION。
UNION 在转换为 VARCHAR 时会使用成员类型的 VARCHAR 转换函数
SELECT u FROM tbl1;
| u |
|---|
| 1 |
| two |
| three |
选择所有 str 成员
SELECT union_extract(u, 'str') AS str
FROM tbl1;
| str |
|---|
| NULL |
| two |
| three |
或者,你可以像使用 STRUCT 那样使用“点语法”。
SELECT u.str
FROM tbl1;
| str |
|---|
| NULL |
| two |
| three |
将 UNION 中当前激活的标签作为 ENUM 选择出来。
SELECT union_tag(u) AS t
FROM tbl1;
| t |
|---|
| num |
| str |
| str |
Union 转换
与其他嵌套类型相比,UNION 允许一组隐式转换,以便在将成员作为“子类型”使用时,操作更自然且具有非侵入性。然而,这些转换的设计遵循了两个原则:避免歧义和避免可能导致信息丢失的转换。这防止了 UNION 变得完全“透明”,同时仍允许 UNION 类型与其成员之间保持“超类型”关系。
因此,UNION 类型通常不能隐式转换为其任何成员类型,因为不匹配目标类型的其他成员中的信息将会“丢失”。如果你想将 UNION 强制转换为其成员之一,你应该显式使用 union_extract 函数。
唯一的例外是将 UNION 转换为 VARCHAR,在这种情况下,所有成员都将使用它们各自对应的 VARCHAR 转换。由于所有内容都可以转换为 VARCHAR,这在某种意义上是“安全”的。
转换为 Union
如果一个类型可以隐式转换为 UNION 的成员类型之一,那么它总是可以隐式转换为该 UNION。
- 如果有多个候选类型,内置的隐式转换优先级规则将决定目标类型。例如,
FLOAT→UNION(i INTEGER, v VARCHAR)转换将始终在VARCHAR之前将FLOAT转换为INTEGER成员。 - 如果转换仍然存在歧义(即存在具有相同隐式转换优先级的多个候选),则会引发错误。这通常发生在
UNION包含多个相同类型的成员时,例如FLOAT→UNION(i INTEGER, num INTEGER)始终是有歧义的。
那么,如果我们想创建一个具有多个相同类型成员的 UNION,该如何消除歧义呢?通过使用 union_value 函数,它接受一个指定标签的关键字参数。例如,union_value(num := 2::INTEGER) 将创建一个包含单个 INTEGER 类型成员且标签为 num 的 UNION。这随后可用于在显式(或隐式,见下文!)UNION 到 UNION 转换中消除歧义,例如 CAST(union_value(b := 2) AS UNION(a INTEGER, b INTEGER))。
Union 之间的转换
如果源类型是目标类型的“子集”,则 UNION 类型之间可以相互转换。换句话说,源 UNION 中的所有标签都必须存在于目标 UNION 中,并且所有匹配标签的类型在源和目标之间必须可以隐式转换。本质上,这意味着 UNION 类型相对于其成员是协变的。
| Ok | 源 | 目标 | 注释 |
|---|---|---|---|
UNION(a A, b B) |
UNION(a A, b B, c C) |
||
UNION(a A, b B) |
UNION(a A, b C) |
如果 B 可以隐式转换为 C |
|
UNION(a A, b B, c C) |
UNION(a A, b B) |
||
UNION(a A, b B) |
UNION(a A, b C) |
如果 B 不能隐式转换为 C |
|
UNION(A, B, D) |
UNION(A, B, C) |
比较与排序
由于 UNION 类型在内部是在 STRUCT 类型之上实现的,因此它们可以与所有比较运算符一起使用,也可以在 WHERE 和 HAVING 子句中使用,其语义与 STRUCT 相同。“标签”始终作为第一个结构条目存储,这确保了 UNION 类型首先按“标签”进行比较和排序。
函数
参见 Union 函数。