首页 > 数据库 >【SQL SERVER】PIVOT与UNPIVOT之行列转换

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

时间:2024-10-09 14:44:07浏览次数:6  
标签:CASE mymonth name SUM myday SERVER attendance UNPIVOT SQL

基础例子

在数据处理的过程中,常常遇到行列转换的问题。例如,人员的考勤。可能表格中,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 ) t1
        PIVOT(
            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 ) t
        PIVOT(
            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 ) t
    PIVOT(
        SUM(myday)
        FOR mymonth IN (' + @columns + ')
    ) as p
'
EXEC(@sql)

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

标签:CASE,mymonth,name,SUM,myday,SERVER,attendance,UNPIVOT,SQL
From: https://www.cnblogs.com/luyj00436/p/18453749

相关文章

  • 通过MySQL Workbench 将 SQL Server 迁移到GreatSQL
    通过MySQLWorkbench将SQLServer迁移到GreatSQL一、概述MySQLWorkbench提供了可以将MicrosoftSQLServer的表结构和数据迁移到GreatSQL的功能,此次将通过MySQLWorkbench将SQLServer的数据迁移到GreatSQL。本文章只是简单演示一下单张表的迁移,如果在项目中使用请根据......
  • MySQL之B+树分析
    概览索引是一种数据结构,用于帮助我们在大量数据中快速定位到我们想要查找的数据。索引好比一本好书的目录页,需要查询某个章节直接在目录页查找,然后打开响应页数。但索引也不是就快,如果章节少,那就直接翻开书找即可很快找到,只有章节非常多时,我们就可以利用索引快速找到。所以,如......
  • flask_sqlalchemy连接建表
    database.pyfromflask_sqlalchemyimportSQLAlchemydb=SQLAlchemy()config.pyimportosfromdatabaseimportdbfromflaskimportFlaskbasedir=os.path.abspath(os.path.dirname(__name__))app=Flask(__name__)#Dabaseconfigurationapp.config[&#......
  • 基于Java+SpringBoot+Mysql在线年度考核考勤管理系统功能设计与实现九
    一、前言介绍:1.1项目摘要随着计算机和网络技术的迅猛发展,学校教学和管理的信息化发展也得到了长足的进步,学校是否具有一流的信息管理、教育教学的平台已经是衡量一个学校信息化建设的重要标志之一。本文首先介绍了在线考试系统的开发背景,开发工具,结构化开发的具体步骤,然......
  • 基于Java+SpringBoot+Mysql在线年度考核考勤管理系统功能设计与实现十
    一、前言介绍:1.1项目摘要随着计算机和网络技术的迅猛发展,学校教学和管理的信息化发展也得到了长足的进步,学校是否具有一流的信息管理、教育教学的平台已经是衡量一个学校信息化建设的重要标志之一。本文首先介绍了在线考试系统的开发背景,开发工具,结构化开发的具体步骤,然......
  • SQLAlchemy模块
    1、执行原生SQLfromsqlalchemyimportcreate_engine,text#创建engine对象engine=create_engine("sqlite:///demo.db",echo=False)withengine.connect()ascon:#先删除persons表con.execute(text('droptableifexistspersons'))#创建一个p......
  • SQLAlchemy入门:详细介绍SQLAlchemy的安装、配置及基本使用方法
    SQLAlchemy是一个流行的PythonSQL工具包和对象关系映射(ORM)框架,它为开发人员提供了一种高效、灵活的方式来与数据库进行交互。本文将详细介绍SQLAlchemy的安装、配置及基本使用方法,并通过代码示例和案例分析,帮助新手朋友快速上手。一、SQLAlchemy简介SQLAlchemy由MikeBa......
  • nacos gateway 调用服务报错503 Server unavailable
    环境springboot3jdk17依赖<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bootstrap</artifactId></dependency><dependency><groupId>org.springframe......
  • 解决ERROR ResizeObserver loop completed with undelivered notifications.
    https://www.cnblogs.com/luo9tian/p/18116299该报错虽然不影响项目运行,但是影响开发效率,总是弹出报错的黑框很烦人该报错原因:newResizeObserver包裹的方法,在布局发生变化时,不支持每帧都调用解决方法:用window.requestAnimationFrame包裹回调函数在App.vue/main.js中加......
  • 【星闪开发连载】SLE_UUID_Server和SLE_UUID_Client程序测试
    引言前一篇博文介绍了SLE_UUID_Server和SLE_UUID_Client程序的基本结构,这篇介绍如何进行测试,从而实现两块星闪开发板之间的连接。服务器的构建在sdk根目录下(即src目录)打开集成终端台,执行python build.py-cws63-liteos-appmenuconfig命令,会出现选择弹窗。menuconfig这......