SQL Server和Oracle里边那种行变列的奇怪方法讲解分享
- 问答
- 2026-01-16 13:55:44
- 1
这个“行变列”的问题,在实际工作中特别常见,说白了就是报表需求,想象一下,你有一张学生成绩表,平时可能是这样存的:
| 学生姓名 | 科目 | 成绩 |
|---|---|---|
| 张三 | 语文 | 90 |
| 张三 | 数学 | 85 |
| 张三 | 英语 | 92 |
| 李四 | 语文 | 88 |
| 李四 | 数学 | 95 |
| 李四 | 英语 | 78 |
但领导让你做报表,希望做成这样,一行就是一个学生的所有成绩,看起来更直观:
| 学生姓名 | 语文 | 数学 | 英语 |
|---|---|---|---|
| 张三 | 90 | 85 | 92 |
| 李四 | 88 | 95 | 78 |
这个过程,就是把原本“科目”这一行的数据,变成了列名(语文、数学、英语),这就是我们常说的“行转列”或者“行列转换”,你可能会觉得用程序代码(比如Java、Python)来处理很简单,循环一下就行,但有时候,我们希望在数据库里直接搞定,让应用程序直接拿结果,这样效率更高,SQL Server和Oracle这两个常用的数据库,都有办法,但路子不太一样。
第一部分:SQL Server的典型方法——PIVOT运算符
SQL Server在2005版本之后,引入了一个专门干这个事的关键字,叫PIVOT,这个单词的意思就是“旋转”,非常形象,用PIVOT来实现上面的例子,代码看起来是这样的:
SELECT 学生姓名, [语文], [数学], [英语]
FROM
(
SELECT 学生姓名, 科目, 成绩 FROM 学生成绩表
) AS 源表
PIVOT
(
MAX(成绩) -- 聚合函数
FOR 科目 IN ([语文], [数学], [英语]) -- 要转成列的字段和值
) AS 透视表;
我们来拆解一下这个“奇怪”的语法:
- 最里层的子查询:
(SELECT 学生姓名, 科目, 成绩 FROM 学生成绩表) AS 源表,这部分很简单,就是把原始数据先查出来,给它起个名字叫“源表”,这是PIVOT要处理的基础。 - PIVOT关键字后面:这里有两个核心部分。
MAX(成绩):这是个聚合函数,你可能会问,张三的语文成绩就一个,为什么要用MAX?这是因为PIVOT的机制要求必须对要显示的值进行聚合,当确定了一个学生(张三)和一个科目(语文)后,成绩只有一条,那么MAX(成绩)、MIN(成绩)甚至AVG(成绩)的结果都是它本身,这里用MAX只是一种形式上的需要,但如果一个学生同一科目有多次成绩,那用MAX就会取最高的那次,这时候就要小心了。FOR 科目 IN ([语文], [数学], [英语]):这是关键中的关键。FOR后面指定的是原始表里哪个列的值要变成新表的列名。IN后面就是列举出这个列(科目)里有哪些具体的值,你想把它们变成新列,注意,在SQL Server里,如果列名是中文或者有空格,需要用方括号[]括起来。
- 最外层的SELECT:就是从
PIVOT操作产生的临时结果集(我们叫它“透视表”)里,选出我们需要的列,也就是学生姓名,以及新生成的语文、数学、英语列。
PIVOT的好处是语法比较直观,一看就知道是专门用于行列转换的,缺点是你必须事先知道“科目”里到底有哪几种值(语文、数学、英语),并手动写在IN子句里,如果将来增加了“物理”科目,你就得改SQL语句。
第二部分:Oracle的典型方法——用CASE WHEN实现
Oracle虽然在新版本(11g之后)也支持PIVOT语法,但更经典、更广为人知的方法是使用CASE WHEN表达式配合聚合函数,这种方法在SQL Server里其实也能用,非常通用,我们用Oracle的风格来写一下:
SELECT
学生姓名,
MAX(CASE WHEN 科目 = '语文' THEN 成绩 ELSE NULL END) AS 语文,
MAX(CASE WHEN 科目 = '数学' THEN 成绩 ELSE NULL END) AS 数学,
MAX(CASE WHEN 科目 = '英语' THEN 成绩 ELSE NULL END) AS 英语
FROM
学生成绩表
GROUP BY
学生姓名;
这个思路非常巧妙,一步步看:
-
CASE WHEN的作用:它像一个条件开关。CASE WHEN 科目 = '语文' THEN 成绩 ELSE NULL END意思是:对于每一行数据,如果科目是‘语文’,就把这行的‘成绩’值拿出来;如果不是,就返回NULL。 对于学生“张三”的三条记录,经过这个CASE WHEN处理后,会变成:- (张三, 语文, 90) -> 在处理“语文”列时,
CASE WHEN返回90。 - (张三, 数学, 85) -> 在处理“语文”列时,
CASE WHEN返回NULL。 - (张三, 英语, 92) -> 在处理“语文”列时,
CASE WHEN返回NULL。 同理,对于“数学”列和“英语”列,也是类似的判断逻辑。
- (张三, 语文, 90) -> 在处理“语文”列时,
-
MAX聚合函数和GROUP BY的作用:我们为每个学生生成了三列“临时数据”(分别是语文成绩、数学成绩、英语成绩的候选值,但每个候选值都混在一堆NULL里),我们用GROUP BY 学生姓名把同一个学生的所有行合并成一行,合并时,对刚才用CASE WHEN生成的那些列使用MAX函数。MAX函数会忽略NULL值,于是在分组后,每个学生对应的“语文”列下,MAX函数就能从一堆NULL和唯一的一个实际成绩(如90)中,取出最大值90,这样,最终就把三个科目的成绩浓缩到了一行里。
这种方法的思想就是“条件判断 + 分组聚合”,它的灵活性比PIVOT更高,因为你可以在CASE WHEN里写更复杂的条件,但缺点也一样,需要手动列出所有要转换的列值。
简单总结一下
- SQL Server的PIVOT:可以看作是“声明式”的,直接告诉数据库:“我要把科目这个字段的值变成列,用成绩来填充”,语法专一,意图明确。
- Oracle经典方法(CASE WHEN):可以看作是“过程式”的,通过一步步的条件判断和分组,自己“拼凑”出最终结果,这种方法更基础,几乎所有的关系型数据库都支持,是基本功。
根据CSDN博客和技术社区的一些文章分享(如「波波说运维」等提到的),在实际应用中,如果转换的列是固定的,用PIVOT更简洁;如果需要更灵活的条件控制,或者数据库版本较低不支持PIVOT,那么CASE WHEN是万金油,对于一些动态列的情况(即不确定到底有多少种科目),这两种静态SQL都搞不定,通常需要借助存储过程或动态SQL来拼接语句,那就是更高级的用法了。
希望这个直接的解释能帮你理解这两种数据库里实现“行变列”的奇怪方法。

本文由凤伟才于2026-01-16发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:https://haoid.cn/wenda/81830.html
