1. 程式人生 > >SQL資料庫查詢實現行轉列與列轉行結果SQL語句

SQL資料庫查詢實現行轉列與列轉行結果SQL語句

 CREATETABLE[StudentScores]
(
   
[UserName]NVARCHAR(20),        --學生姓名[Subject]NVARCHAR(30),        --科目[Score]FLOAT,               --成績)

INSERTINTO[StudentScores]SELECT'Nick', '語文', 80INSERTINTO[StudentScores]SELECT'Nick', '數學', 90INSERTINTO[StudentScores]SELECT'Nick', '英語', 70INSERTINTO[StudentScores]SELECT
'Nick', '生物', 85INSERTINTO[StudentScores]SELECT'Kent', '語文', 80INSERTINTO[StudentScores]SELECT'Kent', '數學', 90INSERTINTO[StudentScores]SELECT'Kent', '英語', 70INSERTINTO[StudentScores]SELECT'Kent', '生物', 85


如果我想知道每位學生的每科成績,而且每個學生的全部成績排成一行,這樣方便我檢視、統計,匯出資料


SELECT
      UserName,
     
MAX(CASE Subject WHEN'
語文'THEN Score ELSE0END) AS'語文',
     
MAX(CASE Subject WHEN'數學'THEN Score ELSE0END) AS'數學',
     
MAX(CASE Subject WHEN'英語'THEN Score ELSE0END) AS'英語',
     
MAX(CASE Subject WHEN'生物'THEN Score ELSE0END) AS'生物'FROM dbo.[StudentScores]GROUPBY UserName




查詢結果如圖所示,這樣我們就能很清楚的瞭解每位學生所有的成績了


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


Code [http://www.oeedu.com]

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)
)

INSERTINTO Inpours SELECT'張三', '2010-05-01', '支付', 50, 1INSERTINTO Inpours SELECT'張三', '2010-06-14', '支付寶', 50, 1INSERTINTO Inpours SELECT'張三', '2010-06-14', '手機簡訊', 100, 1INSERTINTO Inpours SELECT'李四', '2010-06-14', '手機簡訊', 100, 1INSERTINTO Inpours SELECT'李四', '2010-07-14', '支付寶', 100, 1INSERTINTO Inpours SELECT'王五', '2010-07-14', '工商銀行卡', 100, 1INSERTINTO Inpours SELECT'趙六', '2010-07-14', '建設銀行卡', 100, 1

下面來了一個統計資料的需求,要求按日期、支付方式來統計充值金額資訊。這也是一個典型的行轉列的例子。我們可以通過下面的指令碼來達到目的

Code [http://www.oeedu.com]

SELECTCONVERT(VARCHAR(10), CreateTime, 120) AS CreateTime,
CASE PayType WHEN'支付寶'THENSUM(Money) ELSE0ENDAS'支付寶',
CASE PayType WHEN'手機簡訊'THENSUM(Money) ELSE0ENDAS'手機簡訊',
CASE PayType WHEN'工商銀行卡'THENSUM(Money) ELSE0ENDAS'工商銀行卡',
CASE PayType WHEN'建設銀行卡'THENSUM(Money) ELSE0ENDAS'建設銀行卡'FROM Inpours
GROUPBY CreateTime, PayType



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



SELECT
       CreateTime,
      
ISNULL(SUM([支付寶]), 0) AS[支付寶],
      
ISNULL(SUM([手機簡訊]), 0) AS[手機簡訊],
      
ISNULL(SUM([工商銀行卡]), 0) AS[工商銀行卡],
      
ISNULL(SUM([建設銀行卡]), 0) AS[建設銀行卡]FROM
(
   
SELECTCONVERT(VARCHAR(10), CreateTime, 120) AS CreateTime,
          
CASE PayType WHEN'支付寶'THENSUM(Money) ELSE0ENDAS'支付寶',
          
CASE PayType WHEN'手機簡訊'THENSUM(Money) ELSE0ENDAS'手機簡訊',
          
CASE PayType WHEN'工商銀行卡'THENSUM(Money) ELSE0ENDAS'工商銀行卡',
          
CASE PayType WHEN'建設銀行卡'THENSUM(Money) ELSE0ENDAS'建設銀行卡'FROM Inpours
   
GROUPBY CreateTime, PayType
) T
GROUPBY CreateTime



其實行轉列,關鍵是要理清邏輯,而且對分組(Group by)概念比較清晰。上面兩個列子基本上就是行轉列的型別了。但是有個問題來了,上面是我為了說明弄的一個簡單列子。實際中,可能支付方式特別多,而且邏輯也複雜很多,可能涉及匯率、手續費等等(曾經做個這樣一個),如果支付方式特別多,我們的CASE WHEN 會弄出一大堆,確實比較惱火,而且新增一種支付方式,我們還得修改指令碼如果把上面的指令碼用動態SQL改寫一下,我們就能輕鬆解決這個問題


Code [http://www.oeedu.com]

DECLARE@cmdTextVARCHAR(8000);
DECLARE@tmpSqlVARCHAR(8000);

SET@cmdText='SELECT CONVERT(VARCHAR(10), CreateTime, 120) AS CreateTime,'CHAR(10);
SELECT@cmdText=@cmdText' CASE PayType WHEN ''' PayType ''' THEN SUM(Money) ELSE 0 END AS ''' PayType
''','CHAR(10) FROM (SELECTDISTINCT PayType FROM Inpours ) T

SET@cmdText=LEFT(@cmdText, LEN(@cmdText) -2) --注意這裡,如果沒有加CHAR(10) 則用LEFT(@cmdText, LEN(@cmdText) -1)SET@cmdText=@cmdText' FROM Inpours GROUP BY CreateTime, PayType ';

SET@tmpSql='SELECT CreateTime,'CHAR(10);
SELECT@tmpSql=@tmpSql' ISNULL(SUM(' PayType '), 0) AS ''' PayType ''','CHAR(10)
FROM (SELECTDISTINCT PayType FROM Inpours ) T

SET@tmpSql=LEFT(@tmpSql, LEN(@tmpSql) -2) ' FROM ('CHAR(10);

SET@cmdText=@tmpSql@cmdText') T GROUP BY CreateTime ';
PRINT@cmdTextEXECUTE (@cmdText);



下面是通過PIVOT來進行行轉列的用法,大家可以對比一下,確實要簡單、更具可讀性(呵呵,習慣的前提下)



Code [http://www.oeedu.com]

SELECT
CreateTime,
[支付寶] , [手機簡訊],
[工商銀行卡] , [建設銀行卡]FROM
(
SELECTCONVERT(VARCHAR(10), CreateTime, 120) AS CreateTime,PayType, MoneyFROM Inpours
) P
PIVOT (
SUM(Money)
FOR PayType IN
(
[支付寶], [手機簡訊], [工商銀行卡], [建設銀行卡])
)
AS T
ORDERBY 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來實現。假如有下面這麼一個表

程式碼

CREATETABLE ProgrectDetail
(
ProgrectName
NVARCHAR(20), --工程名稱OverseaSupply INT, --海外供應商供給數量NativeSupply INT, --國內供應商供給數量SouthSupply INT, --南方供應商供給數量NorthSupply INT--北方供應商供給數量)

INSERTINTO ProgrectDetail
SELECT'A', 100, 200, 50, 50UNIONALLSELECT'B', 200, 300, 150, 150UNIONALLSELECT'C', 159, 400, 20, 320UNIONALLSELECT'D', 250, 30, 15, 15

我們可以通過下面的指令碼來實現,查詢結果如下圖所示

程式碼

SELECT ProgrectName, 'OverseaSupply'AS Supplier,
MAX(OverseaSupply) AS'SupplyNum'FROM ProgrectDetail
GROUPBY ProgrectName
UNIONALLSELECT ProgrectName, 'NativeSupply'AS Supplier,
MAX(NativeSupply) AS'SupplyNum'FROM ProgrectDetail
GROUPBY ProgrectName
UNIONALLSELECT ProgrectName, 'SouthSupply'AS Supplier,
MAX(SouthSupply) AS'SupplyNum'FROM ProgrectDetail
GROUPBY ProgrectName
UNIONALLSELECT ProgrectName, 'NorthSupply'AS Supplier,
MAX(NorthSupply) AS'SupplyNum'FROM ProgrectDetail
GROUPBY 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