Pivot 和 Unpivot

使用简单的 SQL 以电子表格类型的交叉表报表显示任何关系表中的信息,并将交叉表中的所有数据存储到关系表中。

Pivot

如您所知,关系表是表格化的,即,它们以列-值对的形式出现。假设一个表名为 CUSTOMERS。

SQL> desc customers

Name                                      Null?    Type

----------------------------------------- -------- ---------------------------

CUST_ID                                            NUMBER(10)

CUST_NAME                                          VARCHAR2(20)

STATE_CODE                                         VARCHAR2(2)

TIMES_PURCHASED                                    NUMBER(3)

选定该表:

select cust_id, state_code, times_purchased

from customers

order by cust_id;

输出结果如下:

CUST_ID STATE_CODE TIMES_PURCHASED

------- ---------- ---------------

1 CT                       1

2 NY                      10

3 NJ                       2

4 NY                       4

...and so on

...

注意数据是如何以行值的形式显示的:针对每个客户,该记录显示了客户所在的州以及该客户在商店购物的次数。当该客户从商店购买更多物品时,列 times_purchased 会进行更新。

现在,假设您希望统计一个报表,以了解各个州的购买频率,即,各个州有多少客户只购物一次、两次、三次等等。如果使用常规 SQL,您可以执行以下语句:

select state_code, times_purchased, count(1) cnt

from customers

group by state_code, times_purchased;

输出如下:

ST TIMES_PURCHASED        CNT

-- --------------- ----------

CT               0         90

CT               1        165

CT               2        179

CT               3        173

CT               4        173

CT               5        152

...and so on

...

这就是您所要的信息,但是看起来不太方便。使用交叉表报表可能可以更好地显示这些数据,这样,您可以垂直排列数据,水平排列各个州,就像电子表格一样:

Times_purchased

CT           NY         NJ      ...and so on

...

1             0            1          0      ...

2            23          119         37      ...

3            17           45          1      ...

...and so on

...

Oracle 数据库 11g 推出之前,您需要针对每个值通过 decode 函数进行以上操作,并将每个不同的值编写为一个单独的列。但是,该方法一点也不直观。

庆幸的是,您现在可以使用一种很棒的新特性 PIVOT 通过一种新的操作符以交叉表格式显示任何查询,该操作符相应地称为pivot 。下面是查询的编写方式:

select * from (

select times_purchased, state_code

from customers t

)

pivot 

(

count(state_code)

for state_code in ('NY','CT','NJ','FL','MO')

)

order by times_purchased

/

输出如下:

. TIMES_PURCHASED       'NY'       'CT'       'NJ'       'FL'       'MO'

--------------- ---------- ---------- ---------- ---------- ----------

0      16601         90          0          0          0

1      33048        165          0          0          0

2      33151        179          0          0          0

3      32978        173          0          0          0

4      33109        173          0          1          0

... and so on ...

这表明了pivot 操作符的威力。state_codes 作为标题行而不是列显示。下面是传统的表格化格式的图示:


图 1 传统的表格化显示

在 交叉表报表中,您希望将 Times Purchased 列的位置掉换到标题行,如图 2 所示。该列变为行,就好像该列逆时针旋转 90 度而变为标题行一样。该象征性的旋转需要有一个支点 (pivot point),在本例中,该支点为 count(state_code) 表达式。


图 2 执行了 Pivot 操作的显示

该表达式需要采用以下查询语法:

...

pivot 

(

count(state_code)

for state_code in ('NY','CT','NJ','FL','MO')

)

...

第二行“for state_code ...”限制查询对象仅为这些值。该行是必需的,因此不幸的是,您需要预先知道可能的值。该限制在 XML 格式的查询将有所放宽,如本文后面部分所述。

注意输出中的标题行:

. TIMES_PURCHASED       'NY'       'CT'       'NJ'       'FL'       'MO'

--------------- ---------- ---------- ---------- ---------- ----------

列标题是来自表本身的数据:州代码。缩写可能已经相当清楚无需更多解释,但是假设您希望显示州名而非缩写(“Connecticut”而非“CT”),那又该如何呢?如果是这样,您需要在查询的 FOR 子句中进行一些调整,如下所示:

select * from (

select times_purchased as "Puchase Frequency", state_code

from customers t

)

pivot 

(

count(state_code)

for state_code in ('NY' as "New York",'CT' "Connecticut",'NJ' "New Jersey",'FL' "Florida",'MO' as "Missouri")

)

order by 1

/

Puchase Frequency   New York Connecticut New Jersey    Florida   Missouri

----------------- ---------- ----------- ---------- ---------- ----------

0      16601         90           0          0          0

1      33048        165           0          0          0

2      33151        179           0          0          0

3      32978        173           0          0          0

4      33109        173           0          1          0

...and so on

...

FOR 子句可以提供其中的值(这些值将成为列标题)的别名。

Unpivot

就像有物质就有反物质一样,有 pivot 就应该有“unpivot”,对吧?

好了,不开玩笑,但 pivot 的反向操作确实需要。假设您有一个显示交叉表报表的电子表格,如下所示:

Purchase Frequency New York Connecticut New Jersey Florida Missouri
0 12 11 1 0 0
1 900 14 22 98 78
2 866 78 13 3 9
... .        

现在,您希望将这些数据加载到一个名为 CUSTOMERS 的关系表中:

SQL> desc customers

Name                                      Null?    Type

----------------------------------------- -------- ---------------------------

CUST_ID                                            NUMBER(10)

CUST_NAME                                          VARCHAR2(20)

STATE_CODE                                         VARCHAR2(2)

TIMES_PURCHASED                                    NUMBER(3)

必须将电子表格数据去规范化为关系格式,然后再进行存储。当然,您可以使用 DECODE 编写一个复杂的 SQL*:Loader 或 SQL 脚本,以将数据加载到 CUSTOMERS 表中。或者,您可以使用pivot 的反向操作 UNPIVOT,将列打乱变为行,这在 Oracle 数据库 11g 中可以实现。

通过一个示例对此进行演示可能更简单。让我们首先使用pivot 操作创建一个交叉表:

1  create table

cust_matrix

2  as

3  select * from (

4     select times_purchased as "Puchase Frequency", state_code

5     from customers t

6  )

7  pivot

8  (

9     count(state_code)

10     for state_code in ('NY' as "New York",'CT' "Conn",'NJ' "New Jersey",'FL' "Florida",

'MO' as "Missouri")

11  )

12* order by 1

您可以查看数据在表中的存储方式:

SQL> select * from cust_matrix

2  /

Puchase Frequency   New York       Conn New Jersey    Florida   Missouri

----------------- ---------- ---------- ---------- ---------- ----------

1      33048        165          0          0          0

2      33151        179          0          0          0

3      32978        173          0          0          0

4      33109        173          0          1          0

... and so on ...

这是数据在电子表格中的存储方式:每个州是表中的一个列(“New York”、“Conn”等等)。

SQL> desc cust_matrix

Name                                      Null?    Type

----------------------------------------- -------- ---------------------------

Puchase Frequency                                  NUMBER(3)

New York                                           NUMBER

Conn                                               NUMBER

New Jersey                                         NUMBER

Florida                                            NUMBER

Missouri                                           NUMBER

您需要将该表打乱,使行仅显示州代码和该州的购物人数。通过unpivot 操作可以达到此目的,如下所示:

select *

from cust_matrix

unpivot

(

state_counts

for state_code in ("New York","Conn","New Jersey","Florida","Missouri")

)

order by "Puchase Frequency", state_code

/

输出如下:

Puchase Frequency STATE_CODE STATE_COUNTS

----------------- ---------- ------------

1 Conn                165

1 Florida               0

1 Missouri              0

1 New Jersey            0

1 New York          33048

2 Conn                179

2 Florida               0

2 Missouri              0

...and so on

...

注意每个列名如何变为 STATE_CODE 列中的一个值。Oracle 如何知道 state_code 是一个列名?它是通过查询中的子句知道的,如下所示:

for state_code in ("New York","Conn","New Jersey","Florida","Missouri")

这里,您指定“New York”、“Conn”等值是您要对其执行 unpivot 操作的 state_code 新列的值。我们来看看部分原始数据:

Puchase Frequency   New York       Conn New Jersey    Florida   Missouri

----------------- ---------- ---------- ---------- ---------- ----------

1      33048        165          0          0          0

当列“纽约”突然变为一个行中的值时,您会怎样显示值 33048 呢?该值应该显示在哪一列下呢?上述查询中unpivot 操作符内的 for 子句上面的子句对此进行了解答。您指定了 state_counts,它就是在生成的输出中创建的新列的名称。

Unpivot 可以是pivot 的反向操作,但不要以为前者可以对后者所进行的任何操作进行反向操作。例如,在上述示例中,您对 CUSTOMERS 表使用pivot 操作创建了一个新表 CUST_MATRIX。然后,您对 CUST_MATRIX 表使用了unpivot ,但这并没有取回原始表 CUSTOMERS 的详细信息。相反,交叉表报表以便于您将数据加载到关系表中的不同方式显示。因此unpivot 并不是为了取消pivot 所进行的操作。在使用 pivot 创建一个表然后删除原始表之前,您应该慎重考虑。

unpivot 的某些很有趣的用法超出了通常的强大数据操作功能范围(如上面的示例)。Amis Technologies 的 Oracle ACE 总监 Lucas Jellema 介绍了如何生成若干行特定数据用于 测试 。在此,我将对他的原始代码稍加修改,以显示英语字母表中的元音:

select value

from

(

(

select

'a' v1,

'e' v2,

'i' v3,

'o' v4,

'u' v5

from dual

)

unpivot

(

value

for value_type in

(v1,v2,v3,v4,v5)

)

)

/

输出如下:

V

-

a

e

i

o

u

该模型可以扩展为包含任何类型的行生成器。感谢 Lucas 为我们提供了这一巧妙招术。

XML 类型

在上述示例中,注意您指定有效的 state_codes 的方式:

for state_code in ('NY','CT','NJ','FL','MO')

该要求假设您知道 state_code 列中显示的值。如果您不知道都有哪些值,您怎么构建查询呢?

pivot 操作中的另一个子句 XML 可用于解决此问题。该子句允许您以 XML 格式创建执行了 pivot 操作的输出,在此输出中,您可以指定一个特殊的子句 ANY 而非文字值。示例如下:

select * from (

select times_purchased as "Purchase Frequency", state_code

from customers t

)pivot xml

(

count(state_code)for state_code in (any)

)

order by 1

/

输出恢复 为 CLOB 以确保 LONGSIZE 在查询运行之前设置为大值。

SQL> set long 99999

较之原始的pivot 操作,该查询有两处明显不同(用粗体显示)。首先,您指定了一个子句 pivot xml 而不只是pivot 。该子句生成 XML 格式的输出。其次,for 子句显示 for state_code in (any) 而非长列表的 state_code 值。该 XML 表示法允许您使用 ANY 关键字,您不必输入 state_code 值。输出如下:

Purchase Frequency STATE_CODE_XML

------------------ --------------------------------------------------

1 <PivotSet><item><column name = "STATE_CODE">CT</co

lumn><column name = "COUNT(STATE_CODE)">165</colum

n></item><item><column name = "STATE_CODE">NY</col

umn><column name = "COUNT(STATE_CODE)">33048</colu

mn></item></PivotSet>

2 <PivotSet><item><column name = "STATE_CODE">CT</co

lumn><column name = "COUNT(STATE_CODE)">179</colum

n></item><item><column name = "STATE_CODE">NY</col

umn><column name = "COUNT(STATE_CODE)">33151</colu

mn></item></PivotSet>

... and so on ...

如您所见,列 STATE_CODE_XML 是 XMLTYPE,其中根元素是 <PivotSet>。每个值以名称-值元素对的形式表示。您可以使用任何 XML 分析器中的输出生成更有用的输出。

除了 ANY 子句外,您还可以编写一个子查询。假设您有一个优先州列表并希望仅选择这些州的行。您将优先州放在一个名为 preferred_states 的新表中:

SQL> create table preferred_states

2  (

3     state_code varchar2(2)

4  )

5  /

Table created.

SQL> insert into preferred_states values ('FL')

2> /

1 row created.

SQL> commit;

Commit complete.

现在pivot 操作如下所示:

select * from (

select times_purchased as "Puchase Frequency", state_code

from customers t

)

pivot xml

(

count(state_code)

for state_code in (select state_code from preferred_states)

)

order by 1

/

for 子句中的子查询可以是您需要的任何内容。例如,如果希望选择所有记录而不限于任何优先州,您可以使用以下内容作为 for 子句:

for state_code in (select distinct state_code from customers)

子查询必须返回不同的值,否则查询将失败。这就是我们指定上述 DISTINCT 子句的原因。

结论

Pivot 为 SQL 语言增添了一个非常重要且实用的功能。您可以使用 pivot 函数针对任何关系表创建一个交叉表报表,而不必编写包含大量 decode 函数的令人费解的、不直观的代码。同样,您可以使用unpivot 操作转换任何交叉表报表,以常规关系表的形式对其进行存储。Pivot 可以生成常规文本或 XML 格式的输出。如果是 XML 格式的输出,您不必指定 pivot 操作需要搜索的值域。

有关 pivot 和 unpivot 操作的详细信息,请参考Oracle 数据库 11g SQL 语言参考

转自http://space.itpub.net/1384/viewspace-217977

Pivot 和 Unpivot相关推荐

  1. SQL Server 2008中的Pivot和UnPivot

    SQL Server 2008中SQL应用系列--目录索引 今天给新成员讲解PIVOT 和 UNPIVOT示例,顺便整理了一下其用法.这是自SQL Server 2005起提供的新功能. 官方示例:h ...

  2. [SQL指令]使用 PIVOT 和 UNPIVOT,「扭转」查询的结果。

    原文出处:[SQL指令]使用 PIVOT 和 UNPIVOT,「扭转」查询的结果. http://www.dotblogs.com.tw/mis2000lab/archive/2010/12/24/m ...

  3. PIVOT 和 UNPIVOT 命令的SQL Server版本

    I:使用 PIVOT 和 UNPIVOT 命令的SQL Server版本要求 1.数据库的最低版本要求为 SQL Server 2005 或 更高 2.必须将数据库的兼容级别设置为 90 或 更高 3 ...

  4. pivot 与 unpivot 函数是SQL05新提供的2个函数

    pivot 与 unpivot 函数是SQL05新提供的2个函数   ----------------------------------------------------------------- ...

  5. oracle非常量不能用于privot_Oracle 行列转换函数pivot、unpivot的使用(二)

    一.行转列pivot 关键函数pivot,其用法如下 pivot(聚合函数 for 列名 in(类型)) select * from table_name pivot(max(column_name) ...

  6. pivot unpivot_静态和动态SQL Pivot和Unpivot关系运算符概述

    pivot unpivot In this article, we'll walk-through the SQL Pivot and SQL Unpivot operators and how th ...

  7. SQL 行转列 列转行 Oracle转置函数函数pivot、unpivot 解决wm_concat 没有排序

    https://www.cnblogs.com/mellowsmile/p/4642306.html HH 终风且暴,顾我则笑,谑浪笑敖,中心是悼. 终风且霾,惠然肯来,莫往莫来,悠悠我思. 博客园 ...

  8. PIVOT和UNPIVOT使用详解

    一.使用PIVOT和UNPIVOT命令的SQL Server版本要求 1.数据库的最低版本要求为SQL Server 2005 或更高. 2.必须将数据库的兼容级别设置为90 或更高. 3.查看我的数 ...

  9. 表横竖转换(行列转换)PIVOT 和 UNPIVOT 用法

    可以使用 PIVOT 和 UNPIVOT 关系运算符将表值表达式更改为另一个表.PIVOT 通过将表达式某一列中的唯一值转换为输出中的多个列来旋转表值表达式,并在必要时对最终输出中所需的任何其余列值执 ...

  10. 数据透视表与mysql_通过sql做数据透视表,数据库表行列转换(pivot和Unpivot用法)(一)...

    在mssql中大家都知道可以使用pivot来统计数据,实现像excel的透视表功能 一.MSsqlserver中我们通常的用法 1.Sqlserver数据库测试 ---创建测试表 Create tab ...

最新文章

  1. 想自学stm32不知道怎么买板子?我来告诉你新手该买哪一个!
  2. sklearn解决回归问题
  3. python中判断字符是否为中文
  4. 不要光仅仅知道ipconfig了,你out了
  5. SpringBoot 项目打包部署
  6. 浙大计算机学院绩点,浙江大学学分制管理暂行规定-浙江大学本科生院.doc
  7. C#之float数组转字节流
  8. php静态网页和动态网页,静态网页和动态网页的区别是什么
  9. 《FLUENT 14流场分析自学手册》——导读
  10. arcgis for android(二)显示二维地图
  11. mysql命令(客户端登陆与mysql -uxxx -pxxx的区别;环境变量的使用)
  12. mt6799芯片资料mt6799参考设计资料
  13. Localize Folders and Reports
  14. 程序异常终止:Process finished with exit code -1073741819 (0xC0000005)
  15. 数据库技术:关系型数据库设计总结
  16. 【C语言内功心法】__weak -- 示弱也是一种强大
  17. linux设备驱动中的module_init
  18. Arduino基础入门之十三 温湿度传感器DHT11
  19. 0.前端简历编写和面试前准备
  20. 苹果手机输入屏保后锁屏_iPhone丢失后锁屏显示你的联系方式方法教程

热门文章

  1. 海宝等离子服务器进不了系统,海宝等离子电源机用切割维修常见故障及处理方法...
  2. imageview设置边框
  3. 交通标志结构计算软件开发进程
  4. viewbag 转换html,如何将数据从viewbag传递到CSHTML中的模型
  5. TFT模块闪屏(Flicker)现象分析
  6. vue fullCalendar的使用及扩展(支持农历显示及鼠标右键新建)
  7. MANET模拟器和拓扑可视化研究(Matlab代码实现)
  8. AI开发者看过来:搞定这四道赛题 赢取30万大奖
  9. windows系统实现自动登录
  10. bootp服务器linux,Linux bootp和tftp设置