JSON_AGG需显式ORDER BY保证顺序,嵌套对象须配合JSON_BUILD_OBJECT,LEFT JOIN得空数组非NULL,大数据量易内存溢出。

JSON_AGG 会把多行转成一个 JSON 数组,但默认不保证顺序
直接用 JSON_AGG(col) 确实能把某列的多行值聚合成一个 JSON 数组,比如把订单明细的 product_name 拼成 ["A", "B", "C"]。但 PostgreSQL 不保证原始行序——除非显式加 ORDER BY。
常见错误是写成:
SELECT order_id, JSON_AGG(product_name) FROM orders JOIN items USING(order_id) GROUP BY order_id;
结果数组顺序可能每次查询都不同,尤其在并行执行或数据更新后。实际业务中(比如前端依赖固定顺序渲染列表),这会导致偶发性 bug。
- 必须写成
JSON_AGG(product_name ORDER BY item_id)或JSON_AGG(product_name ORDER BY created_at)显式排序 - 排序字段需在
GROUP BY范围内可用(不能是未分组又没聚合的列) - 如果想按关联表自然顺序(如插入顺序),确保该表有单调递增主键或时间戳字段
嵌套对象要用 JSON_BUILD_OBJECT + JSON_AGG 组合
JSON_AGG 本身只聚合标量或已有 JSON 值,没法直接生成带键名的对象数组。想把每条明细转成 {"id": 1, "name": "A", "qty": 2} 再聚合成数组,得先用 JSON_BUILD_OBJECT 构造单个对象。
典型写法:
SELECT order_id,
JSON_AGG(
JSON_BUILD_OBJECT(
'id', item_id,
'name', product_name,
'qty', quantity
) ORDER BY item_id
) AS items
FROM orders
JOIN items USING(order_id)
GROUP BY order_id;
-
JSON_BUILD_OBJECT参数必须成对出现(键、值),且键名必须是字符串字面量或表达式,不能是变量名 - 如果某个字段可能为
NULL,JSON_BUILD_OBJECT仍会保留该键,值为null;如需跳过,得用CASE过滤或JSON_STRIP_NULLS(PG 16+) - 嵌套层级深时,容易漏写外层
JSON_AGG的ORDER BY,导致子数组乱序
和 LEFT JOIN 配合时,空关联会得到空数组而非 NULL
用 LEFT JOIN 查主表+可选明细时,若某主记录无明细,JSON_AGG 默认返回空 JSON 数组 [],不是 NULL。这通常符合预期,但要注意逻辑判断方式。
例如:
SELECT u.id, u.name, JSON_AGG(p.title) AS posts FROM users u LEFT JOIN posts p ON p.user_id = u.id GROUP BY u.id, u.name;
没发过帖的用户,posts 字段是 [],不是 NULL。
- 判断“是否有明细”应写
JSON_ARRAY_LENGTH(posts) > 0,而不是posts IS NOT NULL - 如果真需要
NULL替代空数组,得套一层NULLIF(JSON_AGG(...), '[]') - 注意
JSON_ARRAY_LENGTH对NULL输入返回NULL,所以先判空再取长度更安全
大数据量下 JSON_AGG 可能内存溢出或变慢
JSON_AGG 是在内存里攒完整数组再输出,明细行数上万时,单次聚合可能吃掉几百 MB 内存,甚至触发 ERROR: out of memory(尤其在 work_mem 设得小的环境)。
- 先确认是否真需要全量聚合:能否用分页查明细、或只聚合关键字段(如 ID 列表)代替完整对象
- 调大会话级
work_mem(如SET LOCAL work_mem = '256MB';),但别全局设太高,防其他查询争抢 - 避免在
JSON_AGG里做复杂计算(如子查询、函数调用),移到外层或预计算列 - PostgreSQL 14+ 支持
JSON_AGG的DISTINCT和过滤子句(FILTER (WHERE ...)),比用CASE更高效,优先用
真正难处理的是“主表一行、明细几万行”的场景——这时候 JSON 聚合已不是最佳选择,该考虑应用层流式组装或物化汇总表。


















