JSON 字段做聚合时,不能把整列直接当分组维度。数组或对象必须先展开成多行,再按原始主键或提取字段 GROUP BY,否则同一行里的多个值会被混算。
为什么 JSON 不能直接分组聚合
关键点只有一条:先展开,再分组。否则一行里多个商品、状态会被当成一个整体,结果能返回,但统计口径不可靠。
MySQL 8.0+:用 JSON_TABLE() 展开后再聚合
MySQL 8.0+ 常用 JSON_TABLE() 把 JSON 数组映射成可查询结果集,再做过滤和聚合。

写 JSON_TABLE() 时要注意什么
- 遍历全部元素要用
'$[*]';写成'$[0]'只会取部分数据。 COLUMNS中类型要匹配实际内容,如TEXT PATH '$'、INT PATH '$.qty'。- 如果列里是字符串形式 JSON,如
"[\"a\",\"b\"]",先CAST(items AS JSON),或直接使用JSON列类型。 NULL或非法 JSON 可能导致漏行,查询里通常配合LEFT JOIN、COALESCE。
SELECT o.id, jt.item_id, SUM(jt.qty) AS total_qty
FROM orders o
LEFT JOIN JSON_TABLE(
o.items,
'$[*]' COLUMNS (
item_id INT PATH '$.id',
qty INT PATH '$.qty'
)
) AS jt ON TRUE
WHERE JSON_VALID(o.items) AND JSON_LENGTH(o.items) > 0
GROUP BY o.id, jt.item_id;
这条 SQL 的关键是:先展开,过滤非法或空数组,再按 o.id、jt.item_id 分组。
PostgreSQL:用 jsonb_array_elements() 配合 LATERAL
PostgreSQL 常用 jsonb_array_elements() 配合 LATERAL 拆数组,再聚合。

PostgreSQL 这条路线的几个坑
jsonb_array_elements()只接受jsonb;原字段是json时先写items::jsonb。- 提前过滤
NULL和空数组[],避免记录消失。 - 字符串字段用
->>'key';参与数值计算时显式转换,如(elem->>'qty')::int。 - 展开后保留原始主键,如
orders.id,否则不同订单会被聚到一起。
SELECT o.id, elem->>'status' AS status, COUNT(*) AS cnt
FROM orders o, LATERAL jsonb_array_elements(o.items) AS elem
WHERE o.items IS NOT NULL
AND jsonb_typeof(o.items) = 'array'
AND jsonb_array_length(o.items) > 0
GROUP BY o.id, elem->>'status';
聚合前先确认分组维度
- 按订单汇总:通常
GROUP BY orders.id。 - 按商品 ID 统计全表销量:按提取字段分组,如
GROUP BY elem->>'id',前提是所有订单都已完整展开。 - 原表里的
order_time、user_id等字段,要么进入GROUP BY,要么用MIN()、ANY_VALUE()包裹。 - MySQL 即使放宽
ONLY_FULL_GROUP_BY能跑通,也不适合作为常规方案。
还要先定业务口径:同一订单里重复商品,是去重还是累加。SQL 只执行规则,不判断语义。

实操时的检查清单
- 确认字段是真 JSON,还是字符串化 JSON。
- 确认展开路径覆盖全部元素。
- 检查数量、金额等字段的类型转换。
- 过滤
NULL、空数组、非法 JSON。 - 最后确认聚合目标是按订单、按商品,还是按状态。









