json_arrayagg不能跨表直接聚合,必须先join或派生表拉平数据再group by聚合;因其只接受当前行集表达式,不支持标量子查询,否则报错或退化为n+1查询。

直接结论:JSON_ARRAYAGG 本身不能跨表聚合,必须先通过 JOIN 或 LATERAL(MySQL 不支持)/派生表把关联数据“拉平”到同一行集里,再聚合;否则会报错或语义错误。
为什么不能在 JSON_ARRAYAGG 里直接写子查询?
常见错误是试图这样写:
SELECT id, JSON_ARRAYAGG((SELECT name FROM tags WHERE post_id = posts.id)) FROM posts;
这会触发 MySQL 报错:This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA。因为 JSON_ARRAYAGG 是聚合函数,只接受当前查询输出列(即 FROM + JOIN 后的临时结果集)中的表达式,不接受标量子查询。
- 子查询在聚合上下文里无法被正确标记为“可重复执行”,MySQL 拒绝执行
- 即使语法侥幸通过(如某些旧版兼容模式),性能也极差:对每行都执行一次子查询,变成类 N+1 查询
- 正确路径永远是:先 JOIN → 再 GROUP BY → 最后
JSON_ARRAYAGG
JOIN + GROUP BY 是最稳的组合方式
假设你有 posts 和 comments 两张表,想为每个 post 生成一个 comments 数组:
详细的 Three.js 3D 图形参考,涵盖场景设置、相机、几何体、材质、光照、动画、控制器、加载器、数学工具和调试。
SELECT
p.id,
p.title,
JSON_ARRAYAGG(
JSON_OBJECT('id', c.id, 'content', c.content, 'created_at', c.created_at)
) AS comments
FROM posts p
LEFT JOIN comments c ON p.id = c.post_id
GROUP BY p.id, p.title;
-
LEFT JOIN确保没有评论的 post 也能出现,此时JSON_ARRAYAGG返回空数组[](不是NULL) -
GROUP BY必须包含所有非聚合列(p.id, p.title),否则 MySQL 8.0+ 会报Expression #X of SELECT list is not in GROUP BY clause - 如果用
INNER JOIN,则只有带评论的 post 才会出现在结果里
遇到 NULL 值和大数据量时怎么避坑?
JSON_ARRAYAGG 对 NULL 字段默认保留为 null 元素,比如 [{"id":1,"content":"ok"},{"id":2,"content":null}],前端解析容易出错;同时大数据量下可能被截断。
- 过滤 NULL 字段:加
WHERE c.content IS NOT NULL,或用IFNULL(c.content, '')替换 - 避免数组截断:检查并调高
group_concat_max_len(JSON_ARRAYAGG复用其缓冲区),例如:SET SESSION group_concat_max_len = 1048576; - 若单条 JSON 数组超大(如 > 4MB),还需确认
max_allowed_packet足够,否则连接直接中断
嵌套结构别手拼 JSON 字符串
有人会这么干:
CONCAT('{ "post": ', JSON_OBJECT('id', p.id, 'title', p.title),
', "comments": ', JSON_ARRAYAGG(...), '}')
这是危险操作——JSON_ARRAYAGG 输出的字符串里的双引号不会被自动转义,最终得到非法 JSON。
- 正确做法是全程用 JSON 函数嵌套:
JSON_OBJECT('post', JSON_OBJECT(...), 'comments', JSON_ARRAYAGG(...)) - MySQL 会自动处理引号、转义、类型转换,保证输出是合法 JSON
- 一旦看到
CONCAT和JSON_ARRAYAGG混用,基本可以判定该字段不可靠
真正麻烦的不是语法,而是忘记 GROUP BY 的粒度是否匹配业务分组意图,以及没意识到 JSON_ARRAYAGG 的内存行为会受 group_concat_max_len 静默限制——这两个点最容易在线上突然暴露。










