聚合查询不加GROUP BY时恒返回一行(含全NULL),加GROUP BY可使无数据时返回空结果集;COALESCE仅防NULL异常,不改变行数。

聚合查询不加 GROUP BY 时,永远返回一行——哪怕 WHERE 筛完没数据,这也不是 bug,是 SQL 标准行为。 应用层看到 resultList.size() == 1 就直接取字段,MAX(created_at) 是 NULL,一调 .toString() 就崩,这种问题线上高频出现。
为什么没数据还返回一行?
因为不写 GROUP BY,数据库把整个结果集(哪怕是空的)当成一个隐式分组来处理:
-
COUNT(*)返回0(它统计行数,空集也计 0) -
SUM()、AVG()、MAX()、MIN()返回NULL(它们需要至少一个非NULL输入才能计算) - 这个“一行 NULL”结构合法、类型完整,ORM 会照常映射成对象,但字段值无效
怎么让无数据时真返回空结果集?
唯一可靠办法:加 GROUP BY。只要 WHERE 后没匹配到任何行,带 GROUP BY 的聚合查询就返回 0 行。
- 错:
SELECT SUM(amount) FROM orders WHERE user_id = 999999→ 返回 1 行,sum为NULL - 对:
SELECT SUM(amount) FROM orders WHERE user_id = 999999 GROUP BY user_id→ 返回空结果集(0 行) - 注意:
GROUP BY字段必须出现在SELECT列表中(或用常量如GROUP BY 1),否则语法报错
LEFT JOIN 场景下“伪空行”更难缠
主表有数据、从表无匹配时,COUNT() 得 0、MAX() 得 NULL,但整行还在——这不是空结果集,而是业务上无效的一行。
- 典型错误:用
WHERE o.id IS NOT NULL过滤 → 把主表记录全干掉了,变成INNER JOIN - 正确解法之一:
HAVING COUNT(o.id) > 0(必须跟在GROUP BY u.id后) - 更稳妥:改用
INNER JOIN,如果业务本意就是只查有订单的用户 - 或者收紧
ON条件:ON u.id = o.user_id AND o.status IN ('shipped', 'delivered'),让无意义的左连变少
COALESCE 是兜底手段,不是根治方案
COALESCE(SUM(amount), 0) 能防 NPE,但它改变不了“返回一行”的事实——你还是得处理 size=1 的结果集,只是字段不为 NULL 了。
- 别写
SUM(COALESCE(amount, 0)):这是把每行NULL当 0 加,语义已扭曲 -
COUNT(*)不需要COALESCE,它永远不会是NULL - 跨库兼容优先用
COALESCE,MySQL 特有的IFNULL少用
最常被忽略的是:是否返回空结果集,和是否显示默认值,是两个独立问题。前者靠 GROUP BY 控制,后者靠 COALESCE 或应用层判断。混在一起处理,代码会越来越难维护。










