1. 程式人生 > >oracle 分析函式、GROUPING函式

oracle 分析函式、GROUPING函式

分析函式

  over(Partition by...) 一個超級牛皮的ORACLE特有函式。

天天都用ORACLE,用了快2年了。最近才接觸到這個功能強大而靈活的函式。真實慚愧啊!


oracle的分析函式over 及開窗函式
一:分析函式over
Oracle從8.1.6開始提供分析函式,分析函式用於計算基於組的某種聚合值,它和聚合函式的不同之處是
對於每個組返回多行,而聚合函式對於每個組只返回一行。 
下面通過幾個例子來說明其應用。                                       
1:統計某商店的營業額。        
     date       sale
     1           20
     2           15
     3           14
     4           18
     5           30
    規則:按天統計:每天都統計前面幾天的總額
    得到的結果:
    DATE   SALE       SUM
    ----- -------- ------
    1      20        20           --1天           
    2      15        35           --1天+2天           
    3      14        49           --1天+2天+3天           
    4      18        67            .          
    5      30        97            .
     
2:統計各班成績第一名的同學資訊
    NAME   CLASS S                         
    ----- ----- ---------------------- 
    fda    1      80                     
    ffd    1      78                     
    dss    1      95                     
    cfe    2      74                     
    gds    2      92                     
    gf     3      99                     
    ddd    3      99                     
    adf    3      45                     
    asdf   3      55                     
    3dd    3      78              
   
    通過:   
    --
    select * from                                                                       
    (                                                                            
    select name,class,s,rank()over(partition by class order by s desc) mm from t2
    )                                                                            
    where mm=1 
    --
    得到結果:
    NAME   CLASS S                       MM                                                                                        
    ----- ----- ---------------------- ---------------------- 
    dss    1      95                      1                      
    gds    2      92                      1                      
    gf     3      99                      1                      
    ddd    3      99                      1          
   
    注意:
    1.在求第一名成績的時候,不能用row_number(),因為如果同班有兩個並列第一,row_number()只返回一個結果         
    2.rank()和dense_rank()的區別是:
      --rank()是跳躍排序,有兩個第二名時接下來就是第四名
      --dense_rank()l是連續排序,有兩個第二名時仍然跟著第三名
     
     
3.分類統計 (並顯示資訊)
    A   B   C                      
    -- -- ---------------------- 
    m   a   2                      
    n   a   3                      
    m   a   2                      
    n   b   2                      
    n   b   1                      
    x   b   3                      
    x   b   2                      
    x   b   4                      
    h   b   3 
   select a,c,sum(c)over(partition by a) from t2                
   得到結果:
   A   B   C        SUM(C)OVER(PARTITIONBYA)      
   -- -- ------- ------------------------ 
   h   b   3        3                        
   m   a   2        4                        
   m   a   2        4                        
   n   a   3        6                        
   n   b   2        6                        
   n   b   1        6                        
   x   b   3        9                        
   x   b   2        9                        
   x   b   4        9                        
  
   如果用sum,group by 則只能得到
   A   SUM(C)                            
   -- ---------------------- 
   h   3                      
   m   4                      
   n   6                      
   x   9                      
   無法得到B列值       
  
=====
select * from test

資料:
A B C 
1 1 1 
1 2 2 
1 3 3 
2 2 5 
3 4 6


---將B欄位值相同的對應的C 欄位值加總
select a,b,c, SUM(C) OVER (PARTITION BY B) C_Sum
from test

A B C C_SUM 
1 1 1 1 
1 2 2 7 
2 2 5 7 
1 3 3 3 
3 4 6 6

 

---如果不需要已某個欄位的值分割,那就要用 null

eg: 就是將C的欄位值summary 放在每行後面

select a,b,c, SUM(C) OVER (PARTITION BY null) C_Sum
from test

A B C C_SUM 
1 1 1 17 
1 2 2 17 
1 3 3 17 
2 2 5 17 
3 4 6 17

 

求個人工資佔部門工資的百分比

SQL> select * from salary;

NAME DEPT SAL
---------- ---- -----
a 10 2000
b 10 3000
c 10 5000
d 20 4000

SQL> select name,dept,sal,sal*100/sum(sal) over(partition by dept) percent from salary;

NAME DEPT SAL PERCENT
---------- ---- ----- ----------
a 10 2000 20
b 10 3000 30
c 10 5000 50
d 20 4000 100

二:開窗函式           
      開窗函式指定了分析函式工作的資料視窗大小,這個資料視窗大小可能會隨著行的變化而變化,舉例如下: 
1:     
   over(order by salary) 按照salary排序進行累計,order by是個預設的開窗函式
   over(partition by deptno)按照部門分割槽
2:
  over(order by salary range between 5 preceding and 5 following)
   每行對應的資料視窗是之前行幅度值不超過5,之後行幅度值不超過5
   例如:對於以下列
     aa
     1
     2
     2
     2
     3
     4
     5
     6
     7
     9
   
   sum(aa)over(order by aa range between 2 preceding and 2 following)
   得出的結果是
            AA                       SUM
            ---------------------- ------------------------------------------------------- 
            1                       10                                                      
            2                       14                                                      
            2                       14                                                      
            2                       14                                                      
            3                       18                                                      
            4                       18                                                      
            5                       22                                                      
            6                       18                                                                
            7                       22                                                                
            9                       9                                                                 
             
   就是說,對於aa=5的一行 ,sum為   5-1<=aa<=5+2 的和
   對於aa=2來說 ,sum=1+2+2+2+3+4=14     ;
   又如 對於aa=9 ,9-1<=aa<=9+2 只有9一個數,所以sum=9    ;
              
3:其它:
     over(order by salary rows between 2 preceding and 4 following)
          每行對應的資料視窗是之前2行,之後4行 
4:下面三條語句等效:           
     over(order by salary rows between unbounded preceding and unbounded following)
          每行對應的資料視窗是從第一行到最後一行,等效:
     over(order by salary range between unbounded preceding and unbounded following)
           等效
     over(partition by null)

 

常用的分析函式如下所列:

row_number() over(partition by ... order by ...)
rank() over(partition by ... order by ...)
dense_rank() over(partition by ... order by ...)
count() over(partition by ... order by ...)
max() over(partition by ... order by ...)
min() over(partition by ... order by ...)
sum() over(partition by ... order by ...)
avg() over(partition by ... order by ...)
first_value() over(partition by ... order by ...)
last_value() over(partition by ... order by ...)
lag() over(partition by ... order by ...)
lead() over(partition by ... order by ...)

示例
SQL> select type,qty from test;

TYPE QTY
---------- ----------
1 6
2 9

 SQL> select type,qty,to_char(row_number() over(partition by type order by qty))||'/'||to_char(count(*) over(partition by type)) as cnt2 from test;

TYPE QTY CNT2 
---------- ---------- ------------
3 1/2
1 6 2/2
2 5 1/3
7 2/3 
2 9 3/3

 SQL> select * from test;
---------- -------------------------------------------------
1 11111
2 22222
3 33333
4 44444

SQL> select t.id,mc,to_char(b.rn)||'/'||t.id)e
2 from test t,
 (select rownum rn from (select max(to_number(id)) mid from test) connect by rownum <=mid ))L
4 where b.rn<=to_number(t.id)
order by id

ID MC TO_CHAR(B.RN)||'/'||T.ID
--------- -------------------------------------------------- ---------------------------------------------------
1 11111 1/1
2 22222 1/2
2 22222 2/2
3 33333 1/3
3 33333 2/3
3 33333 3/3
 44444 1/4 44444 2/4
4 44444 3/4CNOUG4 44444 4/4

10 rows selected

*******************************************************************

關於partition by

這些都是分析函式,好像是8.0以後才有的 row_number()和rownum差不多,功能更強一點(可以在各個分組內從1開時排序) rank()是跳躍排序,有兩個第二名時接下來就是第四名(同樣是在各個分組內) dense_rank()l是連續排序,有兩個第二名時仍然跟著第三名。相比之下row_number是沒有重複值的 lag(arg1,arg2,arg3): arg1是從其他行返回的表示式 arg2是希望檢索的當前行分割槽的偏移量。是一個正的偏移量,時一個往回檢索以前的行的數目。 arg3是在arg2表示的數目超出了分組的範圍時返回的值。

1.
select deptno,row_number() over(partition by deptno order by sal) from emp order by deptno;
2.
select deptno,rank() over (partition by deptno order by sal) from emp order by deptno;
3.
select deptno,dense_rank() over(partition by deptno order by sal) from emp order by deptno;
4.
select deptno,ename,sal,lag(ename,1,null) over(partition by deptno order by ename) from emp ord er by deptno;
5.
select deptno,ename,sal,lag(ename,2,'example') over(partition by deptno order by ename) from em p
order by deptno;
6.
select deptno, sal,sum(sal) over(partition by deptno) from emp;--每行記錄後都有總計值  select deptno, sum(sal) from emp group by deptno;
7. 求每個部門的平均工資以及每個人與所在部門的工資差額

select deptno,ename,sal ,
     round(avg(sal) over(partition by deptno)) as dept_avg_sal, 
     round(sal-avg(sal) over(partition by deptno)) as dept_sal_diff
from emp;
----------------------------------------------------------------------

1、rollup介紹:

group by後帶rollup子句的功能可以理解為:先按一定的規則產生多種分組,然後按各種分組統計資料。(至於統計出的資料是求和還是最大值還是平均值等這就取決於SELECT後的聚合函式)。 oracle中rollup和mysql的差不多,但比mysql的強大(mysql只有下面的第1和第2使用方式),且oracle中rollup可以和order by一起使用。具體使用如下:

 

1)對比沒有帶rollup的goup by :

Group by A,B產生的分組種數:1種;

group by A,B
返回結果集:也就是這一種分組的結果集。

 

2)帶rollup但group by與rollup之間沒有任何內容 :

 A、Group by rollup(A ,B) 產生的分組種數:3種;

第一種:group by A,B
第二種:group by A
第三種:group by NULL
 返回結果集:為以上三種分組統計結果集的並集且未去掉重複資料。

 

 B、Group by rollup(A ,B,C) 產生的分組種數:4種;

第一種:group by A,B,C
第二種:group by A,B
第三種:group by A
第四種:group by NULL
 返回結果集:為以上四種分組統計結果集的並集且未去掉重複資料。

 

 

3)帶rollup但groupby與rollup之間還包含有列資訊

 A、Group by A , rollup(A ,B) 產生的分組種數:3種;

第一種:group by A,A,B    等價於group by A,B
第二種:group by A,A      等價於group by A
第三種:group by A,NULL  等價於group by A
 返回結果集:為以上三種分組統計結果集的並集且未去掉重複資料。

 

 B、Group by C , rollup(A ,B) 產生的分組種數:3種;

第一種:group by C,A,B   
第二種:group by C,A     
第三種:group by C,NULL  等價於group by C
返回結果集:為以上三種分組統計結果集的並集且未去掉重複資料。

 

4)帶rollup且rollup子句括號內又使用括號對列進行組合

 A、Group by rollup((A ,B)) 產生的分組種數:2種;

第一種:group by A,B
第二種:group by NULL
返回結果集:為以上兩種分組統計結果集的並集且未去掉重複資料。

 

 B、Group by rollup(A ,(B,C)) 產生的分組種數:3種;

第一種:group by A,B,C
第二種:group by A
第三種:group by NULL
返回結果集:為以上三種分組統計結果集的並集且未去掉重複資料。

 

  注:對這種情況,可以理解為幾個列被括號括在一起時,就只能被看成一個整體,分組時不需要再細化。因此也可推斷rollup括號內也頂多加到一重括號,加多重了應該沒有任何意義(這個推斷我沒有做驗證的哦)。


2、與rollup組合使用的其它幾個輔助函式:

1)grouping()函式:

 

 必須接受一列且只能接受一列做為其引數。引數列值為空返回1,引數列值非空返回0。(如果引數的列在rollup中,則返回1;否則返回0)

 

2)grouping_id()函式:

 

  必須接受一列或多列做為其引數。返回值為按引數排列順序,依次對各個引數使用grouping()函式,並將結果值依次串成一串二進位制數然後再轉化為十進位制所得到的值。

 

例如:grouping(A) = 0 ;grouping(B) = 1;

     則:grouping_id(A,B) = (01)2 = 1;

                    grouping_id(B,A)= (10)2 =2;

 

3)group_id()函式

 

  呼叫時不需要且不能傳入任何引數。返回值為某個特定的分組出現的重複次數(第一大點中的第3種情況中往往會產生重複的分組)。重複次數從0開始,例如某個分組第一次出現則返回值為0,第二次出現時返回值為1,……,第n次出現返回值為n-1。

 

注:使用以上三個函式往往是為了過濾掉一部分統計資料,而達到美化統計結果的作用。

 

 

3、cube和rollup區別:

帶cube子句的groupby會產生更多的分組統計資料。cube後的列有多少種組合(注意組合是與順序無關的)就會有多少種分組。

1)假設有n個維度,rollup會有n個聚合:

rollup(a,b)   統計列包含:(a,b)、(a)、()

rollup(a,b,c)統計列包含:(a,b,c)、(a,b)、(a)、()

……以此類推ing……

 

2)假設有n個緯度,cube會有2的n次方個聚合:

cube(a,b)     統計列包含:(a,b)、(a)、(b)、()

cube(a,b,c)  統計列包含:(a,b,c)、(a,b)、(a,c)、(b,c)、(a)、(b)、(c)、()

……以此類推ing……

oracle中的GROUP_ID、GROUPING、GROUPING_ID函式

GROUPING函式可以接受一列,返回0或者1。如果列值為空,那麼GROUPING()返回1;如果列值非空,那麼返回0。GROUPING只能在使用ROLLUP或CUBE的查詢中使用。當需要在返回空值的地方顯示某個值時,GROUPING()就非常有用。

 

關於ROLLUP和CUBE函式的使用,請參見我的另一篇文章。

http://blog.csdn.net/wh62592855/archive/2009/11/16/4817920.aspx

 

1、在ROLLUP中對單列使用GROUPING()

SQL> select division_id,sum(salary)
  2  from employees2
  3  group by rollup(division_id)
  4  order by division_id;

DIV SUM(SALARY)
--- -----------
BUS     1610000
OPE     1320000
SAL     4936000
SUP     1015000
        8881000

加上GROUPING來看看

SQL> select grouping(division_id),division_id,sum(salary)
  2  from employees2
  3  group by rollup(division_id)
  4  order by division_id;

GROUPING(DIVISION_ID) DIV SUM(SALARY)
--------------------- --- -----------
                    0 BUS     1610000
                    0 OPE     1320000
                    0 SAL     4936000
                    0 SUP     1015000
                    1         8881000
可以看到,為空的地方返回1,非空的地方返回0。

 

2、使用CASE轉換GROUPING()的返回值

可能你會覺得前面的0和1太枯燥了,代表不了任何意義,說白了就是不夠人性化,呵呵。這個時候我們可以使用CASE來轉換為一些有意義的值。

SQL> select
  2  case grouping(division_id)
  3  when 1 then 'all divisions'
  4  else division_id
  5  end as div,
  6  sum(salary)
  7  from employees2
  8  group by rollup(division_id)
  9  order by division_id;

DIV           SUM(SALARY)
------------- -----------
BUS               1610000
OPE               1320000
SAL               4936000
SUP               1015000
all divisions     8881000

 

3、使用CASE和GROUPING()轉換多個列的值

SQL> select
  2  case grouping(division_id)
  3  when 1 then 'all divisions'
  4  else division_id
  5  end as div,
  6  case grouping(job_id)
  7  when 1 then 'all jobs'
  8  else job_id
  9  end as job,
 10  sum(salary)
 11  from employees2
 12  group by rollup(division_id,job_id)
 13  order by division_id,job_id;

DIV           JOB      SUM(SALARY)
------------- -------- -----------
BUS           MGR           530000
BUS           PRE           800000
BUS           WOR           280000
BUS           all jobs     1610000
OPE           ENG           245000
OPE           MGR           805000
OPE           WOR           270000
OPE           all jobs     1320000
SAL           MGR          4446000
SAL           WOR           490000
SAL           all jobs     4936000

DIV           JOB      SUM(SALARY)
------------- -------- -----------
SUP           MGR           465000
SUP           TEC           115000
SUP           WOR           435000
SUP           all jobs     1015000
all divisions all jobs     8881000

16 rows selected.

 

4、CUBE與GROUPING()結合使用

SQL> select
  2  case grouping(division_id)
  3  when 1 then 'all divisions'
  4  else division_id
  5  end as div,
  6  case grouping(job_id)
  7  when 1 then 'all jobs'
  8  else job_id
  9  end as job,
 10  sum(salary)
 11  from employees2
 12  group by cube(division_id,job_id)
 13  order by division_id,job_id;

DIV           JOB      SUM(SALARY)
------------- -------- -----------
BUS           MGR           530000
BUS           PRE           800000
BUS           WOR           280000
BUS           all jobs     1610000
OPE           ENG           245000
OPE           MGR           805000
OPE           WOR           270000
OPE           all jobs     1320000
SAL           MGR          4446000
SAL           WOR           490000
SAL           all jobs     4936000

DIV           JOB      SUM(SALARY)
------------- -------- -----------
SUP           MGR           465000
SUP           TEC           115000
SUP           WOR           435000
SUP           all jobs     1015000
all divisions ENG           245000
all divisions MGR          6246000
all divisions PRE           800000
all divisions TEC           115000
all divisions WOR          1475000
all divisions all jobs     8881000

21 rows selected.

 

5、使用GROUPING SETS子句

使用GROUPING SETS子句可以只返回小計記錄。

SQL> select division_id,job_id,sum(salary)
  2  from employees2
  3  group by grouping sets(division_id,job_id)
  4  order by division_id,job_id;

DIV JOB SUM(SALARY)
--- --- -----------
BUS         1610000
OPE         1320000
SAL         4936000
SUP         1015000
    ENG      245000
    MGR     6246000
    PRE      800000
    TEC      115000
    WOR     1475000

9 rows selected.
-----------------------------------------------------------------------


建表:

create table earnings
(    earnmonth  varchar2(32),--打工月份
     area varchar2(10),--地區
     sno varchar2(10),--打工者編號
     sname varchar(19),--名字
     times int,--次數
     singleincome  number(10,2),--每次多少錢
     personincome  number---當月總收入入
)


insert into earnings values('200912','北京','511601','大奎',11,30,11*30);
insert into earnings values('200912','北京','511602','大凱',8,25,8*25);
insert into earnings values('200912','北京','511603','小東',30,6.25,6.25*30);
insert into earnings values('200912','北京','511604','大亮',16,8.25,16*8.25);
insert into earnings values('200912','北京','511605','小王',11,30,11*30);
insert into earnings values('200912','南京','511301','小雨',15,12.25,15*12.25);
insert into earnings values('200912','南京','511302','小飯',17,16.67,17*16.67);
insert into earnings values('200912','南京','511303','小妮',27,33.33,17*33.33);
insert into earnings values('200912','南京','511304','小第',16,18,16*18);
insert into earnings values('200912','南京','511305','小陽',11,19.88,11*19.88);
insert into earnings values('201001','北京','511601','大奎',0,30,0);
insert into earnings values('201001','北京','511602','大凱',14,25,14*25);
insert into earnings values('201001','北京','511603','小東',19,6.25,6.25*19);
insert into earnings values('201001','北京','511604','大亮',7,8.25,7*8.25);
insert into earnings values('201001','北京','511605','小王',8,30,8*30);
insert into earnings values('201001','南京','511301','小雨',6,12.25,6*12.25);
insert into earnings values('201001','南京','511302','小飯',11,16.67,11*16.67);
insert into earnings values('201001','南京','511303','小妮',13,33.33,13*33.33);
insert into earnings values('201001','南京','511304','小第',20,18,20*18);
insert into earnings values('201001','南京','511305','小陽',30,19.88,30*19.88);
SELECT * FROM earnings


1 .  sum  函式 統計總和
 按照月份  統計每個地區的總收入
select  earnmonth ,area,sum(personincome) from earnings group by earnmonth,area;
------------------------------------------------------------------------------------------------------
2 .  rollup 函式  
 -- http://blog.itpub.net/519536/viewspace-610995/
 --ROLLUP 分組函式可以理解為Group By分組函式封裝後的精簡用法
 按照月份 地區統計收入
 -- group by 函式後面接rollup  是在純粹的 group by 的分組上再 加上對earnmonth的彙總統計
select  earnmonth ,area,sum(personincome) from earnings group by rollup(earnmonth,area);
 -- *****注意earnmonth,area 誰在前後是有區別的,影響彙總的結果是月份還是區域,
 -- 實現上面效果的普通的sql 語句
select earnmonth ,area,sum(personincome) from earnings group by earnmonth,area
union all 
select earnmonth ,null,sum(personincome) from earnings group by earnmonth
union all 
select null ,null,sum(personincome) from earnings 
order by 1,2;
 --其他知識 上面語句中想問問這個是什麼意思??
order by 1,2 根據第一列和第二列排序 相當於 order by earnmonth ,area
 -- UNION 用法 http://www.w3school.com.cn/sql/sql_union.asp
------------------------------------------------------------------------------------------------------
3 . cube 函式  http://blog.itpub.net/519536/viewspace-610997/
 -- https://msdn.microsoft.com/zh-cn/library/ms175939
按照月份 地區  進行收入總收入
select earnmonth ,area,sum(personincome) from earnings group by cube(earnmonth,area) order by 1,2;
 --group by 後面接cube 是對earnmonth彙總統計的基礎上對area 在統計
 --group by  是分組函式,按照earnmonth,area先後次序分組
 --上面的查詢 是先按照earnmonth 分組,在earnmonth 內部再按照area分組,並且在area組內統計personincome總和
------------------------------------------------------------------------------------------------------
4 . grouping 函式  http://millerrch.iteye.com/blgroupingog/1882423
 -- 上面的rollup 和 cube 函式都會對結果產生null,這時 可用grouping 來確認該記錄是哪個欄位得出來的
select earnmonth ,area,sum(personincome) from earnings group by rollup(earnmonth,area);  -- 這是有空值的
 --grouping 函式的用法 帶一個引數,引數是欄位名稱如果是本身的結果(列值非空)就返回0,如果是合計的結果(列值為空)則返回1
select earnmonth ,(case when(grouping(area)=1 )and (grouping(earnmonth)=0 )then '月份小計'  when(grouping(area)=1 )and (grouping(earnmonth)=1 )then '總計' else area end ) as area,sum(personincome) from earnings group by rollup(earnmonth,area);
------------------------------------------------------------------------------------------------------
5 . rank 函式,dense_rank 函式,row_number 函式
 -- http://www.jb51.net/article/85125.htm
 按照 月份 地區 求打工收入排序
 --   rank 排名會有並列,兩個第一名 會按照人數跳躍產生名次  1,1,3.。。。。。。。  
 --http://www.jb51.net/article/51627.htm
select  earnmonth ,area,sname,personincome,rank () over(partition by earnmonth,area order by personincome desc ) 排名 from earnings;
 -- dense_rank 排名會有並列,兩個第一名 但不會按照人數跳躍產生名次,1,1,2.。。。。。。。
select  earnmonth ,area,sname,personincome,dense_rank () over(partition by earnmonth,area order by personincome desc ) 排名 from earnings;
 -- row_number 排名不會有並列,既是兩個資料一樣,排名也不一樣,1,2,3,4。。。。。。
 -- http://blog.csdn.net/tanzuai/article/details/42391885
select  earnmonth ,area,sname,personincome,row_number () over(partition by earnmonth,area order by personincome desc ) 排名 from earnings;
------------------------------------------------------------------------------------------------------
SUM  累計求和
select earnmonth ,area,sname,personincome,sum (personincome) over(partition by sname order by personincome asc) "總收入" from earnings;
------------------------------------------------------------------------------------------------------
sum,avg.max,min綜合運用
--按照月份,地區 求打工收入最高值,最低,平均,總和
select distinct earnmonth "月份" ,area "地區",max(personincome) over (partition by earnmonth ,area ) "max",min(personincome) over (partition by earnmonth ,area ) "min",avg(personincome) over (partition by earnmonth ,area ) "avg",sum(personincome) over (partition by earnmonth ,area ) "sum" from earnings;
--------------------- 

Oracle分析函式

Oracle分析函式-排序排列(rank、dense_rank、row_number、ntile)

Oracle分析函式-排序排列(rank、dense_rank、row_number、ntile)
(1)rank函式返回一個唯一的值,除非遇到相同的資料時,此時所有相同資料的排名是一樣的,同時會在最後一條相同記錄和下一條不同記錄的排名之間空出排名。 
(2)dense_rank函式返回一個唯一的值,除非當碰到相同資料時,此時所有相同資料的排名都是一樣的。
(3)row_number函式返回一個唯一的值,當碰到相同資料時,排名按照記錄集中記錄的順序依次遞增。
(4)ntile是要把查詢得到的結果平均分為幾組,如果不平均則分給第一組。

例如:

複製程式碼
create table s_score
(   s_id number(6)
   ,score number(4,2)
);
insert into s_score values(001,98);
insert into s_score values(002,66.5);
insert into s_score values(003,99);
insert into s_score values(004,98);
insert into s_score values(005,98);
insert into s_score values(006,80);

select
    s_id 
   ,score
   ,rank() over(order by score desc) rank               --按照成績排名,純排名
   ,dense_rank() over(order by score desc) dense_rank   --按照成績排名,相同成績排名一致
   ,row_number() over(order by score desc) row_number   --按照成績依次排名
   ,ntile(3) over (order by score desc) group_s         --按照分數劃分成績梯隊
from s_score;
複製程式碼


排名/排序的時候,有時候,我們會想到利用偽列row_num,利用row_num確實可以解決某些場景下的問題(但是相對也比較複雜),而且有些場景下的問題卻很難解決。

例:取成績前三名,並且前三名含有並列的情況。通過上面例子,我們可以直觀的看到,結果應該有5條記錄:

複製程式碼
select
    s_id 
   ,score
   ,dense_rank
from (
select
    s_id 
   ,score
   ,rank() over(order by score desc) rank
   ,dense_rank() over(order by score desc) dense_rank
   ,row_number() over(order by score desc) row_number
from s_score
) t
where dense_rank <= 3;

   S_ID  SCORE DENSE_RANK
------- ------ ----------
      3  99.00          1
      1  98.00          2
      5  98.00          2
      4  98.00          2
      6  80.00          3
      
複製程式碼
如果只是簡單的想到去用rownum <= 3 得到的結果顯然不可能是正確的。

組內的排名或者排序是經常遇到的一種場景。
例如,取每個銷售部門內,銷售業績最好的前三名。取每個班級內成績排名資訊等等..
取每個班級內每門課成績排名第一的同學資訊:

複製程式碼
drop table S_SCORE;
create table S_SCORE
(
  S_ID  NUMBER(6),
  CLASS_ID VARCHAR2(2),
  COURSE VARCHAR2(20),
  SCORE NUMBER(5,2)
);

INSERT INTO S_SCORE VALUES(1001,'A','MATH','67');
INSERT INTO S_SCORE VALUES(1004,'B','MATH','88');
INSERT INTO S_SCORE VALUES(1002,'A','MATH','99');
INSERT INTO S_SCORE VALUES(1003,'A','MATH','55');
INSERT INTO S_SCORE VALUES(1001,'B','MATH','88');
INSERT INTO S_SCORE VALUES(1001,'B','MATH','70');
INSERT INTO S_SCORE VALUES(1001,'A','ORACLE','97');
INSERT INTO S_SCORE VALUES(1004,'B','ORACLE','48');
INSERT INTO S_SCORE VALUES(1002,'A','ORACLE','79');
INSERT INTO S_SCORE VALUES(1003,'A','ORACLE','65');
INSERT INTO S_SCORE VALUES(1001,'B','ORACLE','82');
INSERT INTO S_SCORE VALUES(1001,'B','ORACLE','78');

select
   s_id
  ,class_id
  ,course
  ,score
  ,dense_rank() over (partition by class_id,course order by score desc) drk
from S_SCORE;

   S_ID CLASS_ID COURSE                 SCORE        DRK
------- -------- -------------------- ------- ----------
   1002 A        MATH                   99.00          1
   1001 A        MATH                   67.00          2
   1003 A        MATH                   55.00          3
   1001 A        ORACLE                 97.00          1
   1002 A        ORACLE                 79.00          2
   1003 A        ORACLE                 65.00          3
   1004 B        MATH                   88.00          1
   1001 B        MATH                   88.00          1
   1001 B        MATH                   70.00          2
   1001 B        ORACLE                 82.00          1
   1001 B        ORACLE                 78.00          2
   1004 B        ORACLE                 48.00          3
   
select
   s_id
  ,class_id
  ,course
  ,score
from (
select
   s_id
  ,class_id
  ,course
  ,score
  ,dense_rank() over (partition by class_id,course order by score desc) drk
from S_SCORE
) t
where drk = 1;


   S_ID CLASS_ID COURSE                 SCORE
------- -------- -------------------- -------
   1002 A        MATH                   99.00
   1001 A        ORACLE                 97.00
   1004 B        MATH                   88.00
   1001 B        MATH                   88.00
   1001 B        ORACLE                 82.00   
複製程式碼
rank()和dense_rank()用法相似,這裡就不在舉例說明了。可以將上面的例子中dense_rank()替換成rank()實現。

接下來,看一個使用row_number()的場景
例:檢視每個部門最近一筆銷售記錄:

複製程式碼
select * from criss_sales order by dept_id,sale_date desc;

DEPT_ID SALE_DATE   GOODS_TYPE    SALE_CNT
------- ----------- ---------- -----------
D01     2014/5/4    G02                 80
D01     2014/4/30   G03                800
D01     2014/4/8    G01                200
D01     2014/3/4    G00                700
D02     2014/5/2    G03                900
D02     2014/4/27   G01                300
D02     2014/4/8    G02                100
D02     2014/3/6    G00                500
複製程式碼
即,我們希望得到這兩條記錄:

D01     2014/5/4    G02                 80
D02     2014/5/2    G03                900
複製程式碼
select
  dept_id
 ,sale_date
 ,goods_type
 ,sale_cnt
 ,row_number() over (partition by dept_id order by sale_date desc)
from criss_sales;

DEPT_ID SALE_DATE   GOODS_TYPE    SALE_CNT ROW_NUMBER()OVER(PARTITIONBYDE
------- ----------- ---------- ----------- ------------------------------
D01     2014/5/4    G02                 80                              1
D01     2014/4/30   G03                800                              2
D01     2014/4/8    G01                200                              3
D01     2014/3/4    G00                700                              4
D02     2014/5/2    G03                900                              1
D02     2014/4/27   G01                300                              2
D02     2014/4/8    G02                100                              3
D02     2014/3/6    G00                500                              4

select
  dept_id
 ,sale_date
 ,goods_type
 ,sale_cnt
from (
select
  dept_id
 ,sale_date
 ,goods_type
 ,sale_cnt
 ,row_number() over (partition by dept_id order by sale_date desc) rn
from criss_sales
) t
where rn = 1;

DEPT_ID SALE_DATE   GOODS_TYPE    SALE_CNT
------- ----------- ---------- -----------
D01     2014/5/4    G02                 80
D02     2014/5/2    G03                900
複製程式碼
 

有時會有這樣的需求:如果資料排序後分為三部分,業務人員只關心其中的一部分,如何將這中間的三分之一資料拿出來呢?
這時比較好的選擇,就是使用ntile函式:

複製程式碼
select
      dept_id
     ,sale_date
     ,goods_type
     ,sale_cnt
     ,ntile(3) over (order by sale_cnt desc nulls last)  all_cmp
     ,ntile(3) over (partition by dept_id order by sale_cnt desc nulls last) all_dept
from criss_sales;