SQL Server 行轉列重溫

行轉列,列轉行是咱們在開發過程當中常常碰到的問題。行轉列通常經過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

若是我想知道每位學生的每科成績,並且每一個學生的所有成績排成一行,這樣方便我查看、統計,導出數據app

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
 
查詢結果如圖所示,這樣咱們就能很清楚的瞭解每位學生全部的成績了

 

 

接下來咱們來看看第二個小列子。有一個遊戲玩家充值表(僅僅爲了說明,舉的一個小例子),post

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

 

如圖所示,咱們這樣只是獲得了這樣的輸出結果,還需進一步處理,才能獲得想要的結果spa

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改寫一下,咱們就能輕鬆解決這個問題3d

代碼

 

下面是經過PIVOT來進行行轉列的用法,你們能夠對比一下,確實要簡單、更具可讀性(呵呵,習慣的前提下)code

代碼
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

 

有時可能會出現這樣的錯誤:blog

消息 325,級別 15,狀態 1,第 9 行遊戲

'PIVOT' 附近有語法錯誤。您可能須要將當前數據庫的兼容級別設置爲更高的值,以啓用此功能。有關存儲過程 sp_dbcmptlevel 的信息,請參見幫助。ip

這個是由於:對升級到 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
相關文章
相關標籤/搜索