【MYSQL 】SQL 行列转换实战:如何用 CASE WHEN 与 SUM/MAX 重塑部门表
SQL 行列转换实战:如何用 CASE WHEN 与 SUM/MAX 重塑部门表
一道经典的 SQL 题目,带你彻底理解 GROUP BY 与聚合函数的默契配合
一、问题背景:部门表的重构需求
我们有一张名为 Department
的表,结构如下:
id | revenue | month |
---|---|---|
1 | 8000 | Jan |
1 | 7000 | Feb |
2 | 9000 | Jan |
2 | 8000 | Feb |
我们希望将这张表从“行存储”转换为“列存储”,即每个部门一行,每个月一列,效果如下:
id | Jan | Feb | Mar | … | Dec |
---|---|---|---|---|---|
1 | 8000 | 7000 | … | … | … |
2 | 9000 | 8000 | … | … | … |
二、解决方案:CASE WHEN + 聚合函数
✅ 推荐写法(跨数据库通用)
SELECTid,SUM(CASE WHEN month = 'Jan' THEN revenue END) AS Jan_Revenue,SUM(CASE WHEN month = 'Feb' THEN revenue END) AS Feb_Revenue,-- … 依次写出 12 个月SUM(CASE WHEN month = 'Dec' THEN revenue END) AS Dec_Revenue
FROM Department
GROUP BY id
ORDER BY id;
三、理解行列转换的核心逻辑
四、为什么一定要用聚合函数?
❓ 问题:为什么 GROUP BY 之后必须用 MAX/SUM?
答:SQL 规定:GROUP BY 之后,SELECT 中的每一列要么在 GROUP BY 中,要么被聚合函数包裹。
📌 举例说明:
原始数据:
id | month | revenue |
---|---|---|
1 | Jan | 8000 |
1 | Feb | 7000 |
如果你写:
SELECT id, revenue
FROM Department
GROUP BY id; -- 错误!
数据库不知道 revenue
该显示 8000 还是 7000。
五、MAX 还是 SUM?如何选择?
聚合函数 | 作用 | 适用场景 |
---|---|---|
MAX | 取最大值 | 每月只有一行时,效果与 SUM 相同 |
SUM | 求和 | 更健壮,可处理重复数据 |
COUNT | 计数 | ❌ 错误!会返回行数,不是收入值 |
✅ 推荐使用 SUM:
- 语义更清晰:我们是在“汇总”收入,不是“取最大”
- 容错性更强:即使有重复数据,也能正确累加
六、图解“压行”过程(以 id=1 为例)
步骤 | 数据状态 | 说明 |
---|---|---|
原始数据 | (1, 8000, Jan) (1, 7000, Feb) | 两行记录 |
CASE WHEN 后 | (1, 8000, NULL) (1, NULL, 7000) | 月份拆成列 |
GROUP BY + SUM | (1, 8000, 7000) | 多行合并为一行 |
七、常见问题解答(FAQ)
❓ 为什么 MAX 可以“保留值”而不是取最大?
因为每个 id 下每个月只有一行,MAX 实际上只作用在一个非 NULL 值上,相当于“提取唯一值”。
❓ 可以不加 ORDER BY 吗?
可以。ORDER BY 只是为了结果美观,不是语法必需。
❓ 如果一个月有多个收入记录怎么办?
使用 SUM 会自动累加,而 MAX 只会保留其中一个值。
八、总结
- 使用 CASE WHEN 将行转为列
- 使用 SUM/MAX 配合 GROUP BY 将多行合并为一行
- SUM 更健壮,MAX 更“纯粹”,根据需求选择
- 记住:聚合函数在 GROUP BY 中是“压行工具人”
九、完整代码示例
SELECTid,SUM(CASE WHEN month = 'Jan' THEN revenue END) AS Jan_Revenue,SUM(CASE WHEN month = 'Feb' THEN revenue END) AS Feb_Revenue,SUM(CASE WHEN month = 'Mar' THEN revenue END) AS Mar_Revenue,SUM(CASE WHEN month = 'Apr' THEN revenue END) AS Apr_Revenue,SUM(CASE WHEN month = 'May' THEN revenue END) AS May_Revenue,SUM(CASE WHEN month = 'Jun' THEN revenue END) AS Jun_Revenue,SUM(CASE WHEN month = 'Jul' THEN revenue END) AS Jul_Revenue,SUM(CASE WHEN month = 'Aug' THEN revenue END) AS Aug_Revenue,SUM(CASE WHEN month = 'Sep' THEN revenue END) AS Sep_Revenue,SUM(CASE WHEN month = 'Oct' THEN revenue END) AS Oct_Revenue,SUM(CASE WHEN month = 'Nov' THEN revenue END) AS Nov_Revenue,SUM(CASE WHEN month = 'Dec' THEN revenue END) AS Dec_Revenue
FROM Department
GROUP BY id;