SQL行列转换

零、创建基础数据

CREATE TEMPORARY TABLE Scores( ID INT, Student varchar(10), Subject varchar(10), Score INT );

INSERT INTO Scores VALUES(2, '张三', '语文', 93);
INSERT INTO Scores VALUES(3, '张三', '英语', 90);
INSERT INTO Scores VALUES(5, '李四', '语文', 88);
INSERT INTO Scores VALUES(7, '李四', '英语', 78);
INSERT INTO Scores VALUES(8, '王五', '语文', 98);
INSERT INTO Scores VALUES(9, '王五', '英语', 86);

一、 CASE WHEN 语句在DB2, ORACLE, SQL SERVER, SYBASE等数据库都受到支持,是标准的SQL语句.

--1
SELECT student, max(yw) as '语文', max(yu) as '英语' FROM( SELECT student, CASE subject WHEN '语文' THEN score ELSE 0 END as yw, CASE subject WHEN '英语' THEN score ELSE 0 END as yu FROM Scores ) A GROUP BY student

原理:
1) 生成

姓名ywyu
张三930
张三090
李四880
李四078
王五980
王五086

2)按姓名聚合,取名列最大值

--2,1的简化版
SELECT student as '姓名', max(case Subject when '语文' then Score else 0 end) as '语文', max(case Subject when '英语' then score else 0 end) as '英语' from Scores group by student;
--3, 当科目比较多(要转换的列较多时),使用游标生成查询语句
declare @sql varchar(8000)
set @sql = 'select student as 姓名' select @sql = @sql + ', max(case subject when ''' + subject+ ''' then score else 0 end) [' + subject+ ']' from (select distinct subject from Scores) as a set @sql = @sql + ' from Scores group by student' print @sql --打印生成的sql exec(@sql) --执行该sql

二、 PIVOT (SQL Server 2005)

SELECT student, [语文], [英语] --第三步(选择行转列后的结果集的列)这里可以用“*”表示选择所有列,也可以只选择某些列 FROM #Scores --这里是PIVOT第二步骤(准备原始的查询结果,因为PIVOT是对一个原始的查询结果集进行转换操作,所以先查询一个结果集出来)这里可以是一个select子查询,但为子查询时候要指定别名,否则语法错误 PIVOT ( AVG(score) FOR [subject] in ([语文], [英语]) --这里是PIVOT第一步骤,也是核心的地方,进行行转列操作。聚合函数SUM表示你需要怎样处理转换后的列的值,是总和(sum),还是平均(avg)还是min,max等等。例如如果Scores表中有两条数据并且其subject都是“语文”,其中一条的score是50,另一条score是70,那么在这里使用sum,行转列后“语文”这个列的值当然是120了。后面的FOR [subject] in ([语文], [英语])中 for [subject]就是说将subject列的值分别转换成一个个列,也就是“以值变列”。但是需要转换成列的值有可能有很多,我们只想取其中几个值转换成列,那么怎样取呢?就是在in里面了,比如我此刻只想看“语文”的分数,在in里面就只写“语文”。总的来说,AVG(score) FOR [subject] in ([语文], [英语])这句的意思如果直译出来,就是说:将列[subject]值为"语文","英语"分别转换成列,这些列的值取score的平均值。 ) Scores_alias --别名一定要写

pivot只实现 了case的工作,需要我们进一步聚合去空值:

SELECT student, MAX([语文]) AS '语文', MAX([英语]) AS '英语' FROM #Scores PIVOT ( AVG(score) FOR [subject] in ([语文], [英语]) ) Scores_alias GROUP BY student

三、UNPIVOT
很明显,UN这个前缀表明了,它做的操作是跟PIVOT相反的,即列转行。UNPIVOT操作涉及到以下三个逻辑处理阶段。
1,生成副本
2,提取元素
3,删除带有NULL的行

CREATE TABLE #pvt (VendorID int, EmpA int, EmpB int, EmpC int);
GO
INSERT INTO #pvt VALUES (1,4,3,5);
INSERT INTO #pvt VALUES (2,4,NULL,1);
GO
--Unpivot the table.
SELECT VendorID, Employee, Orders FROM (SELECT VendorID, EmpA, EmpB, EmpC FROM pvt) p UNPIVOT (Orders FOR Employee IN (EmpA, EmpB, EmpC) )AS unpvt;
GO

上面UNPIVOT实例的分析
UNPIVOT的输入是左表表达式p子查询。第一步,先为p中的行生成多个副本,在UNPIVOT中出现的每一列,都会生成一个副本。因为这里的IN子句有4个列名称,所以要为每个来源行生成4个副本。结果得到的虚拟表中将新增一个列,用来以字符串格式保存来源列的名称(for和IN之间的,上面例子是 Employee )。

VendorIDEmployee?
1EmpA?
1EmpA?
1EmpA?
1EmpA?
2EmpB?
2EmpB?
2EmpB?
2EmpB?

第二步,根据新增的那一列中的值从来源列中提取出与列名对应的行。

VendorIDEmployeeOrders
1EmpA4
1EmpA3
1EmpA5
2EmpA4
2EmpBNULL
2EmpB1

第三步,删除掉结果列值为null的行(第五行),完成这个查询。

另外,转换中要注意新增列的值的类型,不一致时会出现:列 “score” 的类型与 UNPIVOT 列表中指定的其他列的类型冲突。如下(注意第四列数据):

SELECT * FROM (SELECT * FROM #Scores) p UNPIVOT (Orders FOR Employee IN (subject,score) )AS unpvt;
GO
IDStudentEmployeeOrders
2张三Subject语文
2张三Subject英语
3张三Score93
3张三Score90
    原文作者:拖鞋短裤睡衣
    原文地址: https://blog.csdn.net/gowhere_/article/details/77654837
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞