group by 本身不支持条件聚合,需用 case when 结合聚合函数实现;where 和 having 无法替代,因前者预过滤整行、后者后过滤整组;条件表达式必须置于 sum/count/avg 内部,且应处理 null(如 coalesce)以避免下游错误。

GROUP BY 本身不支持“条件聚合”,但可以用 CASE WHEN + 聚合函数组合实现——这是最直接、最可控的方式,别被“条件聚合”这个词带偏去查不存在的语法。
为什么不能直接在 GROUP BY 里写 WHERE 或 IF?
因为 GROUP BY 只负责分组,不处理行级逻辑;WHERE 在分组前过滤整行,HAVING 在分组后过滤整个组。真正需要“对某类数据单独求和、计数”的场景,必须把判断逻辑塞进聚合函数内部。
-
SELECT department, SUM(CASE WHEN status = 'active' THEN salary ELSE 0 END) AS active_salary_sum FROM employees GROUP BY department—— 这才是条件求和的标准写法 - 误用
WHERE status = 'active'会直接剔除 inactive 行,导致该部门的总人数(COUNT(*))也变少,无法同时统计“总人数”和“在职人数” - 用
HAVING无法做到“同一组内不同条件分别聚合”,它只能决定“这个组留不留”,不能决定“这个组里哪些行参与哪个聚合”
CASE WHEN 放在聚合函数里,不是 SELECT 里
关键点在于:条件表达式必须包裹在 SUM()、COUNT()、AVG() 等函数内部,否则会破坏分组语义。
- ✅ 正确:
COUNT(CASE WHEN paid = true THEN 1 END)—— 统计已支付订单数(NULL不计入COUNT) - ✅ 更稳妥:
SUM(CASE WHEN paid = true THEN 1 ELSE 0 END)—— 明确补零,语义更直白 - ❌ 错误:
SELECT CASE WHEN paid = true THEN order_id END, COUNT(*) FROM orders GROUP BY ...—— 这会让COUNT(*)按CASE结果分组,完全偏离本意
NULL 值和默认值要主动处理
条件聚合结果天然容易出 NULL,尤其当某组内所有行都不满足条件时,SUM(CASE ...) 返回 NULL 而非 0,前端或下游计算常因此报错。
- 加
COALESCE是最简单可靠的兜底:COALESCE(SUM(CASE WHEN type = 'refund' THEN amount END), 0) - 避免在
CASE里漏写ELSE—— 不写ELSE等价于ELSE NULL,而COUNT(NULL)是 0,SUM(NULL)是NULL,行为不一致 - 如果业务上“无数据”和“明确为 0”有区别(比如风控场景),那就别硬转 0,保留
NULL并让上游显式判断
多条件聚合别堆 CASE,用 FILTER(PostgreSQL)或 IIF(SQL Server)可读性更高
标准 SQL 的 FILTER 子句是专为此设计的语法糖,但仅 PostgreSQL 10+ 和部分新版本数据库支持;MySQL 和 SQL Server 需靠 CASE 或方言函数。
- PostgreSQL:
COUNT(*) FILTER (WHERE status = 'shipped')—— 比COUNT(CASE WHEN ... THEN 1 END)少嵌套,意图更清晰 - SQL Server:
COUNT(IIF(status = 'shipped', 1, NULL))—— 功能等价,但IIF比CASE简短 - MySQL:没有
FILTER,也没有IIF(8.0+ 才有),老老实实用CASE WHEN,别强求语法简洁
真正容易被忽略的,是条件聚合和分组键之间的业务一致性——比如按 region 分组,却在 CASE 里用 country 判断,而 region 和 country 并非一一映射,结果就变成按 region 分组、再在组内按 country 条件计算,逻辑上没问题,但业务同学很可能以为你在按 country 聚合。写之前,先确认“条件字段是否属于当前分组粒度的属性”。










