行轉列,列轉行是我們在開發過程中經常碰到的問題。行轉列一般通過CASE WHEN 語句來實現,也可以通過 SQL SERVER 的運算符PIVOT來實現。用傳統的方法,比較好理解。層次清晰,而且比較習慣。 但是PIVOT 、UNPIVOT提供的語法比一系列復雜的SELECT…CASE 語句中所指定的語法更簡單、更具可讀性。下面我們通過幾個簡單的例子來介紹一下列轉行、行轉列問題。
我們首先先通過一個老生常談的例子,學生成績表(下面簡化了些)來形象了解下行轉列
- CREATETABLE[StudentScores]
- (
- [UserName]NVARCHAR(20),--學生姓名
- [Subject]NVARCHAR(30),--科目
- [Score]FLOAT,--成績
- )
- INSERTINTO[StudentScores]SELECT'Nick','語文',80
- INSERTINTO[StudentScores]SELECT'Nick','數學',90
- INSERTINTO[StudentScores]SELECT'Nick','英語',70
- INSERTINTO[StudentScores]SELECT'Nick','生物',85
- INSERTINTO[StudentScores]SELECT'Kent','語文',80
- INSERTINTO[StudentScores]SELECT'Kent','數學',90
- INSERTINTO[StudentScores]SELECT'Kent','英語',70
- INSERTINTO[StudentScores]SELECT'Kent','生物',85
如果我想知道每位學生的每科成績,而且每個學生的全部成績排成一行,這樣方便我查看、統計,導出數據
- SELECT
- UserName,
- MAX(CASESubjectWHEN'語文'THENScoreELSE0END)AS'語文',
- MAX(CASESubjectWHEN'數學'THENScoreELSE0END)AS'數學',
- MAX(CASESubjectWHEN'英語'THENScoreELSE0END)AS'英語',
- MAX(CASESubjectWHEN'生物'THENScoreELSE0END)AS'生物'
- FROMdbo.[StudentScores]
- GROUPBYUserName
查詢結果如圖所示,這樣我們就能很清楚的了解每位學生所有的成績了
接下來我們來看看第二個小列子。有一個游戲玩家充值表(僅僅為了說明,舉的一個小例子),
- CREATETABLE[Inpours]
- (
- [ID]INTIDENTITY(1,1),
- [UserName]NVARCHAR(20),--游戲玩家
- [CreateTime]DATETIME,--充值時間
- [PayType]NVARCHAR(20),--充值類型
- [Money]DECIMAL,--充值金額
- [IsSuccess]BIT,--是否成功1表示成功,0表示失敗
- CONSTRAINT[PK_Inpours_ID]PRIMARYKEY(ID)
- )
- INSERTINTOInpoursSELECT'張三','2010-05-01','支付寶',50,1
- INSERTINTOInpoursSELECT'張三','2010-06-14','支付寶',50,1
- INSERTINTOInpoursSELECT'張三','2010-06-14','手機短信',100,1
- INSERTINTOInpoursSELECT'李四','2010-06-14','手機短信',100,1
- INSERTINTOInpoursSELECT'李四','2010-07-14','支付寶',100,1
- INSERTINTOInpoursSELECT'王五','2010-07-14','工商銀行卡',100,1
- INSERTINTOInpoursSELECT'趙六','2010-07-14','建設銀行卡',100,1
下面來了一個統計數據的需求,要求按日期、支付方式來統計充值金額信息。這也是一個典型的行轉列的例子。我們可以通過下面的腳本來達到目的
- SELECT
- CONVERT(VARCHAR(10),CreateTime,120)ASCreateTime,
- CASEPayTypeWHEN'支付寶'THENSUM(Money)ELSE0ENDAS'支付寶',
- CASEPayTypeWHEN'手機短信'THENSUM(Money)ELSE0ENDAS'手機短信',
- CASEPayTypeWHEN'工商銀行卡'THENSUM(Money)ELSE0ENDAS'工商銀行卡',
- CASEPayTypeWHEN'建設銀行卡'THENSUM(Money)ELSE0ENDAS'建設銀行卡'
- FROMInpours
- GROUPBYCreateTime,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)ASCreateTime,
- CASEPayTypeWHEN'支付寶'THENSUM(Money)ELSE0ENDAS'支付寶',
- CASEPayTypeWHEN'手機短信'THENSUM(Money)ELSE0ENDAS'手機短信',
- CASEPayTypeWHEN'工商銀行卡'THENSUM(Money)ELSE0ENDAS'工商銀行卡',
- CASEPayTypeWHEN'建設銀行卡'THENSUM(Money)ELSE0ENDAS'建設銀行卡'
- FROMInpours
- GROUPBYCreateTime,PayType
- )T
- GROUPBYCreateTime
其實行轉列,關鍵是要理清邏輯,而且對分組(Group by)概念比較清晰。上面兩個列子基本上就是行轉列的類型了。但是有個問題來了,上面是我為了說明弄的一個簡單列子。實際中,可能支付方式特別多,而且邏輯也復雜很多,可能涉及匯率、手續費等等(曾經做個這樣一個),如果支付方式特別多,我們的CASE WHEN 會弄出一大堆,確實比較惱火,而且新增一種支付方式,我們還得修改腳本如果把上面的腳本用動態SQL改寫一下,我們就能輕松解決這個問題
- DECLARE@cmdTextVARCHAR(8000);
- DECLARE@tmpSqlVARCHAR(8000);
- SET@cmdText='SELECTCONVERT(VARCHAR(10),CreateTime,120)ASCreateTime,'+CHAR(10);
- SELECT@cmdText=@cmdText+'CASEPayTypeWHEN'''+
- PayType+'''THENSUM(Money)ELSE0ENDAS'''+PayType
- +''','+CHAR(10)FROM(SELECTDISTINCTPayTypeFROMInpours)T
- SET@cmdText=LEFT(@cmdText,LEN(@cmdText)-2)--注意這里,如果沒有加CHAR(10)則用LEFT(@cmdText,LEN(@cmdText)-1)
- SET@cmdText=@cmdText+'FROMInpours
- GROUPBYCreateTime,PayType';
- SET@tmpSql='SELECTCreateTime,'+CHAR(10);
- SELECT@tmpSql=@tmpSql+'ISNULL(SUM('+PayType+'),0)AS'''+
- PayType+''','+CHAR(10)
- FROM(SELECTDISTINCTPayTypeFROMInpours)T
- SET@tmpSql=LEFT(@tmpSql,LEN(@tmpSql)-2)+'FROM('+CHAR(10);
- SET@cmdText=@tmpSql+@cmdText+')TGROUPBYCreateTime';
- PRINT@cmdText
- EXECUTE(@cmdText);
下面是通過PIVOT來進行行轉列的用法,大家可以對比一下,確實要簡單、更具可讀性
- SELECTCreateTime,[支付寶],[手機短信],[工商銀行卡],[建設銀行卡]
- FROM
- (
- SELECTCONVERT(VARCHAR(10),CreateTime,120)ASCreateTime,PayType,Money
- FROMInpours
- )P
- PIVOT(
- SUM(Money)
- FORPayTypeIN
- ([支付寶],[手機短信],[工商銀行卡],[建設銀行卡])
- )AST
- ORDERBYCreateTime
有時可能會出現這樣的錯誤:
消息 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來實現。假如有下面這么一個表
- CreateTableProgrectDetail
- (
- ProgrectNameNVARCHAR(20),--工程名稱
- OverseaSupplyINT,--海外供應商供給數量
- NativeSupplyINT,--國內供應商供給數量
- SouthSupplyINT,--南方供應商供給數量
- NorthSupplyINT--北方供應商供給數量
- )
- INSERTINTOProgrectDetail
- SELECT'A',100,200,50,50
- UNIONALL
- SELECT'B',200,300,150,150
- UNIONALL
- SELECT'C',159,400,20,320
- UNIONALL
- SELECT'D',250,30,15,15
我們可以通過下面的腳本來實現,查詢結果如下圖所示
- SELECTProgrectName,'OverseaSupply'ASSupplier,
- MAX(OverseaSupply)AS'SupplyNum'
- FROMProgrectDetail
- GROUPBYProgrectName
- UNIONALL
- SELECTProgrectName,'NativeSupply'ASSupplier,
- MAX(NativeSupply)AS'SupplyNum'
- FROMProgrectDetail
- GROUPBYProgrectName
- UNIONALL
- SELECTProgrectName,'SouthSupply'ASSupplier,
- MAX(SouthSupply)AS'SupplyNum'
- FROMProgrectDetail
- GROUPBYProgrectName
- UNIONALL
- SELECTProgrectName,'NorthSupply'ASSupplier,
- MAX(NorthSupply)AS'SupplyNum'
- FROMProgrectDetail
- GROUPBYProgrectName
用UNPIVOT 實現如下:
- SELECTProgrectName,Supplier,SupplyNum
- FROM
- (
- SELECTProgrectName,OverseaSupply,NativeSupply,
- SouthSupply,NorthSupply
- FROMProgrectDetail
- )T
- UNPIVOT
- (
- SupplyNumFORSupplierIN
- (OverseaSupply,NativeSupply,SouthSupply,NorthSupply)
- )P
原文鏈接:https://mp.weixin.qq.com/s?__biz=MzA3MTg4NjY4Mw==&mid=2457323770&idx=2&sn=d3209bed52a39106bbe452a9a5b35b3c&chksm=88a5dacebfd253d8acaf8278b10e3b8331ed12b4fcb41eaab6a6d05cae82869ad56111e1a6b2&mpshare=1&