case when本身不能单独完成行列转换,必须配合group by和sum/max等聚合函数才能实现“一行一主体、多列一类别”的透视效果。

GROUP BY 配合 CASE WHEN 做行转列,本质是聚合 + 条件计数/求和
直接说结论:这不是真正的“行列转换”,而是用 CASE WHEN 把不同类别的值映射到新列,再用 GROUP BY 按主键聚合,最终达到“一行一主体、多列一类别”的效果。它不生成新表结构,也不动态建列,适合类别固定、数量少的场景(比如统计男女数量、各等级订单数)。
常见错误是把 CASE WHEN 写在 GROUP BY 子句里——这会导致分组逻辑错乱;正确做法是只在 SELECT 中用 CASE WHEN 配合聚合函数,GROUP BY 仍按原始维度字段写。
-
CASE WHEN必须嵌套在SUM()、COUNT()、MAX()等聚合函数内,否则会报错“non-aggregated column” - 每个
CASE WHEN分支要确保返回同类型值(如都返回INT或都为NULL),否则可能隐式转换出错 - 漏写
ELSE 0或ELSE NULL容易让整列结果变NULL(尤其当某组完全不匹配任何条件时)
写法模板:COUNT + CASE WHEN 统计分类频次
比如有一张 orders 表,含 user_id 和 status(值为 'pending'、'shipped'、'delivered'),想按用户统计各状态订单数:
SELECT user_id, COUNT(CASE WHEN status = 'pending' THEN 1 END) AS pending_cnt, COUNT(CASE WHEN status = 'shipped' THEN 1 END) AS shipped_cnt, COUNT(CASE WHEN status = 'delivered' THEN 1 END) AS delivered_cnt FROM orders GROUP BY user_id;
注意点:
-
COUNT(...)中THEN 1是惯用写法,也可写THEN status,但不能写THEN 1 ELSE 0—— 因为COUNT(0)会把 0 当作非空值计入,导致总数虚高 - 若想用
SUM,就得写SUM(CASE WHEN ... THEN 1 ELSE 0 END),语义更清晰,也避免COUNT的陷阱 - MySQL 8.0+ 支持
COUNT(*) FILTER (WHERE ...)(标准 SQL),但兼容性差,生产环境慎用
NULL 值处理不当会让整列消失
当 CASE WHEN 没有 ELSE 分支,且某组数据全不满足条件时,该列所有值都是 NULL。有些客户端或 BI 工具会默认隐藏全 NULL 列,让人误以为查询失败。
安全写法必须显式补 ELSE 0(数值型)或 ELSE ''(字符串型):
SELECT dept, SUM(CASE WHEN gender = 'M' THEN salary ELSE 0 END) AS male_salary, SUM(CASE WHEN gender = 'F' THEN salary ELSE 0 END) AS female_salary FROM employees GROUP BY dept;
这里用 SUM(... ELSE 0) 而不是 COUNT,是因为要算薪资总和;若用 COUNT 却没 ELSE,遇到某部门无女性员工,female_salary 列就全是 NULL,加总后仍是 NULL,而非 0。
性能与可维护性陷阱:别硬扛上百个分类
当分类维度超过 10–20 个(比如商品类目、地区编码),硬写上百个 CASE WHEN 表达式不仅难读难调,还会显著拖慢解析和执行速度——每行都要顺序匹配所有分支。
- PostgreSQL 可考虑用
crosstab()(需安装tablefunc扩展) - MySQL / SQL Server 建议先用子查询或 CTE 把分类预聚合,再用应用层拼列,或者改用
PIVOT(SQL Server)或JSON_OBJECTAGG(MySQL 5.7+)做更灵活的结构化输出 - 真正需要动态列时,SQL 不是首选工具;导出后用 Python/Pandas 或 BI 工具处理更稳
最常被忽略的一点:这种写法的结果列名是静态的,一旦新增分类,就必须改 SQL 并发版,没法自动适配——这点在报表需求频繁变动的团队里,比语法细节更致命。











