json_agg()必须配合GROUP BY使用,否则因违反SQL标准而报错;正确用法包括按业务字段分组、空元组分组或子查询包裹;需配合json_build_object构造对象,ORDER BY须置于函数内,返回json类型需显式转jsonb,空结果返回NULL需COALESCE处理。

json_agg() 必须配合 GROUP BY 使用,否则报错
直接在普通 SELECT 中写 json_agg() 会触发错误:column "xxx" is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause。这不是 PostgreSQL 的 bug,而是 SQL 标准要求:只要用了聚合函数,非聚合列就必须出现在 GROUP BY 中。
常见误写:
SELECT id, name, json_agg(tags) FROM users; -- ❌ 报错
正确写法(按业务逻辑分组):
- 如果想把每个用户的多条标签聚合成一个数组:用
GROUP BY id, name - 如果想把整张表所有行打包成一个 JSON 数组:用
GROUP BY ()(空元组,PostgreSQL 9.5+ 支持) - 如果只是临时“假装聚合”,又不想分组,可改用子查询:
SELECT json_agg(t) FROM (SELECT * FROM users) t
用 json_build_object 动态构造每项结构,别硬拼字段名
json_agg() 本身不生成键值对,它只把输入值原样塞进数组。想得到 [{"id": 1, "name": "Alice"}, ...] 这种格式,必须先用 json_build_object() 构建单个对象。
错误示范(字段名写死、无法复用):
SELECT json_agg(ROW(id, name)::TEXT) FROM users; -- ❌ 得到字符串数组,不是 JSON 对象
推荐写法:
SELECT json_agg(json_build_object('id', id, 'name', name)) FROM users;- 字段名带表达式也没问题:
json_build_object('full_name', first_name || ' ' || last_name) - 注意 NULL 值:
json_build_object()会忽略 NULL 键值对,不是报错,但可能造成字段缺失
排序和去重必须在 json_agg 内部指定,不能靠外层 ORDER BY
json_agg() 是聚合函数,它的输入顺序决定了最终 JSON 数组元素顺序。外层的 ORDER BY 对聚合结果无效。
想让数组内元素按时间倒序排列:
SELECT json_agg(json_build_object('ts', created_at, 'val', value) ORDER BY created_at DESC) FROM events;
去重同理:必须在聚合前处理,比如加 DISTINCT ON (user_id) 或用子查询先 SELECT DISTINCT,json_agg(DISTINCT ...) 不合法。
容易踩的坑:
- 误以为
ORDER BY写在json_agg()外面就能控制数组顺序 —— 实际上没用 - 对含 NULL 的字段排序时,
NULLS FIRST/LAST要显式声明,否则不同版本行为可能不一致 - 嵌套聚合(如学生+课程)中,内层
json_agg()的ORDER BY和外层互不影响
json_agg 返回 json 类型,存入 jsonb 字段要显式转换
如果你要把结果插入 jsonb 列或参与 jsonb 运算,json_agg() 默认返回的是 json 类型,不能直接比较或索引。
不转换就存会导致后续查询失败:
INSERT INTO logs(data) VALUES (json_agg(...)); -- data 是 jsonb 列?❌ 类型不匹配
安全做法:
- 插入时强转:
json_agg(...)::jsonb - 查询时需要路径提取(如
->>'name'),也建议先转jsonb,因为jsonb的路径操作符更稳定、支持索引 - 注意:
jsonb会自动去重键、忽略对象字段顺序,而json保留原始顺序和重复键(后者极少用)
最易被忽略的一点:当聚合结果为空(0 行)时,json_agg() 返回 NULL,不是空数组 []。如果业务依赖非空数组,得用 COALESCE(json_agg(...), '[]'::json) 补默认值。











