该用 array_agg 而不是 jsonb_agg 时:需生成原生数组以支持 @>、&& 等操作符、gin 索引及 unnest(),且数据类型统一、需保留 null 或要求高性能;而 jsonb_agg 更适合嵌套结构、动态字段及 api 直出场景。

什么时候该用 array_agg 而不是 jsonb_agg
当你要把多行数据聚合成一个 PostgreSQL 原生数组(比如 text[]、integer[]),且后续会走数组操作符(@>、&&、ANY)或传给函数如 unnest() 时,array_agg 是更直接的选择。它不带类型转换开销,索引友好,也更容易被查询规划器优化。
常见错误现象:用 jsonb_agg 聚合整数后,想用 WHERE my_col @> '[42]' 查包含某值,结果慢得离谱——因为 jsonb 的存在性查询没法走 GIN 索引的 jsonb_path_ops 高效路径,而 integer[] @> ARRAY[42] 可以配合 gin 索引秒出结果。
- 场景举例:用户标签列表(固定字符串集合)、权限码数组、订单状态变迁序列
- 注意
array_agg要求所有输入值类型一致;混用text和integer会报错ERROR: array_agg cannot accept arguments of type integer and text - 空组返回
NULL,不是空数组;需要空数组得套一层COALESCE(array_agg(...), ARRAY[]::text[])
为什么 jsonb_agg 更适合嵌套结构和动态字段
jsonb_agg 把每行转成一个 jsonb 对象再聚合,天然支持异构字段、嵌套对象、缺失键——这正是它不可替代的地方。比如你有一张订单明细表,要按订单 ID 聚合成“每个订单含哪些商品+数量+单价”,用 array_agg(ROW(product_id, qty, price)) 得不到可读结构,而 jsonb_agg(jsonb_build_object('id', product_id, 'qty', qty, 'price', price)) 直接产出清晰的数组。
性能影响明显:生成 jsonb 比生成数组贵,尤其字段多、文本长时;但如果你后续要用 jsonb_path_query 或 #> 提取深层字段,那省下的解析成本远超聚合开销。
- 典型场景:API 接口直出、审计日志聚合、配置项批量导出
- 别忘了加
ORDER BY:默认聚合顺序不确定,jsonb_agg(... ORDER BY created_at)才能保证时间序 - 如果某列可能为
NULL,jsonb_build_object会自动跳过该键;不想丢键就用jsonb_build_object('field', COALESCE(val, 'null'::jsonb))
array_agg 和 jsonb_agg 在 NULL 处理上的关键差异
两者对 NULL 输入的默认行为不同:array_agg 会把 NULL 当作数组元素保留(例如 array_agg(x) 输入 1, NULL, 3 → {1,NULL,3}),而 jsonb_agg 默认忽略 NULL(同例 → [1,3])。这个差异常导致逻辑错位,尤其在统计类查询里。
容易踩的坑:用 jsonb_agg 统计“用户最近 5 次登录 IP”,但某次登录 IP 字段为空,结果只返回 4 条——你以为漏了数据,其实是被静默过滤了。
- 让
jsonb_agg保留NULL:改用jsonb_agg(COALESCE(ip, 'null'::jsonb))或jsonb_agg(to_jsonb(ip)) - 让
array_agg过滤NULL:加FILTER (WHERE x IS NOT NULL),如array_agg(x) FILTER (WHERE x IS NOT NULL) - 聚合前没处理
NULL导致 JSON 解析失败?检查前端是否假设数组长度恒定,后端却因 NULL 过滤变短
跨版本兼容性和函数组合的隐性成本
PostgreSQL 9.5+ 支持 jsonb_agg,但早期版本只能用 array_agg + to_jsonb 模拟,性能差一截。如果你的业务要兼容 9.4 或更低,别硬上 jsonb_agg。
另一个隐形坑是函数链式调用:比如 jsonb_agg(jsonb_build_object(...)) 看似干净,但如果内层 jsonb_build_object 里混用了 text 和 numeric,PostgreSQL 会尝试隐式转换,某些边界值(如 NaN、极长小数)可能触发 ERROR: invalid input syntax for type numeric —— 这种错不在聚合函数本身,而在构建阶段。
- 安全做法:对非确定性字段显式 cast,比如
jsonb_build_object('score', (score::text)::jsonb) - 别在
jsonb_agg里直接套子查询,尤其是相关子查询;先LATERAL JOIN再聚合,避免重复执行 - 用
EXPLAIN (ANALYZE, BUFFERS)对比两种聚合的实际执行时间和内存占用,有时差别超 3 倍
最常被忽略的一点:jsonb_agg 返回的是无序集合,哪怕你加了 ORDER BY,也不能保证 JSON 数组里对象的键顺序——PostgreSQL 的 jsonb 总是按键名排序存储。如果前端依赖 key 出现顺序,得换 json 类型或自己拼字符串。










