• Oracle 分析函数(转载)


     

    实际应用

    我们在做项目中常遇到类似这样的统计需求 , 列出一些数据列表,最后来一个合计的功能,类似如下:             

    姓名

    工资

    SMITH

    800

    ALLEN

    1600

    WARD

    1250

    JONES

    2975

    MARTIN

    1250

    BLAKE

    2850

    CLARK

    2450

    SCOTT

    3000

    KING

    5000

    TURNER

    1500

    ADAMS

    1100

    JAMES

    950

    FORD

    3000

    MILLER

    1300

    合计

    29025

    表结构基本如下:

    字段

    数据类型

    EMPNO

    NUMBER(4)

    ENAME

    VARCHAR2(10)

    JOB

    VARCHAR2(9)

    MGR

    NUMBER(4)

    HIREDATE

    DATE

    SAL

    NUMBER(7,2)

    COMM

    NUMBER(7,2)

    DEPTNO

    NUMBER(2)

    实现这样的功能,一般通过如下四种方式实现:

    简称

    实现方式

    实现方法

    a

    程序

    取出符合条件的数据列表,在程序中遍历该数据列表统计合计数据,在列表中增加一行合计统计数据,最后页面展现信息

    b

    oracle特性功能(临时表、包)

    创建临时表,创建包将查询数据以及统计的数据插入到临时表中,返回一个游标,程序遍历该游标,生成包含统计的数据列表

    c

    标准sql

    通过sql union 联合
    select 'total',sum(sal) from emp union select ename,sal from emp

    d

    oracle特性功能(分析函数)

    select nvl(ename,'total'),sum(sal) from emp group by rollup(ename);

    在没有学习本章前,我一般是通过方式 a b 的方式实现用户需求。

    简单评价一下四种实现方式:

    实现方式

    优点

    缺点

    a

    和数据库无关,这对一个在多个数据库的产品来说比较适用

    实际上没有特别的缺点,就是多了一定的开发工作量

    b

    主要体现在复杂的应用场景中,如果统计数据要和其他表关联查询,分析,采用a方式往往需要写大量的java代码并且多了很多次数据库交互,

    绑定了数据库,对应的数据库必须支持临时表功能,并且每次移值都需要重写对应数据库的过程代码, 实际开发的工作量比a方式可能更大

    c

    比较简单,开发工作量比较小,是标准sql,一般数据库均支持

    性能是相对比较差,如果数据量不大可以考虑

    d

    相对于c,d性能更好,和a、b方式性能应该差距不大。
    相对于b方式优点就是工作量较小,

    绑定oracle数据库

    比较

    方式c

    方式d

    分析

    对emp表做了两次全表扫描

    对emp表做一次全表扫描

    测试数据

    测试数据库中测试简单统计百万级数据表时间差异在2秒左右,还是比较大的

    前提条件

    我们的项目一般很少会做数据库迁移(那个代价是非常昂贵的),如果在确定使用oracle数据库,熟悉其特性功能对开发还是非常有帮助的,可以考虑在开发时使用相关特性、提高开发效率。

    使用心得

        oracle分析函数实际上操作对象是查询出的数据集,也就是说不需二次查询数据库,实际上就是oracle实现了一些我们自身需要编码实现的统计功能,对于简化开发工作量有很大的帮助,特别在开发第三方报表软件时是非常有帮助的。

    1.1基本语法

    oracle分析函数的语法:

    function_name(arg1,arg2,...)

    over

    (<partition-clause> <order-by-clause ><windowing clause>)

          说明:

    1.       partition-clause 数据记录集分组

    2.       order-by-clause   数据记录集排序

    3.       windowing clause 功能非常强大、比较复杂,定义分析函数在操作行的集合

    例一

    通过avg分析函数实现查询每个人的工资,以及对应部门的平均工资,

    select ename,sal, avg(sal) over ( partition by deptno order by deptno) from emp;

    查询结果:

    ENAME

    SAL

    AVG_DEPT

    CLARK

    2450

    2916.6667

    KING

    5000

    2916.6667

    MILLER

    1300

    2916.6667

    SMITH

    800

    2175

    ADAMS

    1100

    2175

    FORD

    3000

    2175

    SCOTT

    3000

    2175

    JONES

    2975

    2175

    ALLEN

    1600

    1566.6667

    BLAKE

    2850

    1566.6667

    MARTIN

    1250

    1566.6667

    JAMES

    950

    1566.6667

    TURNER

    1500

    1566.6667

    WARD

    1250

    1566.6667

    1.1.1 partition-clause

    数据记录集分组, 比较好理解,就不多说。

    1.1.2 order-by-clause

    a 、要和查询对应的记录集排序一致,否则统计数据交叉比较很理解。

    b 、如果查询条件表达式没有排序语句,返回记录集会按照 order-by-clause 排序

    1.1.3 windowing-clause

    个人理解其为分析函数统计数据范围设定。

    a、窗口使用前提:分析函数必须有order-by-clause语句

    b、默认窗口范围:RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW  

    c、窗口有三种:range、row、specifying

    1.1.3.1 range窗口

    只对日期和数字类型数据生效,只能按照一个字段排序,在对应的字段数据范围内进行搜索.说明范围和排序的升降有关

    a、升序,查找[本行字段数据值-range值,本行数据值]数据集合

    b、降序, 查找[本行数据值,本行字段数据值+range值,]数据集合

     

    例二

    查询本人工资以及和本人工资差距在100内的员工个数(和自己相同工资的算高于自己)

    select ename,sal,greater_num+lower_num

    from

    (select ename,sal,

    count( ename) over ( order by sal desc range 100 preceding)-1

    as greater_num ,

    ((count(ename) over ( order by sal asc range 100 preceding)-1) -

    (count(ename) over ( order by sal asc range 0 preceding)-1))

    as lower_num

    from emp) a

    order by sal asc;

    查询结果:

    ENAME

    SAL

    GREATER_NUM

    SMITH

    800

    0

    JAMES

    950

    0

    ADAMS

    1100

    0

    WARD

    1250

    2

    MARTIN

    1250

    2

    MILLER

    1300

    2

    TURNER

    1500

    1

    ALLEN

    1600

    1

    CLARK

    2450

    0

    BLAKE

    2850

    0

    JONES

    2975

    2

    SCOTT

    3000

    2

    FORD

    3000

    2

    KING

    5000

    0

     

    1.1.3.2 row 窗口

        row 窗口是设定分析函数的数据行数,使用该窗口基本没有限制

               rows n preceding

        即为该窗口数据包括本行前的 n 行以及本行共 (n+1) 行数据

    1.1.3.3 specifying 窗口

        实际上统计的函数都是由 specifying 窗口设定, range row 窗口实际是指定了分析的对象(字段、数据行),而具体的行数由 specifying 窗口设定,常用表达式如下:

        unbounded preceding   从当前分区第一行开始,结束于处理的当前行

        current row         从当前行开始 ( 并结束 )

        numberic expression preceding 从当前行的数字表达式之前的行开始

        numberic expression following 从当前行的数字表达式之前的行结束

        在这边可以简化以前的前面的 sql, 查询本人工资以及和本人工资差距在100内的员工个数,sql如下:

        select ename,sal,
    count( ename) over ( orderby sal ascrangebetween100 preceding and 100 following)-1
    as all_num
    from emp

    ENAME

    SAL

    GREATER_NUM

    SMITH

    800

    0

    JAMES

    950

    0

    ADAMS

    1100

    0

    WARD

    1250

    2

    MARTIN

    1250

    2

    MILLER

    1300

    2

    TURNER

    1500

    1

    ALLEN

    1600

    1

    CLARK

    2450

    0

    BLAKE

    2850

    0

    JONES

    2975

    2

    SCOTT

    3000

    2

    FORD

    3000

    2

    KING

    5000

    0

    数据一致。

    1.2 常用分析函数

    1.         avg(distinct|all expression)   计算组内平均值, distinct 可去除组内重复数据

    (参见 #例一 )。

    2.         count(<distinct><*><expression>) 对组内数据进行计数 (参见 #例二 )。

    3.         cume_dist()   计算一行在组中的相对位置,值的范围( 0 1 ]

    4.         dense_rank() 根据 order by 子句表达式的值,从查询返回的每一行,计算和其他行的相对位置,序号从 1 开始,有重复值时序号不跳号。

    这个函数比较重要,

    例三

    统计每个部门工资前三名的人员信息(重复人员也展现)

    select ename,sal,deptno from
    (select ename,sal,deptno,dense_rank() over (partitionby deptno orderby sal desc ) as seq_num
    from emp
    ) a
    where seq_num<=3

    查询结果

    ENAME

    SAL

    deptno

    KING

    5000

    10

    CLARK

    2450

    10

    MILLER

    1300

    10

    SCOTT

    3000

    20

    FORD

    3000

    20

    JONES

    2975

    20

    ADAMS

    1100

    20

    BLAKE

    2850

    30

    ALLEN

    1600

    30

    TURNER

    1500

    30

    5.       first_value(显示的表达式),last_value(显示的表达式) 返回组内第一个值,最后一个值。

    例四

    查询每个部门工资高和最低的人

    一般查询sql

    select max(sal),min(sal),deptno from emp groupby deptno

    但是无法查询对应人员名称,通过分析函数可以变通实现

        select distinct deptno,
    first_value(ename||' : ' ||sal) over ( partitionby deptno orderby sal) asfirst,

    first_value(ename||' : ' ||sal) over ( partitionby deptno orderby sal desc) aslast
    from emp;

       要说明的last_value()并不类似于max函数,从分析函数语法解析知道,默认窗口下的last_value分析的是当前组的当前的以前数据行以及当前行,因此

       first_value(ename||' : ' ||sal) over ( partitionby deptno orderby sal)

       并不等同于

       last_value(ename||' : ' ||sal) over (partitionby deptno orderby sal desc)

    6.       min(expression),max(expression)    返回组内最小,最大值

      select  distinct max(sal) over (partitionby deptno),min(sal) over(partitionby deptno),deptno from emp

     该sql和

    select max(sal),min(sal),deptno from emp groupby deptno有点类似

     查看过两者的执行计划,采用分析函数多做了一次排序(在大数据量下没有做测试)。

    7.       rank() 和 dense_rank ()函数功能类似,但是有重复值时序号是跳号的  

    8.       row_number()   返回有序组中的一行的偏移量,也就是对应的序号。

    例五

        显示每个的信息以及在工作在部门中的(从高到低)排名

        select ename,sal ,deptno,row_number() over (partitionby deptno orderby sal desc) as sortno from emp;

    查询结果:

    ENAME

    SAL

    DEPTNO

    SORTNO

    KING

    5000

    10

    1

    CLARK

    2450

    10

    2

    MILLER

    1300

    10

    3

    SCOTT

    3000

    20

    1

    FORD

    3000

    20

    2

    JONES

    2975

    20

    3

    ADAMS

    1100

    20

    4

    SMITH

    800

    20

    5

    BLAKE

    2850

    30

    1

    ALLEN

    1600

    30

    2

    TURNER

    1500

    30

    3

    WARD

    1250

    30

    4

    MARTIN

    1250

    30

    5

    JAMES

    950

    30

    6

    9.       sum(expression)   计算组中表达式的累计和

    1.3 经典案例

    行列转换

    将如下表格的数据从行式

    ENAME

    DEPTNO

    SORTNO

    KING

    10

    1

    CLARK

    10

    2

    MILLER

    10

    3

    SCOTT

    20

    1

    FORD

    20

    1

    JONES

    20

    2

    ADAMS

    20

    3

    BLAKE

    30

    1

    ALLEN

    30

    2

    TURNER

    30

    3

    改为列式

    DEPTNO

    HIGHEST

    SEC_HIGHEST

    THIRD_HIGHEST

    10

    KING

    CLARK

    MILLER

    20

    FORD

    JONES

    ADAMS

    30

    BLAKE

    ALLEN

    TURNER

    可通过如下在行式 sql 基础上生成, sql 如下:

    select deptno, min(decode(seq_num,1,ename,null)) as highest,
    min(decode(seq_num,2,ename,null)) as sec_highest,
    min(decode(seq_num,3,ename,null)) as third_highest from
    (select ename,deptno,seq_num from
    (select ename,deptno,dense_rank() over (partitionby deptno orderby sal desc ) as seq_num
    from emp) a
    where seq_num<=3) b
    groupby b.deptno

    1.4 说明

    1 、在 oracle9i pl/sql 分析器支持分析函数,在程序块中也可以使用(简单测试验证)

    2 、使用函数时注意考虑 null 特殊情况,默认值最大,降序排列在最前列。

    3 、分析函数使用时需要考虑排序、筛选的复杂度,大批量数据的过于复杂排序、筛选会导致系统资源繁忙。

  • 相关阅读:
    软件工程第二次作业
    第一次作业
    理论物理特训-02
    左神-06 二叉树
    哲学通论之人-05(end)
    哲学通论之人-04
    左神-05 二分搜索(多看几遍)
    哲学通论之人-01
    航空母舰-04(end)
    题解 P3126 【[USACO15OPEN]回文的路径Palindromic Paths】
  • 原文地址:https://www.cnblogs.com/lee/p/898564.html
Copyright © 2020-2023  润新知