1. 程式人生 > 其它 >SQL優化之——行轉列,列轉行

SQL優化之——行轉列,列轉行

行轉列,列轉行是我們在開發過程中經常碰到的問題。行轉列一般通過CASE WHEN 語句來實現,也可以通過 SQL SERVER 2005 新增的運算子PIVOT來實現。用傳統的方法,比較好理解。層次清晰,而且比較習慣。 但是PIVOT 、UNPIVOT提供的語法比一系列複雜的SELECT...CASE 語句中所指定的語法更簡單、更具可讀性。下面我們通過幾個簡單的例子來介紹一下列轉行、行轉列問題。

我們首先先通過一個老生常談的例子,學生成績表(下面簡化了些)來形象瞭解下行轉列

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,SupplyNum
FROM
(
SELECTProgrectName, OverseaSupply, NativeSupply,
SouthSupply, NorthSupply
FROMProgrectDetail
)T
UNPIVOT
(
SupplyNumFORSupplierIN
(OverseaSupply, NativeSupply, SouthSupply, NorthSupply )
) P