在SQL数据处理过程中,行转列是常见需求,比如将不同类别的统计数据从多行合并为单行多列展示,使用CASE WHEN语句就能实现类似PIVOT的效果,无需依赖数据库特定的PIVOT关键字,兼容性更好。
行转列的核心逻辑
行转列的本质是根据某一列的枚举值,将其他列的数值拆分到多个新的列中。CASE WHEN语句的作用就是判断目标列的枚举值,当匹配到对应值时返回需要展示的字段值,不匹配时返回空值或者默认值,最后配合聚合函数将同一行的多个结果合并。
示例表结构与测试数据
首先创建一张学生成绩表,存储每个学生在不同科目下的分数,表结构如下:
-- 创建学生成绩表
CREATE TABLE student_score (
student_id INT,
student_name VARCHAR(50),
subject VARCHAR(20),
score INT
);
-- 插入测试数据
INSERT INTO student_score (student_id, student_name, subject, score) VALUES
(1, '张三', '语文', 85),
(1, '张三', '数学', 92),
(1, '张三', '英语', 78),
(2, '李四', '语文', 90),
(2, '李四', '数学', 88),
(2, '李四', '英语', 95),
(3, '王五', '语文', 76),
(3, '王五', '数学', 89),
(3, '王五', '英语', 82);
使用CASE WHEN实现行转列
我们需要将每个学生的语文、数学、英语成绩分别转为列展示,每个学生对应一行数据,包含三个科目对应的分数列。查询语句如下:
SELECT
student_id,
student_name,
-- 语文成绩列,匹配科目为语文时返回分数,否则返回NULL
MAX(CASE WHEN subject = '语文' THEN score ELSE NULL END) AS 语文,
-- 数学成绩列,匹配科目为数学时返回分数,否则返回NULL
MAX(CASE WHEN subject = '数学' THEN score ELSE NULL END) AS 数学,
-- 英语成绩列,匹配科目为英语时返回分数,否则返回NULL
MAX(CASE WHEN subject = '英语' THEN score ELSE NULL END) AS 英语
FROM student_score
-- 按学生和姓名分组,将同一学生的多行数据合并为一行
GROUP BY student_id, student_name
ORDER BY student_id;
执行上述查询后,得到的结果如下:
| student_id | student_name | 语文 | 数学 | 英语 |
|---|---|---|---|---|
| 1 | 张三 | 85 | 92 | 78 |
| 2 | 李四 | 90 | 88 | 95 |
| 3 | 王五 | 76 | 89 | 82 |
关键细节说明
- 聚合函数的选择:这里使用
MAX函数是因为每个学生在每个科目下只有一条分数记录,MAX会取到唯一的分数值,也可以使用SUM、MIN等聚合函数,只要保证同一分组下对应条件的记录唯一即可。 - ELSE子句的处理:如果不写ELSE NULL,CASE WHEN不匹配时会返回空,效果和ELSE NULL一致,但显式写出可读性更好。
- 分组规则:GROUP BY必须包含所有非聚合的查询字段,也就是student_id和student_name,否则查询会报错。
动态科目场景的适配
如果科目是动态新增的,无法提前写死CASE WHEN的分支,可以通过拼接SQL的方式实现。首先查询所有不重复的科目,然后动态生成CASE WHEN的语句,最后执行拼接后的完整SQL。以下是MySQL动态行转列的示例逻辑:
-- 定义变量存储动态SQL
SET @sql = NULL;
-- 拼接CASE WHEN的字段部分
SELECT GROUP_CONCAT(DISTINCT
CONCAT(
'MAX(CASE WHEN subject = ''',
subject,
''' THEN score ELSE NULL END) AS `',
subject,
'`'
)
) INTO @sql
FROM student_score;
-- 拼接完整查询语句
SET @sql = CONCAT('SELECT student_id, student_name, ', @sql,
' FROM student_score GROUP BY student_id, student_name ORDER BY student_id');
-- 执行动态SQL
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
适用场景总结
使用CASE WHEN实现简单PIVOT行转列的方式,适合科目、分类等枚举值固定的场景,兼容MySQL、PostgreSQL、SQL Server、Oracle等几乎所有关系型数据库。如果数据库支持原生PIVOT关键字,也可以结合使用,但CASE WHEN的方式在跨数据库迁移时不需要修改代码,维护性更强。