Oracle 中 rownum、row_number()、rank()、dense_rank() 函數的用法


Ø  簡介

在之前還以為在 Oracle 中只能使用 rownum 這個偽列來實現分頁,其實不然。在 Oracle 也與 MSSQL 一樣,同樣支持 row_number 函數,以及和 rankdense_rank 這兩個函數。下面就來討論 rownum row_number 函數的區別,以及另外兩個函數的使用。

1.   rownum 的使用

2.   rownum 使用需謹慎

3.   row_number() 函數

4.   rank() dense_rank() 函數

5.   over() 函數結合聚合函數的使用

6.   綜合案例

 

1.   rownum 的使用

rownum Oracle 在查詢時對結果集輸出的一個偽列,這個列並不是真實存在的,當我們進行每一個 SELECT 查詢時,Oracle 會幫我們自動生成這個序列號(rownum),該序列號是順序遞增的,用於標識行號。通常可以借助 rownum 來實現分頁,下面來看具體實現,比如我們需要取 emp 表中46行的記錄:

1)   首先,我們來看一個奇怪的現象

SELECT * FROM emp WHERE rownum >= 4 AND rownum <= 6;

啪,一執行,呀,怎么沒數據啊?這並不是我們寫錯了,要解釋這個問題,我們先來看一個圖,就明白其中原由了。

clip_image002[1]

由圖可以看出,當我們取出第一條記錄時,此時(rownum = 1) >= 3不成立,所以該記錄會被排除;然后再取第二條,此時任然 rownum = 1,因為只有成功滿足一條記錄,rownum 才會加1,所以不滿足又被排除掉了。這樣依次類推,最終都不滿足條件,所以全部都被排除掉了。所以,以下語句始終查不出數據:

SELECT * FROM emp WHERE rownum > 1;

 

然后,在看另外一邊(就是接下來用的這種判斷方式),首先取第一條(滿足),第二條也滿足,直到(rownum = 7) <= 6,所以會取出6條記錄,此時 rownum 的值為1,2,3,4,5,6。好了,搞清楚原理后我們就來實現。

 

2)   根據對 rownum 的分析,便改為以下語句

SELECT rownum, t1.* FROM (

  SELECT rownum rnum, t1.* FROM emp t1 WHERE rownum <= 6

) t1 WHERE t1.rnum >= 4;

clip_image004[1]

這樣,通過子查詢,先取出前6行,再過濾掉前3行,就得到了我們需要的數據。注意:之前提過,每個 SELECT 都會產生一個 rownum 序列號,所有上面會可以輸出兩個 rownum 序列號,dual 也不例外

SELECT t1.*, rownum FROM dual t1;

clip_image005[1]

 

3)   除了使用以上語句,我們還可以這樣寫

SELECT rownum, t1.* FROM (

  SELECT rownum rnum, t1.* FROM emp t1

) t1 WHERE t1.rnum >= 4 AND rnum <= 6; --或使用 BETWEEN 子句

clip_image007[1]

同樣,可以完成以上功能。但分析一下,這種方式視乎沒有上面的方式效率高,因為,這里是先查出所有(先將 rownum 分配好)數據,再進行第二次 rownum 過濾。

 

4)   有時候,我們還需要通過排序后再分頁,該怎么實現呢?

使用排序並分頁,也需要注意以下問題。

首先,我們來看下排序的全部數據:

SELECT * FROM emp ORDER BY sal;

clip_image009[1]

按照上面的要求,我們應該是取出 empno(7521,7654,7934) 的員工,OK

 

不是說用第一種方式,效率很高么?那就來使用它實現,更改的后的 SQL

SELECT * FROM (

  SELECT rownum rnum, t1.* FROM emp t1 WHERE rownum <= 6 ORDER BY sal

) WHERE rnum >= 4;

clip_image011[1]

結果是不是又納悶了?怎么76987566也出來,而且還不是按我們預想的排序的!

 

好,我們再做個假設,以上語法是不是先查詢出結果后,再將結果集過濾和排序的呢?為了驗證這個疑點,很簡單我們做以下測試:

SELECT * FROM (

  SELECT * FROM (SELECT rownum rnum, t1.* FROM emp t1)

  WHERE rownum <= 6 ORDER BY sal

) WHERE rnum >= 4;

clip_image013[1]

結果與前面的推斷是一樣的,就是先查詢出結果(產生的 rownum 是沒有經過排序的),再排序,最后分頁(過濾)。我們看一下未排序的原始數據:

SELECT rownum, t1.* FROM emp t1;

clip_image015[1]

所以,我們得出一個結論:當我們同時過濾 rownum 和排序時,是先按默認的排序生成 rownum 后,再進行排序和過濾的

 

5)   其實上面的排序和分頁,並不是准確有效的。因為我們需要的是,rownum 的順序是根據我們指定的排序產生的,這樣再進行分頁才是准確的。所以正確的排序和分頁應該這樣寫:

SELECT * FROM (

  SELECT rownum rnum, t1.* FROM (SELECT * FROM emp ORDER BY sal) t1

) WHERE rnum BETWEEN 4 AND 6;

clip_image017[1]

執行步驟:先根據指定的字段排序;再產生 rownum 序列號;最后進行分頁。

 

2.   rownum 需謹慎

前面說到了,rownum 是先按默認的排序生成 rownum 后,再進行排序和過濾的,這一點很重要,不要搞錯了。下面再來看個例子:

SELECT 'SQL1' AS SQL1, rownum AS rnum, "GoodsId", "CreateTime" FROM "G_Goods" t;

SELECT 'SQL2' AS SQL2, rownum AS rnum, "GoodsId", "CreateTime" FROM "G_Goods" t ORDER BY t."CreateTime" DESC;

SELECT 'SQL3' AS SQL3, rownum AS rnum, "GoodsId", "CreateTime" FROM "G_Goods" t WHERE rownum <= 10 ORDER BY t."CreateTime" DESC;

SELECT 'SQL4' AS SQL4, rownum AS rnum, t.* FROM (SELECT "GoodsId", "CreateTime" FROM "G_Goods" t ORDER BY t."CreateTime" DESC) t WHERE rownum <= 10;

SELECT 'SQL5' AS SQL5, t.* FROM (SELECT ROW_NUMBER() OVER(ORDER BY "CreateTime" DESC) AS rnum, "GoodsId", "CreateTime" FROM "G_Goods" t) t WHERE rnum <= 10;

clip_image019[1]

clip_image021[1]

clip_image023[1]

clip_image025

clip_image027

1)   分析:

1.   首先看 SQL1 的默認排序結果,其實並不是按時間排序的;

2.   然后再看 SQL2,發現 rnum 並不是順序遞增的,而是根據時間排序顯示的,最終取得數據應該是這些

3.   好,再看 SQL3,是不是發現它其實取的是,默認排序后的前10條,然后再按時間倒序的(這是重點)

4.   然后 SQL4,它是根據時間排序后,在輸出結果集,在結果集中再取前10條,這樣就對了。

5.   最后是 SQL5,當然也是對的,根據時間排序並同時生成行號。

 

2)   總結

rownum 作為條件判斷時,一定不能同時與 ORDER BY 存在,切記!

 

3.   row_number() 函數

在前面使用 rownum 實現分頁,雖然是可以實現的,但是看似是否有點別扭。因為當需要對分頁排序時,rownum 總是先生成序列號再排序,其實這不時我們想要的。 row_number() 函數則是先排序,再生成序列號。這也是 row_number rownum 主要的區別。下面來看 row_number() 的使用:

n  語法:

row_number() over([partition by col1] order by col2 [ASC | DESC] [,col3 [ASC | DESC]]...)

參數解釋:

row_number() over(): 是固定寫法,即不能單獨使用 row_nubmer() 函數;

partition by: 可選的。用於指定分組(或分開依據)的列,類似 SELECT 中的 group by 子句;

order by: 用於指定排序的列,類似 SELECT 中的 order by 子句。

 

1.   基本用法

SELECT row_number() over(order by empno) AS rnum, t1.* FROM emp t1;

clip_image029

 

2.   使用 row_number() 分頁

SELECT * FROM (

  SELECT row_number() over(order by empno) AS rnum, t1.* FROM emp t1

) t WHERE t.rnum BETWEEN 4 AND 6;

clip_image031

是不是看上去,比使用 rownum 優雅了許多。

 

3.   使用 partition by 參數分區生成序號

當使用 partition by 參數時,序號將可能不是唯一的,因為序號的生成只會在當前分區中唯一,下一個分區又將從1開始計算,例如:

SELECT row_number() over(partition by deptno order by empno) AS rnum, t1.* FROM emp t1;

clip_image033

 

4.   rank() dense_rank() 函數

rank() row_number() 的區別在於,rank() 會按照排序值相同的為一個序號(以下稱為:),第二個不同排序值將顯示所有行的遞增值,而不是當前序號加1。看示例:

SELECT rank() over(order by job) rnum, job, ename FROM emp t1;

clip_image034

 

dense_rank() 函數,與 rank() 區別在於,第二個不同排序值,是對當前序號值加1,看示例:

SELECT dense_rank() over(order by job) rnum, job, ename FROM emp t1;

clip_image035

 

當指定 partition by 參數時,將根據指定的字段分組,進行分組計算序號值,序號值只在當前分組中有效,例如:

SELECT rank() over(partition by deptno order by job) rnum, job, ename, deptno FROM emp t1;

clip_image036

 

SELECT dense_rank() over(partition by deptno order by job) rnum, job, ename, deptno FROM emp t1;

clip_image037

 

5.   over() 函數結合聚合函數的使用

SELECT empno, ename, sal, hiredate, COUNT(sal) OVER(ORDER BY hiredate DESC) count FROM emp;

clip_image038

 

SELECT empno, ename, sal, hiredate, MAX(sal) OVER(ORDER BY hiredate ASC) max FROM emp;

clip_image039

 

SELECT empno, ename, sal, hiredate, MIN(sal) OVER(ORDER BY hiredate DESC) min FROM emp;

clip_image040

 

SELECT empno, ename, sal, hiredate, AVG(sal) OVER(ORDER BY hiredate DESC) avg FROM emp;

clip_image041

 

SELECT empno, ename, sal, hiredate, SUM(sal) OVER(ORDER BY hiredate DESC) sum FROM emp;

clip_image042

 

6.   綜合案例

1)   查詢前 100 條記錄

SELECT * FROM emp WHERE rownum <= 100;

注意:如果以上語句需要排序后再篩選,並不是能使用 rownum 實現,而需要使用 row_number() 函數。

 

2)   查出 4 ~ 6 條的記錄,並按員工編號排序(分頁運用)

SELECT * FROM (SELECT row_number() over(order by empno) rnum, t.* FROM emp t) t

WHERE t.rnum >= 4 AND t.rnum <= 6;

clip_image044[1]

 

3)   查出每個部門工資最高的員工

SELECT * FROM (SELECT row_number() over(partition by deptno order by sal DESC) rnum, t.* FROM emp t) t WHERE t.rnum = 1;

clip_image046

 

4)   查出每個部門工資最高的所有員工(排名並列的)

SELECT * FROM (SELECT rank() over(partition by deptno order by sal DESC) rnum, t.* FROM emp t) t WHERE t.rnum = 1;

clip_image048

 

5)   查出每個部門工資排名第三的所有員工(排名並列的)

SELECT * FROM (SELECT dense_rank() over(partition by deptno order by sal ASC) rnum, t.* FROM emp t) t WHERE t.rnum = 3;

clip_image050

 

注意:如果使用 rank() 是不行的,因為20號部門並列第二的員工有2個,序號3就被跳掉了,直接跳到了序號4,使用以下語句可以查看到:

SELECT rank() over(partition by deptno order by sal ASC) rnum, t.* FROM emp t;

clip_image052

所以,使用 rank() 將會得到錯誤的結果:

SELECT * FROM (SELECT rank() over(partition by deptno order by sal ASC) rnum, t.* FROM emp t) t WHERE t.rnum = 3;

clip_image054

 

Ø  總結

好了,關於排序函數就討論到這里了,感覺有點難記住它們的區別。所以可以結合上面的案例去記憶:

1.   如果需要取前多少條記錄,就使用 rownum 偽列。rownum 就類似於 SQL Server TOP 子句的用法,但是 rownum 不能用於排序並過濾的場合。

 

2.   如果取多少條到多少條的記錄(分頁),就是使用 row_number() 函數。

例如:查出 4 ~ 6 條的記錄,並按員工編號排序。

 

3.   如果取某個組別中最大值記錄或最小值的記錄,也可以使用 row_number() 函數,並結合 partition by 參數。

例如:查出每個部門工資最高的員工。

 

4.   如果取某個組別中並列最大值或最小值得記錄,就使用 rank() 函數,並結合 partition by 參數。

例如:查出每個部門工資最高的所有員工。

 

5.   如果取某個組別中並列排名幾記錄,就使用 dense_rank() 函數,並結合 partition by 參數。

例如:查出每個部門工資排名第三的所有員工。

 

當然,以上只是舉例,還有更多的用法需要我們去舉一反三。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM