Oracle高級查詢之over(partition by...)


為了方便學習和測試,所有的例子都是在Oracle自帶用戶Scott下建立的。

 

[sql]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. create table EMP  
  2. (  
  3.   empno    NUMBER(4) not null,  
  4.   ename    VARCHAR2(10),  
  5.   job      VARCHAR2(9),  
  6.   mgr      NUMBER(4),  
  7.   hiredate DATE,  
  8.   sal      NUMBER(7,2),  
  9.   comm     NUMBER(7,2),  
  10.   deptno   NUMBER(2)  
  11. )  
  12. alter table EMP  
  13.   add constraint PK_EMP primary key (EMPNO);  
  14.   
  15. insert into EMP (empno, ename, job, mgr, hiredate, sal, comm, deptno)   
  16.        values (7369, 'SMITH', 'CLERK', 7902, to_date('17-12-1980', 'dd-mm-yyyy'), 800, null, 20);  
  17. insert into EMP (empno, ename, job, mgr, hiredate, sal, comm, deptno)   
  18.        values (7499, 'ALLEN', 'SALESMAN', 7698, to_date('20-02-1981', 'dd-mm-yyyy'), 1600, 300, 30);  
  19. insert into EMP (empno, ename, job, mgr, hiredate, sal, comm, deptno)   
  20.        values (7521, 'WARD', 'SALESMAN', 7698, to_date('22-02-1981', 'dd-mm-yyyy'), 1250, 500, 30);  
  21. insert into EMP (empno, ename, job, mgr, hiredate, sal, comm, deptno)   
  22.        values (7566, 'JONES', 'MANAGER', 7839, to_date('02-04-1981', 'dd-mm-yyyy'), 2975, null, 20);  
  23. insert into EMP (empno, ename, job, mgr, hiredate, sal, comm, deptno)   
  24.        values (7654, 'MARTIN', 'SALESMAN', 7698, to_date('28-09-1981', 'dd-mm-yyyy'), 1250, 1400, 30);  
  25. insert into EMP (empno, ename, job, mgr, hiredate, sal, comm, deptno)   
  26.        values (7698, 'BLAKE', 'MANAGER', 7839, to_date('01-05-1981', 'dd-mm-yyyy'), 2850, null, 30);  
  27. insert into EMP (empno, ename, job, mgr, hiredate, sal, comm, deptno)   
  28.        values (7782, 'CLARK', 'MANAGER', 7839, to_date('09-06-1981', 'dd-mm-yyyy'), 2450, null, 10);  
  29. insert into EMP (empno, ename, job, mgr, hiredate, sal, comm, deptno)   
  30.        values (7788, 'SCOTT', 'ANALYST', 7566, to_date('19-04-1987', 'dd-mm-yyyy'), 3000, null, 20);  
  31. insert into EMP (empno, ename, job, mgr, hiredate, sal, comm, deptno)   
  32.        values (7839, 'KING', 'PRESIDENT', null, to_date('17-11-1981', 'dd-mm-yyyy'), 5000, null, 10);  
  33. insert into EMP (empno, ename, job, mgr, hiredate, sal, comm, deptno)   
  34.        values (7844, 'TURNER', 'SALESMAN', 7698, to_date('08-09-1981', 'dd-mm-yyyy'), 1500, 0, 30);  
  35. insert into EMP (empno, ename, job, mgr, hiredate, sal, comm, deptno)   
  36.        values (7876, 'ADAMS', 'CLERK', 7788, to_date('23-05-1987', 'dd-mm-yyyy'), 1100, null, 20);  
  37. insert into EMP (empno, ename, job, mgr, hiredate, sal, comm, deptno)   
  38.        values (7900, 'JAMES', 'CLERK', 7698, to_date('03-12-1981', 'dd-mm-yyyy'), 950, null, 30);  
  39. insert into EMP (empno, ename, job, mgr, hiredate, sal, comm, deptno)   
  40.        values (7902, 'FORD', 'ANALYST', 7566, to_date('03-12-1981', 'dd-mm-yyyy'), 3000, null, 20);  
  41. insert into EMP (empno, ename, job, mgr, hiredate, sal, comm, deptno)   
  42.        values (7934, 'MILLER', 'CLERK', 7782, to_date('23-01-1982', 'dd-mm-yyyy'), 1300, null, 10);  

 

注:標題中的紅色order by是說明在使用該方法的時候必須要帶上order by

一、rank()/dense_rank() over(partition by ...order by ...)

現在客戶有這樣一個需求,查詢每個部門工資最高的雇員的信息,相信有一定oracle應用知識的同學都能寫出下面的SQL語句:

 

[sql]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. select * from (select ename, job, hiredate, e.sal, e.deptno  
  2.           from emp e,  
  3.                (select deptno, max(sal) sal from emp group by deptno) t  
  4.          where e.deptno = t.deptno  
  5.            and e.sal = t.sal)  
  6.  order by deptno;  
  7.           
  8. select * from (select ename 姓名, job 職業, hiredate 入職日期, e.sal 工資, e.deptno 部門  
  9.           from emp e,  
  10.                (select deptno, max(sal) sal from emp group by deptno) t  
  11.          where e.deptno = t.deptno  
  12.            and e.sal = t.sal)  
  13.  order by 部門;  

 


在滿足客戶需求的同時,大家應該習慣性的思考一下是否還有別的方法。這個是肯定的,就是使用本小節標題中rank() over(partition by...)或dense_rank() over(partition by...)語法,SQL分別如下:

 

[sql]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. select empno, ename, job, hiredate, sal, deptno  
  2.   from (select empno, ename, job, hiredate, sal, deptno, rank() over(partition by deptno order by sal desc) r from emp)  
  3.  where r = 1;  
  4.    
  5. select empno, ename, job, hiredate, sal, deptno  
  6.   from (select empno, ename, job, hiredate, sal, deptno, dense_rank() over(partition by deptno order by sal desc) r from emp)  
  7.  where r = 1  

為什么會得出跟上面的語句一樣的結果呢?這里補充講解一下rank()/dense_rank() over(partition by e.deptno order by e.sal desc)語法。
over:  在什么條件之上。
partition by e.deptno:  按部門編號划分(分區)。
order by e.sal desc:  按工資從高到低排序(使用rank()/dense_rank() 時,必須要帶order by否則非法)
rank()/dense_rank():  分級
整個語句的意思就是:在按部門划分的基礎上,按工資從高到低對雇員進行分級,“級別”由從小到大的數字表示(最小值一定為1)。 

 

那么rank()和dense_rank()有什么區別呢?
rank():  跳躍排序,如果有兩個第一級時,接下來就是第三級。
dense_rank():  連續排序,如果有兩個第一級時,接下來仍然是第二級。

小作業:查詢部門最低工資的雇員信息。

二、min()/max() over(partition by ...)

現在我們已經查詢得到了部門最高/最低工資,客戶需求又來了,查詢雇員信息的同時算出雇員工資與部門最高/最低工資的差額。這個還是比較簡單,在第一節的groupby語句的基礎上進行修改如下:

-- 查詢每位雇員信息的同時算出雇員工資與所屬部門最高/最低員工工資的差額

[sql]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. select ename 姓名, job 職業, hiredate 入職日期, e.deptno 部門, e.sal 工資, e.sal-me.min_sal 最低差額, me.max_sal-e.sal 最高差額  
  2.   from emp e, (select deptno, min(sal) min_sal, max(sal) max_sal from emp group by deptno) me    
  3.  where e.deptno = me.deptno order by e.deptno, e.sal;    

上面我們用到了min()和max(),前者求最小值,后者求最大值。如果這兩個方法配合over(partition by ...)使用會是什么效果呢?大家看看下面的SQL語句:

[sql]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. select ename 姓名, job 職業, hiredate 入職日期, deptno 部門,   
  2.      min(sal) over(partition by deptno) 部門最低工資,   
  3.      max(sal) over(partition by deptno) 部門最高工資   
  4.   from emp order by deptno, sal;    
  5.   
  6. select ename 姓名, job 職業, hiredate 入職日期, deptno 部門,   
  7.      nvl(sal - min(sal) over(partition by deptno), 0) 部門最低工資差額,   
  8.      nvl(max(sal) over(partition by deptno) - sal, 0) 部門最高工資差額  
  9.   from emp order by deptno, sal;  

這兩個語句的查詢結果是一樣的,大家可以看到min()和max()實際上求的還是最小值和最大值,只不過是在partition by分區基礎上的。

小作業:如果在本例中加上order by,會得到什么結果呢?

三、lead()/lag() over(partition by ... order by ...)

中國人愛攀比,好面子,聞名世界。客戶更是好這一口,在和最高/最低工資比較完之后還覺得不過癮,這次就提出了一個比較變態的需求,計算個人工資與比自己高一位/低一位工資的差額。這個需求確實讓我很是為難,在groupby語句中不知道應該怎么去實現。不過。。。。現在我們有了over(partition by ...),一切看起來是那么的簡單。如下:

-- 計算個人工資與比自己高一位/低一位工資的差額

[sql]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. select ename 姓名, job 職業, sal 工資, deptno 部門,    
  2.    lead(sal, 1, 0) over(partition by deptno order by sal)  比自己工資高的部門前一個,    
  3.    lag(sal, 1, 0) over(partition by deptno order by sal)  比自己工資低的部門后一個,    
  4.    nvl(lead(sal) over(partition by deptno order by sal) - sal, 0)  比自己工資高的部門前一個差額,     
  5.    nvl(sal - lag(sal) over(partition by deptno order by sal), 0)   比自己工資高的部門后一個差額  
  6.  from emp;     

看了上面的語句后,大家是否也會覺得虛驚一場呢(驚出一身冷汗后突然雞凍起來,這樣容易感冒)?我們還是來講解一下上面用到的兩個新方法吧。
lead(列名,n,m):  當前記錄后面第n行記錄的<列名>的值,沒有則默認值為m;如果不帶參數n,m,則查找當前記錄后面第一行的記錄<列名>的值,沒有則默認值為null。
lag(列名,n,m):  當前記錄前面第n行記錄的<列名>的值,沒有則默認值為m;如果不帶參數n,m,則查找當前記錄前面第一行的記錄<列名>的值,沒有則默認值為null。

下面再列舉一些常用的方法在該語法中的應用(注:帶order by子句的方法說明在使用該方法的時候必須要帶order by):

[sql]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. select ename 姓名, job 職業, sal 工資, deptno 部門,   
  2.     first_value(sal) over(partition by deptno) first_sal,   
  3.     last_value(sal) over(partition by deptno) last_sal,   
  4.     sum(sal) over(partition by deptno) 部門總工資,   
  5.     avg(sal) over(partition by deptno) 部門平均工資,   
  6.     count(1) over(partition by deptno) 部門總數,   
  7.     row_number() over(partition by deptno order by sal) 序號   
  8.  from emp;    

重要提示:大家在讀完本片文章之后可能會有點誤解,就是OVER (PARTITION BY ..)比GROUP BY更好,實際並非如此,前者不可能替代后者,而且在執行效率上前者也沒有后者高,只是前者提供了更多的功能而已,所以希望大家在使用中要根據需求情況進行選擇。

 

原文摘自傅老師課堂 Mr傅:http://blog.csdn.NET/fu0208/article/details/7179001

http://blog.csdn.net/huxu981598436/article/details/38129177

 

row_number()淺析:

 

 

row_number() OVER (PARTITION BY COL1 ORDER BY COL2) 表示根據COL1分組,在分組內部根據 COL2排序,而此函數計算的值就表示每組內部排序后的順序編號(組內連續的唯一的).

  與rownum的區別在於:使用rownum進行排序的時候是先對結果集加入偽列rownum然后再進行排序,而此函數在包含排序從句后是先排序再計算行號碼.

  row_number()和rownum差不多,功能更強一點(可以在各個分組內從1開時排序)。

     在使用 row_number() over()函數時候,over()里頭的分組以及排序的執行晚於 where group by  order by 的執行。

partition by 是數據的分區取數,用於給結果集分組,如果沒有指定那么它把整個結果集作為一個分組,它和聚合函數不同的地方在於它能夠返回一個分組中的多條記錄,而聚合函數一般只有一個反映統計值的記錄。

  rank()是跳躍排序,有兩個第二名時接下來就是第四名(同樣是在各個分組內).

  dense_rank()l是連續排序,有兩個第二名時仍然跟着第三名。相比之下row_number是沒有重復值的 .

  lag(arg1,arg2,arg3): 
arg1是從其他行返回的表達式 
arg2是希望檢索的當前行分區的偏移量。是一個正的偏移量,時一個往回檢索以前的行的數目。 
arg3是在arg2表示的數目超出了分組的范圍時返回的值。

 

舉例:

SQL> DESC T1;

Name                                           Null?    Type
----------------------------------------- -------- ----------------------------
ID                                                    NUMBER
NAME                                                    VARCHAR2(10)
DATE1                                                    DATE

SQL> SELECT * FROM T1;
        ID NAME                           DATE1
---------- ------------------------------ ------------------
       101 aaa                                  09-SEP-13

       101 bbb                                  10-SEP-13

       101 ccc                                  11-SEP-13

       102 ddd                                  08-SEP-13

       102 eee                                  11-SEP-13

 

SQL> SELECT ID,NAME,DATE1,ROW_NUMBER() OVER(partition by ID order by DATE1 desc) as RN FROM T1;

    ID NAME               DATE1               RN
---------- ------------------------------ ------------------ ----------
       101 ccc                  11-SEP-13              1
       101 bbb                  10-SEP-13              2
       101 aaa                  09-SEP-13              3
       102 eee                  11-SEP-13              1
       102 ddd                  08-SEP-13              2

 

把上面語句作為一個子表語句,嵌入到另一條語句中:

 

SQL> SELECT ID,NAME,DATE1 FROM (SELECT ID,NAME,DATE1,ROW_NUMBER() OVER(partition by ID order by DATE1 desc) as RN FROM T1)T WHERE T.RN=1;

        ID NAME                           DATE1
---------- ------------------------------ ------------------
       101 ccc                                  11-SEP-13
       102 eee                                  11-SEP-13

再看幾個SQL語句:

語句一:

select row_number() over(order by sale/cnt desc) as sort, sale/cnt 
from (
select -60 as sale,3 as cnt from dual union 
select 24 as sale,6 as cnt from dual union 
select 50 as sale,5 as cnt from dual union 
select -20 as sale,2 as cnt from dual union 
select 40 as sale,8 as cnt from dual);

執行結果:

          SORT       SALE/CNT
---------- ----------
             1             10
             2              5
             3              4
             4            -10
             5            -20

 

語句二:查詢員工的工資,按部門排序

select ename,sal,row_number() over (partition by deptno order by sal desc) as sal_order from scott.emp;

執行結果:

ENAME                           SAL      SAL_ORDER
-------------------- ---------- ----------
KING                           5000              1
CLARK                          2450              2
MILLER                         1300              3
SCOTT                          3000              1
FORD                           3000              2
JONES                          2975              3
ADAMS                          1100              4
SMITH                           800              5
BLAKE                          2850              1
ALLEN                          1600              2
TURNER                         1500              3
WARD                           1250              4
MARTIN                         1250              5
JAMES                           950              6

已選擇14行。

語句三:查詢每個部門的最高工資

select deptno,ename,sal from 
     (select deptno,ename,sal,row_number() over (partition by deptno order by sal desc) as sal_order 
         from scott.emp) where sal_order <2;

執行結果:

       DEPTNO ENAME                          SAL
---------- -------------------- ----------
           10 KING                          5000
           20 SCOTT                         3000
           30 BLAKE                         2850

已選擇3行。

語句四:

select deptno,sal,rank() over (partition by deptno order by sal) as rank_order from scott.emp order by deptno;

執行結果:

     DEPTNO         SAL RANK_ORDER
---------- ---------- ----------
         10        1300           1
         10        2450           2
         10        5000           3
         20         800           1
         20        1100           2
         20        2975           3
         20        3000           4
         20        3000           4
         30         950           1
         30        1250           2
         30        1250           2
         30        1500           4
         30        1600           5
         30        2850           6

已選擇14行。

語句五:

select deptno,sal,dense_rank() over(partition by deptno order by sal) as dense_rank_order from scott.emp order by deptn;

執行結果:

     DEPTNO         SAL DENSE_RANK_ORDER
---------- ---------- ----------------
         10        1300                 1
         10        2450                 2
         10        5000                 3
         20         800                 1
         20        1100                 2
         20        2975                 3
         20        3000                 4
         20        3000                 4
         30         950                 1
         30        1250                 2
         30        1250                 2
         30        1500                 3
         30        1600                 4
         30        2850                 5

已選擇14行。

語句六:

select deptno,ename,sal,lag(ename,1,null) over(partition by deptno order by ename) as lag_ from scott.emp order by deptno;

執行結果:

 

 

     DEPTNO ENAME                        SAL LAG_
---------- -------------------- ---------- --------------------
         10 CLARK                       2450
         10 KING                        5000 CLARK
         10 MILLER                      1300 KING
         20 ADAMS                       1100
         20 FORD                        3000 ADAMS
         20 JONES                       2975 FORD
         20 SCOTT                       3000 JONES
         20 SMITH                        800 SCOTT
         30 ALLEN                       1600
         30 BLAKE                       2850 ALLEN
         30 JAMES                        950 BLAKE
         30 MARTIN                      1250 JAMES
         30 TURNER                      1500 MARTIN
         30 WARD                        1250 TURNER

已選擇14行。

很多年以后,還有很多程序員不知道SQL Server2005有了更方便的分頁方法,這就是ROW_NUMBER()函數。我們知道SQL2000時代的分頁方式是TOP加NOT IN截取中間數據,效率也是很不錯的,但這兩種效率到底如何呢,我們這次以一萬、十萬和百萬數據量的數據做演示,比較這兩種分頁方式的效率。另外為何使用 TOP+NOT IN來和ROW_NUMBER()比較,是因為和游標方式及ISNULL方式分頁來說,TOP+NOT IN方式效率更高。前人已有證明,可參考這篇文章:http://www.cnblogs.com/morningwang/archive/2009/01/02/1367277.html ,或者自行搜索更權威文章。

准備工作

准備工具:電腦(當然了o(∩_∩)o )和程序員一名。

同一測試環境,電腦配置如下,數據如有不實,請找周鴻禕~

建立數據表,插入相應數據。表結構如下,Id為自增長主鍵:

插入100萬條測試數據:

?
user  table3
go
declare  @ index  int
set  @ index =0
while @ index <1000000
     begin
         insert  into  Users( Name ) values ( 'walkingp' )
         set  @ index =@ index +1
     end

接下來先掃盲一下ROW_NUMBER()函數。

ROW_NUMBER()函數

ROW_NUMBER()函數是根據參數傳遞過來的order by子句的值,返回一個不斷遞增的整數值,也就是它會從1一直不斷自增1,直到條件不再滿足。例如表Users(Id,Name),使用以下sql語句進行查詢: 

?
select  id, name ,row_number() over( order  by  Id desc ) as  rowNum from  users where  id<10
select  id, name ,row_number() over( order  by  Id) as  rowNum from  users where  id<10

兩條語句order by排序相反,執行結果如下:

孰優孰劣

以下兩種情況,同樣取500000到500100中間的數據。

1、使用ROW_NUMBER()函數。

SQL語句如下:

?
declare  @ time  datetime
declare  @ms int
set  @ time = getdate()
select  Id, Name  from  ( select  row_number() over( order  by  Id) as  rowNum,* from  users) as t where  rowNum between  500000 and  500100
set  @ms=datediff(ms,@ time ,getdate())
print @ms --毫秒數

測試了幾次,平均在250毫秒:

2、使用TOP加NOT IN方法。

SQL語句如下:

?
select  top  100 * from  users where  Id not  in
     ( select  top  500000 id from  users order  by  id)
     order  by  id

平均在236毫秒:

好吧,一起執行看看結果:

或者你認為SQL存在緩存的問題,把兩部分順序對掉一下:

以上是百萬數據量的對比,再看看1萬條數據下的對比:

結論

通過以上對比可以我們可以得到這樣的結論:在小數據量下(一般應該認為是10萬以下,TOP+NOT IN分頁方式效率要比ROW_NUMBER()高;在大數據量下(百萬級)ROW_NUMBER()分頁方式效率要更高一些。

 


免責聲明!

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



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