Skip to Main Content
  • Questions
  • 2 distinct uses of rownum in where clause. Why did this work?

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question.

Asked: May 29, 2002 - 9:43 am UTC

Last updated: August 11, 2004 - 10:04 am UTC

Version: 8.1.7

Viewed 1000+ times

You Asked

I was looking for a pattern in some data and typed out this query(with more columns)
very quickly without thinking it through.
The result was what I wanted, but after a second of thought, not what I expected.
I know from the distribution of data that the rownum that forms the stopkey can't be
the same rownum as the one used in the filter. They are 80% 'E' and the first 100, at
least, on disk are 'E'.
What's going on? Can the Oracle engine read minds? ;)
Thanks,
Chuck


SQL> select rownum,
2 sale_v_endo
3 from tax.lien_sales
4 where rownum <= 10
5 and ((mod(rownum, 2) = 0 and sale_v_endo = 'S')
6 or(mod(rownum, 2) = 1 and sale_v_endo = 'E'));

ROWNUM S
---------- -
1 E
2 S
3 E
4 S
5 E
6 S
7 E
8 S
9 E
10 S

10 rows selected.


Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=84 Card=54071 Bytes=
54071)

1 0 COUNT (STOPKEY)
2 1 FILTER
3 2 TABLE ACCESS (FULL) OF 'LIEN_SALES' (Cost=84 Card=5407
1 Bytes=54071)



and Tom said...

Well, it worked because rownum is only incremented after a SUCCESSFUL row is returned.

That query is somewhat equivalent to this procedural code:


rownum = 1
for x in ( select * from tax.lien_sales )
loop
exit when rownum > 10; (your where rownum <= 10 condition)
if ( (mod(rownum,2) = 0 and x.sale_v_endo = 'S')
or
(mod(rownum,2) = 1 and x.sale_v_endo = 'E') )
then
OUTPUT ROW;
rownum = rownum + 1;
end if;
end loop

So, this query started reading tax.lien_sales. rownum is <= 10 (it is one). mod(rownum,2) = 1. Until it found a row with 'E' in it -- rownum would stay at 1. So, suppose the first row we found had sale_v_endo = 'X' -- the predicate would not be satisfied, the rownum would not be incremented. We would goto the second fetched row. Suppose this did have an 'E'. Now we would output the row and increment rownum. Now it would continue through the rows until it found an 'S' (rownum = 2 until we find an 'S')

As long as there were enough 'E's and 'S's in the right order -- we would get what we wanted (as long as there were at least 5 E's in the table that were followed by at least one S -- PHYSICALLY on disk.

Consider:


ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( sale_v_endo varchar2(1) );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t select 'E' from all_users where rownum <= 10;

10 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t select 'S' from all_users where rownum <= 10;

10 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select sale_v_endo, count(*) from t group by sale_v_endo;

S COUNT(*)
- ----------
E 10
S 10

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select rownum, sale_v_endo
2 from t
3 where rownum <= 10
4 and ((mod(rownum, 2) = 0 and sale_v_endo = 'S')
5 or(mod(rownum, 2) = 1 and sale_v_endo = 'E'));

ROWNUM S
---------- -
1 E
2 S

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> truncate table t;

Table truncated.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t select decode( mod(rownum,2) ,1,'E','S' ) from all_users where rownum <= 20;

20 rows created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;

Commit complete.

ops$tkyte@ORA817DEV.US.ORACLE.COM> select sale_v_endo, count(*) from t group by sale_v_endo;

S COUNT(*)
- ----------
E 10
S 10

ops$tkyte@ORA817DEV.US.ORACLE.COM> select rownum, sale_v_endo
2 from t
3 where rownum <= 10
4 and ((mod(rownum, 2) = 0 and sale_v_endo = 'S')
5 or(mod(rownum, 2) = 1 and sale_v_endo = 'E'));

ROWNUM S
---------- -
1 E
2 S
3 E
4 S
5 E
6 S
7 E
8 S
9 E
10 S

10 rows selected.


now, in both cases -- there were 20 rows, 10 of each. Note how the rownum query was RADICALLY affected by the physical order of the data in the table!!!

Beware!!! (not really beware -- just be aware of how it works)





Rating

  (5 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Great !!!!

A reader, May 29, 2002 - 10:26 am UTC


Interesting, Thanks!

Chuck Jolley, May 29, 2002 - 10:34 am UTC

Well, I wanted a sample with both types and that's what I got.
The explanation of rownum is very interesting.
Thanks,
Chuck


Thank you Tom!!!

Kelivin Tian, February 03, 2004 - 3:20 am UTC


You are a born teacher... Great explanation

A reader, July 13, 2004 - 12:39 pm UTC


Rownums and Complex Queries.

Alvin, August 11, 2004 - 4:58 am UTC

Is there a rule that states that rownums cannot be used on complex queries such as UNIONs ??

The following query wont work :

SELECT * FROM (
SELECT ROWNUM RN,temp.*
FROM (
SELECT cdr.*, plan.PLANCHARGEABLEDURATION, plan.PLANCALLCHARGE, lu.DESCRIPTION
FROM RTBETELEPHONY.RTBETELCALLDETAILRECORDS cdr,
RTBETELEPHONY.RTBETELCDRPLANS plan,
RTBETELEPHONY.RTBETELCDRPLANLU lu
WHERE cdr.PLANID>0
AND (cdr.TIME >= 1090970909984 AND cdr.TIME <= 1092180509984)
AND cdr.calldetailrecordid=plan.calldetailrecordid(+)
AND plan.PLANID=lu.PLANID(+)
UNION
SELECT cdr.*, plan.PLANCHARGEABLEDURATION, plan.PLANCALLCHARGE, lu.DESCRIPTION
FROM RTBETELEPHONY.RTBETELSWITCHLOGS cdr,
RTBETELEPHONY.RTBETELCDRPLANS plan,
RTBETELEPHONY.RTBETELCDRPLANLU lu
WHERE cdr.PLANID>0
AND (cdr.TIME >= 1090970909984 AND cdr.TIME <= 1092180509984)
AND cdr.calldetailrecordid=plan.calldetailrecordid(+)
AND plan.PLANID=lu.PLANID(+)
) temp
) WHERE RN BETWEEN 1 AND 10 ORDER BY TIME ASC

SELECT ROWNUM RN,temp.*
*
ERROR at line 2:
ORA-00918: column ambiguously defined

Seems straightforward enough a sql to work.

Tom Kyte
August 11, 2004 - 10:04 am UTC

  1  select * from (
  2  select rownum rn, temp.*
  3  from (
  4  select dual.*, 1 time from dual
  5  union
  6  select dual.*, 1 time from dual
  7  ) temp
  8* ) where rn between 1 and 10 order by time asc
ops$tkyte@ORA9IR2> /
 
        RN D       TIME
---------- - ----------
         1 X          1



works for me -- you'd have to give us create tables, etc in order to reproduce-- also versions are generally "useful"

you've done the query wrong though, that I can say.

You do undersnat that you are getting 10 random rows and then sorting them, you are not sorting and then getting the top-10


to do a top-n query you code:


select * 
 from ( select a.*, rownum r 
          from (YOUR_QUERY_HERE_INCLUDING_ORDER_BY)
         where rownum <= :MAX_ROW )
 where r >= :MIN_ROW;

 

More to Explore

Performance

Get all the information about database performance in the Database Performance guide.