xsb Oracle Blog

暂无签名

  • 博客访问: 3064866
  • 博文数量: 343
  • 用 户 组: 普通用户
  • 注册时间: 1970-01-01 08:00
个人简介

鏆傛棤浠嬬粛

文章分类

全部博文(343)

文章存档

2012年(1)

2011年(6)

2010年(2)

2009年(6)

2008年(32)

2007年(28)

2006年(182)

2005年(78)

2004年(8)

分类: Oracle

2009-01-06 13:42:50

在 Oracle 数据库 11g 推出之前,您需要针对每个值通过使用decode 函数或case语句进行列转换操作,
并将每个不同的值编写为一个单独的列。但是,该方法不是很不直观也不是很方便。
oracle11g可以使用一种很好的新特性PIVOT和UNPIVOT,通过一种新的操作符以交叉表格式显示任何查询,
oracle为了实现更强大的数据仓库应用,在SQL和PL/SQL方面有很大的加强。

SQL Server2005引入了很多迎合开发者口味的新特性,虽然改动不大,却大大了减少了开发者的工作量,这种替用户考虑的开发思路,值得称赞。

[@more@]
1. 11g以前的行列转换

领袖又说了:“温故而知新”。那就让我们先看看11g以前是怎么实现地。行列转换一直当作甄别老手和新手的试金石,面试的时候面试官不问这个都不好意思张嘴。Itpub的Oracle开发版更是每隔十天半个月就有人问这个,你说重要不重要。

假设有表emp_phone如下:

NAME TYPE PHONE
张三 1 1234-5678
张三 2 4567-7890
张三 3 6000-1001
李四 1 2123-1237
李四 3 6001-5600
马五u 1 3248-1378
马五 2 3423-3948
王二(没麻子) 2 2890-1245
。。。

表里放着张三李四王二麻子等等主人翁的电话号码。(TYPE 1/2/3分别对应家/办公室/手机)。如果要把每个人的所有电话放在一行上,就是行转列了。结果如下:

NAME HOME OFFICE MOBILE
张三 1234-5678 4567-7890 6000-1001
李四 2123-1237 6001-5600
马五 3248-1378 3423-3948
王二(没麻子) 2890-1245


写这个SQL的技巧就是按姓名分组,然后使每一组每一类的电话号码最多只有一个,里边用到的分组函数都是聋子的耳朵-摆设。用MAX可以,MIN也行。

这个查询写出来就是: SELECT
name,
MAX(decode(type, 1, phone)) Home,
MAX(decode(type, 2, phone)) Office,
MAX(decode(type, 3, phone)) Mobile
FROM
emp_phone
GROUP BY
Name



那位看官说了:“能不能再变回去?”能,不能戏法不就漏了不是?

这儿要用到另一的技巧就是笛卡尔乘积,将一行复制成三行,每一行取一个类型的电话

偷个懒儿把上边的结果表叫emp_phone_x,把列还原成行的SQL: SELECT
NAME,
DECODE (lvl, 1, home, 2, office, 3, mobile) phone
FROM
emp_phone_x,
(SELECT LEVEL lvl
FROM DUAL
CONNECT BY LEVEL <= 3)
WHERE
DECODE (lvl, 1, home, 2, office, 3, mobile) IS NOT NULL /


2. 11g 自带的行列转换

旁边那个带眼镜,说的就是你,眼珠子直勾勾的怎么了?上面的没看懂? 要是以前,我老先生就得语重心长地教育你,那么重要的东西没看懂,将来想不想换工作了?但现在这话就说不出口了,因为11g的SQL自己就带这个了。

11g在SELECT语句中新加了关键词PIVOT和UNPIVOT,用这两个关键词,重写上面的两个查询,就变成这个样子的了:

行变列: SELECT * FROM emp_phone
PIVOT (
MAX(phone) for type IN (1 as home, 2 as office, 2 as mobile)
)



PIVOT以后的字句都是新加的。但万变不离其宗,还是要用到分组函数。IN后边是按type的不同值映射成不同的列。简单吧?

列变行,这是UNPIVOT的工作,写法如下: SELECT * FROM emp_phone_x
UNPIVOT (
phone FOR type in (HOME AS 1, OFFICE AS 2, MOBILE AS 3)
)
/


这里是把不同的列转换成不同的type的数值。

再用SCOTT用户里的EMP表做个例子,列出各部门之间工资总和: SELECT * FROM
(
(SELECT sal, deptno FROM emp)
PIVOT (
SUM(sal) FOR deptno IN (10 as dept_10, 20 as dept_20, 30 as dept_30)
)
)
/
DEPT_10 DEPT_20 DEPT_30
---------- ---------- ----------
8750 10875 9400


再往深里想,前边的所有例子都有一个局限,电话的type和emp的deptno都是有限的、可穷举的。如果这些列都是可随时可添加的,又该怎么办呢?11g以前肯定是要动用动态SQL的法宝。那11g又是怎么处理的呢?刚看SQL参考手册的时候,看到里边豁然写着IN后边可以接子查询或ANY,当时是佩服的眼泪哗哗的,迫不及待赶紧试一试: SELECT * FROM
(
(SELECT sal, deptno FROM emp)
PIVOT (
SUM(sal) FOR deptno IN (SELECT deptno FROM dept)
)
)
/
ERROR at line 5:
ORA-00936: missing expression
SELECT * FROM
(
(SELECT sal, deptno FROM emp)
PIVOT (
SUM(sal) FOR deptno IN (ANY)
)
)
/
ERROR at line 5:
ORA-00936: missing expression


这一下又变成拔凉拔凉的,这么大个ORACLE也不能无耻到这个地步吧?正准备再确认一下手册,抓他个人赃俱获,突然有发现里边豁然写着: A subquery is used only in conjunction with the XML keyword…

The ANY keyword is used only in conjunction with the XML keyword…

学习不认真,该打。原来是给生成XML串用的,正确用法如下: SELECT * FROM
(
(SELECT sal, deptno FROM emp)
PIVOT XML (
SUM(sal) FOR deptno IN (ANY)
)
)


10
8750


20
10875


30
9400




这个东西的结果具体怎么用就留给大家做作业了。反正XML咱也不熟,借这个机会就下了。。。
------------------------------------------------------------

在SQL Server2000中,要实现行列转换,需要综合利用聚合函数和动态SQL,实现起来需要一定的技巧,所以在CSDN的SQL讨论区里可以看到大量询问行列转换如何实现的问题。到了2005中,使用新引进的关键字PIVOT/UNPIVOT,可以轻松实现行列转换的需求。

好像Oracle11g也准备引入PIVOT/UNPIVOT特性,对于Oracle开发来说,It's a good news。

本文通过两个简单的例子展示PIVOT/UNPIVOT的用法。详细的语法请参考联机帮助。

PIVOT

创建测试表,插入测试数据

create table test(id int,name varchar(20),quarter int,profile int)
insert into test values(1,'a',1,1000)
insert into test values(1,'a',2,2000)
insert into test values(1,'a',3,4000)
insert into test values(1,'a',4,5000)
insert into test values(2,'b',1,3000)
insert into test values(2,'b',2,3500)
insert into test values(2,'b',3,4200)
insert into test values(2,'b',4,5500)

select * from test
id name quarter profile
----------- -------------------- ----------- -----------
1 a 1 1000
1 a 2 2000
1 a 3 4000
1 a 4 5000
2 b 1 3000
2 b 2 3500
2 b 3 4200
2 b 4 5500

(8 row(s) affected)

利用PIVOT将个季度的利润转成横向显示:

select id,name,
[1] as "一季度",
[2] as "二季度",
[3] as "三季度",
[4] as "四季度"
from
test
pivot
(
sum(profile)
for quarter in
([1],[2],[3],[4])
)
as pvt

id name 一季度 二季度 三季度 四季度
----------- -------------------- ----------- ----------- ----------- -----------
1 a 1000 2000 4000 5000
2 b 3000 3500 4200 5500

(2 row(s) affected)

UNPIVOT

建立测试表,插入测试数据

drop table test

create table test(id int,name varchar(20), Q1 int, Q2 int, Q3 int, Q4 int)

insert into test values(1,'a',1000,2000,4000,5000)
insert into test values(2,'b',3000,3500,4200,5500)


select * from test

id name Q1 Q2 Q3 Q4
----------- -------------------- ----------- ----------- ----------- -----------
1 a 1000 2000 4000 5000
2 b 3000 3500 4200 5500

(2 row(s) affected)

利用UNPIVOT,将同一行中四个季度的列数据转换成四行数据:

select id,name,quarter,profile
from
test
unpivot
(
profile
for quarter in
([Q1],[Q2],[Q3],[Q4])
)
as unpvt

id name quarter profile
----------- -------------------- ---------- -----------
1 a Q1 1000
1 a Q2 2000
1 a Q3 4000
1 a Q4 5000
2 b Q1 3000
2 b Q2 3500
2 b Q3 4200
2 b Q4 5500

(8 row(s) affected)

阅读(5437) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~
评论热议
请登录后评论。

登录 注册