天天看點

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;

Oracle之分析函數(Analytic Function)入門

這就引出了聚合函數的一個主要特征,聚合之後,同組隻保留下一條資料,由上圖可知表中由“20th Century Fox”公司出品的影片共有7部,最終記錄是一條。

這符合某些統計需求,然而有時候,我們并不希望聚合函數中的這種“合并”操作,尤其是我們常常希望在SELECT子句出現未參與統計的字段,此時我們便可以使用分析函數。對于表中的每一行記錄,分析函數都能傳回一個統計值,下面我們來看一個具體的執行個體:

SELECT title,year,corp,

COUNT(*) OVER (PARTITION BY corp) af

FROM film;

Oracle之分析函數(Analytic Function)入門

注: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;

Oracle之分析函數(Analytic Function)入門

讓我們看看在OVER内應用ORDER BY之後的情形:

SELECT title,year,corp,

COUNT(*) OVER (PARTITION BY corp ORDER BY year) af

FROM film;

Oracle之分析函數(Analytic Function)入門

這個結果容易讓人非常困惑,實際上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;

Oracle之分析函數(Analytic Function)入門

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;

Oracle之分析函數(Analytic Function)入門

RANK, DENSE_RANK,ROW_NUMBER具有類似的行為,隻有當排序列包含重複值時,它們的差別才能展現出來。注意上圖中紅色辨別部分,對于相同的年份1997,RANK, 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;

Oracle之分析函數(Analytic Function)入門

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,-9999) OVER (PARTITION BY corp ORDER BY box_office) af1,

LAG(box_office,1,-9999) OVER (PARTITION BY corp ORDER BY box_office) af2

FROM film;

Oracle之分析函數(Analytic Function)入門

我們先來分析LEAD函數的結果,即上圖中的AF1字段,對于同一行分區内,AF1字段第N行的值 = 原BOX_OFFICE第N+1行的值,參見紅色辨別部分。為什麼是N+1呢?實際這取決到我們在SQL語句中指定的offset,我們上面指定的是1。

如果偏移之後超過了分區的範圍,則傳回函數中指定的default值,這裡我們指定的是-9999。

LAG與LEAD類似,隻不過它的偏移是向前的,這點與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;

Oracle之分析函數(Analytic Function)入門

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;

Oracle之分析函數(Analytic Function)入門

由于分析函數中給定的視窗是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, ..., NPRECEDING|FOLLOWING

(2) UNBOUNDEDPRECEDING|FOLLOWING

(3) CURRENT ROW

還存在一種更簡單的文法:

ROWS 1, 2, ..., NPRECEDING 或ROWS UNBOUNDEDPRECEDING

此時,統計“視窗”預設結束于目前行。

再來看一個綜合例子:

  代碼

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 UNBOUNDEDFOLLOWING) 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;

Oracle之分析函數(Analytic Function)入門

注:可以看到查詢結果中的AF3這一列是NULL值,這是因為我們在指定“視窗”的時候,<start_expr>所指向的記錄一定要位于<end_expr>之前,而産生AF3統計結果的“視窗”是ROWS BETWEEN 1 PRECEDINGAND 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;

Oracle之分析函數(Analytic Function)入門

由于我們是按字段“year”進行排序的,那麼上面的視窗就表示從目前行的年份往前2年,到目前行的年份往後1年,共4年的範圍。

比如對于上圖中紅色辨別的行中,該行的視窗即[2003, 2006],由于這個時間段内隻有它自己,則傳回對應的BOX_OFFICE;再比如藍色辨別部分,該行的視窗即[2007, 2010],傳回此範圍内的最大值2560000000。

--

到這裡,文章寫完了,文章中介紹了幾個相對比較常見的分析函數,作為入門之用,其它衆多的分析函數在使用上大同小異,有興趣的同仁可以深入研究。祝閱讀愉快。