15 数组与 ENUM
另外两种值得尽早了解的 PostgreSQL 专用类型。数组允许任意列存放同一类型值组成的有序列表——有时,一对多关系的范围非常明确,单独建表就有些小题大做(例如文章上的标签、用户的角色名)。ENUM 类型为列提供一组固定的命名值,由数据库强制约束,并以紧凑形式存储。二者都不是标准类型,但支持良好,在实际 Postgres 应用中广泛使用。
本章要点
- 声明和查询数组列
- 数组字面量、数组下标(从 1 开始)及实用函数
- 数组上的
ANY/ALL操作符 - 定义和使用
ENUM类型 - 何时适合使用数组/枚举,以及何时单独建表更好
示例
PostgreSQL 中的数组是一等公民:可以为它们建立索引,在 WHERE 子句中使用,用 ARRAY[] 构造,用 unnest() 将其展开成行,并用 @>、<@ 查询包含关系,用 && 查询重叠。它们以紧凑形式内联存储(非常大的数组则使用超长属性存储技术(TOAST))。
最重要的一个特殊之处是:数组下标默认从 1 开始。这里的 tags[1] 是数组中的第一个元素,不过 PostgreSQL 也支持显式指定下界。'{a, b, c}' 这种字面量形式是字符串形式的字面量简写;ARRAY['a', 'b', 'c'] 是构造器——两者生成相同的值。
ENUM 类型非常适合这样的场景:允许值集合已知且规模较小,并且应由数据库强制约束('open' | 'in_progress' | 'closed'、'small' | 'medium' | 'large')。无论标签长度如何,它们都以 4 字节存储,并且按声明顺序而不是字母顺序排序——对于状态工作流来说很有用,因为 'open' < 'in_progress' < 'closed' 正是自然顺序。
运行示例
运行方式:
常见陷阱
- 使用其他语言的开发者很容易在从 1 开始的数组下标上栽跟头。
tags[0]的结果是NULL,而不是报错。 - 修改 ENUM(添加一个值)需要使用
ALTER TYPE;不重建类型,删除一个值基本上不可能。对于经常变化的集合,优先使用通过外键连接的小型查找表。 - 数组看起来很适合“我只需要一个列表”的场景——但只要你需要单独查询、连接、排序或分别约束其中的元素,另建一张带外键的表几乎总是更清晰。只有当元素属于该行、而不是独立实体时,才使用数组。
tag = 'x'在数组列上不起作用——使用'x' = ANY(tag)或tag @> ARRAY['x']。
使用 GIN 为数组建立索引:CREATE INDEX ON posts_example USING gin (tags); 可让 @>、<@ 和 && 查询在 tags 列上快速执行。没有这个索引时,这些查询会扫描整张表。
添加一行 tags = ARRAY['intro','postgres','tips'],然后运行 SELECT id, tag FROM posts_example, unnest(tags) AS tag;,将每个标签展开为一行。使用 ALTER TYPE ticket_status ADD VALUE 'reopened'; 添加一个新的 ENUM 值,再插入一条具有该状态的工单。
下载
SQL 文件:arrays-and-enum.sql · 全部示例文件 · 准备工作