小議ROWNUM
如何使用ROWNUM是個老生常談的問題了,本來沒有打算專門強調(diào)這個問題,但是最近在看Oracle的官方PL/SQL文檔時發(fā)現(xiàn)了一個嚴重的錯誤,借這個機會還是簡單說一下。
首先來看Oracle文檔的描述,在10.2的PL/SQL文檔中,Oracle關(guān)于PL/SQL中直接使用SELECT的查詢描述為:
Selecting At Most One Row: SELECT INTO Statement
If you expect a query to only return one row, you can write a regular SQL SELECT statement with an additional INTO clause specifying the PL/SQL variable to hold the result.
If the query might return more than one row, but you do not care about tb values after the first, you can restrict any result set to a single row by comparing the ROWNUM value. If the query might return no rows at all, use an exception handler to specify any actions to take when no data is found.
這個描述是沒有問題的,但是到了11.2中,文檔的描述變成了:
Single-Row Result Sets
If you expect the query to return only one row, then use the SELECT INTO statement to store values from that row in either one or more scalar variables (see "Assigning Values to Variables with the SELECT INTO Statement") or one record variable (see "SELECT INTO Statement for Assigning Row to Record Variable").
If the query might return multiple rows, but you care about only the nth row, then restrict the result set to that row with the clause WHERE ROWNUM=n. For more information about the ROWNUM pseudocolumn, see Oracle Database SQL Language Reference.
第一個反應(yīng)是不是我看錯了,居然可以通過WHERE ROWNUM = N來限制只返回第N條記錄 ,再仔細看了一遍,并和10g的文檔對比,發(fā)現(xiàn)11.2和10.2中的不同。于是第二個反應(yīng)是Oracle在11.2中提供了新特性,使得PL/SQL語句中直接SELECT可以通過WHERE ROWNUM來直接控制游標,于是特意在11.2上進行了測試,發(fā)現(xiàn)結(jié)果和10.2上沒有區(qū)別,ROWNUM = N是行不通的,除非N等于1。
SQL> select * from v$version;
BANNER
------------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production
PL/SQL Release 11.2.0.1.0 - Production
CORE 11.2.0.1.0 Production
TNS for 32-bit Windows: Version 11.2.0.1.0 - Production
NLSRTL Version 11.2.0.1.0 - Production
SQL> select * from tab;
TNAME TABTYPE CLUSTERID
------------------------------ ------- ----------
COMPANIES TABLE
MLOG$_T_F TABLE
MLOG$_T_P TABLE
MV_T_ORACLE TABLE
SERVICES TABLE
SERVICE_RATES TABLE
SERVICE_USAGE TABLE
SERVICE_USERS TABLE
T TABLE
T_DEFER TABLE
T_F TABLE
T_LOAD_LOB TABLE
T_P TABLE
T_PART TABLE
已選擇14行。
SQL> set serverout on
SQL> declare
2 v_name varchar2(30);
3 begin
4 select tname into v_name from tab where rownum = 5;
5 dbms_output.put_line(v_name);
6 exception
7 when no_data_found then
8 dbms_output.put_line('rownum equal the num bigger than 1 is incorrect!');
9 end;
10 /
rownum equal the num bigger than 1 is incorrect!
PL/SQL過程已成功完成。
顯然Oracle文檔這里出現(xiàn)了嚴重的錯誤,如果要使用ROWNUM來控制返回第幾行結(jié)果,那么至少需要2層嵌套查詢才可以。
最后簡單總結(jié)一下ROWNUM,很多人都知道ROWNUM只適用于小于或小于等于,如果進行等于判斷,那么只能等于1,不能進行大于的比較。但是卻并不了解造成這種限制條件的機制是什么。
其實ROWNUM的返回很簡單,ROWNUM總是從1開始,不管當前的記錄是否滿足查詢結(jié)果,ROWNUM返回的值都是1,如果這條記錄的值最終滿足所有的條件,那么ROWNUM會遞加,下一條記錄的ROWNUM會返回2,否則下一條記錄的ROWNUM仍然返回1。
理解了這一點,就清楚為什么一般的ROWNUM大于某個值或等于某個不為1的值是無法返回結(jié)果的,因此對于每條記錄的ROWNUM都是1,而ROWNUM為1不滿足查詢的結(jié)果,所以下一條記錄的ROWNUM不會遞增,仍然是1,因此所有的記錄都不滿足條件。
了解了原理,就可以很容易的寫出ROWNUM大于某值的例子:
SQL> select * from tab where rownum = 1 or rownum > 1;
TNAME TABTYPE CLUSTERID
------------------------------ ------- ----------
COMPANIES TABLE
MLOG$_T_F TABLE
MLOG$_T_P TABLE
MV_T_ORACLE TABLE
SERVICES TABLE
SERVICE_RATES TABLE
SERVICE_USAGE TABLE
SERVICE_USERS TABLE
T TABLE
T_DEFER TABLE
T_F TABLE
T_LOAD_LOB TABLE
T_P TABLE
T_PART TABLE