【DB筆試面試460】在Oracle中,有哪些常用的分析函數?

  • 2019 年 10 月 11 日
  • 筆記

題目部分

在Oracle中,有哪些常用的分析函數?

答案部分

分析函數是Oracle從8.1.6開始引入的一個新的概念,為分析數據提供了一種簡單高效的處理方式。在分析函數出現以前,實現相同的功能必須使用自聯查詢、子查詢或者內聯視圖,甚至需要複雜的存儲過程來實現。有了分析函數後,只要一條簡單的SQL語句就可以實現了,而且在執行效率方面也有相當大的提高。Oracle的分析函數主要用於報表開發和數據倉庫。分析函數的功能強大,可以用於SQL語句的優化,在某些情況下,能達到事半功倍的效果。

分析函數的一般格式是:函數名(參數列表) OVER ([PARTITION BY 欄位名或表達式] [ORDER BY 欄位名或表達式]),其中OVER()部分稱為開窗函數,它是可以選填的。開窗函數指定了分析函數工作的數據窗口大小,這個數據窗口大小可能會隨著行的變化而變化。分析函數的寫法比較複雜,下面將講解幾個常用的分析函數。

(一)RANK()分析函數

該函數的作用是根據ORDER BY子句中表達式的值,從查詢返回的每一行,計算它們與其它行的相對位置。該函數的結果是不連續的,如果有4個人,其中有3個是並列第1名,那麼最後的排序結果結果如:1 1 1 4。

例子:下例中計算每個員工按部門分區再按薪水排序,依次出現的序列號

SYS@lhrdb> SELECT d.department_id,    2         e.last_name,    3         e.salary,    4         RANK() OVER(PARTITION BY e.department_id ORDER BY e.salary) AS drank    5  FROM   hr.employees   e,    6         hr.departments d    7  WHERE  e.department_id = d.department_id    8  AND    d.department_id IN ('60', '90');  DEPARTMENT_ID LAST_NAME                     SALARY      DRANK  ------------- ------------------------- ---------- ----------             60 Lorentz                         4200          1             60 Pataballa                       4800          2             60 Austin                          4800          2             60 Ernst                           6000          4             60 Hunold                          9000          5             90 De Haan                        17000          1             90 Kochhar                        17000          1             90 King                           24000          3

RANK()分析函數可以用於Top-N查詢中,例如,在上例中,若要查詢每個部門薪水排在第一的員工,則SQL可以如下:

SYS@lhrdb> SELECT *    2    FROM (SELECT D.DEPARTMENT_ID,    3                 E.LAST_NAME,    4                 E.SALARY,    5                 RANK() OVER(PARTITION BY E.DEPARTMENT_ID ORDER BY E.SALARY) AS DRANK    6            FROM HR.EMPLOYEES E, HR.DEPARTMENTS D    7           WHERE E.DEPARTMENT_ID = D.DEPARTMENT_ID    8             AND D.DEPARTMENT_ID IN ('60', '90'))    9   WHERE DRANK = 1;  DEPARTMENT_ID LAST_NAME                     SALARY      DRANK  ------------- ---------------------- ---------- ----------             60    Lorentz                         4200          1             90    Kochhar                        17000          1             90    De Haan                        17000          1

需要注意的是,除了ORDER BY子句的運算外,分析函數在SQL語句中將會最後執行。因此,分析函數只能應用於SELECT的列或ORDER BY子句中。

還有一個類似的函數為:DENSE_RANK()OVER(ORDER BY 列名排序),它的排序結果是連續的,如果有4個人,其中有3個是並列第1名,那麼最後的排序結果如:1 1 1 2,如下:

SYS@lhrdb> SELECT d.department_id,    2         e.last_name,    3         e.salary,    4         DENSE_RANK() OVER(PARTITION BY e.department_id ORDER BY e.salary) as drank    5    FROM hr.employees e, hr.departments d    6   WHERE e.department_id = d.department_id    7     AND d.department_id IN ('60', '90');  DEPARTMENT_ID LAST_NAME                     SALARY      DRANK  ------------- ------------------------- ---------- ----------             60 Lorentz                         4200          1             60 Pataballa                       4800          2             60 Austin                          4800          2             60 Ernst                           6000          3             60 Hunold                          9000          4             90 De Haan                        17000          1             90 Kochhar                        17000          1             90 King                           24000          2

如果不想並列排序,那麼可以使用ROW_NUMBER分析函數,如下所示:

SYS@lhrdb> SELECT D.DEPARTMENT_ID,    2                 E.LAST_NAME,    3                 E.SALARY,    4                 ROW_NUMBER() OVER(PARTITION BY E.DEPARTMENT_ID ORDER BY E.SALARY) AS DRANK    5            FROM HR.EMPLOYEES E, HR.DEPARTMENTS D    6           WHERE E.DEPARTMENT_ID = D.DEPARTMENT_ID    7             AND D.DEPARTMENT_ID IN ('60', '90');  DEPARTMENT_ID LAST_NAME                     SALARY      DRANK  ------------- ------------------------- ---------- ----------             60 Lorentz                         4200          1             60 Pataballa                       4800          2             60 Austin                          4800          3             60 Ernst                           6000          4             60 Hunold                          9000          5             90 De Haan                        17000          1             90 Kochhar                        17000          2             90 King                           24000          3

(二)LAG和LEAD分析函數

LAG和LEAD函數可以在一次查詢中取出同一欄位的前N行的數據和後N行的值。這種操作可以使用對相同表的表連接來實現,不過使用LAG和LEAD有更高的效率。LAG可以訪問當前行之前的行,LEAD與LAG相反,LEAD可以訪問當前行之後的行。如下的程式碼查詢了AWR中的快照號:

SYS@lhrdb> SELECT TO_CHAR(D.BEGIN_INTERVAL_TIME, 'YYYY-MM-DD HH24:MI:SS') STARTUP_TIME,    2         LAG(D.SNAP_ID) OVER(PARTITION BY D.STARTUP_TIME ORDER BY SNAP_ID) SNAP_ID_PRE,    3         D.SNAP_ID,    4         LEAD(D.SNAP_ID) OVER(PARTITION BY D.STARTUP_TIME ORDER BY SNAP_ID) SNAP_ID1_NEXT    5    FROM DBA_HIST_SNAPSHOT D, V$INSTANCE ND    6   WHERE D.INSTANCE_NUMBER = ND.INSTANCE_NUMBER    7   AND d.snap_id BETWEEN 10 AND 15    8   ORDER BY D.SNAP_ID DESC;  STARTUP_TIME        SNAP_ID_PRE    SNAP_ID SNAP_ID1_NEXT  ------------------- ----------- ---------- -------------  2016-11-30 12:00:59          14         15  2016-11-30 11:00:56          13         14            15  2016-11-30 10:00:54          12         13            14  2016-11-30 08:51:50          11         12            13  2016-11-29 15:00:32          10         11            12  2016-11-29 14:00:25                     10            11

(三)RULLUP分析函數

ROLLUP分組函數可以理解為Group By分組函數封裝後的精簡用法。

SQL> SELECT NVL(a.deptno||'','總計') AS 部門編碼,    2         a.job AS 工作,    3         SUM(sal) AS 工資小計    4  FROM   scott.emp a    5  GROUP  BY ROLLUP((a.deptno, a.job));  部門編碼 工作 工資小計  ---------------------------------------- --------- ----------  10                                       CLERK           1300  10                                       MANAGER         2450  10                                       PRESIDENT       5000  20                                       CLERK           1900  20                                       ANALYST         6000  20                                       MANAGER         2975  30                                       CLERK            950  30                                       MANAGER         2850  30                                       SALESMAN        5600  總計                                                    29025

對每份工作還能進行小計,如下:

SELECT CASE GROUPING(A.DEPTNO)           WHEN 1 THEN            '總計'           ELSE            TO_CHAR(DEPTNO)         END AS 部門編碼,         CASE           WHEN GROUPING(A.DEPTNO) = 1 THEN            NULL           WHEN GROUPING(JOB) = 1 THEN            '小計'           ELSE            JOB         END AS 工作,           CASE             WHEN GROUPING(a.job) = 1 THEN              NULL             WHEN GROUPING(mgr) = 1 THEN              '小計'             ELSE              to_char(mgr)         END AS 主管,         SUM(SAL) AS 工資合計    FROM SCOTT.EMP A   WHERE A.DEPTNO IN (10, 20)   AND A.JOB IN ('CLERK','MANAGER')   GROUP BY ROLLUP(A.DEPTNO, A.JOB, A.MGR);

除此之外,還有COUNT() OVER、GROUP BY CUBE、RATIO_TO_REPORT、AVG OVER、MAX OVER等等常用的分析函數,讀者可自行查閱相關Oracle文檔進行學習。

本文選自《Oracle程式設計師面試筆試寶典》,作者:李華榮。

About Me:小麥苗

● 本文作者:小麥苗,只專註於資料庫的技術,更注重技術的運用

● 作者部落格地址:http://blog.itpub.net/26736162/abstract/1/

● 本系列題目來源於作者的學習筆記,部分整理自網路,若有侵權或不當之處還請諒解

● 版權所有,歡迎分享本文,轉載請保留出處

● 題目解答若有不當之處,還望各位朋友批評指正,共同進步