聚合函数不写GROUP BY时天然返回一行:整表视为单一分组,无匹配行时COUNT(*)为0、其余为NULL;加GROUP BY则无数据时返回空结果集。

GROUP BY 缺失时,聚合函数天然返回一行
不写 GROUP BY,SUM()、AVG()、MAX() 这些函数就会把整张表(或 WHERE 筛选后的结果集)当作一个隐式分组来处理。哪怕筛选后没数据,也仍会返回一行——COUNT(*) 是 0,其余聚合字段是 NULL。
典型错误写法:SELECT SUM(amount), MAX(created_at) FROM orders WHERE user_id = 999999 AND status = 'paid'。若无匹配记录,结果不是空集,而是:{sum: NULL, max: NULL}(COUNT(*) 除外,它返回 0)。
这不是数据库 bug,是 SQL 标准行为。应用层拿到 size=1 的 List,却误以为有有效数据,后续调用 obj.getMax().toString() 就直接 NPE。
有 GROUP BY 却仍返回空行?检查分组键是否全为 NULL
当 GROUP BY 字段本身大量为 NULL,MySQL 会把所有 NULL 值归为同一组,并返回一行,其中聚合结果可能全为 NULL 或部分为 NULL。
-
SELECT dept_id, COUNT(*) FROM users GROUP BY dept_id—— 若dept_id全是NULL,结果只有一行:{dept_id: NULL, count: N} - 这种“
NULL分组行”容易被当成脏数据忽略,但其实它合法存在 - 排查方式:先
SELECT COUNT(*) FROM users WHERE dept_id IS NOT NULL,确认是否有非空分组键
LEFT JOIN + 聚合导致的“伪空行”最隐蔽
LEFT JOIN 场景下,主表记录存在,但从表无匹配行,聚合字段就会全为 NULL,但整行仍保留——这是业务上无效、结构上合法的一行。
例如:SELECT u.id, COUNT(o.id), MAX(o.created_at) FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE u.status = 'active' GROUP BY u.id。某个活跃用户没下单,就会产出:{id: 123, count: 0, max: NULL}。
这类行不能靠 WHERE o.id IS NOT NULL 解决(会把主表记录过滤掉),正确做法是:
- 改用
INNER JOIN如果你只关心有订单的用户 - 或在
ON条件里收紧从表逻辑,比如ON u.id = o.user_id AND o.status IN ('shipped', 'delivered') - 或用
HAVING COUNT(o.id) > 0在分组后剔除无效组(注意HAVING必须跟在GROUP BY后)
想让无数据时返回空结果集?必须带 GROUP BY
这是最常被忽略的技巧:只要加了 GROUP BY,且 WHERE 后无任何匹配行,查询就返回 0 行,而不是 1 行 NULL。
对比:
-
SELECT SUM(sal) FROM emp WHERE ename = 'allen'→ 返回 1 行,sum为NULL -
SELECT SUM(sal) FROM emp WHERE ename = 'allen' GROUP BY ename→ 返回空结果集(0 行)
所以,如果你的接口/报表要求“没数据就是没数据”,别省略 GROUP BY;哪怕只是按主键分组,也比不写强。但要注意 MySQL 5.7+ 严格模式下,SELECT 列中未出现在 GROUP BY 中的非聚合字段会报错 ERROR 1055。










