json_object_agg()将两列结果聚合成单个JSON对象,首列为非空text键,次列为可转JSON的值;键重复时后值覆盖前值,需显式ORDER BY控制顺序,大结果集应前置过滤。

用 json_object_agg() 把行转成单个 JSON 对象
这是最直接的方式:把查询结果的每行当作一个键值对,组合成一个顶层 JSON 对象。要求结果集恰好两列——第一列是字符串类型的键(会自动转为 text),第二列是任意可转 JSON 的值。
常见错误是忘记列顺序或类型不匹配,比如键列含 NULL 或非标量值,这时 json_object_agg() 会报错:ERROR: field name must be non-null。
- 键列必须为非空
text(可用coalesce(col::text, 'fallback')防止 NULL) - 值列会自动调用
to_json(),支持int、boolean、json、jsonb等,但不支持数组嵌套作为值(需先用json_build_array()封装) - 若需按特定顺序生成键,必须显式
ORDER BY(如json_object_agg(key_col VALUE val_col ORDER BY key_col))
示例:
SELECT json_object_agg(name VALUE age) FROM (VALUES ('alice', 30), ('bob', 25)) t(name, age);
返回:{"alice" : 30, "bob" : 25}
当键不是单一列时:先用 json_build_object() 构造中间对象
如果“键”本身是表达式(比如拼接字段、条件判断),不能直接喂给 json_object_agg(),因为它的键参数只接受列名或标量表达式,且不支持别名重命名逻辑。
此时应先用 json_build_object() 每行生成一个子对象,再用 jsonb_object_agg() 或 json_agg() 合并——但注意:后者生成的是 JSON 数组,不是单个对象。
- 真正需要“动态键 + 聚合为对象”,只能靠子查询 +
json_object_agg()前做投影,例如:SELECT json_object_agg(k VALUE v) FROM (SELECT concat('user_', id)::text AS k, to_jsonb(data) AS v FROM users) s -
json_build_object()是每行建一个对象,不是聚合函数;误用它替代json_object_agg()会导致结果是多行 JSON 对象,而非一行聚合结果 - 若值列是
jsonb类型,优先用jsonb_object_agg(),避免隐式转换开销
处理重复键:PostgreSQL 默认覆盖,无法报错或合并
json_object_agg() 遇到相同键时,**后出现的值会静默覆盖前面的值**,不会报错,也不会累积成数组。这点和 JavaScript 或 Python 的字典行为一致,但容易被忽略导致数据丢失。
- 没有内置选项控制冲突策略(比如跳过、报错、合并为数组)
- 若业务上不允许覆盖,必须提前去重,例如用
DISTINCT ON (key_col)或窗口函数ROW_NUMBER() OVER (PARTITION BY key_col ORDER BY ...)过滤 - 想把重复键的值聚合成数组?得绕路:先
array_agg(val_col),再用json_object_agg(key_col VALUE array_agg_val)——但要注意键仍需唯一,所以这实际适用于“一主多从”场景下的预聚合
性能与大结果集:别在聚合前漏掉 WHERE 或 LIMIT
json_object_agg() 是 final aggregate 函数,必须扫描全部输入行才能输出结果。如果源表很大,又没加过滤条件,可能内存暴涨甚至 OOM。
- 它不支持流式构造,也不支持 partial aggregation(比如分片后 merge),所以无法水平扩展
- 若只是取前 N 个键值对,务必在子查询里加
LIMIT,而不是对整个聚合结果再截断(后者已构建完大对象) - 索引对聚合本身无加速作用,但能加速前置的
WHERE或JOIN——确保键列上有合适索引,尤其是用于ORDER BY场景
真正棘手的是键太多(比如上万键)时,JSON 解析/传输成本陡增,这时候该考虑是否真需要单个大对象,还是改用游标分页 + 流式 JSON 行(json_each() 反向拆解)更合理。











