Union Type
UNION type(不要与 SQL UNION operator 混淆)是一种嵌套类型,可在多个“备选”值中持有其一,类似 C 语言中的 union。主要区别在于,这里的 UNION 类型是 tagged unions,因此总会携带一个用于区分当前持有哪个备选值的“tag”,即使内部值本身为 null 也是如此。因此,UNION 类型更接近 C++17 的 std::variant、Rust 的 Enum,或多数函数式语言中的“sum type”。
UNION 类型必须至少包含一个成员。它们可以包含多个相同类型的成员,但 tag 名称必须唯一。UNION 类型最多可有 256 个成员。
在实现层面,UNION 类型构建在 STRUCT 类型之上,并将“tag”作为第一个条目存储。
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 |
另外,你也可以像 STRUCTs 一样使用“点语法”。
SELECT u.str
FROM tbl1;
| str |
|---|
| NULL |
| two |
| three |
将 UNION 当前激活的 tag 以 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)总会优先将FLOAT转到INTEGER成员,而不是VARCHAR。 - 若转换仍有歧义(即存在多个具有相同隐式转换优先级的候选),会报错。这通常发生在
UNION含有多个相同类型成员时,例如FLOAT→UNION(i INTEGER, num INTEGER)始终有歧义。
如果我们想创建一个包含多个相同类型成员的 UNION,该如何消除歧义?可以使用 union_value 函数,并通过关键字参数指定 tag。例如,union_value(num := 2::INTEGER) 会创建一个只有 INTEGER 成员且 tag 为 num 的 UNION。随后可在显式(或隐式,见下文)UNION 到 UNION 的转换中用于消歧,例如 CAST(union_value(b := 2) AS UNION(a INTEGER, b INTEGER))。
Union 之间的转换
如果源类型是目标类型的“子集”,UNION 类型之间就可以互相转换。换言之,源 UNION 中的所有 tag 都必须在目标 UNION 中存在,且匹配 tag 的类型在源与目标之间必须可隐式转换。本质上,这意味着 UNION 类型相对于其成员是协变的。
| 是否可行 | Source | Target | 说明 |
|---|---|---|---|
| ✅ | 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 子句,并遵循与 STRUCTs 相同的语义。“tag” 始终存储为 struct 的第一个条目,这保证了 UNION 类型会先按 “tag” 进行比较和排序。
函数
参见 Union Functions。