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('人事部','','','差旅费',2014,100000.00,80000.00,20000.00,GETDATE());
INSERT INTO tbDeptBudget(DeptName,DeptCode,ProCode,ProName,YEAR,BudgetAmount,FactAmount,RemainAmount,CreateTime)
VALUES('人事部','','','差旅费',2015,110000.00,90000.00,50000.00,GETDATE());
INSERT INTO tbDeptBudget(DeptName,DeptCode,ProCode,ProName,YEAR,BudgetAmount,FactAmount,RemainAmount,CreateTime)
VALUES('人事部','','','差旅费',2016,120000.00,100000.00,80000.00,GETDATE());
INSERT INTO tbDeptBudget(DeptName,DeptCode,ProCode,ProName,YEAR,BudgetAmount,FactAmount,RemainAmount,CreateTime)
VALUES('人事部','','','办公用品',2015,200000.00,150000.00,10000.00,GETDATE());
INSERT INTO tbDeptBudget(DeptName,DeptCode,ProCode,ProName,YEAR,BudgetAmount,FactAmount,RemainAmount,CreateTime)
VALUES('人事部','','','办公用品',2016,160000.00,120000.00,80000.00,GETDATE());
INSERT INTO tbDeptBudget(DeptName,DeptCode,ProCode,ProName,YEAR,BudgetAmount,FactAmount,RemainAmount,CreateTime)
VALUES('财务部','','','办公用品',2014,50000.00,40000.00,0.00,GETDATE());
INSERT INTO tbDeptBudget(DeptName,DeptCode,ProCode,ProName,YEAR,BudgetAmount,FactAmount,RemainAmount,CreateTime)
VALUES('财务部','','','办公用品',2015,50000.00,50000.00,10000.00,GETDATE());
INSERT INTO tbDeptBudget(DeptName,DeptCode,ProCode,ProName,YEAR,BudgetAmount,FactAmount,RemainAmount,CreateTime)
VALUES('财务部','','','办公用品',2016,60000.00,50000.00,40000.00,GETDATE());
INSERT INTO tbDeptBudget(DeptName,DeptCode,ProCode,ProName,YEAR,BudgetAmount,FactAmount,RemainAmount,CreateTime)
VALUES('财务部','','','采购费',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

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

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

    1.[Sql Server](70)  SELECT CONVERT(varchar(100), CAST(@testFloat AS decimal(38,2)))SELECT STR(@testF ...

  2. Sql Server中启用分布式事务小结

    1.web服务器与数据库服务器同时启动msdtc服务 2. 2台服务器做出如下配置: 控制面板->管理工具->组件服务->计算机->我的电脑->本地DTC .Net示例: ...

  3. SQL Server 中使用 convert 转换 datetime 格式示例

    Select CONVERT(varchar(100), GETDATE(), 0): 05 16 2006 10:57AMSelect CONVERT(varchar(100), GETDATE() ...

  4. SQL Server中行列转换 Pivot UnPivot

    SQL Server中行列转换 Pivot UnPivot PIVOT用于将列值旋转为列名(即行转列),在SQL Server 2000可以用聚合函数配合CASE语句实现 PIVOT的一般语法是:PI ...

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

        http://www.cnblogs.com/shanksgao/p/4254942.html 高兄这篇文章很好的谈论了由于数据隐式转换造成执行计划不准确,从而造成了死锁.那如果在事情出现之前 ...

  6. (转)SQL Server中使用convert进行日期转换

    原文链接:http://www.cnblogs.com/weiqt/articles/1826847.html SQL Server中使用convert进行日期转换 一般存入数据库中的时间格式为yyy ...

  7. SQL Server 中对 FOR XML和FROM的转换处理

    在SQL Server中对XML的再操作转换: 方法1: --生成XML SELECT * FROM [T_BAS_预算科目] FOR XML PATH --把XML转成SQL表 declare @X ...

  8. SQL Server中datetimeset转换datetime类型问题浅析

    在SQL Server中,数据类型datetimeoffset转换为datetime类型或datetime2类型时需要特别注意,有可能一不小心你可能会碰到下面这种情况.下面我们构造一个简单案例,模拟一 ...

  9. delphi 转换sql server 中的 bit类型

    FieldByName('e').AsBoolean = false 其中e为 sql server 中的bit类型.

随机推荐

  1. sc7731 Android 5.1 LCD驱动简明笔记之二

    此篇笔记基于sc7731 - android 5.1,对lcd的framebuffer做一个简明笔记. 一共分为两大部分:第一部分,关于LCD的硬件方面的:第二部分,关于lcd核心处理(framebu ...

  2. 对Slony-I中wait on的理解

    http://slony.info/documentation/2.1/advanced.html#AEN1425 4.1.2. Event Confirmations When an event i ...

  3. 防火墙没关导致 ORA-12541: TNS: 无监听程序

    电脑用着用着突然Oracle就报出下面的错误,按照网上的办法搞了几个小时都没有搞好. Oracle重装了好几次也没用,实在没办法又花了个多小时装了个虚机,结果也是同样的错误. 于是恍然大悟,可能是物理 ...

  4. MySQL中ON DUPLICATE KEY UPDATE使用

    今天做推断插入用到了MySQL中ON DUPLICATE KEY UPDATE,如今Mark下面! 假设你想做到数据库中没有数据的话插入数据.有数据的话更新数据,那么你能够选择ON DUPLICATE ...

  5. SQL Server-删除表中重复的记录!

    比如现在有一人员表  (表名:peosons)若想将姓名.身份证号.住址这三个字段完全相同的记录查询出来 select   p1.*   from   persons   p1,persons   p ...

  6. Codeforces Gym 100187E E. Two Labyrinths bfs

    E. Two Labyrinths Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/gym/100187/prob ...

  7. IE10、IE11出现“__doPostBack未定义”的解决办法。

    方法一:浏览器设置成兼容模式,这个是超级掩耳盗铃方法,你就没想过其他人也会出这个问题. 方法二.安装服务器版的.Net40的补丁.http://download.csdn.net/detail/565 ...

  8. C#生成软件注册码

    开发软件时,当用到商业用途时,注册码与激活码就显得很重要了.现在的软件破解技术实在在强了,各种国内外大型软件都有注册机制,但同时也不断地被破解.下面发的只是一个常用版本,发出源码被破就更容易了,但我们 ...

  9. android手机打电话代码分析

    智能手机的打电话功能是由RIL部分来实现的,见下图: 开始分析Android源代码中的RIL部分. 又上图,以及其他相关资料,我得知在Android中有一个叫rild的守护进程.我猜测此进程与电话的拨 ...

  10. 【JavsScript】XMLHttpRequest Level 2 使用指南

    XMLHttpRequest是一个浏览器接口,使得Javascript可以进行HTTP(S)通信. 最早,微软在IE 5引进了这个接口.因为它太有用,其他浏览器也模仿部署了,ajax操作因此得以诞生. ...