SQL行列转换6种方法
在进行报表开发时,很多时候会遇到行列转换操作,很对开发人员针对于SQL级别行列转换操作一直不甚理解,今天正好抽空对其进行了一些简单的总结。这里主要列举3种可以实现SQL行列转换的方法,包括通用SQL解法以及Oracle支持解法。
一、测试数据
测试表依旧采用Oracle经典的scott模式下的dept表和emp表,结构如下:
DEPT:
create table DEPT ( DEPTNO NUMBER(2) not null, DNAME VARCHAR2(14), LOC VARCHAR2(13) ) -- Create/Recreate primary, unique and foreign key constraints alter table DEPT add constraint PK_DEPT primary key (DEPTNO) using index; |
SQL> select * from dept; DEPTNO DNAME LOC ------ -------------- ------------- 10 ACCOUNTING NEW YORK 20 RESEARCH DALLAS 30 SALES CHICAGO 40 OPERATIONS BOSTON |
EMP:
create table EMP ( EMPNO NUMBER(4) not null, ENAME VARCHAR2(10), JOB VARCHAR2(9), MGR NUMBER(4), HIREDATE DATE, SAL NUMBER(7,2), COMM NUMBER(7,2), DEPTNO NUMBER(2), SEX VARCHAR2(2) default '男' ) -- Create/Recreate primary, unique and foreign key constraints alter table EMP add constraint PK_EMP primary key (EMPNO) using index; alter table EMP add constraint FK_DEPTNO foreign key (DEPTNO) references DEPT (DEPTNO); |
SQL> select * from emp; EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO SEX ----- ---------- --------- ----- ----------- --------- --------- ------ --- 7369 SMITH CLERK 7902 1980/12/17 800.00 20 男 7499 ALLEN SALESMAN 7698 1981/2/20 1600.00 300.00 30 女 7521 WARD SALESMAN 7698 1981/2/22 1250.00 500.00 30 女 7566 JONES MANAGER 7839 1981/4/2 2975.00 20 女 7654 MARTIN SALESMAN 7698 1981/9/28 1250.00 1400.00 30 女 7698 BLAKE MANAGER 7839 1981/5/1 2850.00 30 女 7782 CLARK MANAGER 7839 1981/6/9 2450.00 10 女 7788 SCOTT ANALYST 7566 1987/4/19 3000.00 20 男 7839 KING PRESIDENT 1981/11/17 5000.00 10 女 7844 TURNER SALESMAN 7698 1981/9/8 1500.00 0.00 30 女 7876 ADAMS CLERK 7788 1987/5/23 1100.00 20 男 7900 JAMES CLERK 7698 1981/12/3 950.00 30 女 7902 FORD ANALYST 7566 1981/12/3 3000.00 20 女 7934 MILLER CLERK 7782 1982/1/23 1300.00 10 男 14 rows selected |
二、行列转换方法
需求:查询每个部门中不同职位总工资。
1、最传统的SQL解法如下:
SQL> select d.dname, e.job, sum(sal) as sum_sal 2 from emp e, dept d 3 where e.deptno = d.deptno 4 group by d.dname, e.job; DNAME JOB SUM_SAL -------------- --------- ---------- SALES MANAGER 2850 SALES CLERK 950 ACCOUNTING MANAGER 2450 ACCOUNTING PRESIDENT 5000 ACCOUNTING CLERK 1300 RESEARCH MANAGER 2975 SALES SALESMAN 5600 RESEARCH ANALYST 6000 RESEARCH CLERK 1900 9 rows selected |
以上方法确实得到了不同部门不同职位的工资总和,可是我们看到每一个部门出现了多行信息,这并不是我们想要的结果,我们更希望每个部门显示为1行。
2、使用子查询实现行列转换:
SQL> select e.deptno, 2 (select sum(sal) from emp where emp.deptno = e.deptno and job='ANALYST') as analyst_job, 3 (select sum(sal) from emp where emp.deptno=e.deptno and job='CLERK') as clerk_job, 4 (select sum(sal) from emp where emp.deptno=e.deptno and job='MANAGER') as manager_job, 5 (select sum(sal) from emp where emp.deptno=e.deptno and job='PRESIDENT') as persident_job, 6 (select sum(sal) from emp where emp.deptno=e.deptno and job='SALESMAN') as salesman_job 7 from emp e 8 group by e.deptno; DEPTNO ANALYST_JOB CLERK_JOB MANAGER_JOB PERSIDENT_JOB SALESMAN_JOB ------ ----------- ---------- ----------- ------------- ------------ 30 950 2850 5600 20 6000 1900 2975 10 1300 2450 5000 |
3、尝试更改SQL如以下形式达到目的:
SQL> select DNAME, 2 SUM(MANAGER_JOB) AS SUM_MANAGER_SAL, 3 SUM(PRESIDENT_JOB) AS SUM_PRESIDENT_SAL, 4 SUM(CLERK_JOB) AS SUM_CLERK_SAL, 5 SUM(ANALYST_JOB) AS SUM_ANALYST_SAL, 6 SUM(SALESMAN_JOB) AS SUM_SALESMAN_SAL 7 from (select d.dname, 8 (select sum(sal) 9 from emp 10 where emp.empno = e.empno 11 and emp.job = 'MANAGER') as MANAGER_JOB, 12 (select sum(sal) 13 from emp 14 where emp.empno = e.empno 15 and emp.job = 'PRESIDENT') as PRESIDENT_JOB, 16 (select sum(sal) 17 from emp 18 where emp.empno = e.empno 19 and emp.job = 'CLERK') as CLERK_JOB, 20 (select sum(sal) 21 from emp 22 where emp.empno = e.empno 23 and emp.job = 'ANALYST') as ANALYST_JOB, 24 (select sum(sal) 25 from emp 26 where emp.empno = e.empno 27 and emp.job = 'SALESMAN') as SALESMAN_JOB 28 from emp e, dept d 29 where e.deptno = d.deptno) temp 30 group by temp.dname 31 order by temp.dname; DNAME SUM_MANAGER_SAL SUM_PRESIDENT_SAL SUM_CLERK_SAL SUM_ANALYST_SAL SUM_SALESMAN_SAL -------------- --------------- ----------------- ------------- --------------- ---------------- ACCOUNTING 2450 5000 1300 RESEARCH 2975 1900 6000 SALES 2850 950 5600 |
通过查询结果可以看到,这里我们已经实现了行列转换,同时这个结果也符合我们日常阅读习惯。但是代码过于复杂,执行计划回表次数明显很会很多。这里的代码基本符合任何一种支持SQL的数据库基本语法,属于通用模式,那么有没有简单一点的方法呢?答案是肯定的!
4、基于Oracle decode()函数的行列转换:
SQL> select d.dname, 2 sum(decode(job, 'MANAGER', sal, 0)) as SUM_MANAGER_SAL, 3 sum(decode(job, 'PRESIDENT', sal, 0)) as SUM_PRESIDENT_SAL, 4 sum(decode(job, 'CLERK', sal, 0)) as SUM_CLERK_SAL, 5 sum(decode(job, 'ANALYST', sal, 0)) as SUM_ANALYST_SAL, 6 sum(decode(job, 'SALESMAN', sal, 0)) as SUM_SALESMAN_SAL 7 from emp e, dept d 8 where e.deptno = d.deptno 9 group by d.dname; DNAME SUM_MANAGER_SAL SUM_PRESIDENT_SAL SUM_CLERK_SAL SUM_ANALYST_SAL SUM_SALESMAN_SAL -------------- --------------- ----------------- ------------- --------------- ---------------- ACCOUNTING 2450 5000 1300 0 0 RESEARCH 2975 0 1900 6000 0 SALES 2850 0 950 0 5600 |
根据上面执行结果,我们可以看到通过Oracle数据库的decode()函数达到了进行行列装换的目的,而且明显针对于前一种行列转换操作效率提升不少。
5、通用行列转换可以通过case when ... then ...else ... end实现:
SQL> select d.dname, 2 sum(case 3 when e.job = 'ANALYST' then 4 sal 5 else 6 0 7 end) as ANALYST_JOB, 8 sum(case 9 when e.job = 'CLERK' then 10 sal 11 else 12 0 13 end) as CLERK_JOB, 14 sum(case 15 when e.job = 'MANAGER' then 16 sal 17 else 18 0 19 end) as MANAGER_JOB, 20 sum(case 21 when e.job = 'PRESIDENT' then 22 sal 23 else 24 0 25 end) as PRESIDENT_JOB, 26 sum(case 27 when e.job = 'SALESMAN' then 28 sal 29 else 30 0 31 end) as SALESMAN_JOB 32 from emp e, dept d 33 where e.deptno = d.deptno 34 group by d.dname; DNAME ANALYST_JOB CLERK_JOB MANAGER_JOB PRESIDENT_JOB SALESMAN_JOB -------------- ----------- ---------- ----------- ------------- ------------ ACCOUNTING 0 1300 2450 5000 0 RESEARCH 6000 1900 2975 0 0 SALES 0 950 2850 0 5600 |
6、在Oracle 11g中引入了pivot和unpivot函数功能,专门用来进行行列转换,pivot函数语法如下:
select *|列 [别名],... from 子查询 pivot( 统计函数(列)s for 转换列名称 in( 内容1 [[as] 别名], 内容2 [[as] 别名], ... ... 内容n [[as] 别名] ) ) [where 条件(s)] [group by 分组字段1,分组字段2,... ...] [having 过滤条件(s)] [order by 排序字段 asc|desc] |
使用pivot函数实现行列转换:
SQL> select * 2 from (select d.dname, e.job, e.sal 3 from emp e, dept d 4 where e.deptno = d.deptno) pivot(sum(sal) for job in('ANALYST' AS 5 ANALYST_JOB, 6 'CLERK' AS 7 CLERK_JOB, 8 'MANAGER' AS 9 MANAGER_JOB, 10 'PRESIDENT' AS 11 PRESIDENT_JOB, 12 'SALESMAN' AS 13 SALESMAN_JOB)) 14 order by dname; DNAME ANALYST_JOB CLERK_JOB MANAGER_JOB PRESIDENT_JOB SALESMAN_JOB -------------- ----------- ---------- ----------- ------------- ------------ ACCOUNTING 1300 2450 5000 RESEARCH 6000 1900 2975 SALES 950 2850 5600 |
以上SQL使用pivot函数实现了行列转换操作。至此,对于行列转换操作我们使用3种方法实现。
以下是pivot函数实现更复杂一些SQL的例子:
6.1、查询每个部门的总工资,每个部门不同职位的总工资和每个部门的最高工资和最低工资:
SQL> select * 2 from (select deptno, 3 job, 4 sal, 5 sum(sal) over(partition by deptno) as sum_sal, 6 max(sal) over(partition by deptno) as max_sal, 7 min(sal) over(partition by deptno) as min_sal 8 from emp) pivot(sum(sal) for job in('ANALYST' AS ANALYST_JOB, 9 'CLERK' AS CLERK_JOB, 10 'MANAGER' AS MANAGER_JOB, 11 'PRESIDENT' AS PRESIDENT_JOB, 12 'SALESMAN' AS SALESMAN_JOB)) 13 order by deptno; DEPTNO SUM_SAL MAX_SAL MIN_SAL ANALYST_JOB CLERK_JOB MANAGER_JOB PRESIDENT_JOB SALESMAN_JOB ------ ---------- ---------- ---------- ----------- ---------- ----------- ------------- ------------ 10 8750 5000 1300 1300 2450 5000 20 10875 3000 800 6000 1900 2975 30 9400 2850 950 950 2850 5600 |
6.2、查询每个部门不同职位总工资以及每个部门不同职位最高工资:
SQL> select * 2 from (select deptno, job, sal from emp) 3 pivot(sum(sal) as sum_sal, max(sal) as sum_max 4 for job in('ANALYST' AS ANA_JOB, 5 'CLERK' AS CLE_JOB, 6 'MANAGER' AS MAN_JOB/*, 7 'PRESIDENT' AS PRE_JOB, 8 'SALESMAN' AS SAL_JOB*/) 9 ) 10 order by deptno; DEPTNO ANA_JOB_SUM_SAL ANA_JOB_SUM_MAX CLE_JOB_SUM_SAL CLE_JOB_SUM_MAX MAN_JOB_SUM_SAL MAN_JOB_SUM_MAX ------ --------------- --------------- --------------- --------------- --------------- --------------- 10 1300 1300 2450 2450 20 6000 3000 1900 1100 2975 2975 30 950 950 2850 2850 |
6.3、统计每个部门不同职位总工资和不同性别的最高工资:
SQL> select * 2 from (select deptno, job, sex, sal from emp) 3 pivot(sum(sal) as sum_sal, max(sal) as sum_max 4 for(job, sex) in(('MANAGER', '男') AS MANAGER_JOB_MAN, 5 ('MANAGER', '女') AS MANAGER_JOB_WOMAN/*, 6 ('CLERK', '男') AS CLERK_JOB_MAN, 7 ('CLERK', '女') AS CLERK_JOB_WOMAN, 8 ('ANALYST', '男') AS ANALYST_JOB_MAN, 9 ('ANALYST', '女') AS ANALYST_JOB_WOMAN, 10 ('PRESIDENT', '男') AS PRESIDENT_JOB_MAN, 11 ('PRESIDENT', '女') AS PRESIDENT_JOB_WOMAN, 12 ('SALESMAN', '男') AS SALESMAN_JOB_MAN, 13 ('SALESMAN', '女') AS SALESMAN_JOB_WOMAN*/)) 14 order by deptno; DEPTNO MANAGER_JOB_MAN_SUM_SAL MANAGER_JOB_MAN_SUM_MAX MANAGER_JOB_WOMAN_SUM_SAL MANAGER_JOB_WOMAN_SUM_MAX ------ ----------------------- ----------------------- ------------------------- ------------------------- 10 2450 2450 20 2975 2975 30 2850 2850 |
6.4、如果在pivot中增加xml显示,可以利用any设置所要操作的所有数据,目前any仅能使用在xml显示中:
SQL> select * 2 from (select deptno, job, sal from emp) pivot xml(sum(sal) for job in(any)) 3 order by deptno; |
6.5、pivot功能是将行转换为列;同样unpivot功能进行列转行操作,操作语法如下:
select *|列 [别名],... from 子查询 unpivot [include nulls|exclude nulls(默认)]( 统计函数(列)s for 转换列名称 in( 内容1 [[as] 别名], 内容2 [[as] 别名], ... ... 内容n [[as] 别名] ) ) [where 条件(s)] [group by 分组字段1,分组字段2,... ...] [having 过滤条件(s)] [order by 排序字段 asc|desc] |
如把步骤2中行转列的SQL结果再转回行:
SQL> with temp as 2 (select * 3 from (select d.dname, e.job, e.sal 4 from emp e, dept d 5 where e.deptno = d.deptno) pivot(sum(sal) for job in('ANALYST' AS 6 ANALYST_JOB, 7 'CLERK' AS 8 CLERK_JOB, 9 'MANAGER' AS 10 MANAGER_JOB, 11 'PRESIDENT' AS 12 PRESIDENT_JOB, 13 'SALESMAN' AS 14 SALESMAN_JOB)) 15 order by dname) 16 select * 17 from temp unpivot(sum_sal for job in(ANALYST_JOB as 'ANALYST', 18 CLERK_JOB as 'CLERK', 19 MANAGER_JOB as 'MANAGER', 20 PRESIDENT_JOB as 'PRESIDENT', 21 SALESMAN_JOB as 'SALESMAN')); DNAME JOB SUM_SAL -------------- --------- ---------- ACCOUNTING CLERK 1300 ACCOUNTING MANAGER 2450 ACCOUNTING PRESIDENT 5000 RESEARCH ANALYST 6000 RESEARCH CLERK 1900 RESEARCH MANAGER 2975 SALES CLERK 950 SALES MANAGER 2850 SALES SALESMAN 5600 9 rows selected |
从上述结果可以看到,这里的结果已经将列转换为行了,但是不包含nulls值得行,如果想包含nulls值的行业显示出来需要显示的生命include nulls关键字:
SQL> with temp as 2 (select * 3 from (select d.dname, e.job, e.sal 4 from emp e, dept d 5 where e.deptno = d.deptno) pivot(sum(sal) for job in('ANALYST' AS 6 ANALYST_JOB, 7 'CLERK' AS 8 CLERK_JOB, 9 'MANAGER' AS 10 MANAGER_JOB, 11 'PRESIDENT' AS 12 PRESIDENT_JOB, 13 'SALESMAN' AS 14 SALESMAN_JOB)) 15 order by dname) 16 select * 17 from temp unpivot include nulls(sum_sal for job in(ANALYST_JOB as 18 'ANALYST', 19 CLERK_JOB as 'CLERK', 20 MANAGER_JOB as 21 'MANAGER', 22 PRESIDENT_JOB as 23 'PRESIDENT', 24 SALESMAN_JOB as 25 'SALESMAN')); DNAME JOB SUM_SAL -------------- --------- ---------- ACCOUNTING ANALYST ACCOUNTING CLERK 1300 ACCOUNTING MANAGER 2450 ACCOUNTING PRESIDENT 5000 ACCOUNTING SALESMAN RESEARCH ANALYST 6000 RESEARCH CLERK 1900 RESEARCH MANAGER 2975 RESEARCH PRESIDENT RESEARCH SALESMAN SALES ANALYST SALES CLERK 950 SALES MANAGER 2850 SALES PRESIDENT SALES SALESMAN 5600 15 rows selected |
SQL行列转换6种方法的更多相关文章
- 数据透视表sql:用SQL行列转换实现数据透视的一些思考
用SQL行列转换实现数据透视的一些思考 摘要:根据对报表开发过程中碰到的需要用SQL行列转换进行解决的一类查询统计问题的分析,逐步探索求解得到一种较通用的解决思路,并用函数进行实现.该解决思路及函数实 ...
- MySQL命令执行sql文件的两种方法
MySQL命令执行sql文件的两种方法 摘要:和其他数据库一样,MySQL也提供了命令执行sql脚本文件,方便地进行数据库.表以及数据等各种操作.下面笔者讲解MySQL执行sql文件命令的两种方法,希 ...
- PL/SQL Developer执行.sql文件的几种方法
1.复制SQL 第一种方法非常常见,也非常简单,先用文本编辑器打开.sql文件,然后把sql复制到PL/SQL Developer的SQL窗口或者命令窗口中运行就行了,本来我也是这么做的,但是我将SQ ...
- mysql:sql行列转换
今天一个同学遇到一个问题问我了,由于本人平时学习的mysql比较基础,确实没解决,后来google了一下,才知道是sql的一种技法[行列转换],话不多说先上图: 想得到下面的结果: +------+- ...
- SQL行列转换:报表_公司采购表_每个公司各采购了些什么产品
有同学问了个比较典型行列转换的问题,想想,解答如下:数据库有一张表: 是个公司采购表,想转化成如下报表,显示每个公司各采购了些什么产品: 哪些公司采购哪些产品是不确定的,所以报表的列有哪几项是不确定的 ...
- sql行列转换
首先我们建立一张表,名为scoreInfo,各个字段的设计如下图,分别是name,course,score,表示姓名,成绩与分数,如图所示.
- Java学习笔记——可视化Swing中JTable控件绑定SQL数据源的两种方法
在 MyEclipse 的可视化 Swing 中,有 JTable 控件. JTable 用来显示和编辑常规二维单元表. 那么,如何将 数据库SQL中的数据绑定至JTable中呢? 在这里,提供两种方 ...
- 绝妙的SQL行列转换语句
说明:普通行列转换(version 1.0)仅针对sql server 2000提供静态和动态写法,version 2.0增加sql server 2005的有关写法. 问题:假设有张学生成绩表( ...
- sql 优化的几种方法
.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. .应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而 ...
随机推荐
- 关于tcp中time_wait状态的4个问题
time_wait是个常问的问题.tcp网络编程中最不easy理解的也是它的time_wait状态,这也说明了tcp/ip四次挥手中time_wait状态的重要性. 以下通过4个问题来描写叙述它 问题 ...
- java File.separator 简介
在Windows下的路径分隔符和Linux下的路径分隔符是不一样的,当直接使用绝对路径时,跨平台会暴出“No such file or diretory”的异常. 比如说要在temp目录下建立一个te ...
- Fiddler插件开发 - 实现网站离线浏览功能
有这么一种应用场景: 你是做前端或APP开发的,需要调用服务端提供的接口,接口只能在公司内网访问:在公司外就无法调试代码了. 想在公司外访问怎么办呢? 如果在公司的时候将所有接口的响应内容都保存起来, ...
- Source Insight 中文注释为乱码解决办法(完美解决,一键搞定)
我从网上查了一堆解决办法,但是都是2017年以前的解决方案,并且都是针对于source insight 3.5及以下版本的,目前SI软件版本都到4.0了,应该有新方法出现了. ------------ ...
- linux每日命令(21):find命令之exec
find是我们很常用的一个Linux命令,但是我们一般查找出来的并不仅仅是看看而已,还会有进一步的操作,这个时候exec的作用就显现出来了. 一. exec参数说明: -exec 参数后面跟的是com ...
- Mac下不显示设备
使用命令行adb devices 试了下,没设备列表. 第一步: 查看usb设备信息 在 终端输入:system_profiler SPUSBDataType 可以查看连接的usb设备的信息 ...
- Android Launcher分析和修改5——HotSeat分析
今天主要是分析一下Launcher里面的快捷方式导航条——HotSeat,一般我们使用手机底下都会有这个导航条,但是如果4.0的Launcher放到平板电脑里面运行,默认是没有HotSeat的,刚好我 ...
- 【GMT43智能液晶模块】例程二:串口通信实验
实验原理: GMT43智能液晶模块的串口包括USB_UART(CH340),TTL,RS-232,RS-485/ RS-422等四部分,USB_UART部分通过CH340芯片与STM32F429的US ...
- 【GMT43智能液晶模块】例程六:WWDG看门狗实验——复位ARM
实验原理: STM32内部包含窗口看门狗,通过看门狗可以监控程序运行,程序运行 错误时,未在规定时间喂狗,自动复位ARM.本实验通过UI界面中按钮按下 停止喂狗,制造程序运行错误,从而产生复位. 示例 ...
- Spring Security 认证流程
请求之间共享SecurityContext原因: