Oracle rownum 僞列

Oracle 僞列 rownum


ROWNUM是一種僞列,它會根據返回記錄生成一個序列化的數字。利用ROWNUM,我們可以生產一些原先難以實現的結果輸出,但因爲它是僞列的這個特殊性,我們在使用時也需要注意一些事項,不要掉入“陷阱”。


一、特殊結果輸出


1.Top N 結果輸出


select * from t_test4 where rownum <= 5;
但是,如果你希望對一個排序結果取Top N數據的話,使用ROWNUM存在一些“陷阱”。


1.2 分頁查詢


下面三種方法得到第6-10條記錄
select * from (select a.*, rownum as rn from css_bl_view a where capture_phone_num='925-4604800') b 
where b.rn between 6 and 10;


select * from (select a.*, rownum as rn from css_bl_view a where capture_phone_num='925-4604800' and rownum<=10) b
where b.rn > 5;


select * from css_bl_view a 
where capture_phone_num = '925-4604800' and rownum<=10 
minus
select * from css_bl_view a 
where capture_phone_num = '925-4604800' and rownum<=5


1.3 分組子排序


SELECT DECODE(nu - min_sno, 0, a.owner, null) owner,
       DECODE(nu - min_sno, 0, 1, nu + 1 - min_sno) sno,
       a.table_name
  FROM (select l.*, rownum nu
          from (SELECT owner, table_name
                  FROM dba_tables
                 ORDER BY owner, table_name) l) a,
       (SELECT owner, MIN(rownum) min_sno
          FROM (SELECT owner, table_name
                  FROM dba_tables
                 ORDER BY owner, table_name)
         GROUP BY owner
         ORDER BY owner) b
 WHERE a.owner = b.owner




二、性能


我們很多程序員在確認某個表中是否有相應數據時,喜歡加上ROWNUM=1,其思路就是
只要存在一條數據就說明有相應數據,查詢就可以直接返回了,這樣就能提高性能了。但
是在10G 之前,使用ROWNUM=1 是不能達到預期的性能效果的,而是需要通過<2 或<=1 作
爲過濾條件才能達到預期效果。




三、ROWNUM的使用“陷阱”


由於ROWNUM 是一個僞列,只有有結果記錄時,ROWNUM 纔有相應數據,因此對它的使
用不能向普通列那樣使用,否則就會陷入一些“陷阱”當中。


3.1 對ROWNUM進行>、>=、=操作


不能對ROWNUM 使用>(大於或等於1的數值)、>=(大於1的數值)、=(0或者大於1的數值),否則無結果。


select l.*,rownum from tb_snacks_analyse_list l where l.sum_time='201212' and rownum>1 --無值
select l.*,rownum from tb_snacks_analyse_list l where l.sum_time='201212' and rownum>=2 --無值
select l.*,rownum from tb_snacks_analyse_list l where l.sum_time='201212' and rownum=2 --無值


這是因爲:
1、ROWNUM 是僞列,必須要有返回結果後,每條返回記錄就會對應產生一個ROWNUM數值;
2、返回結果記錄的ROWNUM 是從1 開始排序的,因此第一條始終是1;這樣,當查詢到第一條記錄時,該記錄的ROWNUM 爲1,但條件要求ROWNUM>1,因此不符合,繼續查詢下一條;因爲前面沒有符合要求的記錄,因此下一條記錄過來後,其ROWNUM 還是爲1,如此循環,就不會產生結果。


上述查詢可以通過子查詢來替代:
select * from (select l.*,rownum cn from tb_snacks_analyse_list l where l.sum_time='201212') where cn>1


3.2 ROWNUM 和Order BY


select l.*,rownum cn from tb_snacks_analyse_list l where l.sum_time='201212' rownum<=5 order by create_table


要注意的是:在使用ROWNUM 時,只有當Order By 的字段是主鍵時,查詢結果纔會先排序再計算ROWNUM,但是,對非主鍵字段OBJECT_NAME 進行排序時卻不是。
出現這種混亂的原因是:Oracle 先按物理存儲位置(rowid)順序取出滿足rownum 條件的記錄,即物理位置上的前5 條數據,然後在對這些數據按照Order By 的字段進行排序,而不是我們所期望的先排序、再取特定記錄數。


select * from (select l.*,rownum cn from tb_snacks_analyse_list l where l.sum_time='201212' order by create_table) where cn>1 rownum<=5


3.3 排序分頁


當對存在重複值的字段排序後再分頁輸出,我們很容易會陷入到另外一個“陷阱”。


t_test1表owner字段內容一致,object_name內容唯一。
OWNER    OBJECT_NAME
-------- --------------
AFWOWNER AFWADAPTER
AFWOWNER AFWADAPTERFQN_PK
AFWOWNER AFWADAPTERCONFIGURATION
AFWOWNER AFWADAPTERCONFIGURATION_PK


select owner, object_name 
from (select a.*, rownum as rn 
      from (select owner, object_name from t_test1 order by owner) a
      where rownum <= 10)
where rn >= 1;


select owner, object_name 
from (select a.*, rownum as rn 
      from (select owner, object_name from t_test1 order by owner) a
      where rownum <= 20)
where rn >= 11;


第一個查詢結果所得到的記錄會有部分存在於第二個查詢結果中,出現着這種情況大多由優化器採用了“SORT (ORDER BY STOPKEY)”引起。
“SORT (ORDER BY STOPKEY)”不需要對所有數據進行排序,而是隻要找出結果集中的按特定順序的最前N 條記錄,
一旦找出了這N 條記錄,就無需再對剩下的數據進行排序,而直接返回結果。這種算法我們可以視爲是“快速排序”算法的變種。
快速排序算法的基本思想是:先將數據分2 組集合,保證第一集合中的每個數據都大於第二個集合中每個數據,
然後再按這個原則對每個集合進行遞歸分組,直到集合的單位最小。在進行“SORT(ORDER BY STOPKEY)”時,
首先找出N 條數據(這些數據並沒有做排序)放在第一組,保證第一組的數據都大於第二組的數據,然後只對第一組數據進行遞歸。
可以看到,基於這樣的算法基礎上,如果N 的數值不同,數據的分組也不同(如N=20時,第一次分組比例爲12:8,然後繼續遞歸;當N=10 時,第一次分組比例爲3:7 … …),
這樣,在數據的排序字段值都相等時,輸出結果的順序就會因爲N 值不同而不同。


如何解決?
(1)、讓查詢計劃避免“SORT (ORDER BY STOPKEY)”,採用“SORT (ORDER BY)”,使數據排序不受ROWNUM 的影響。但這樣會使所有數據都做排序。


select owner, object_name 
from (select a.*, rownum as rn 
      from (select owner, object_name, rowid from t_test1 order by owner) a)
where rn <= 10 and rn >= 1;


select owner, object_name 
from (select a.*, rownum as rn 
      from (select owner, object_name, rowid from t_test1 order by owner) a)
where rn <= 20 and rn >= 11;


(2)在排序時,加上一個或多個字段(如主鍵字段、ROWID),使排序結果具有唯一性:


select owner, object_name 
from (select a.*, rownum as rn 
      from (select owner, object_name, rowid from t_test1 order by owner, object_id) a
      where rownum <= 10)
where rn >= 1;


select owner, object_name 
from (select a.*, rownum as rn 
      from (select owner, object_name, rowid from t_test1 order by owner, object_id) a
      where rownum <= 20)
where rn >= 11;


(3)對排序字段建立索引,並強制使用索引。這樣就能利用索引已經建立好的排序結果


create index t_test1_idx1 on t_test1(owner);


select owner, object_name 
from (select a.*, rownum as rn 
      from (select /*+index(t T_TEST1_IDX1)*/owner, object_name from t_test1 t order by owner) a
      where rownum <= 10)
where rn >= 1;


select owner, object_name 
from (select a.*, rownum as rn 
      from (select /*+index(t T_TEST1_IDX1)*/owner, object_name from t_test1 t order by owner) a
      where rownum <= 20)
where rn >= 11;


3.4 性能陷阱


create index t_test1_idx1 on T_COM(COMP_ID, VIEW_TYPE, DELETED_IND, SH_ID) --複合索引


CREATE OR REPLACE VIEW MY_VIEW AS
SELECT ROWNUM ID,T0.COMP_ID COMPANY_ID,T0.SH_ID B_NUM,T0.REC_UPD_DT 
FROM T_COM T0
WHERE T0.DELETED_IND = 0
AND T0.VIEW_TYPE = 'BK';


執行
select * from MY_VIEW where company_id='12'; 
看查詢計劃發現並沒有命中索引。仔細分析視圖的定義語句,其中包含了對ROWNUM 的查詢。ROWNUM 是一個虛字段,只有產生結果集時纔會有值。
因此,爲保證邏輯結果,優化器並沒有將視圖查詢條件與外部主查詢條件合併後再執行查詢操作,而是先執行子查詢部分(條件不足以命中索引),
得到結果集(執行計劃中的第一步Full Table Scan)和對應的ROWNUM值(執行計劃中的第二步COUNT)以後再根據外部條件對結果集過濾(執行計劃中的第三步FILTER)。
當我們將ROWNUM 從視圖中拿掉,執行計劃就能像我們之前所預想的命中索引了。




另外
select id, owner, object_name
from (select rownum as id, owner, object_name from t_test1 where object_name like 'TEM%') v
where owner='DEMO';


select id, owner, object_name
from (select rownum as id, owner, object_name from t_test1 where object_name like 'TEM%' and owner='DEMO') v;


上面2句的owner, object_name結果都一致,但是ID不同。

發佈了65 篇原創文章 · 獲贊 24 · 訪問量 41萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章