SQL優化之——行轉列,列轉行
阿新 • • 發佈:2021-12-13
行轉列,列轉行是我們在開發過程中經常碰到的問題。行轉列一般通過CASE WHEN 語句來實現,也可以通過 SQL SERVER 2005 新增的運算子PIVOT來實現。用傳統的方法,比較好理解。層次清晰,而且比較習慣。 但是PIVOT 、UNPIVOT提供的語法比一系列複雜的SELECT...CASE 語句中所指定的語法更簡單、更具可讀性。下面我們通過幾個簡單的例子來介紹一下列轉行、行轉列問題。
CASEPayTypeWHEN'支付寶'THENSUM(Money)ELSE0ENDAS'支付寶',
CASEPayTypeWHEN'手機簡訊'THENSUM(Money)ELSE0ENDAS'手機簡訊',
CASEPayTypeWHEN'工商銀行卡'THENSUM(Money)ELSE0ENDAS'工商銀行卡',
CASEPayTypeWHEN'建設銀行卡'THENSUM(Money)ELSE0ENDAS'建設銀行卡'
FROMInpours
GROUPBYCreateTime, PayType
(
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
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
FROM
(
SELECTProgrectName, OverseaSupply, NativeSupply,
SouthSupply, NorthSupply
FROMProgrectDetail
)T
UNPIVOT
(
SupplyNumFORSupplierIN
(OverseaSupply, NativeSupply, SouthSupply, NorthSupply )
) P
我們首先先通過一個老生常談的例子,學生成績表(下面簡化了些)來形象瞭解下行轉列
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
如果我想知道每位學生的每科成績,而且每個學生的全部成績排成一行,這樣方便我檢視、統計,匯出資料
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下面來了一個統計資料的需求,要求按日期、支付方式來統計充值金額資訊。這也是一個典型的行轉列的例子。我們可以通過下面的指令碼來達到目的 程式碼 SELECTCONVERT(VARCHAR(10), CreateTime,120)ASCreateTime,
CASEPayTypeWHEN'支付寶'THENSUM(Money)ELSE0ENDAS'支付寶',
CASEPayTypeWHEN'手機簡訊'THENSUM(Money)ELSE0ENDAS'手機簡訊',
CASEPayTypeWHEN'工商銀行卡'THENSUM(Money)ELSE0ENDAS'工商銀行卡',
CASEPayTypeWHEN'建設銀行卡'THENSUM(Money)ELSE0ENDAS'建設銀行卡'
FROMInpours
GROUPBYCreateTime, PayType
如圖所示,我們這樣只是得到了這樣的輸出結果,還需進一步處理,才能得到想要的結果
在做報表的時候經常會遇到統計 1.2.3.4月的從一月到當前月的總金額等這類的需求,常規的做法就是
select sum(t1.當月金額) as 到目前為止總金額,date as 日期 from TableName t1 inner join TableName as t2 on t1.id=t2.id and t1.time-'12 Month'>=t2.time
這種寫法 效能會比較差,藉助分組來做統計 效能上就會提升很多倍
=============================================================================================================
下面我們來看看列轉行,主要是通過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,SupplyNumFROM
(
SELECTProgrectName, OverseaSupply, NativeSupply,
SouthSupply, NorthSupply
FROMProgrectDetail
)T
UNPIVOT
(
SupplyNumFORSupplierIN
(OverseaSupply, NativeSupply, SouthSupply, NorthSupply )
) P