行轉列,列轉行是咱們在開發過程當中常常碰到的問題。行轉列通常經過CASE WHEN 語句來實現,也能夠經過 SQL SERVER 2005 新增的運算符PIVOT來實現。用傳統的方法,比較好理解。層次清晰,並且比較習慣。 可是PIVOT 、UNPIVOT提供的語法比一系列複雜的SELECT...CASE 語句中所指定的語法更簡單、更具可讀性。下面咱們經過幾個簡單的例子來介紹一下列轉行、行轉列問題。數據庫
咱們首先先經過一個老生常談的例子,學生成績表(下面簡化了些)來形象瞭解下行轉列 3d
CREATE TABLE [StudentScores]
(
[UserName] NVARCHAR(20), --學生姓名
[Subject] NVARCHAR(30), --科目
[Score] FLOAT, --成績
)
INSERT INTO [StudentScores] SELECT 'Nick', '語文', 80
INSERT INTO [StudentScores] SELECT 'Nick', '數學', 90
INSERT INTO [StudentScores] SELECT 'Nick', '英語', 70
INSERT INTO [StudentScores] SELECT 'Nick', '生物', 85
INSERT INTO [StudentScores] SELECT 'Kent', '語文', 80
INSERT INTO [StudentScores] SELECT 'Kent', '數學', 90
INSERT INTO [StudentScores] SELECT 'Kent', '英語', 70
INSERT INTO [StudentScores] SELECT 'Kent', '生物', 85
若是我想知道每位學生的每科成績,並且每一個學生的所有成績排成一行,這樣方便我查看、統計,導出數據blog
SELECT
UserName,
MAX(CASE Subject WHEN '語文' THEN Score ELSE 0 END) AS '語文',
MAX(CASE Subject WHEN '數學' THEN Score ELSE 0 END) AS '數學',
MAX(CASE Subject WHEN '英語' THEN Score ELSE 0 END) AS '英語',
MAX(CASE Subject WHEN '生物' THEN Score ELSE 0 END) AS '生物'
FROM dbo.[StudentScores]
GROUP BY UserName
查詢結果如圖所示,這樣咱們就能很清楚的瞭解每位學生全部的成績了
接下來咱們來看看第二個小列子。有一個遊戲玩家充值表(僅僅爲了說明,舉的一個小例子),遊戲
CREATE TABLE [Inpours]
(
[ID] INT IDENTITY(1,1),
[UserName] NVARCHAR(20), --遊戲玩家
[CreateTime] DATETIME, --充值時間
[PayType] NVARCHAR(20), --充值類型
[Money] DECIMAL, --充值金額
[IsSuccess] BIT, --是否成功 1表示成功, 0表示失敗
CONSTRAINT [PK_Inpours_ID] PRIMARY KEY(ID)
)
INSERT INTO Inpours SELECT '張三', '2010-05-01', '支付寶', 50, 1
INSERT INTO Inpours SELECT '張三', '2010-06-14', '支付寶', 50, 1
INSERT INTO Inpours SELECT '張三', '2010-06-14', '手機短信', 100, 1
INSERT INTO Inpours SELECT '李四', '2010-06-14', '手機短信', 100, 1
INSERT INTO Inpours SELECT '李四', '2010-07-14', '支付寶', 100, 1
INSERT INTO Inpours SELECT '王五', '2010-07-14', '工商銀行卡', 100, 1
INSERT INTO Inpours SELECT '趙六', '2010-07-14', '建設銀行卡', 100, 1
下面來了一個統計數據的需求,要求按日期、支付方式來統計充值金額信息。這也是一個典型的行轉列的例子。咱們能夠經過下面的腳原本達到目的支付寶
代碼開發
SELECT CONVERT(VARCHAR(10), CreateTime, 120) AS CreateTime,
CASE PayType WHEN '支付寶' THEN SUM(Money) ELSE 0 END AS '支付寶',
CASE PayType WHEN '手機短信' THEN SUM(Money) ELSE 0 END AS '手機短信',
CASE PayType WHEN '工商銀行卡' THEN SUM(Money) ELSE 0 END AS '工商銀行卡',
CASE PayType WHEN '建設銀行卡' THEN SUM(Money) ELSE 0 END AS '建設銀行卡'
FROM Inpours
GROUP BY CreateTime, PayType數學
如圖所示,咱們這樣只是獲得了這樣的輸出結果,還需進一步處理,才能獲得想要的結果語法
SELECT
CreateTime,
ISNULL(SUM([支付寶]) , 0) AS [支付寶] ,
ISNULL(SUM([手機短信]) , 0) AS [手機短信] ,
ISNULL(SUM([工商銀行卡]), 0) AS [工商銀行卡] ,
ISNULL(SUM([建設銀行卡]), 0) AS [建設銀行卡]
FROM
(
SELECT CONVERT(VARCHAR(10), CreateTime, 120) AS CreateTime,
CASE PayType WHEN '支付寶' THEN SUM(Money) ELSE 0 END AS '支付寶' ,
CASE PayType WHEN '手機短信' THEN SUM(Money) ELSE 0 END AS '手機短信',
CASE PayType WHEN '工商銀行卡' THEN SUM(Money) ELSE 0 END AS '工商銀行卡',
CASE PayType WHEN '建設銀行卡' THEN SUM(Money) ELSE 0 END AS '建設銀行卡'
FROM Inpours
GROUP BY CreateTime, PayType
) T
GROUP BY CreateTime
其實行轉列,關鍵是要理清邏輯,並且對分組(Group by)概念比較清晰。上面兩個列子基本上就是行轉列的類型了。可是有個問題來了,上面是我爲了說明弄的一個簡單列子。實際中,可能支付方式特別多,並且邏輯也複雜不少,可能涉及匯率、手續費等等(曾經作個這樣一個),若是支付方式特別多,咱們的CASE WHEN 會弄出一大堆,確實比較惱火,並且新增一種支付方式,咱們還得修改腳本若是把上面的腳本用動態SQL改寫一下,咱們就能輕鬆解決這個問題方法
代碼im
下面是經過PIVOT來進行行轉列的用法,你們能夠對比一下,確實要簡單、更具可讀性(呵呵,習慣的前提下)
代碼
SELECT
CreateTime, [支付寶] , [手機短信],
[工商銀行卡] , [建設銀行卡]
FROM
(
SELECT CONVERT(VARCHAR(10), CreateTime, 120) AS CreateTime,PayType, Money
FROM Inpours
) P
PIVOT (
SUM(Money)
FOR PayType IN
([支付寶], [手機短信], [工商銀行卡], [建設銀行卡])
) AS T
ORDER BY CreateTime
有時可能會出現這樣的錯誤:
消息 325,級別 15,狀態 1,第 9 行
'PIVOT' 附近有語法錯誤。您可能須要將當前數據庫的兼容級別設置爲更高的值,以啓用此功能。有關存儲過程 sp_dbcmptlevel 的信息,請參見幫助。
這個是由於:對升級到 SQL Server 2005 或更高版本的數據庫使用 PIVOT 和 UNPIVOT 時,必須將數據庫的兼容級別設置爲 90 或更高。有關如何設置數據庫兼容級別的信息,請參閱 sp_dbcmptlevel (Transact-SQL)。 例如,只需在執行上面腳本前加上 EXEC sp_dbcmptlevel Test, 90; 就OK了, Test 是所在數據庫的名稱。
下面咱們來看看列轉行,主要是經過UNION ALL ,MAX來實現。假若有下面這麼一個表
代碼
CREATE TABLE ProgrectDetail
(
ProgrectName NVARCHAR(20), --工程名稱
OverseaSupply INT, --海外供應商供給數量
NativeSupply INT, --國內供應商供給數量
SouthSupply INT, --南方供應商供給數量
NorthSupply INT --北方供應商供給數量
)
INSERT INTO ProgrectDetail
SELECT 'A', 100, 200, 50, 50
UNION ALL
SELECT 'B', 200, 300, 150, 150
UNION ALL
SELECT 'C', 159, 400, 20, 320
UNION ALL
SELECT 'D', 250, 30, 15, 15
咱們能夠經過下面的腳原本實現,查詢結果以下圖所示
代碼
SELECT ProgrectName, 'OverseaSupply' AS Supplier,
MAX(OverseaSupply) AS 'SupplyNum'
FROM ProgrectDetail
GROUP BY ProgrectName
UNION ALL
SELECT ProgrectName, 'NativeSupply' AS Supplier,
MAX(NativeSupply) AS 'SupplyNum'
FROM ProgrectDetail
GROUP BY ProgrectName
UNION ALL
SELECT ProgrectName, 'SouthSupply' AS Supplier,
MAX(SouthSupply) AS 'SupplyNum'
FROM ProgrectDetail
GROUP BY ProgrectName
UNION ALL
SELECT ProgrectName, 'NorthSupply' AS Supplier,
MAX(NorthSupply) AS 'SupplyNum'
FROM ProgrectDetail
GROUP BY ProgrectName
用UNPIVOT 實現以下:
代碼
SELECT ProgrectName,Supplier,SupplyNum
FROM
(
SELECT ProgrectName, OverseaSupply, NativeSupply,
SouthSupply, NorthSupply
FROM ProgrectDetail
)T
UNPIVOT
(
SupplyNum FOR Supplier IN
(OverseaSupply, NativeSupply, SouthSupply, NorthSupply )
) P