行轉列 (將列值變為欄位名) 是報表常見需求。傳統寫法用 `CASE ... END` 搭配聚合函數;SQL Server 2005+ 提供原生 `PIVOT`/`UNPIVOT` 語法。本文以學生成績表為例,對比兩種寫法並示範雙向轉換。
DECLARE @t TABLE (StudentName NVARCHAR(20), Subject NVARCHAR(20), Score INT);
INSERT @t VALUES
('學生A','中文',80),('學生A','數學',78),('學生A','英語',92),
('學生B','中文',89),('學生B','數學',87),('學生B','英語',75),
('學生C','中文',92),('學生C','數學',74),('學生C','英語',65),
('學生D','中文',79),('學生D','數學',83),('學生D','英語',81),
('學生E','中文',73),('學生E','數學',84),('學生E','英語',93),
('學生F','中文',79),('學生F','數學',86),('學生F','英語',84);
SELECT
StudentName,
SUM(CASE WHEN Subject = N'中文' THEN Score ELSE 0 END) AS Chinese,
SUM(CASE WHEN Subject = N'數學' THEN Score ELSE 0 END) AS Math,
SUM(CASE WHEN Subject = N'英語' THEN Score ELSE 0 END) AS English
FROM @t
GROUP BY StudentName;
SELECT <非轉換欄位>, [轉換後欄位清單]
FROM (SELECT <來源欄位> FROM 來源表) AS src
PIVOT (
聚合函數(值欄位)
FOR 轉軸欄位 IN ([欄位1], [欄位2], ...)
) AS pvt;
SELECT
StudentName,
[中文] AS Chinese,
[數學] AS Math,
[英語] AS English
FROM (SELECT * FROM @t) AS src
PIVOT (
SUM(Score) FOR Subject IN ([中文], [英語], [數學])
) AS pvt;
關鍵點: `IN` 清單順序決定輸出欄位順序;欄位名含特殊字元需用 `[]` 括起。
SELECT StudentName, Subject, Score
FROM (SELECT * FROM @t) AS src
PIVOT (SUM(Score) FOR Subject IN ([中文],[英語],[數學])) AS pvt
UNPIVOT (
Score FOR Subject IN ([中文],[英語],[數學])
) AS unpvt;
輸出還原為原始長表格式 (StudentName, Subject, Score)。
DECLARE @cols NVARCHAR(MAX), @sql NVARCHAR(MAX);
SELECT @cols = STRING_AGG(QUOTENAME(Subject), ',') -- SQL 2017+
FROM (SELECT DISTINCT Subject FROM @t) t;
SET @sql = N'
SELECT StudentName, ' + @cols + '
FROM (SELECT StudentName, Subject, Score FROM @t) src
PIVOT (SUM(Score) FOR Subject IN (' + @cols + ')) pvt;';
EXEC sp_executesql @sql;
SQL Server 2016 以前用 `STUFF` + `FOR XML PATH` 組裝 `@cols`。
| 場景 | 推薦寫法 |
|---|---|
| 固定、少量類別 (如季度、固定科目) | 原生 PIVOT 最簡潔 |
| 類別動態/未知、需複雜邏輯 (如條件聚合、多欄位轉換) | CASE 聚合 彈性最高 |
| 跨資料庫平台相容 (MySQL/PostgreSQL/Oracle) | CASE 聚合 (標準 SQL) |
| 效能敏感、大資料量 | 實測比較,通常 CASE 聚合較穩定 |
掌握 `PIVOT`/`UNPIVOT` 原生語法與 `CASE` 聚合雙技能,即可應對靜態/動態、簡單/複雜各類行列轉換需求。