今天在浏览oracle优化、调优相关的博客,无意中看到了oracle的pivot函数,学习了,为了以后复习,先收藏了,下面是转载的博客和相关博客。

转载博客:http://blog.itpub.net/17203031/viewspace-754807【请尊重原创,谢谢】

相关博客:http://5073392.blog.51cto.com/5063392/1536828



作为数据库应用开发人员,我们有很大的精力应付在处理各种各样的数据类型,展现各种维度的报表上面。

行转列、列转行是我们经常会遇到的“诡异”需求。标准SQL没有提供此类型操作的支持函数,早期Oracle的版本中,我们也只能通过复杂的变通手段实现。在Oracle11g中,这种情况改变了,我们可以利用pivot函数轻松实现行列互转并且聚合的查询需求。

 

 

1、环境准备

 

 

我们选择Oracle 11g进行实验。

 

 

SQL> select * from v$version;

BANNER

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

Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production

PL/SQL Release 11.2.0.1.0 - Production

CORE 11.2.0.1.0 Production

 

 

 

创建数据表T,进行有选择的数据筛选。

 

 

SQL> create table t (owner varchar2(30), object_type varchar2(100), value number);

Table created

SQL> insert into t select owner, object_type, object_id from dba_objects where object_type in ('TABLE','INDEX');

10386 rows inserted

SQL> commit;

Commit complete

 

 

 

每个owner最多有两种类型的type值,每个ownerobject_type组合包括多条记录。

 

 

SQL> select * from t where rownum<5;

OWNER OBJECT_TYP      VALUE

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

SYS   TABLE              20

SYS   INDEX              46

SYS   TABLE              28

SYS   TABLE              15

 

 

要求以ownerobject_type进行汇总,并且将TABLEINDEX分别成列,显示汇总值。简而言之,就是一个owner一行记录,每列对应一种object_type取值。

 

2、行转列处理

 

 

按照抽丝剥茧的原则,我们聚合一次ownerobject_type

 

 

--聚合汇总结

 

SQL> select owner, object_type, sum(value) from t group by owner, object_type;

OWNER                OBJECT_TYP SUM(VALUE)

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

SCOTT                INDEX          146392

PRITEST              TABLE          172006

(篇幅原因,有省略......

DICT                 INDEX        18490973

COMMON               TABLE         9076700

63 rows selected

 

 

 

下面就是避免不了的行转列操作了。在11g以前的版本中,我们可能需要借助专门的复杂SQL书写乃至存储过程才能解决。在11g中,我们可以使用pivot函数,一次性的将聚合操作和行转列操作完成。

 

 

 

select *

from t

pivot 

(

   sum(value)  --聚合操作函数

   for object_type --行转列标准

   in ('TABLE','INDEX') --行转列列取值和顺序

)

 

 

上面的SQL就是pivot函数的基本格式。看起来非常简单,主要要定义三个部分的内容。

 

 

ü 聚合列取值。需要告诉pivot函数进行转列的过程中,聚合操作的函数和处理对象;

ü 行转列标准。依据那个列进行行转列;

ü 列转行取值。因为要将数据行取值转成列,我们需要告诉Oracle那些取值成列,并且这些取值成列的过程中,列顺序是如何的;

 

 

 

下面我们看执行结果。

 

 

SQL> select *

  2  from t

  3  pivot

  4  (

  5     sum(value)

  6     for object_type

  7     in ('TABLE','INDEX')

  8  )

  9  ;

OWNER                   'TABLE'    'INDEX'

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

MDSYS                   8003137    7378327

MELODY                   299266 

RMAN                    8292775   18094518

OUTLN                      1351       1805

(篇幅原因,有省略......

COMMON                  9076700   17161092

34 rows selected

处理成功,我们查看一下执行计划。

SQL> explain plan for select *

  2  from t

  3  pivot

  4  (

  5     sum(value)

  6     for object_type

  7     in ('TABLE','INDEX')

  8  );

Explained

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT

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

Plan hash value: 3924414983

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

| Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)| Time     |

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

|   0 | SELECT STATEMENT    |      |    34 |   612 |    13   (8)| 00:00:01 |

|   1 |  HASH GROUP BY PIVOT|      |    34 |   612 |    13   (8)| 00:00:01 |

|   2 |   TABLE ACCESS FULL | T    | 10386 |   182K|    12   (0)| 00:00:01 |

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

9 rows selected

 

 

 

从执行计划上看,Oracle为了pivot函数引入了一个专门操作pivot。执行计划中的hast group by pivot操作就是将数据转移到专门的处理区(猜想是PGA)进行流程化的处理。

下面是使用autotrace处理过的情况。

 

 

SQL> set autotrace traceonly;

SQL> select *

  2  from t

  3  pivot

  4  (

  5     sum(value)

  6     for object_type

  7     in ('TABLE','INDEX')

  8  );

已选择34行。

执行计划

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

Plan hash value: 3924414983

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

| Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)| Time     |

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

|   0 | SELECT STATEMENT    |      |    34 |   612 |    13   (8)| 00:00:01 |

|   1 |  HASH GROUP BY PIVOT|      |    34 |   612 |    13   (8)| 00:00:01 |

|   2 |   TABLE ACCESS FULL | T    | 10386 |   182K|    12   (0)| 00:00:01 |

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

统计信息

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

        196  recursive calls

          0  db block gets

         65  consistent gets

         42  physical reads

          0  redo size

       1503  bytes sent via SQL*Net to client

        441  bytes received via SQL*Net from client

          4  SQL*Net roundtrips to/from client

          3  sorts (memory)

          0  sorts (disk)

         34  rows processed

 

 

 

注意在统计信息中,引用了3sorts操作。

应该说,pivot函数是我们可以找到比较好的行转列的操作方式了。在Oracle端,是要消耗一定的排序操作。当数据集合特别大的时候,我们可以猜测会有一些性能的退化(特别是发生sorts(disk))。Pivot作为标准Oracle函数,Oracle也会不断进行优化处理。所以,性能一般是可以接受的。

 

3、进一步的需求

 

在结果集合中,我们看到了结果标题如下:

 

 

 

OWNER                   'TABLE'    'INDEX'

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

MDSYS                   8003137    7378327

MELODY                   299266 

 

 

 

Oracle是使用两个列值作为标题。那么我们结果如何引用这个列

名称呢?

 

 

SQL> select owner, 'TABLE','INDEX' from (

  2  select *

  3  from t

  4  pivot

  5  (

  6     sum(value)

  7     for object_type

  8     in ('TABLE','INDEX')

  9  )) where rownum<3;

OWNER                'TABLE' 'INDEX'

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

APEX_030200          TABLE   INDEX

APPQOSSYS            TABLE   INDEX

 

 

 

显然,Oracle将其识别为了字符串。应该怎么做呢?我们需要使用双引号。之所以Oracle会识别错误,就因为’’的常量识别优先级高于列名称优先级。我们需要让Oracle忽略这个特点,就使用双引号。

 

 

SQL> select owner, 'TABLE' PRI_TYP,"'TABLE'" PRI_AMT,'INDEX' SEC_TYP,"'INDEX'" SEC_AMT from (

  2  select *

  3  from t

  4  pivot

  5  (

  6     sum(value)

  7     for object_type

  8     in ('TABLE','INDEX')

  9  )) where rownum<3;

OWNER                PRI_TYP    PRI_AMT SEC_TYP    SEC_AMT

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

APEX_030200          TABLE     25757431 INDEX     67809456

APPQOSSYS            TABLE        25887 INDEX   

 

 

 

实现成功!

 

 

4、结论

 

 

在现实工作中,我们遇到的需求千奇百怪。在选择工具的时候,要有选择类库、预定义提供的方法函数,不要轻易的选择自定义方法。因为对现成的类库和产品来说,它在实现这个方法的时候倾注了很大心血和能力在其中,其效率一定是比我们自己写的要强很多。这也就要求我们需要不断关注新特性,新方法和改进,不断的学习和进步。