PIVOT:行转列(透视) ,把某一列的值,横向变成新的列;必须搭配聚合函数 (SUM/COUNT/AVG/MAX/MIN),SQL Server 2005+支持 UNPIVOT 是反向操作:列转行(逆透视)
一、核心三要素(必记)
- 行分组列(不透视列) :最终结果里保留、用来分组的字段(比如姓名、销售员)
- 透视列(FOR 后面) :它里面的值,变成新的列名(比如科目、月份)
- 聚合值列(聚合函数里面) :要填充到交叉单元格的数据(成绩、金额)
底层等价于
CASE WHEN + GROUP BY,PIVOT 写法更简洁
二、标准语法
vbnet
SELECT 不透视列, [值1] AS 别名, [值2] AS 别名, [值3]
FROM (
-- 内层子查询:只保留【分组列、透视列、值列】三列!多余字段不要带
SELECT 分组列, 透视列, 值列 FROM 表
) AS 子查询别名
PIVOT (
聚合函数(值列)
FOR 透视列 IN ([值1],[值2],[值3]) -- 固定写死,静态PIVOT
) AS Pivot别名
ORDER BY 分组列;
⚠️ 重点坑:内层子查询不要带无关字段,否则会被当成分组字段,导致结果行变多、数据错乱!
三、静态PIVOT示例(最常用)
测试表
sql
CREATE TABLE Score(
Name NVARCHAR(10),
Subject NVARCHAR(10),
Score INT
);
INSERT INTO Score VALUES
('张三','语文',80),('张三','数学',90),('张三','英语',75),
('李四','语文',88),('李四','数学',76),('李四','英语',92);
原始长表:
| Name | Subject | Score |
|---|---|---|
| 张三 | 语文 | 80 |
| 张三 | 数学 | 90 |
| 张三 | 英语 | 75 |
目标:转为宽表,一行一个学生,语文、数学、英语作为列
vbnet
SELECT
Name,
[语文] AS 语文成绩,
[数学] AS 数学成绩,
[英语] AS 英语成绩
FROM (
SELECT Name, Subject, Score FROM Score
) AS t
PIVOT (
MAX(Score) -- 同一个人同一科目只有一条,MAX/SUM都可以
FOR Subject IN ([语文],[数学],[英语])
) AS pvt;
输出:
| Name | 语文成绩 | 数学成绩 | 英语成绩 |
|---|---|---|---|
| 张三 | 80 | 90 | 75 |
| 李四 | 88 | 76 | 92 |
对比等价CASE写法(方便理解底层)
sql
SELECT Name,
MAX(CASE WHEN Subject='语文' THEN Score END) AS 语文,
MAX(CASE WHEN Subject='数学' THEN Score END) AS 数学,
MAX(CASE WHEN Subject='英语' THEN Score END) AS 英语
FROM Score
GROUP BY Name;
四、动态PIVOT(透视值不固定,重点!)
静态PIVOT的 IN([语文],[数学]) 是写死的;如果科目/月份不确定,需要拼接SQL字符串 + sp_executesql
sql
DECLARE @cols NVARCHAR(MAX),@sql NVARCHAR(MAX);
-- 自动拼接所有透视列,去重
SELECT @cols = STUFF((SELECT DISTINCT ',' + QUOTENAME(Subject)
FROM Score FOR XML PATH(''),TYPE).value('.','NVARCHAR(MAX)'),1,1,'');
SET @sql = N'
SELECT Name, ' + @cols + '
FROM (SELECT Name,Subject,Score FROM Score) t
PIVOT (MAX(Score) FOR Subject IN (' + @cols + ')) pvt';
EXEC sp_executesql @sql;
QUOTENAME():自动加方括号,防止字段名是数字、特殊字符报错STUFF + FOR XML PATH:SQL Server经典拼接列字符串方案
五、UNPIVOT 逆透视(列转行)
把宽表转回长表
css
-- 先构造宽表cte
WITH cte AS (
SELECT Name,[语文],[数学],[英语] FROM (SELECT Name, Subject, Score FROM Score) t
PIVOT (MAX(Score) FOR Subject IN ([语文],[数学],[英语])) pvt
)
SELECT Name, Subject, Score
FROM cte
UNPIVOT (
Score FOR Subject IN ([语文],[数学],[英语])
) AS unpvt;
UNPIVOT 不会还原PIVOT之前完全一模一样的数据:聚合丢失的信息无法恢复,NULL值会直接过滤掉
六、高频坑与注意事项
- 必须聚合:PIVOT语法强制要求聚合函数;即使一行一条数据,也要写MAX/MIN
- NULL处理 :没有对应数据时,透视单元格默认NULL;如果要显示0,外面套
ISNULL(列,0)
vbnet
SELECT Name, ISNULL([语文],0) AS 语文 FROM ...
- 内层子查询字段控制:内层只保留3个核心字段,多余字段会参与分组,结果炸裂
- 透视值是数字 :一定要包
[],比如[2025],否则语法报错 - 排序:PIVOT里IN的顺序 = 输出列顺序;动态PIVOT需要额外控制排序
- 性能:大表慎用动态PIVOT;PIVOT多次嵌套性能差
- 兼容级别:数据库兼容级别≥90(2005)才支持PIVOT
七、常用聚合选择参考
- 每个分组+透视值只有一条:MAX / MIN
- 求和、汇总:SUM(销售金额)
- 计数:COUNT(字段)
- 平均值:AVG
八、PIVOT vs CASE WHEN 怎么选
- PIVOT:透视列少、写起来短、可读性好;静态固定列简单,动态需要拼接字符串
- CASE WHEN:灵活,支持多条件、复杂计算;适合复杂业务,不需要动态拼接也能控制NULL默认值