postgresql子查询中不可直接用unnest,因其返回多行而标量子查询仅允许零或一个值;正确用法是将其置于from子句配合lateral、join或where中的@>、any操作符。

子查询里直接用 UNNEST 会报错?先确认执行上下文
PostgreSQL 不允许在子查询的 SELECT 列表中直接使用 UNNEST(比如 SELECT (SELECT UNNEST(arr))),因为 UNNEST 返回的是多行结果,而标量子查询只允许返回零或一个值。这是最常卡住人的第一步。
真正能用的场景是:把 UNNEST 放在 FROM 子句中,作为集合返回函数参与 JOIN 或 LATERAL 扩展;或者用在 WHERE 中配合 IN 或存在性判断。
- ✅ 正确:
SELECT * FROM t, LATERAL UNNEST(t.tags) AS tag - ❌ 错误:
SELECT id, (SELECT UNNEST(tags)) FROM t - ⚠️ 注意:
UNNEST对空数组返回零行,不是NULL,这点会影响LEFT JOIN行为
用 LATERAL UNNEST 展开数组并关联原表字段
这是处理数组最常用也最安全的方式——借助 LATERAL 让 UNNEST 能引用外层表的列,实现“每行数组展开成多行”。它天然支持子查询嵌套,只要把整个 LATERAL 结构写进 FROM 即可。
例如,有表 products(id, name, categories text[]),想查每个分类对应的商品数:
PostgreSQL 18.4 官方 Ubuntu 安装包现已发布,这是目前最新的稳定版本。推荐通过官方 APT 仓库安装:先执行 sudo apt update 更新索引,再运行 sudo apt install postgresql-18 即可完成部署。新版本引入了异步 I/O 子系统,在顺序扫描与 VACUUM 场景下性能提升显著,同时支持 UUID v7 原生生成函数与虚拟生成列。
SELECT cat, COUNT(*) FROM products, LATERAL UNNEST(categories) AS cat GROUP BY cat;
-
LATERAL是关键,没有它,UNNEST(categories)无法访问products.categories - 若需保留无分类的商品,改用
LEFT JOIN LATERAL UNNEST(...) AS cat ON true - 多个数组字段要同时展开?写多个
LATERAL,但注意笛卡尔积风险
子查询中判断数组是否包含某值:别用 UNNEST,用 @> 或 ANY
如果目标只是检查数组里有没有某个元素(比如 “用户是否拥有 admin 权限”),完全没必要展开数组——用操作符更高效、语义更清晰。
- ✅ 推荐:
WHERE roles @> ARRAY['admin'](包含操作符,走 GIN 索引) - ✅ 兼容老版本:
WHERE 'admin' = ANY(roles)(支持索引,但需CREATE INDEX ON t USING btree ((roles::text[]))) - ❌ 避免:
WHERE 'admin' IN (SELECT UNNEST(roles))——触发嵌套循环 + 多行子查询,性能差且难读 - 注意:
ANY对空数组返回FALSE,符合直觉;@>同理
聚合后重建数组时,ARRAY_AGG 和 UNNEST 的配合边界在哪?
UNNEST 是“拆”,ARRAY_AGG 是“合”,两者常成对出现,但要注意它们不在同一抽象层级:一个作用于行集,一个作用于分组结果。子查询中混用容易混淆执行顺序。
比如想把用户所有标签去重合并为数组:
SELECT user_id, ARRAY_AGG(DISTINCT tag) FROM users, LATERAL UNNEST(preferences->'tags') AS tag GROUP BY user_id;
- 必须先
LATERAL UNNEST得到扁平行集,再GROUP BY+ARRAY_AGG - 不能在子查询里写
(SELECT ARRAY_AGG(t) FROM UNNEST(...))——语法错误,UNNEST不能当表名用 - 若原始数据已是二维数组(如
text[][]),UNNEST默认展平为一维,加WITH ORDINALITY也救不回结构
数组嵌套层级和空值处理是隐形地雷,展开前最好先用 array_length(arr, 1) 或 COALESCE(arr, '{}') 做兜底。










