数组
PostgreSQL 支持任意内置或用户定义类型的数组,例如 integer[]、text[] 和 uuid[]。数组适合元素数量有限、整体一起读写的值集合;如果元素需要独立查询、去重、排序或建立外键,通常应使用子表。
声明和写入
CREATE TABLE articles (
article_id bigint PRIMARY KEY,
tags text[] NOT NULL DEFAULT '{}',
related_ids uuid[]
);
INSERT INTO articles (article_id, tags)
VALUES (1, ARRAY['sql', 'postgresql']);
数组字面量也可以写成字符串形式,但需要正确处理引号和反斜杠:
SELECT '{"sql","data types"}'::text[];
应用驱动通常能直接绑定数组,优先使用参数化接口。
下标、切片和维度
PostgreSQL 数组下标默认从 1 开始:
SELECT tags[1], tags[1:2], array_length(tags, 1)
FROM articles;
数组可以多维,但每一维的边界和长度需要明确。空数组、NULL 数组和包含 NULL 元素的数组是不同状态:
SELECT cardinality('{}'::int[]), cardinality(ARRAY[NULL::int]);
不要假设所有数组都从 1 开始;手工构造的数组可以有其他下界,应使用 array_lower 和 array_upper 查询边界。
查询和展开
SELECT article_id
FROM articles
WHERE 'sql' = ANY(tags);
SELECT a.article_id, tag
FROM articles AS a
CROSS JOIN LATERAL unnest(a.tags) AS tag;
ANY、ALL、@>、<@ 和 && 分别支持元素匹配、包含和重叠判断。unnest 把数组变成行,适合统计和连接,但大量展开会增加查询成本。
数组索引
CREATE INDEX articles_tags_gin_idx
ON articles USING gin (tags);
GIN 适合数组包含和重叠查询。表达式索引可支持固定下标或数组长度过滤,但查询表达式必须匹配索引定义。索引不会自动让任意 unnest 查询变快。
修改与并发
数组元素更新会产生新的行版本:
UPDATE articles
SET tags = array_append(tags, 'index')
WHERE article_id = $1;
多个会话同时修改同一数组时仍会产生行级更新冲突。若标签需要独立审计、频繁增删或按标签反向查询,使用关联表通常更可靠:
CREATE TABLE article_tags (
article_id bigint REFERENCES articles,
tag text NOT NULL,
PRIMARY KEY (article_id, tag)
);
常见误区
- 忘记数组下标默认从 1 开始。
- 把空数组、
NULL数组和[NULL]当成同一状态。 - 用数组保存需要外键、唯一约束或独立权限的实体。
- 用字符串逗号分隔代替数组,之后无法可靠处理元素中的逗号和转义。
- 建立 GIN 后不检查查询计划和数据选择性。