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)

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

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)

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

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

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

测试脚本附件

4. 参考资料

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

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

时间: 2024-10-10 08:15:03

SQL SERVER 中的行列转换小结的相关文章

Sql Server中Float格式转换字符串varchar方法(转)

SELECT CONVERT(varchar(100), CAST(@testFloat AS decimal(38,2)))SELECT STR(@testFloat, 38, 2) 从Excel中导入到sql2000,有一列“联系方式”变成了float类型,我想转换成nvarchar类型,用下面的语句 select convert(nvarchar(30),convert(int,联系方式)) from employeego //数据溢出,不行! select convert(nvarcha

SQL Server中时间格式转换函数convert()的使用

convert(varchar(10),字段名,转换格式) CONVERT为日期转换函数,一般就是在时间类型(datetime,smalldatetime)与字符串类型(nchar,nvarchar,char,varchar)相互转换的时候才用到:函数的3个参数:第1个参数为转换后的大:第2个为转换日期的字段或函数:第3个为转换的格式. 具体例子: SELECT CONVERT(varchar(100), GETDATE(), 0): 05 16 2011 10:57AM SELECT CONV

SQL Server 中日期格式转换CONVERT(varchar, getdate(), 120 )

CONVERT(varchar, getdate(), 120 ) 这是一个mssql数据库的函数,Convert函数的作用,是进行数据类型的转换. 而您所问的这个convert(char(20),openDate,120)则是对日期字段,进行格式化转换成字符格式的函数. 接下来,对您函数中的三个参数,分别进行说明: 1.char(20),是要转换成的目标数据类型及长度,这里您还可以使用varchar(20),也可以使用varchar(10),如果使用20,则转换后的字符串可以是20的长度,如果

SQL Server中行列转换 Pivot UnPivot

SQL Server中行列转换 Pivot UnPivot PIVOT用于将列值旋转为列名(即行转列),在SQL Server 2000可以用聚合函数配合CASE语句实现 PIVOT的一般语法是:PIVOT(聚合函数(列) FOR 列 in (-) )AS P 完整语法: table_source PIVOT( 聚合函数(value_column) FOR pivot_column IN(<column_list>) ) UNPIVOT用于将列明转为列值(即列转行),在SQL Server 2

SQL Server中行列转换

典型实例 一.行转列 1.建立表格 ifobject_id('tb')isnotnulldroptabletb go createtabletb(姓名varchar(10),课程varchar(10),分数int) insertintotbvalues('张三','语文',74) insertintotbvalues('张三','数学',83) insertintotbvalues('张三','物理',93) insertintotbvalues('李四','语文',74) insertinto

SQL中PIVOT 行列转换

SQL中PIVOT 行列转换 本文导读:T-SQL语句中,Pivot运算符用于在列和行之间对数据进行旋转或透视转换,PIVOT命令可以实现数据表的列转行,同时执行聚合运算,UNPIVOT则与其相反,实现数据的行转列. PIVOT通过将表达式某一列中的唯一值转换为输出中的多个列来旋转表值表达式,并在必要时对最终输出中所需的任何其余列值执行聚合.UNPIVOT与PIVOT执行相反的操作,将表值表达式的列转换为列值. 通俗简单的说:PIVOT就是行转列,UNPIVOT就是列传行 一.PIVOT实例 1

SQL Server中提前找到隐式转换提升性能的办法

    http://www.cnblogs.com/shanksgao/p/4254942.html 高兄这篇文章很好的谈论了由于数据隐式转换造成执行计划不准确,从而造成了死锁.那如果在事情出现之前发现了这类潜在的风险岂不是更好?     那么我们来看一个简单的例子,如代码清单1所示.   1: SELECT * 2: FROM HumanResources.Employee 3: WHERE NationalIDNumber = 243322160 4:  5: SELECT * 6: FR

SQL Server中使用convert进行日期转换

SQL Server中使用convert进行日期转换 一般存入数据库中的时间格式为yyyy-mm-ddhh:mm:ss 如果要转换为yyyy-mm-dd  短日期格式.可以使用convert函数.下面是sqlserver帮助中关于convert函数的声明: 使用 CONVERT: CONVERT (data_type[(length)],expression[,style]) 参数 expression 是任何有效的 Microsoft® SQL Server™ 表达式.data_type 目标

在SQL Server中为什么不建议使用Not In子查询

原文:在SQL Server中为什么不建议使用Not In子查询     在SQL Server中,子查询可以分为相关子查询和无关子查询,对于无关子查询来说,Not In子句比较常见,但Not In潜在会带来下面两种问题: 结果不准确 查询性能低下       下面我们来看一下为什么尽量不使用Not In子句.   结果不准确问题     在SQL Server中,Null值并不是一个值,而是表示特定含义,其所表示的含义是"Unknow",可以理解为未定义或者未知,因此任何与Null值