【SQL SERVER】PIVOT与UNPIVOT之行列转换

news/2024/10/9 14:55:18

基础例子

在数据处理的过程中,常常遇到行列转换的问题。例如,人员的考勤。可能表格中,1~12月都在同一个字段,实际中,为了查看方便,同一个人的考勤记录,能在同一行,这样查询起来比较方便(行转列)。或者,表格设计的时候就是1~12月,在其他数据分析时需要将列转行。即类似于以下两张表之间的相互转换。

 接下来,我们使用数据如下:

 1 CREATE TABLE t_attendance(
 2     id INT NOT NULL IDENTITY(1,1) PRIMARY KEY,        -- 主键
 3     name nvarchar(255),        -- 姓名
 4     mymonth INT,            -- 月份
 5     myday float                -- 出勤天数
 6 )
 7 INSERT INTO t_attendance(name,mymonth,myday) VALUES('张三',1,24)
 8 INSERT INTO t_attendance(name,mymonth,myday) VALUES('张三',2,18)
 9 INSERT INTO t_attendance(name,mymonth,myday) VALUES('张三',3,21)
10 INSERT INTO t_attendance(name,mymonth,myday) VALUES('张三',4,22)
11 INSERT INTO t_attendance(name,mymonth,myday) VALUES('张三',5,21)
12 INSERT INTO t_attendance(name,mymonth,myday) VALUES('张三',6,19)
13 INSERT INTO t_attendance(name,mymonth,myday) VALUES('张三',7,23)
14 INSERT INTO t_attendance(name,mymonth,myday) VALUES('张三',8,22)
15 INSERT INTO t_attendance(name,mymonth,myday) VALUES('张三',9,21)
16 INSERT INTO t_attendance(name,mymonth,myday) VALUES('张三',10,19)
17 INSERT INTO t_attendance(name,mymonth,myday) VALUES('张三',11,21)
18 INSERT INTO t_attendance(name,mymonth,myday) VALUES('张三',12,22)
19 INSERT INTO t_attendance(name,mymonth,myday) VALUES('李四',1,23)
20 INSERT INTO t_attendance(name,mymonth,myday) VALUES('李四',2,17)
21 INSERT INTO t_attendance(name,mymonth,myday) VALUES('李四',3,20)
22 INSERT INTO t_attendance(name,mymonth,myday) VALUES('李四',4,21)
23 INSERT INTO t_attendance(name,mymonth,myday) VALUES('李四',5,20)
24 INSERT INTO t_attendance(name,mymonth,myday) VALUES('李四',6,18)
25 INSERT INTO t_attendance(name,mymonth,myday) VALUES('李四',7,22)
26 INSERT INTO t_attendance(name,mymonth,myday) VALUES('李四',8,21)
27 INSERT INTO t_attendance(name,mymonth,myday) VALUES('李四',9,20)
28 INSERT INTO t_attendance(name,mymonth,myday) VALUES('李四',10,18)
29 INSERT INTO t_attendance(name,mymonth,myday) VALUES('李四',11,20)
30 INSERT INTO t_attendance(name,mymonth,myday) VALUES('李四',12,21)
考勤数据

除了表数据(t_attendance),我还创建了(v_attendance)用于演示表格之间行列转换方法。

 常规使用方法

行转列时,我们可以使用CASE关键字,计算不同条件下的分组。

 1 SELECT name
 2     ,ISNULL(SUM(CASE  myMonth WHEN 1 THEN myday END ), 0) AS '1月出勤'
 3     ,ISNULL(SUM(CASE  myMonth WHEN 2 THEN myday END ), 0) AS '2月出勤'
 4     ,ISNULL(SUM(CASE  myMonth WHEN 3 THEN myday END ), 0) AS '3月出勤'
 5     ,ISNULL(SUM(CASE  myMonth WHEN 4 THEN myday END ), 0) AS '4月出勤'
 6     ,ISNULL(SUM(CASE  myMonth WHEN 5 THEN myday END ), 0) AS '5月出勤'
 7     ,ISNULL(SUM(CASE  myMonth WHEN 6 THEN myday END ), 0) AS '6月出勤'
 8     ,ISNULL(SUM(CASE  myMonth WHEN 7 THEN myday END ), 0) AS '7月出勤'
 9     ,ISNULL(SUM(CASE  myMonth WHEN 8 THEN myday END ), 0) AS '8月出勤'
10     ,ISNULL(SUM(CASE  myMonth WHEN 9 THEN myday END ), 0) AS '9月出勤'
11     ,ISNULL(SUM(CASE  myMonth WHEN 10 THEN myday END ), 0) AS '10月出勤'
12     ,ISNULL(SUM(CASE  myMonth WHEN 11 THEN myday END ), 0) AS '11月出勤'
13     ,ISNULL(SUM(CASE  myMonth WHEN 12 THEN myday END ), 0) AS '12月出勤'
14 FROM t_attendance
15 GROUP BY name

列传行是,我们可以使用Union all,将各个月份的结果集联合起来。

 1 SELECT *
 2 FROM(
 3     SELECT name, 1 AS mymonth, [1] AS myday
 4     FROM v_attendance
 5     UNION ALL 
 6     SELECT name, 2 AS mymonth, [2] AS myday
 7     FROM v_attendance
 8     UNION ALL 
 9     SELECT name, 3 AS mymonth, [3] AS myday
10     FROM v_attendance
11     UNION ALL 
12     SELECT name, 4 AS mymonth, [4] AS myday
13     FROM v_attendance
14     UNION ALL 
15     SELECT name, 5 AS mymonth, [5] AS myday
16     FROM v_attendance
17     UNION ALL 
18     SELECT name, 6 AS mymonth, [6] AS myday
19     FROM v_attendance
20     UNION ALL 
21     SELECT name, 7 AS mymonth, [7] AS myday
22     FROM v_attendance
23     UNION ALL 
24     SELECT name, 8 AS mymonth, [8] AS myday
25     FROM v_attendance
26     UNION ALL 
27     SELECT name, 9 AS mymonth, [9] AS myday
28     FROM v_attendance
29     UNION ALL 
30     SELECT name, 10 AS mymonth, [10] AS myday
31     FROM v_attendance
32     UNION ALL 
33     SELECT name, 11 AS mymonth, [11] AS myday
34     FROM v_attendance
35     UNION ALL 
36     SELECT name, 12 AS mymonth, [12] AS myday
37     FROM v_attendance
38 ) t 
39 ORDER by name desc

使用PIVOT和UNPIVOT进行行列转换

行列转换中,使用Case WITH和 UION ALL行列转换。相比较PIVOT和UNPIVOT不够直观。如果,采用PIVOT进行行转列,或采用UNPIVOT进行列转换则会简化很多。

 1 -- 行转列
 2 SELECT *
 3 FROM (SELECT name, mymonth,myday FROM t_attendance ) t
 4 PIVOT(
 5     SUM(myday)
 6     FOR mymonth IN ([1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12])
 7 ) as p
 8 
 9 -- 列转行
10 SELECT name,mymonth,myday
11 FROM v_attendance
12 UNPIVOT(
13     myday For mymonth IN ([1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12])
14 ) as up

PIVOT不能汇总多个列

现在增加字段outdays,为出差天数。

我们希望行转列后,结果如下。

 这里,使用分组的代码如下。

 1 SELECT name
 2     ,ISNULL(SUM(CASE  myMonth WHEN 1 THEN myday END ), 0) AS '1月出勤'
 3     ,ISNULL(SUM(CASE  myMonth WHEN 2 THEN myday END ), 0) AS '2月出勤'
 4     ,ISNULL(SUM(CASE  myMonth WHEN 3 THEN myday END ), 0) AS '3月出勤'
 5     ,ISNULL(SUM(CASE  myMonth WHEN 4 THEN myday END ), 0) AS '4月出勤'
 6     ,ISNULL(SUM(CASE  myMonth WHEN 5 THEN myday END ), 0) AS '5月出勤'
 7     ,ISNULL(SUM(CASE  myMonth WHEN 6 THEN myday END ), 0) AS '6月出勤'
 8     ,ISNULL(SUM(CASE  myMonth WHEN 7 THEN myday END ), 0) AS '7月出勤'
 9     ,ISNULL(SUM(CASE  myMonth WHEN 8 THEN myday END ), 0) AS '8月出勤'
10     ,ISNULL(SUM(CASE  myMonth WHEN 9 THEN myday END ), 0) AS '9月出勤'
11     ,ISNULL(SUM(CASE  myMonth WHEN 10 THEN myday END ), 0) AS '10月出勤'
12     ,ISNULL(SUM(CASE  myMonth WHEN 11 THEN myday END ), 0) AS '11月出勤'
13     ,ISNULL(SUM(CASE  myMonth WHEN 12 THEN myday END ), 0) AS '12月出勤'
14     -- 出差
15      ,ISNULL(SUM(CASE  myMonth WHEN 1 THEN outdays END ), 0) AS '1月出差'
16     ,ISNULL(SUM(CASE  myMonth WHEN 2 THEN outdays END ), 0) AS '2月出差'
17     ,ISNULL(SUM(CASE  myMonth WHEN 3 THEN outdays END ), 0) AS '3月出差'
18     ,ISNULL(SUM(CASE  myMonth WHEN 4 THEN outdays END ), 0) AS '4月出差'
19     ,ISNULL(SUM(CASE  myMonth WHEN 5 THEN outdays END ), 0) AS '5月出差'
20     ,ISNULL(SUM(CASE  myMonth WHEN 6 THEN outdays END ), 0) AS '6月出差'
21     ,ISNULL(SUM(CASE  myMonth WHEN 7 THEN outdays END ), 0) AS '7月出差'
22     ,ISNULL(SUM(CASE  myMonth WHEN 8 THEN outdays END ), 0) AS '8月出差'
23     ,ISNULL(SUM(CASE  myMonth WHEN 9 THEN outdays END ), 0) AS '9月出差'
24     ,ISNULL(SUM(CASE  myMonth WHEN 10 THEN outdays END ), 0) AS '10月出差'
25     ,ISNULL(SUM(CASE  myMonth WHEN 11 THEN outdays END ), 0) AS '11月出差'
26     ,ISNULL(SUM(CASE  myMonth WHEN 12 THEN outdays END ), 0) AS '12月出差'
27 FROM t_attendance
28 GROUP BY name

 如果使用PIVOT进行行列转换,不能直接加一个出差的汇总(据说Oracle可以)。

 这样会语法错误。如果使用两个PIVOT呢?注意,不能两个字段名一致(要不然没办法区分)。

 如果连续使用PIVOT呢?

 显然,返回的结果不是我们想要的。原来,执行PIVOT的时候,已经把mymonth2和outdays作为条件列。

这时候,我们也可以分别将出勤和出差的列行转列单独使用PIVOT,然后将两次查询结果拼接起来。

SELECT *  -- 字段应该列出来,这里省略
FROM (SELECT *FROM (SELECT name, mymonth,myday FROM t_attendance ) t1PIVOT(SUM(myday)FOR mymonth IN ([1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12])) as p1) a LEFT JOIN (SELECT *FROM (SELECT name, mymonth,outdays FROM t_attendance ) tPIVOT(SUM(outdays)FOR mymonth IN ([1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12])) as p2
) b ON a.name = b.name

 这样的语句显然也不简便。所以,PIVOT和UNPIVOT行列转换只对一个数据汇总时,能够看起来比较易读(当然,这已经解决了大部分问题)、

PIVOT动态列问题

前面的例子,我们行列转换的列数是固定的,如果列的值是动态的呢? (比如产品的系列,人员)。为了演示这种情况,查询考勤记录时,没有3、4月的数据。如果,我们使用子查询,会发现语法错误:

 这时候,我们可以使用T-SQL动态语句。将要汇总的列,拼接成合适的字符串( QUOTENAME 为标识符包裹在[]中的函数)

DECLARE @columns NVARCHAR(MAX) , @sql NVARCHAR(MAX)SET @columns = STUFF( ( SELECT distinct ',' + QUOTENAME( mymonth) FROM t_attendance  FOR XML PATH('') ),1,1,'' )SET @sql = 'SELECT *FROM (SELECT name, mymonth,myday FROM t_attendance ) tPIVOT(SUM(myday)FOR mymonth IN (' + @columns + ')) as p
'
EXEC(@sql)

实际业务中,可以根据需要,可以创建存储过程,动态列用存储过程包裹起来。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/69392.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

SkyWalking组件自定义链路追踪

SkyWalking组件通过添加相关配置就可以获取到接口的相关信息,更加方便的追踪和处理问题 接下去讲下步骤: 1、在service层添加两个注解;@Trace@Tags({@Tag(key = "getDataByCode",value = "returnedObj"),@Tag(key = "getDataByCode",value = …

沈师傅食品携手纷享销客CRM系统,加速数字化转型

沈师傅食品有限公司是一家专业研发、生产和销售鸡蛋干系列产品的大型集团 公司,技术与研发实力雄厚,先后获得多项国家专利。公司成立于2006年,开创 了全新的鸡蛋干品类,创办人沈国平先生素有“鸡蛋干之父”之称,先后被央视、 四川电视台、北京卫视、优酷、凤凰网等国内知名…

总奖金高达10万元!华为算法精英实战营“亲和任务调度系统”来啦!

在无线领域,利用AI技术对任务准确建模、多核系统任务最优调度等问题都是非常有价值的算法难题。随着物联网、大数据、AI时代的到来,时延、可靠性等指标要求越来越高,海量的数据分析、大量复杂的运算对CPU的算力要求越来越高。CPU内部的大部分资源用于缓存和逻辑控制,适合运…

高产胜那啥,带你上线我的新项目!

希望大家能通过这个项目掌握企业级项目的开发、优化和上线方法,得到全方面编程技能和程序员素养的提升。大家好,我是程序员鱼皮。9月,我处于极度爆肝状态,成功完结了最新带大家做的项目 面试刷题平台 。当我们做完一个项目后,一定要记得把项目上线,这样才算是完成了学习的…

webapi 创建(空)

1. 打开vs2019 ,选择创建新项目2. 选择ASP.NET Web 应用程序(.NET Framework)3. 配置项目信息(名称,位置,框架)4. 选择空模板(WebAPI复选框选中)5. 这样里面就没有MVC的三层,因为前后端分离,webapi中只有两层。6. 空的WebApi程序创建完成。

.NET 代码混淆工具-JIEJIE.NETWX

阅读目录前言 项目介绍 项目功能 项目效果:蓝猫机场 项目地址 最后前言 JIEJIE.NET是一款强大的开源.NET程序集混淆工具。它利用深度加密技术和多样化的混淆策略,有效地保护了.NET软件的版权和源代码安全,防止未经授权的访问和篡改。 项目介绍 JIEJIE.NET是一个用C#开发的开源…

深入理解 HDFS 错误恢复

我们从动态的角度来看 hdfs 先从场景出发,我们知道 hdfs 的写文件的流程是这样的:数据以 pipeline 的方式写入 hdfs ,然后对于读取操作,客户端选择其中一个保存块副本的 DataNode 来读数据.考虑这样两个场景:hbase rs 在写 wal log 的时候.如果一个 rs 挂了.那么这个 rs 会转移…

深入理解HDFS 错误恢复

我们从动态的角度来看 hdfs 先从场景出发,我们知道 hdfs 的写文件的流程是这样的:数据以 pipeline 的方式写入 hdfs ,然后对于读取操作,客户端选择其中一个保存块副本的 DataNode 来读数据.考虑这样两个场景:hbase rs 在写 wal log 的时候.如果一个 rs 挂了.那么这个 rs 会转移…