结论:CASE WHEN + GROUP BY 是最可控、兼容性最好、语义最清晰的行转列方式;PIVOT 仅限 SQL Server/Oracle 且需硬编码列名,不支持动态场景,MySQL 完全不支持,故不推荐首选。

直接说结论:用 CASE WHEN + GROUP BY 是最可控、兼容性最好、语义最清晰的行转列条件聚合方式;PIVOT 仅在 SQL Server / Oracle 中可用,且对动态列支持弱,不推荐作为首选。
为什么不用 PIVOT?
很多人搜“SQL 行转列”第一反应是 PIVOT,但它实际限制很多:
-
PIVOT要求列名必须写死,无法用变量或子查询动态生成 —— 比如“把每个用户最近 3 个订单状态转成三列”,这种场景它直接失效 - MySQL 完全不支持
PIVOT,PostgreSQL 直到 14 才引入实验性PIVOT(需开启扩展),而CASE WHEN全版本通用 -
PIVOT隐式做SUM/COUNT,想用MAX或自定义逻辑时反而绕路,不如显式控制
CASE WHEN + GROUP BY 的标准写法
核心思路:对每一行,用 CASE WHEN 判断该行是否属于目标列,是则取值,否则返回 NULL;再用聚合函数(如 MAX、COUNT)在 GROUP BY 后收口。
例如:统计每个部门中不同职级的人数:
SELECT dept, COUNT(*) AS total, COUNT(CASE WHEN level = 'S1' THEN 1 END) AS s1_count, COUNT(CASE WHEN level = 'S2' THEN 1 END) AS s2_count, MAX(CASE WHEN level = 'S2' THEN salary END) AS max_s2_salary FROM employees GROUP BY dept;
注意点:
- 用
COUNT(CASE ... THEN 1 END)而不是COUNT(CASE ... THEN 1 ELSE 0 END)—— 否则0会被计入计数 - 想取非聚合值(如某个职级的最高薪资),必须配
MAX/MIN,不能裸写CASE - 如果某部门没有 S2 员工,
max_s2_salary自动为NULL,符合预期
遇到 NULL 值和空字符串要小心
当源字段本身含 NULL,或你想把空字符串也视作“无值”,CASE WHEN 的判断条件容易出错:
-
CASE WHEN status = 'done' THEN 1—— 若status是NULL,整条CASE返回NULL,不影响聚合结果;但若你写了CASE WHEN status != 'done' THEN 0 ELSE 1,NULL != 'done'结果仍是NULL,导致逻辑错误 - 安全写法是显式处理:
CASE WHEN status IS NOT NULL AND status = 'done' THEN 1 - 如果业务上 '' 和
NULL等价,统一转成NULL再处理:NULLIF(status, '')
性能与可读性的平衡点
列一多,CASE WHEN 就容易变长,但别急着抽象成视图或 CTE —— 大多数 OLAP 场景下,数据库优化器能很好处理这类静态条件分支。
- 超过 5–7 个目标列时,建议把重复逻辑提取成子查询或 CTE,但仅限提升可读性,不为“性能” —— 实测中 CTE 在 PostgreSQL/MySQL 中未必比内联
CASE快 - 避免在
CASE中调用函数(如UPPER(status)),否则可能使索引失效;提前在WHERE或物化列中标准化数据 - 如果列名来自配置表(比如动态报表),那就别硬写 SQL —— 改用应用层拼接,或用存储过程生成 SQL 字符串
真正难的从来不是语法,而是想清楚“哪些值该归到同一列”——这取决于业务语义,不是 SQL 能自动推断的。

















