必须配合jsonb_build_object使用,因为jsonb_agg仅聚合值而不建模结构;单独对普通字段调用会丢失字段名和上下文,导致json数组无键名、无法区分数据归属。

直接用 jsonb_agg 就行,但必须配合 jsonb_build_object 才能得到结构清晰的 JSON 对象数组;单独用 jsonb_agg 只会把原始列值塞进去,容易丢字段或类型错乱。
为什么不能直接 jsonb_agg(my_column)
常见错误是以为对某列直接聚合就能生成带键名的 JSON 数组。比如:
SELECT category, jsonb_agg(price) FROM products GROUP BY category;
结果是 [1200.00, 800.00, 150.00] —— 没有 name 字段,无法区分哪个价格对应哪个商品。
-
jsonb_agg只做“收集”,不负责“建模” - 如果源列本身就是
jsonb类型,它会原样打包;但多数场景下你手头是普通字段(name、price),得先转成对象再聚合 - 漏掉
jsonb_build_object是新手最常踩的坑,查出来数据看着像,实际字段全丢了
正确写法:用 jsonb_build_object 构造单条,再用 jsonb_agg 收集
要把每行变成 {"name": "Laptop", "price": 1200.00} 这样的对象,再聚合成数组:
SELECT category,
jsonb_agg(jsonb_build_object('name', name, 'price', price)) AS products_array
FROM products
GROUP BY category;
关键点:
-
jsonb_build_object参数必须成对出现(键、值),且键必须是字符串字面量或表达式,不能是列别名 - 值可以是任意表达式,比如
COALESCE(price, 0)或UPPER(name),但要注意类型兼容性(如把text和numeric混用没问题,但jsonb_build_object('id', id::text)更稳妥) - 聚合结果是
jsonb类型,可直接被应用层解析为数组,无需额外转换
遇到 NULL 值怎么办?jsonb_agg 默认跳过 NULL,但 jsonb_build_object 不会
如果 name 或 price 有 NULL,jsonb_build_object 仍会生成 {"name": null, "price": 1200.00};而 jsonb_agg 遇到整个表达式为 NULL 才跳过整条记录。
- 想过滤掉含 NULL 的行:加
WHERE name IS NOT NULL AND price IS NOT NULL - 想保留行但把 NULL 转成默认值:用
COALESCE(name, 'unknown')或NULLIF(price, 0) - 注意:
jsonb_agg对空集合返回[],不是NULL,这点和array_agg一致
性能与索引影响:别在聚合里做复杂计算
jsonb_agg 本身开销不大,但里面套的表达式会逐行执行。比如:
jsonb_build_object('name_upper', UPPER(name), 'price_taxed', price * 1.1)
这类计算会在分组前每行都跑一遍,数据量大时明显拖慢。
- 优先在 WHERE 或 JOIN 阶段完成过滤和关联,别堆在
jsonb_build_object里 - 如果要按 JSON 内某个字段筛选(比如只取
price > 100的商品),务必在聚合前用子查询或 CTE 过滤,而不是聚合完再用jsonb_path_query拆开筛 - GIN 索引对聚合结果无效——索引只能加速聚合前的 WHERE 条件,不能加速
jsonb_agg输出内容的后续查询
真正容易被忽略的是字段顺序和重复键:PostgreSQL 不保证 jsonb_build_object 的键顺序(虽然通常按参数顺序),且如果传入重复键名(如 jsonb_build_object('id', 1, 'id', 2)),后一个会覆盖前一个——这种逻辑错误在测试小数据时根本看不出来,上线后才暴露。











