SQL SERVER 中的行列轉換小結


1. 介紹說明

前段時間組內的小伙伴在升級維護項目中,經常涉及一些復雜的數據轉換問題,讓我去看下有些地方怎么處理,我發現好多都是涉及到行列轉換的問題,處理起來經常會比較麻煩,借此也總結一下,方便以后的查閱使用。該總結參照了網上的一些資料,也做了一些變動,如有更好的方法也歡迎指出。

演示的腳本見 3.測試數據腳本

 

2. 例子演示

2.1 實現行轉列

(1) Case WHEN 實現行轉列 

/*-----1.1 Case WHEN 實現行轉列----------*/

--(1)靜態SQL
SELECT [姓名],
 max(CASE 課程 WHEN '語文' THEN 分數 ELSE 0 end) AS 語文,
 max(CASE 課程 WHEN '數學' THEN 分數 ELSE 0 end)AS 數學,
 max(CASE 課程 WHEN '物理' THEN 分數 ELSE 0 end)AS 物理,
 SUM(分數) AS 總分,
 AVG(分數) AS 平均分
FROM tbScore GROUP BY [姓名]

--(2)動態SQL
DECLARE @sql VARCHAR(500)
SET @sql = 'SELECT [姓名]'
SELECT  @sql = @sql + ',MAX(CASE [課程] WHEN ''' + [課程] + ''' THEN [分數] ELSE 0 END)[' + [課程] + ']'
FROM    ( 
            SELECT DISTINCT [課程] FROM tbScore
        ) T1
--同FROM tbScore  GROUP BY [課程],默認按課程名排序
SET @sql = @sql + ' FROM tbScore GROUP BY [姓名]'
PRINT '@sql: ' + @sql
EXEC(@sql)
View Code

 

 

 (2) PIVOT 實現行轉列,其中的NULL值發現還不好處理為0

--(1)靜態SQL
SELECT  [姓名] ,
        [語文] ,
        [數學] ,
        [物理]
FROM    ( SELECT    [分數] ,
                    [課程] ,
                    [姓名]
          FROM      tbScore
        ) AS SourceTable PIVOT ( AVG([分數]) FOR [課程] IN ( 語文, 數學, 物理 ) ) T


--(2)動態SQL
DECLARE @sql2 VARCHAR(8000)
SET @sql2 = ''
SELECT @sql2 = @sql2 + ',' + [課程] FROM dbo.tbScore GROUP BY [課程]
--STUFF: 刪除指定長度的字符,並在指定的起點處插入另一組字符。
SET @sql2= STUFF(@sql2,1,1,'')  --去掉首個','
SET @sql2 = 'SELECT [姓名],' + @sql2 + ' FROM (SELECT [分數],[課程],[姓名] FROM tbScore ) AS SourceTable PIVOT ( AVG([分數]) FOR [課程] IN ( ' + @sql2 + ') ) T'
PRINT @sql2
EXEC(@sql2)
View Code

 

2.1 實現轉行

 (1) UNION 實現列轉行

--(1)靜態SQL
SELECT * FROM (
    SELECT [姓名],'語文' AS 課程,[語文] AS 分數 ,[日期] FROM tbScoreNew
    UNION ALL
    SELECT [姓名],'數學' AS 課程,[數學] AS 分數 ,[日期] FROM tbScoreNew
    UNION ALL
    SELECT [姓名],'物理' AS 課程,[物理] AS 分數 ,[日期] FROM tbScoreNew
) T ORDER BY [姓名]

--(2)動態SQL
DECLARE @sql3 VARCHAR(8000)
SELECT @sql3 = ISNULL(@sql3 + ' UNION ALL ','') + ' SELECT [姓名],' + QUOTENAME(name,'''') + ' AS 課程,' + QUOTENAME(name) + ',[日期] FROM tbScoreNew'
FROM sys.columns 
WHERE object_id = OBJECT_ID('tbScoreNew') AND  name NOT IN ('姓名','日期')
SET @sql3 = 'SELECT * FROM ( ' + @sql3  + ' ) T ORDER BY [姓名]'
PRINT @sql3
EXEC (@sql3)
View Code

 

 (2) UNPIVOT 實現列轉行

--(1)靜態SQL
SELECT * FROM (
    SELECT [姓名],[日期],[語文],[數學],[物理] FROM dbo.tbScoreNew
) T UNPIVOT ([分數] FOR [課程] IN ([語文],[數學],[物理])) T2
ORDER BY [姓名]


--(2)動態SQL
DECLARE @sql4 VARCHAR(8000)
SELECT @sql4 = ISNULL(@sql4 + ',','') + QUOTENAME(name)
FROM sys.columns 
WHERE object_id = OBJECT_ID('tbScoreNew') AND  name NOT IN ('姓名','日期')
SET @sql4 = 'SELECT * FROM ( SELECT [姓名],[日期],' + @sql4 + ' FROM dbo.tbScoreNew ) T UNPIVOT ([分數] FOR [課程] IN ('+ @sql4 +')) T2 ORDER BY [姓名]'
PRINT @sql4
EXEC (@sql4)
View Code

 

2.3 動態增加列實現行轉列 

這個參照部門小伙伴的項目上的要求寫的一個例子, 由於涉及的轉換列同時有多個字段,用上面的行列轉換處理起來都很不方便,所以采用比較普通的動態增加列的方式處理

測試數據腳本為附件腳本中的 “3.動態增加列實現行轉列" 腳本

要求: 將【部門預算】、【實際預算】、【剩余預算】按照年份橫向統計顯示,且統計數據按部門、項目分組顯示

CREATE TABLE #tmpYear
(
    [YEAR] INT,
    ID INT IDENTITY
)

--保存最終結果
CREATE TABLE #tmpResult
(
    ID INT IDENTITY,
    DeptCode VARCHAR(20),--部門編碼
    DeptName NVARCHAR(100), --部門名稱
    ProCode VARCHAR(20),--項目編碼
    ProName NVARCHAR(100),--項目名稱
    KeyCode VARCHAR(50)
)
GO

--1.寫入分組數據
INSERT INTO #tmpResult( DeptCode ,DeptName , ProCode ,ProName,KeyCode)
SELECT DeptCode,MAX(DeptName), ProCode,MAX(ProName),DeptCode + '_' + ProCode FROM tbDeptBudget GROUP BY DeptCode,ProCode

--2.計算預算結果數據
--寫入年份數據
INSERT INTO #tmpYear SELECT DISTINCT Year FROM dbo.tbDeptBudget

DECLARE @SQL VARCHAR(5000)
DECLARE @ColName1 VARCHAR(50)
DECLARE @ColName2 VARCHAR(50)
DECLARE @ColName3 VARCHAR(50)
DECLARE @Year INT
DECLARE @ID INT
DECLARE @RowNum INT
SET @Year = 0
SET @ID = 1
SET @RowNum = (SELECT COUNT(0) FROM #tmpYear)
WHILE @ID <= @RowNum
BEGIN
    SET @Year = (SELECT [YEAR] FROM #tmpYear WHERE ID = @ID)    
    SET @ColName1 = 'Bduget_' + CAST(@Year AS VARCHAR(10))
    SET @ColName2 = 'Fact_' + CAST(@Year AS VARCHAR(10))
    SET @ColName3 = 'Remain_' + CAST(@Year AS VARCHAR(10))
    
    --增加動態列
    SET @SQL = 'ALTER TABLE #tmpResult ADD ' + @ColName1 + ' Decimal(18,2)'
              + 'ALTER TABLE #tmpResult ADD ' + @ColName2 + ' Decimal(18,2)'
              + 'ALTER TABLE #tmpResult ADD ' + @ColName3 + ' Decimal(18,2)'
    EXEC(@SQL)
    
    --寫入動態列數據
    SET @SQL = 'UPDATE T SET ' + @ColName1 + ' = S.BudgetAmount,' + @ColName2 + ' = S.FactAmount,'+ @ColName3 + ' = S.RemainAmount '
        + ' FROM #tmpResult T INNER JOIN ( '
        + ' SELECT (DeptCode + ' + QUOTENAME('_','''') +' + ProCode) AS KeyCode,MAX(BudgetAmount)AS BudgetAmount ,MAX(FactAmount)AS FactAmount,MAX(RemainAmount)AS RemainAmount '
        + ' FROM dbo.tbDeptBudget WHERE Year= ' + CAST (@Year AS VARCHAR(10))
        + ' GROUP BY DeptCode,ProCode '
        + ') S ON T.KeyCode = S.KeyCode '
    
    PRINT @SQL
    EXEC(@SQL)
        
    SET @ID = @ID  + 1
END

--3.返回結果
SELECT * FROM #tmpResult

--4.清理臨時表
IF OBJECT_ID('tempdb..#tmpYear') IS NOT NULL
BEGIN
    DROP TABLE #tmpYear
END
IF OBJECT_ID('tempdb..#tmpResult') IS NOT NULL
BEGIN
    DROP TABLE #tmpResult
END
View Code

 

 

3. 測試數據腳本

/*-----1.行轉列的測試數據--------------------------*/
IF OBJECT_ID('tbScore') IS NOT NULL 
    DROP TABLE tbScore

GO

CREATE TABLE tbScore
    (
      姓名 VARCHAR(10) ,
      課程 VARCHAR(10) ,
      分數 INT,
      日期 DATETIME
    )
GO

INSERT  INTO tbScore VALUES  ( '張三', '語文', 74,GETDATE() )
--INSERT  INTO tbScore VALUES  ( '張三', '數學', 83 ,GETDATE() )
INSERT  INTO tbScore VALUES  ( '張三', '物理', 93 ,GETDATE() )
INSERT  INTO tbScore VALUES  ( '李四', '語文', 74 ,GETDATE() )
INSERT  INTO tbScore VALUES  ( '李四', '數學', 84 ,GETDATE() )
INSERT  INTO tbScore VALUES  ( '李四', '物理', 94 ,GETDATE() )
GO

/*-----2.列轉行的測試數據--------------------------*/
IF OBJECT_ID('tbScoreNew') IS NOT NULL 
    DROP TABLE tbScoreNew

GO

CREATE TABLE tbScoreNew(
      姓名 VARCHAR(10) ,
      語文 INT,
      數學 INT,
      物理 INT,
      日期 DATETIME
    )
GO

INSERT  INTO tbScoreNew VALUES  ( '李四', 74,84,94,GETDATE() )
INSERT  INTO tbScoreNew VALUES  ( '張三', 74,83,93,GETDATE() )
GO


/*-----3.動態增加列實現行轉列(模擬組內項目要求)--------------------------*/
IF OBJECT_ID('tbDeptBudget') IS NOT NULL 
    DROP TABLE tbDeptBudget

GO
--部門預算
CREATE TABLE tbDeptBudget
(
    ID INT IDENTITY(1,1) PRIMARY KEY,
    DeptCode VARCHAR(20),--部門編碼
    DeptName NVARCHAR(100), --部門名稱
    ProCode VARCHAR(20),--項目編碼
    ProName NVARCHAR(100),--項目名稱
    Year INT, --年度
    BudgetAmount DECIMAL(18,2), --預算金額
    FactAmount DECIMAL(18,2), --實際金額
    RemainAmount DECIMAL(18,2), --剩余金額
    CreateTime DATETIME  --創建時間
)
GO

INSERT INTO tbDeptBudget(DeptName,DeptCode,ProCode,ProName,YEAR,BudgetAmount,FactAmount,RemainAmount,CreateTime)
VALUES('人事部','010000','01','差旅費',2014,100000.00,80000.00,20000.00,GETDATE());
INSERT INTO tbDeptBudget(DeptName,DeptCode,ProCode,ProName,YEAR,BudgetAmount,FactAmount,RemainAmount,CreateTime)
VALUES('人事部','010000','01','差旅費',2015,110000.00,90000.00,50000.00,GETDATE());
INSERT INTO tbDeptBudget(DeptName,DeptCode,ProCode,ProName,YEAR,BudgetAmount,FactAmount,RemainAmount,CreateTime)
VALUES('人事部','010000','01','差旅費',2016,120000.00,100000.00,80000.00,GETDATE());
INSERT INTO tbDeptBudget(DeptName,DeptCode,ProCode,ProName,YEAR,BudgetAmount,FactAmount,RemainAmount,CreateTime)
VALUES('人事部','010000','02','辦公用品',2015,200000.00,150000.00,10000.00,GETDATE());
INSERT INTO tbDeptBudget(DeptName,DeptCode,ProCode,ProName,YEAR,BudgetAmount,FactAmount,RemainAmount,CreateTime)
VALUES('人事部','010000','02','辦公用品',2016,160000.00,120000.00,80000.00,GETDATE());
INSERT INTO tbDeptBudget(DeptName,DeptCode,ProCode,ProName,YEAR,BudgetAmount,FactAmount,RemainAmount,CreateTime)
VALUES('財務部','020000','02','辦公用品',2014,50000.00,40000.00,0.00,GETDATE());
INSERT INTO tbDeptBudget(DeptName,DeptCode,ProCode,ProName,YEAR,BudgetAmount,FactAmount,RemainAmount,CreateTime)
VALUES('財務部','020000','02','辦公用品',2015,50000.00,50000.00,10000.00,GETDATE());
INSERT INTO tbDeptBudget(DeptName,DeptCode,ProCode,ProName,YEAR,BudgetAmount,FactAmount,RemainAmount,CreateTime)
VALUES('財務部','020000','02','辦公用品',2016,60000.00,50000.00,40000.00,GETDATE());
INSERT INTO tbDeptBudget(DeptName,DeptCode,ProCode,ProName,YEAR,BudgetAmount,FactAmount,RemainAmount,CreateTime)
VALUES('財務部','020000','03','采購費',2016,100000.00,80000.00,60000.00,GETDATE());
View Code

 

測試腳本附件

4. 參考資料

 http://www.cnblogs.com/zhangzt/archive/2010/07/29/1787825.html  

 http://www.cnblogs.com/gaizai/p/3753296.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM