UNNEST将数组展开为多行但不保证原始顺序,必须用WITH ORDINALITY保留索引;不可用于INSERT VALUES,须配合INSERT SELECT;空数组返回零行,需COALESCE补NULL;多表关联展开必须用LATERAL。

UNNEST 会把数组变成多行,但默认不保留原始顺序
UNNEST 是 PostgreSQL 中最直接的数组展开函数,它接受一个或多个同长度数组,返回一行一元素的结果集。但要注意:如果只传单个数组,UNNEST 不保证输出顺序与原数组一致(尽管实践中常碰巧一致)。真正可靠的方式是配合 generate_subscripts 或用 WITH ORDINALITY。
- 单数组展开:
SELECT UNNEST(ARRAY['a','b','c'])→ 可能返回 b/a/c,顺序不确定 - 带序号展开(推荐):
SELECT * FROM UNNEST(ARRAY['a','b','c']) WITH ORDINALITY→ 第二列自动给出 1/2/3 下标 - 多数组对齐展开:
SELECT * FROM UNNEST(ARRAY[10,20], ARRAY['x','y'])→ 两列严格按位置配对,长度必须相等
UNNEST 和 JOIN 一起用时,记得加 ON TRUE 或用 LATERAL
想把某张表的每一行关联其字段里的数组并展开,不能直接写 JOIN UNNEST(t.tags)——PostgreSQL 会报错 “set-returning function called in context that cannot accept a set”。必须用 LATERAL 显式声明依赖关系。
- 错误写法:
SELECT u.name, t.tag FROM users u JOIN UNNEST(u.tags) AS t(tag) ON true - 正确写法:
SELECT u.name, t.tag FROM users u, LATERAL UNNEST(u.tags) AS t(tag) - 更清晰写法:
SELECT u.name, t.tag FROM users u CROSS JOIN LATERAL UNNEST(u.tags) AS t(tag)
空数组或 NULL 数组会导致整行消失,需要 COALESCE 处理
UNNEST(NULL) 或 UNNEST(ARRAY[]::text[]) 不产生任何行,这在 LEFT JOIN 场景下会让左表记录“丢失”。常见补救方式是用 COALESCE 把 NULL 数组转成含一个 NULL 元素的数组,再展开。
- 原始行为:
SELECT id, UNNEST(tags) FROM posts→ tags 为 NULL 的行完全不出现 - 保底写法:
SELECT id, UNNEST(COALESCE(tags, ARRAY[NULL::text])) FROM posts - 若要区分“空数组”和“NULL”,得先判断:
CASE WHEN tags IS NULL THEN ARRAY[NULL::text] WHEN array_length(tags,1) = 0 THEN ARRAY[NULL::text] ELSE tags END
性能注意:UNNEST 本身很快,但大量展开易触发嵌套循环
对每行调用 UNNEST(尤其配合 LATERAL)本质是 nested loop,如果左表有 10 万行、平均每行展开 5 个元素,就会生成 50 万行结果——此时执行计划里常看到 Hash Join 消失,变成 Nested Loop,且 Rows Removed by Filter 可能飙升。提前限制数组长度或预过滤能缓解。
- 加长度保护:
WHERE array_length(tags, 1) 避免单行爆炸式展开 - 避免在 WHERE 中用
UNNEST做条件(如WHERE 'x' = ANY(UNNEST(tags))),改用'x' = ANY(tags)更高效 - 展开后立即
LIMIT仅适用于调试;真实分页需结合OFFSET+ 主键范围,否则LIMIT/OFFSET在展开结果上效率极差
UNNEST 看似简单,但顺序保障、NULL 处理、连接语法和执行计划这几处,稍不留意就产出不符合预期的结果或突然变慢。特别是 WITH ORDINALITY 和 LATERAL,不是可选项,而是多数生产场景的必选项。

















