zoukankan      html  css  js  c++  java
  • ORACLE HANDBOOK系列之一:Oracle分析函数(Analytic Function)

    1.      引言

    最近心血来潮去参加了一个PL/SQL工程师的面试,期间被问到了Oracle分析函数,PL/SQL开发并非我的老本行,在之前的工作中,也很少使用分析函数,原因之一是对数据库移植问题的考虑;其二是很少遇到非用分析函数不可的情况;其三是分析函数的语法相对复杂,令人缺乏兴趣。这几天看了一些入门内容,发现它们还是很强大的,唯一的遗憾是目前身边没有真实的应用场景,所以这里举的例子看起来难免有点纸上谈兵的感觉。

     

    2.      分析函数(Analytic function)与聚合函数(Aggregate function

    我们先从“ORA-00979: not a GROUP BY expression”说起,相信大家在开始使用SQL的过程中都遇到过这个错误,比如你写了下面这样的SQL

     

    SELECT title, corp, COUNT(*) cnt
    FROM film
    GROUP BY corp
    ORDER BY corp;

    ORA-00979错误表明,SELECT子句中出现的字段,要么包含于GROUP BY子句,要么作为聚合函数(上面的COUNT)的输入,除此之外不能包含其它字段。我们可以修改上面的SQL使它可以正常运行:

     

    SELECT corp, COUNT(*) af
    FROM film
    GROUP BY corp
    ORDER BY corp;

    这就引出了聚合函数的一个主要特征,聚合之后,同组只保留下一条数据,由上图可知表中由“20th Century Fox”公司出品的影片共有7部,最终记录是一条。

    这符合某些统计需求,然而有时候,我们并不希望聚合函数中的这种合并操作,尤其是我们常常希望在SELECT子句出现未参与统计的字段,此时我们便可以使用分析函数。对于表中的每一行记录,分析函数都能返回一个统计值,下面我们来看一个具体的实例:

     

    SELECT title,year,corp,
    COUNT(*OVER (PARTITION BY corp) af
    FROM film;

    注:PARTITION BY不仅导致分区(类似于GROUP BY),而且分区之间是排序好的,也算是它的一个副作用

     

    3.      基本语法

    function_name(arg1,arg2,...) OVER (<partition-clause> <order-by-clause > <window clause>)

    其中<order-by-clause >子句会在下面的例子中穿插提到,<window clause>子句则会在最后一节进行解释。


    另外,还需要提到的一点是,在有分析函数参与的SQL语句中,执行流程依次是:

    1) JOIN, WHERE, GROUP BY, HAVING

    2) 创建分区(通常通过PARTITION BY),而后分析函数将作用于分区中的每一行

    3) 主语句中ORDER BY(这个我们以前就知道,主语句的ORDER BY总是最后执行)。

     

    4.      AVG, SUM, MAX, MIN, COUNT

    这些大家熟知的聚合函数,同样可作为分析函数使用,当然要符合第3节中给出的分析函数的语法,下面我们来看几个实例:

     

    SELECT title,corp,year,box_office,
    ROUND(AVG(box_office) OVER (PARTITION BY corp)) af
    FROM film;

    让我们看看在OVER内应用ORDER BY之后的情形:

     

    SELECT title,year,corp,
    COUNT(*OVER (PARTITION BY corp ORDER BY year) af
    FROM film;

    这个结果容易让人非常困惑,实际上OVER内的ORDER BY子句导致了分区(PARTITION)内的数据进行了逐步累加。通常,这种累加始于排序后该分区的第一条记录,结束于当前记录。当排序列出现相同值(比如上面的两个1997、两个2009),累加则结束于相同记录的最后一条。

    让我们再来看一个例子:

     

    SELECT title,corp,year,box_office,
    MAX(box_office) OVER (PARTITION BY corp ORDER BY year) af
    FROM film;

     

    5.      RANK, DENSE_RANK, ROW_NUMBER

     

    代码
    SELECT title, corp, year,
    RANK() OVER (PARTITION BY corp ORDER BY year) r,
    DENSE_RANK() OVER (PARTITION BY corp ORDER BY year) dr,
    ROW_NUMBER() OVER (PARTITION BY corp ORDER BY year) rn
    FROM film;

    RANK, DENSE_RANK, ROW_NUMBER具有类似的行为,只有当排序列包含重复值时,它们的区别才能体现出来。注意上图中红色标识部分,对于相同的年份1997RANK, DENSE_RANK都返回相同的值,不同的时,DENSE_RANK采用密集编号,两个1之后接着的编号是2。对于ROW_NUMBER,则总是产生连续的编号。

    利用这几个函数的特性,可以相对简单地实现TOP N的查询,例如查询表中各电影公司年份最早的电影:

     

    SELECT * FROM (
      
    SELECT title,corp,year,
      RANK() OVER (PARTITION BY corp ORDER BY year) r
      
    FROM film
    ) t
    WHERE t.r=1;

     

    6.      LEAD, LAG

    基本语法:

    LEAD(<sql_expr>, <offset>, <default>) OVER (<analytic_clause>)

    LAG(<sql_expr>, <offset>, <default>) OVER (<analytic_clause>)

    <sql_expr> :通常是字段名。

    <offset> :表示相对于当前行的偏移幅度(对LEAD来说是向后偏移,对LAG来说则是向前),正整数,默认为1

    <default> :当偏移幅度超出该分区(PARTITION)的范围时返回的值。

     

    SELECT title,corp,year,box_office,
    LEAD(box_office,1,-9999OVER (PARTITION BY corp ORDER BY box_office) af1,
    LAG(box_office,1,-9999OVER (PARTITION BY corp ORDER BY box_office) af2
    FROM film;

    我们先来分析LEAD函数的结果,即上图中的AF1字段,对于同一行分区内,AF1字段第N行的值 = BOX_OFFICEN+1行的值,参见红色标识部分。为什么是N+1呢?实际这取决到我们在SQL语句中指定的offset,我们上面指定的是1

    如果偏移之后超过了分区的范围,则返回函数中指定的default值,这里我们指定的是-9999

    LAGLEAD类似,只不过它的偏移是向前的,这点与LEAD相反。

     

    7.      FIRST_VALUE, LAST_VALUE

    FIRST_VALUE返回各分区内指定排序后的第一条记录的值,LAST_VALUE则返回最后一条记录的值。

     

    SELECT title,corp,year,box_office,
    box_office-(FIRST_VALUE(box_office) OVER (PARTITION BY corp ORDER BY box_office)) af
    FROM film;

     

    8.      Window子句

    我们在第3节提到了分析函数中还有一个window clause,该子句为分析函数指定统计窗口。在前面的例子中,大多数的统计窗口都是整个分区(PARTITION),也就是说每个统计结果值都是基于相应分区内的所有数据计算而得。使用Window子句则可以将统计窗口进一步缩小,我们看一个例子:

     

    SELECT title,corp,year,box_office,
    MAX(box_office) OVER (PARTITION BY corp ORDER BY year ROWS BETWEEN 2 PRECEDING AND 1 FOLLOWING) af
    FROM film;

    由于分析函数中给定的窗口是ROWS BETWEEN 2 PRECEDING AND 1 FOLLOWING,基本上我们可以按字面意思理解“2 Preceding”跟“1 Following”,这表示“窗口”始于当前行之前两行,终于当前行之后一行,“窗口”大小共4行,所以我们看最后一列中红色标识的1840000000是由第1行到第4行中求得的MAX值;而最后一列中蓝色标识的920000000则是由第2行到第5行中求得的MAX值。


    回过来看Window子句的具体语法:

    ROWS BETWEEN <start_expr> AND <end_expr>

    其中<start_expr><end_expr> 可能是以下形式:

    (1) 1, 2, ..., N PRECEDING|FOLLOWING

    (2) UNBOUNDED PRECEDING|FOLLOWING

    (3) CURRENT ROW

    还存在一种更简单的语法:

    ROWS 1, 2, ..., N PRECEDING ROWS UNBOUNDED PRECEDING

    此时,统计窗口默认结束于当前行。

    再来看一个综合例子:

     

    代码
    SELECT title,corp,year,box_office,
    MAX(box_office) OVER (PARTITION BY corp ORDER BY year ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) af1,
    MAX(box_office) OVER (PARTITION BY corp ORDER BY year ROWS BETWEEN 2 PRECEDING AND UNBOUNDED FOLLOWING) af2,
    MAX(box_office) OVER (PARTITION BY corp ORDER BY year ROWS BETWEEN 1 PRECEDING AND 2 PRECEDING) af3,
    MAX(box_office) OVER (PARTITION BY corp ORDER BY year ROWS 2 PRECEDING) af4,
    MAX(box_office) OVER (PARTITION BY corp ORDER BY year ROWS BETWEEN UNBOUNDED PRECEDING AND 1 FOLLOWING) af5,
    MAX(box_office) OVER (PARTITION BY corp ORDER BY year ROWS BETWEEN 1 FOLLOWING AND 2 FOLLOWING) af6
    FROM film;

    注:可以看到查询结果中的AF3这一列是NULL值,这是因为我们在指定窗口的时候,<start_expr>所指向的记录一定要位于<end_expr>之前,而产生AF3统计结果的窗口ROWS BETWEEN 1 PRECEDING AND 2 PRECEDING,与要求不符,导致返回NULL


    除了上面用到的窗口子句——我们称之为Row Type Window,还有另外一种窗口子句,叫作Range Type Window,下面我们来看一个例子:

     

    SELECT title,corp,year,box_office,
    MAX(box_office) OVER (PARTITION BY corp ORDER BY year RANGE BETWEEN 2 PRECEDING AND 1 FOLLOWING) af
    FROM film;

    由于我们是按字段“year”进行排序的,那么上面的窗口就表示从当前行的年份往前2年,到当前行的年份往后1年,共4年的范围。

    比如对于上图中红色标识的行中,该行的窗口即[2003, 2006],由于这个时间段内只有它自己,则返回对应的BOX_OFFICE;再比如蓝色标识部分,该行的窗口即[2007, 2010],返回此范围内的最大值2560000000

    --

    到这里,文章写完了,文章中介绍了几个相对比较常见的分析函数,作为入门之用,其它众多的分析函数在使用上大同小异,有兴趣的同仁可以深入研究。祝阅读愉快。

     

  • 相关阅读:
    Java 的垃圾回收机制
    Java 变参函数的实现
    对已知有限集合中缺失或重复元素的查找
    Java 旋转数组查找旋转点和任意元素(元素可重复)
    第三章 磁盘分区
    第二章 一切都是对象
    发布 AutoRssReceiver Console 机器人! 兼讲: .Net 中的 Attribute (特性)
    .Net/C#/VB/TSQL/Java 实现: 将天文数字转换成中文大写 (2000 年前的思路,打劫的,一点儿技术含量都没有)
    .Net/C#/VB/TSQL/Java/Script 实现: 将天文数字转换成中文大写 (2000 年前的思路,打劫的,一点儿技术含量都没有)
    利用"委托"实现类的对象实例按"多字段嵌套"排序
  • 原文地址:https://www.cnblogs.com/morvenhuang/p/1821351.html
Copyright © 2011-2022 走看看