zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    dos cmd重启2003命令shutdown -r -t 0
    asp的RegExp对象正则表达式功能用法
    sql查询百分号的方法
    tabbar颜色与文字大小,状态栏样式
    打印所有系统字体名字,创建可拉伸图片,获取文字长度
    判断推送权限是否开启
    mac xcode 快捷键
    一个view相对于屏幕或者另外一个view 的坐标
    swift 2 选择头像图片
    scrapyd在window上的部署
  • 原文地址:https://www.cnblogs.com/johden2/p/5692765.html
Copyright © 2011-2022 走看看