Home>Question Details



Rajesh -- Thanks for the question regarding "getting rows N through M of a result set", version

Submitted on 2-May-2000 13:21 Central time zone
Last updated 2-Sep-2009 10:36

You Asked

I would like to fetch data after joining 3 tables and 
sorting based on some field. As this query results into approx 
100 records, I would like to cut the result set into 4, each of 
25 record. and I would like to give sequence number to each 
record. Can I do using SQL Plus ?


 

and we said...


In Oracle8i, release 8.1 -- yes.

select * 
  from ( select a.*, rownum rnum
           from ( YOUR_QUERY_GOES_HERE -- including the order by ) a
          where rownum <= MAX_ROWS )
 where rnum >= MIN_ROWS
/

that'll do it.  It will *not* work in 8.0 or before.
 

Reviews    
5 stars Pagination   August 3, 2001 - 1am Central time zone
Reviewer: Karthik from India
It was to the point and very very useful
i would keep pestering you with more questions in the weeks to come. 


4 stars Yes,   September 25, 2001 - 1am Central time zone
Reviewer: A reader 
it was useful. 


5 stars Lifesaver....   October 16, 2001 - 4pm Central time zone
Reviewer: Robert Jackson from Dallas, Texas
This information was invaluable... I would have had to "kludge" something.... 


5 stars Parag   March 31, 2002 - 5am Central time zone
Reviewer: Parag Mehta from India
Tom :

Great .... I think Ora ( Oracle ) has been made for u.
I am highly impressed by ur answere.

Regards
- Parag 


Followup   March 31, 2002 - 9am Central time zone:

you = u
your = ur

is your keyboard broken such that Y and O do not work anymore?  cle might be the next to go.

(there are enough abbreviations and three letter acronyms in the world, do we really have to make 
it HARDER to read stuff everyday by making up new ones all of the time) 

1 stars Upset   March 31, 2002 - 10am Central time zone
Reviewer: Parag from India
I am very Upset with "YOUR" Behaviour. I have not expected the same from " YOU". You could have 
convey the same in a different Professional Words.

For " YOUR" kind information Dear Tom , My KEYBOARD has not broken down at all. It's working 
perfectly. 
 


5 stars With you tom on 'YOUR' comment on 'u' or 'ur'   March 31, 2002 - 5pm Central time zone
Reviewer: Sean from Atlanta
Mr. Parag, 

You just way over reacted. 


5 stars U R GR8   April 1, 2002 - 8am Central time zone
Reviewer: Mark A. Williams from Indianapolis, IN USA
Tom,

Maybe you could put something on the main page indicating appropriate use of abbreviations?  
Although, now that I think about it, it probably wouldn't do much good, as it appears people ignore 
what is there (and on the 'acceptance' page) anyway...

- Mark 


Followup   April 1, 2002 - 10am Central time zone:

Already there ;)

It's my new crusade (along with bind variables).  But yes, you are correct -- most people don't 
read it anyway.

You would probably be surprised how many people ask me "where can I read about your book" -- 
surprising given that it is right there on the home page... 

5 stars Saw it was there after the fact   April 1, 2002 - 10am Central time zone
Reviewer: Mark A. Williams from Indianapolis, IN USA
Tom:

Saw that you had added the message about the abbreviations after the fact.  That's what I get for 
having my bookmark point to the 'Search/Archives' tab instead of the main page...

- Mark 


5 stars   April 1, 2002 - 11am Central time zone
Reviewer: A reader from Los Angeles, CA
Excellent query. I just want to be sure I understand it.
You run the query 4 times, each time changeing the MAX and MIN rownumbers. Correct? 


Followup   April 1, 2002 - 1pm Central time zone:

You just change min and max to get different ranges of rows, yes. 

5 stars Very good   April 1, 2002 - 12pm Central time zone
Reviewer: Natthawut from IA,USA
This will be useful for me in the future.
Thanks.

PS. Don't listen to Mr.Parag. He just envy you ;) 


4 stars between   April 1, 2002 - 7pm Central time zone
Reviewer: Mikito harakiri 
Returning to the old discussion about difference between 

select p.*, rownum rnum
    from (select * from hz_parties ) p
          where rownum between 90 and 100

vs

select * from ( 
   select p.*, rownum rnum
           from (select * from hz_parties ) p
          where rownum < 100
) where rnum >= 90

I claim that they are identical fron perfomance standpoint. Indeed, the plan for the first one

  SELECT STATEMENT  20/100
      VIEW  20/100
          Filter Predicates
              from$_subquery$_001.RNUM>=90
          COUNT (STOPKEY)  
              Filter Predicates
                  ROWNUM<=100
              TABLE ACCESS (FULL) hz_parties 20/3921
 
seems to be faster than

  SELECT STATEMENT  20/100
      COUNT (STOPKEY)  
          Filter Predicates
              ROWNUM<=100
          FILTER  
              Filter Predicates
                  ROWNUM>=90
              TABLE ACCESS (FULL) hz_parties 20/3921
 

But, note that all nodes in the plan are unblocking!. Therefore, it doesn't matter which condition 
is evaluated earier... 
 


Followup   April 1, 2002 - 8pm Central time zone:

Please don't claim -- benchmark and PROVE (come on -- I do it all of the time).

Your first query "where rownum between 90 and 100" never returns ANY data.  that predicate will 
ALWAYS evaluate to false -- always.

I've already proven in another question (believe it was with you again) that 

select * from ( 
   select p.*, rownum rnum
           from (select * from hz_parties ) p
          where rownum < 100
) where rnum >= 90

is faster then:

select * from ( 
   select p.*, rownum rnum
           from (select * from hz_parties ) p
) where rnum between 90 and 100

which is what I believe you INTENDED to type.  It has to do with the way we process the 
COUNT(STOPKEY) and the fact that we must evaluate 

   select p.*, rownum rnum
           from (select * from hz_parties ) p

AND THEN apply the filter where as the other will find the first 100 AND THEN stop.

so, say I have an unindexed table:

ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*) from big_table;

  COUNT(*)
----------
   1099008

(a copy of all_objects over and over and over) and I run three queries.  Yours to show it fails (no 
data), what I think you meant to type and what I would type:

select p.*, rownum rnu
  from ( select * from big_table ) p
 where rownum between 90 and 100

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      6.17      15.31      14938      14985         81           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      6.17      15.31      14938      14985         81           0

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 216

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  COUNT STOPKEY
      0   FILTER
1099009    TABLE ACCESS FULL BIG_TABLE


your query -- no data found....  Look at the number of rows inspected however



select *
from (
select p.*, rownum rnum
  from ( select * from big_table ) p
)
 where rnum between 90 and 100

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      7.93      17.03      14573      14986         81          11
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      7.93      17.03      14573      14986         81          11

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 216

Rows     Row Source Operation
-------  ---------------------------------------------------
     11  VIEW
1099008   COUNT
1099008    TABLE ACCESS FULL BIG_TABLE

what I believe you mean to type in -- agein -- look at the rows processed!

Now, what I've been telling everyone to use:


select * from (
   select p.*, rownum rnum
           from (select * from big_table ) p
          where rownum < 100
) where rnum >= 90

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.01          1          7         12          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.01          1          7         12          10

Misses in library cache during parse: 0
Optimizer goal: CHOOSE
Parsing user id: 216

Rows     Row Source Operation
-------  ---------------------------------------------------
     10  VIEW
     99   COUNT STOPKEY
     99    TABLE ACCESS FULL BIG_TABLE


HUGE difference.  Beat that...  

Claims -- don't want em.
Benchmark, metrics, statistics -- love em -- want em -- need em.





 

5 stars Over the top!   April 1, 2002 - 10pm Central time zone
Reviewer: Thevaraj Subramaniam from Kuala Lumpur, Malaysia
Tom, I am really very impressed with the way you prove it with examples and explanations. Answering 
questions from around the world, and at the same time facing hurdles along the way and overcoming 
it. You are the best! Will be always supporting asktom.oracle.com. Cheers. 


5 stars Thank goodness!   April 3, 2002 - 1am Central time zone
Reviewer: Jim from Oz
 Tom,

 Liked the solution and your new rule.

 You have my vote on the rule not to use "u" for you
 and "ur" for your. It's not clever, it simply makes
 things harder to read, in fact I think it's just plain
 lazy

 Anyone that doesn't like it can simply ask someone else.
 


5 stars between   April 3, 2002 - 3pm Central time zone
Reviewer: Mikito harakiri 
Thanks Tom. I finally noticed that you have rownum in one predicate and rnum in the other and they 
are different:-) 

sql>select * from ( 
  2     select p.*, rownum rnum
  3             from (select * from hz_parties ) p
  4            where rownum < 100
  5  ) where rnum >= 90  

Statistics
----------------------------------------------------------
          7  consistent gets
          5  physical reads

The best solution I was able to get:

appsmain>select * from (
  2   select * from ( 
  3     select p.*, rownum rnum
  4             from (select * from hz_parties ) p
  5   ) where rnum between 90 and 100
  6  ) where rownum < 10

Statistics
----------------------------------------------------------
         15  consistent gets
          5  physical reads

It's neither faster, nor more elegant:-( 


3 stars actual "between" test   April 3, 2002 - 8pm Central time zone
Reviewer: Mikito harakiri 
Tom,

Sorry, but I see no difference:

public static void main(String[] args) throws Exception {
    Class.forName("oracle.jdbc.driver.OracleDriver");
    System.out.println(execute("select * from (select p.*, rownum rnum "
+ "  from (select * from hz_parties ) p "
+ "           where rownum < 100 "
+ " ) where rnum >= 90  "));
    System.out.println(execute("select * from ( \n"
+ "   select p.*, rownum rnum "
+ "             from (select * from hz_parties ) p "
+ "   ) where rnum between 90 and 100"));
  
}
static long execute( String query ) throws Exception {
    Connection con = 
DriverManager.getConnection("jdbc:oracle:thin:@dlserv7:1524:main","apps","apps");
    con.setAutoCommit(false);

    con.createStatement().execute("alter system flush shared_pool");
    long t1 = System.currentTimeMillis();
    ResultSet rs = con.createStatement().executeQuery(query);
    rs.next();
    rs.next();
    rs.next();
    rs.next();
    rs.next();
    rs.next();
    long t2 = System.currentTimeMillis();
        
    con.rollback();        
    con.close();
    return t2 - t1;
}

Both queries return in 0.6 sec. Here is my interpretation: The "between" query in the context where 
we open cursor, read first rows, and then discard the rest is essentially the same as "between" 
query with stopcount (that goofy sql in my last reply). The execution engine doesn't seem to go 
forward and check the between predicate for the whole table, or does it? 


Followup   April 4, 2002 - 11am Central time zone:

TKPROF, TKPROF, TKPROF.  

thats all you need to use.  

This query:


select * 
  from ( select p.*, rownum rnum 
           from ( YOUR_QUERY )
          where rownum < 100
       )
  where rnum >= 90


runs your query and gathers the first 100 rows and stops.  IF YOUR_QUERY must materialize all of 
the rows before it can get the first row (eg: it has certain constructs like groups by and such) -- 
then the difference in your case may not be as large -- but its there.  Use TKPROF to get RID of 
the java overhead in the timings (timing in a client like that isn't very reliable).

Consider:

here we obviously don't need to get the last row before the first row -- it's very "fast"

select *
  from ( select p.*, rownum rnum
           from ( select owner, object_name, object_type
                    from big_table
                ) p
          where rownum <= 100
       )
 where rnum >= 90

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.01       0.00         63          7         12          11
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.01       0.00         63          7         12          11

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 216

Rows     Row Source Operation
-------  ---------------------------------------------------
     11  VIEW
    100   COUNT STOPKEY
    100    TABLE ACCESS FULL BIG_TABLE



Now, lets add an aggregate -- here we do have to process all rows in the table HOWEVER, since the 
rownum is pushed down as far as we can push it - we can do some suboptimizations that make this 
faster

select *
  from ( select p.*, rownum rnum
           from ( select owner, object_name, object_type, count(*)
                    from big_table
                   group by owner, object_name, object_type
                ) p
          where rownum <= 100
       )
 where rnum >= 90

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      5.78      18.08      14794      14985         81          11
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      5.79      18.08      14794      14985         81          11

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 216

Rows     Row Source Operation
-------  ---------------------------------------------------
     11  VIEW
    100   COUNT STOPKEY
    100    VIEW
    100     SORT GROUP BY STOPKEY
1099008      TABLE ACCESS FULL BIG_TABLE

Lastly, we'll do it your way -- here we don't push the rownum down, the chance for optimization 
is gone and you run really slow

select *
  from ( select p.*, rownum rnum
           from ( select owner, object_name, object_type, count(*)
                    from big_table
                   group by owner, object_name, object_type
                ) p
       )
 where rnum between 90 and 100

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.03          0          0          0           0
Execute      2      0.00       0.00          0          0          0           0
Fetch        2     20.15     112.44      24136      14985        184          11
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        5     20.15     112.47      24136      14985        184          11

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 216

Rows     Row Source Operation
-------  ---------------------------------------------------
     11  VIEW
  17172   COUNT
  17172    VIEW
  17172     SORT GROUP BY
1099008      TABLE ACCESS FULL BIG_TABLE


I guess, at the end of the day, it is up to you.  I can only show you that it is faster so many 
times.  In the end -- it is your choice. 

In your case, this is what I am guessing:

o hz_parties is a view (recognize it from apps)
o its a view that gets the last row before it can get the first
o the number of rows you can see is not significant (maybe a thousand or so, something that fits in 
RAM nicely)
o the rownum optimization in your case doesn't do much -- if you see the tkprof, you'll be able to 
quantify what it does for you.


In general I can say this:

you would be doing the wrong thing to use "where rnum between a and b" when you can push the rownum 
DOWN into the inner query and achieve PHENOMEMAL performance gains in general.  But again, that is 
your choice.


nuff said

 

5 stars Performance difference   July 25, 2002 - 5pm Central time zone
Reviewer: Ken Chiu from toronto, ON canada
The 1st query below is more than half faster than the 2nd query, please explain what happened ?

select b.*
(Select * from A Order by A.Id) b
where rownum<100

select * from 
(select b.*,rownum rnum
(Select * from A Order by A.Id) b
where rownum<100)
and rnum >= 50

thanks.
 


Followup   July 25, 2002 - 10pm Central time zone:

half faster... Hmmm.... wonder what that means.

I can say that (after fixing your queries) -- My findings differ from yours.  In my case, big_table 
is a 1,000,000 row table and I see:

big_table@ORA920.US.ORACLE.COM> set autotrace traceonly
big_table@ORA920.US.ORACLE.COM> select b.*
  2  from (Select * from big_table A Order by A.Id) b
  3  where rownum<100
  4  /

99 rows selected.


Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=15735 Card=99 Bytes=141000000)
   1    0   COUNT (STOPKEY)
   2    1     VIEW (Cost=15735 Card=1000000 Bytes=141000000)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE' (Cost=15735 Card=1000000 Byte
          s=89000000)

   4    3         INDEX (FULL SCAN) OF 'BIG_TABLE_PK' (UNIQUE) (Cost=2090 Card=1000000)




Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         19  consistent gets
          0  physical reads
          0  redo size
       9701  bytes sent via SQL*Net to client
        565  bytes received via SQL*Net from client
          8  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         99  rows processed

big_table@ORA920.US.ORACLE.COM> 
big_table@ORA920.US.ORACLE.COM> select * from
  2  (select b.*,rownum rnum
  3  from (Select * from big_table A Order by A.Id) b
  4  where rownum<100)
  5  where rnum >= 50
  6  /

50 rows selected.


Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=15735 Card=99 Bytes=15246)
   1    0   VIEW (Cost=15735 Card=99 Bytes=15246)
   2    1     COUNT (STOPKEY)
   3    2       VIEW (Cost=15735 Card=1000000 Bytes=141000000)
   4    3         TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE' (Cost=15735 Card=1000000 By
          tes=89000000)

   5    4           INDEX (FULL SCAN) OF 'BIG_TABLE_PK' (UNIQUE) (Cost=2090 Card=1000000)




Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         13  consistent gets
          0  physical reads
          0  redo size
       5667  bytes sent via SQL*Net to client
        532  bytes received via SQL*Net from client
          5  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         50  rows processed

big_table@ORA920.US.ORACLE.COM> 
big_table@ORA920.US.ORACLE.COM> set autotrace off
big_table@ORA920.US.ORACLE.COM> spool off



The second query is more efficient then the first. 

4 stars Food for thought   September 9, 2002 - 7pm Central time zone
Reviewer: Mike Moore from Santa Clara, CA
The testing shows stats for a range at the beginning of a large table. I wonder what that stats 
look like when selecting rows 999000 thru 999100 ... in other words, rows at the end of a large 
table?
I'd try it myself if I could. 


Followup   September 9, 2002 - 8pm Central time zone:

Every subsequent query as you page down can get slower and slower (goto google, you'll see that 
there as well)

HOWEVER, common sense says that no end user will have the patience 10 or 25 rows at a time to get 
to rows 999000 thru 999100 -- even google cuts you off WAY before you get crazy.  A result set that 
large is quite simply meaningless for us humans.

But then again you can goto asktom and search from somthing and keep paging forward till you get 
board.  It is true you'll get 18,000 hits at most since thats all thats in there so for -- but your 
NEVER have the patience to get to the end.  


Sort of like the old commericial if you remember the wise old owl "how many licks does it take to 
get to the center of a tootsie pop" (i think the owl only got to three before he just bit the 
lollipop).  For those not in the US and who didn't grow up in the 70's -- ignore that last couple 
of sentences ;)

 

4 stars Food for thought (cont)   September 10, 2002 - 9pm Central time zone
Reviewer: Michael J. Moore 
Good point! I mean about nobody actually going to page throught that much data. I confess that I 
don't completely understand how to read an EXECUTE PLAN so my question is only intended to prove to 
myself that I do or don't understand what is actually going on. Suppose a person wanted to use your 
SELECT technique for choosing rows N thru M towards the end of a large table as I earlier 
suggested. Maybe they are not using it for paging, but for some bizarre twilight zone reason that 
is what they want to do. Is it true that one could expect the performance of the SELECT to degrade 
as ranges deeper and deeper into the table are selected? If 'yes' then I say 'great, I understand 
what is happening. If 'no', then I say, "darn, I still don't have a clue." 
As for the 70's, I voted for McCarthy, but Dick Nixon won. 


Followup   September 11, 2002 - 7am Central time zone:

I would order the result set backwards and get the first page instead of the last then (flip the 
order of the data around).

Yes, it'll take longer to get the last N rows then the first N rows in general (not every time, but 
you can reasonable expect it to be the case) 

4 stars problem in query   September 21, 2002 - 3am Central time zone
Reviewer: Ankit Chhibber from India
I tried this query on an ordered view, the view has about 7000 records with eventseverity as 64.

select * from 
( select fmeventsview.* , rownum rnum from 
(select * from fmeventsview where EventSeverity = 64 )fmeventsview where rownum <=500 ) where rnum 
>0;

but i get just 234 rows in the result set.

if i fire the embeded query

"select fmeventsview.* , rownum rnum from 
(select * from fmeventsview where EventSeverity = 64 )fmeventsview where rownum <=500 "
i do get 500 records with RNUM values from 1-500

I don't know where i am goofing up :-(
please advice on the same

 


Followup   September 21, 2002 - 11am Central time zone:

I hate views with order bys.  Add the order by to the query itself.  The order by doesn't have to 
be specifically obeyed in the view once you start doing wacky things to the query.  It must be 
throwing off the rownum somehow -- but not have a test case to play with -- I cannot say. 

5 stars Getting rows 10,00,001 to 10,00,010 - Query taking forever to execute   September 22, 2002 - 4am Central time zone
Reviewer: Brijesh 
Hi Tom,
The query which you've shown is very good and working very fast within a range of 100,000 to 
150,000 rows but 
when trying to get rows more than 500,000's it is taking a minute for doing so.

The query :

select fatwaid,fatwatitle
   from (select a.*,rownum r
           from (select * from fatwa order by fatwaid) a
   where rownum <= &upperbound )
where r >= &lowerbound

when executed with 150001 and 150010 gives me
following output and plan

10 rows selected.

Elapsed: 00:00:02.01

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=826 Card=150010 Byte
          s=11700780)

   1    0   VIEW (Cost=826 Card=150010 Bytes=11700780)
   2    1     COUNT (STOPKEY)
   3    2       VIEW (Cost=826 Card=1282785 Bytes=83381025)
   4    3         TABLE ACCESS (BY INDEX ROWID) OF 'FATWA' (Cost=826 C
          ard=1282785 Bytes=2837520420)

   5    4           INDEX (FULL SCAN) OF 'PK_FATWA' (UNIQUE) (Cost=26
          Card=1282785)

When executed with values of
1000001 and 1000010 

Following is the plan and time
10 rows selected.

Elapsed: 00:01:01.08

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=826 Card=1000010 Byt
          es=78000780)

   1    0   VIEW (Cost=826 Card=1000010 Bytes=78000780)
   2    1     COUNT (STOPKEY)
   3    2       VIEW (Cost=826 Card=1282785 Bytes=83381025)
   4    3         TABLE ACCESS (BY INDEX ROWID) OF 'FATWA' (Cost=826 C
          ard=1282785 Bytes=2837520420)

   5    4           INDEX (FULL SCAN) OF 'PK_FATWA' (UNIQUE) (Cost=26
          Card=1282785)


How Can I speed up the process of getting last rows? 


Followup   September 22, 2002 - 10am Central time zone:

Nope, no go -- this is good for paging through a result set.  Given that HUMANS page through a 
result set and pages are 10-25 rows and we as humans would NEVER in a billion years have the 
patience to page down 100,000 times -- it is very workable.

Perhaps you want to order by DESC and get the first page?

(think about it -- to get the "last page", one must iterate over all of the preceding pages.  a 
desc sort would tend to read the index backwards)

 

3 stars invalid coloumn name exception   October 4, 2002 - 5am Central time zone
Reviewer: Ankit Chhibber from India
Hi,
when this query is fired simultanously ( from Java application using JDBC) from multiple threads, 
oracle sometimes gives an exception "invalid coloumn name" :-(
can you please explain the reason ???
 


Followup   October 4, 2002 - 8am Central time zone:

Umm, magic.  A bug.  Programmer error.  I don't know.

Sounds like time to file a TAR with support.  One would need tons of information such as (and don't 
give it to me, give it to support) type of driver used, version of driver, version of db, a test 
case (as small as humanly possible) that can be run to reproduce the issue.

that last part will be the hard part maybe.  but you should be able to start up a small java 
program with a couple of threads all that just wildly parse and execute queries that eventually 
hits this error. 

4 stars An old question revivied again   October 21, 2002 - 12pm Central time zone
Reviewer: Ankit Chhibber from India
Hi Tom,
I am using your queryto do a lot of DB operations :-), I am reading records 1000 at a time based on 
your approach. when there are 100,000 records in DB (This is acceptable situation, that is what 
people tell me :-) ), the fetch for first 1000 rows takes about 50 seconds :-(.(Our Os is solaris, 
and i use JDBC for accesing the DB)
I am doing a sort (order by) on one of the primary keys.
Can you suggest some way of improving the performance here ???

It would be of real help

regards
Ankit 


Followup   October 21, 2002 - 1pm Central time zone:

http://asktom.oracle.com/~tkyte/tkprof.html
use that tool (sql_trace + TIMED_STATISTICS) to see the query plan, rows flowing through the steps 
of the plan and use that as your jump off point for tuning.

You might be a candidate for FIRST_ROWS optimization.

why 1000 rows, 25 or 100 is more reasonable.  But anyway -- it is probably the fact that you need 
to sort 100k rows each time -- check your sort area size as well. 

5 stars Getting rows 10,00,001 to 10,00,010 - Query taking forever to execute   October 22, 2002 - 12am Central time zone
Reviewer: Brijesh 
Now i've got it, 
its just a matter of thinking why would a user would page through all the 100000 pages to go to 
100001.

Even me searched on google many times but never went beyond the tenth page.

Thanks for all you are doing for developers,
and for the reply.
Regards Brijesh
 


5 stars get the count for my query   November 11, 2002 - 1pm Central time zone
Reviewer: Cesar 
How i can get the count in my query?

select * 
  from ( select a.*, rownum rnum
           from ( YOUR_QUERY_GOES_HERE -- HOW GET HOW MANY ROWS ARE HERE?? ) a
          where rownum <= MAX_ROWS )
 where rnum >= MIN_ROWS

 


5 stars Excellent stuff   December 12, 2002 - 12am Central time zone
Reviewer: Harpreet from India
hi 
 I was having the same problem for a few days of how to do pagination. Howard suggested to look in 
to your site and i found the answer, with some very good discussions. 

 this is really good work.

 


5 stars how to group it by   December 17, 2002 - 2pm Central time zone
Reviewer: A reader 
I have table t as

select * from t;


        T1 COLOR
---------- --------------------
         1 PINK
         2 GREEN
         3 BLUE
         4 RED
         5 YELLOW

select * from xabc;
COLOR                C_DATE
-------------------- -----------------
RED                  MON
RED                  TUE
RED                  WED
RED                  THU
RED                  FRI
RED                  SAT
RED                  SUN
PINK                 MON
PINK                 TUE
PINK                 WED


now I need get the resuleset as follows

COLOR                C_DATE
-------------------- -----------------
RED                  MON
RED                  TUE
RED                  WED
RED                  THU
PINK                 MON

  because red = 4 in t and pink = 1 in t
how to do it ?

TIA 


Followup   December 18, 2002 - 10am Central time zone:

does not compute.  No idea what you mean.  so what if red = 4 and pink = 1? 

5 stars Thanks,   December 17, 2002 - 4pm Central time zone
Reviewer: A reader 
don't spend time answering that I got it !!



 1  select p.*
 2               from (
 3                        select x.color,x.c_date,
 4                               row_number() over (partition by x.color order by c_date) r
 5                        from xabc x,t
 6                        where x.color = t.color
 7                      ) p , t
 8              where p.color = t.color
 9*        and   r <= t.t1
nydev168-->/

COLOR                C_DATE                        R
-------------------- -------------------- ----------
PINK                 MON                           1
RED                  FRI                           1
RED                  MON                           2
RED                  SAT                           3
RED                  SUN                           4




Thanks :) 


3 stars Scrollable cursors   December 18, 2002 - 5pm Central time zone
Reviewer: A reader from moscow
Tom,

Are scrollable cursors (9.2) available in pl/sql and jdbc, or only pro c/c++?

If not, when will this feature become available from pl/sql? 


Followup   December 19, 2002 - 7am Central time zone:

jdbc has then.

I cannot imagine a case whereby plsql would need/desire them.  I can see their usefulness in a 
situation where you have a client/server stated connection and want to page up/down through a 
result set - but plsql does not lend itself to that sort of environment?  We rely on the client to 
do that (eg: something like forms, or jdbc).  In a stored procedure -- when would you want to "go 
backwards"? 

5 stars what if red = 4 and pink = 1?   December 20, 2002 - 11am Central time zone
Reviewer: A reader 
it means there should be only 4 rows returned for red
and only 1 row should be returned for pink evenif ther are 10 rows for pink

 


5 stars Master Oracle Guru   February 5, 2003 - 4pm Central time zone
Reviewer: Denise from Virginia Beach, VA
Tom

I wish I had 1/5 your knowledge...everytime I come
here seeking answers and solutions you always seem to
hit the target head on...and then top it off with superb
code that is easy to understand and apply.

Everytime I come here my answers are solved and I learn
something new everytime.

I am DEFINITELY buying your book!

as a newbie I can't express enough how important it is
for those of us venturing into this brave new world of
Oracle to have someone of your stature, expertise & knowledge paving the way.

I think your(errrrr...'ur') TERRIFIC!!!
Denise
 


5 stars   February 13, 2003 - 2am Central time zone
Reviewer: Helena Markova from Bratislava, Slovakia


5 stars Excellent.   February 20, 2003 - 8am Central time zone
Reviewer: Chandra S.Reddy from India
Hi Tom,
You are really great. This solution is very much useful for me.
I believe, there will not be any much resource utilization with this approach.
Is that right Tom? 


Followup   February 20, 2003 - 8am Central time zone:

there will be as much resource utilitization as needed to process the query? 

4 stars How can I do this in sql?   February 22, 2003 - 3am Central time zone
Reviewer: A reader 
Tom,
If I want to return set n from each group of records based upon key, for example, the data is like 
this..
store     customer   qty
1         10         10
1         100        20
1         1000       30
.......................
2         20        20
2         200       200
...........
...........
I want to return any two records from each group of store i.e, two records for each store.
Thanks 


Followup   February 22, 2003 - 10am Central time zone:

select *
  from ( select t.*, row_number() over (partition by store order by customer) rn
           from t
       )
 where rn <= 2;


would do it -- or


 

4 stars Thanks a lot !   February 22, 2003 - 5pm Central time zone
Reviewer: A reader 
Tom,
This is regarding followup:
"select *
  from ( select t.*, row_number() over (partition by store order by customer) rn
           from t
       )
 where rn <= 2;
would do it -- or"

1.What is or....?
2.Where can I find more about such type of queries?

Thanks 


Followup   February 22, 2003 - 5pm Central time zone:

ignore the or ;)


analytics are documented in

o sql reference manual
o data warehousing guide

and I think the write up I have on them in my book "Expert one on one Oracle" is pretty 
understandable if you have that - I have a chapter on analytics.

row_number is one of about 40 analytic functions we have 

1 stars Upset   February 23, 2003 - 3pm Central time zone
Reviewer: A reader 
u r the hard 1 not Parag 


Followup   February 23, 2003 - 3pm Central time zone:

maybe if you use real words next time, I'll actually understand what you are trying to communicate. 
 

it is not that much to ask is it?  T

That is what is known as a rhetorical question, I don't expect a response.  The answer is "no, when 
communicating, it is not too much to ask people to use a common agreed upon language as opposed to 
making one up"...

so, don't go away mad, just....
 

4 stars Rownum   February 25, 2003 - 5am Central time zone
Reviewer: phunghung from VIE
Exellent !!!
  It's very useful for me
  Special thanks :D 


5 stars Pagination in other scenario.   February 28, 2003 - 4am Central time zone
Reviewer: Chandra S.Reddy from India
Tom,
This is further R&D for the approach you have provided.
If someone wants to get M to N records of Dept Number instaed of Emp Number.

select * from (select tmp1.*, rownum1 rnum
            from (select e.* from scott.emp e, scott.dept d where e.deptno = d.deptno)tmp1,
            (select deptno, rownum rownum1 from scott.dept)tmp2
            where tmp1.deptno = tmp2.deptno and 
        rownum1 <= :End ) where rnum >= :Start  ;
/
 


Followup   February 28, 2003 - 10am Central time zone:

Well, there are other ways to fry that fish.

analytics rock and roll:

scott@ORA920> select dept.deptno, dname, ename,
  2         dense_rank() over ( order by dept.deptno ) dr
  3    from emp, dept
  4   where emp.deptno = dept.deptno
  5  /

    DEPTNO DNAME          ENAME              DR
---------- -------------- ---------- ----------
        10 ACCOUNTING     CLARK               1
        10 ACCOUNTING     KING                1
        10 ACCOUNTING     MILLER              1
        20 RESEARCH       SMITH               2
        20 RESEARCH       ADAMS               2
        20 RESEARCH       FORD                2
        20 RESEARCH       SCOTT               2
        20 RESEARCH       JONES               2
        30 SALES          ALLEN               3
        30 SALES          BLAKE               3
        30 SALES          MARTIN              3
        30 SALES          JAMES               3
        30 SALES          TURNER              3
        30 SALES          WARD                3

14 rows selected.

scott@ORA920>
scott@ORA920> variable x number
scott@ORA920> variable y number
scott@ORA920>
scott@ORA920> exec :x := 2; :y := 3;

PL/SQL procedure successfully completed.

scott@ORA920>
scott@ORA920> select *
  2    from (
  3  select dept.deptno, dname, ename,
  4         dense_rank() over ( order by dept.deptno ) dr
  5    from emp, dept
  6   where emp.deptno = dept.deptno
  7         )
  8   where dr between :x and :y
  9  /

    DEPTNO DNAME          ENAME              DR
---------- -------------- ---------- ----------
        20 RESEARCH       SMITH               2
        20 RESEARCH       ADAMS               2
        20 RESEARCH       FORD                2
        20 RESEARCH       SCOTT               2
        20 RESEARCH       JONES               2
        30 SALES          ALLEN               3
        30 SALES          BLAKE               3
        30 SALES          MARTIN              3
        30 SALES          JAMES               3
        30 SALES          TURNER              3
        30 SALES          WARD                3

11 rows selected. 

5 stars Using dates is giving error.   March 2, 2003 - 9am Central time zone
Reviewer: Chandra S.Reddy from India
Tom,
Very nice to see many approaches to implent  the pagination.

When I try to implement one of your method, I got some problems.

Issue #1.

Please see below.

SQL> create or replace procedure sp(out_cvGenric OUT PKG_SWIP_CommDefi.GenCurTyp) is
  2  begin
  3  
  4  OPEN out_cvGenric FOR 
  5  select *
  6      from (
  7    select dept.deptno, dname, ename,to_char(hiredate,'dd-mm-yyyy'),
  8           dense_rank() over ( order by dept.deptno ) dr
  9      from emp, dept
 10     where emp.deptno = dept.deptno and hiredate between '17-DEC-80' and '17-DEC-82'
 11           )
 12  where dr between 2 and 3;

 19  end ;
 20  /

Warning: Procedure created with compilation errors.
SQL> show err;

LINE/COL ERROR
-------- -----------------------------------------------------------------
8/28     PLS-00103: Encountered the symbol "(" when expecting one of the
         following:
         , from

I managed this problem by keeping the query in strings(OPEN out_cvGenric FOR 'select * from ... ' ) 
and using USING clause. It worked very fine.

Why is this error Tom.?

Issue #2.

Please check below code. This is my actual implementation.Above is PL/SQL shape for your answer.

procedure sp_clips_reports_soandso (
                in_noperationcenterid in number,
                in_dreportfromdt in  date , 
                in_dreporttodt in date ,
                in_cusername in varchar2,
                in_ntirestatuscode in number,
                in_cwipaccount in varchar2,
                in_npagestart in  number,
                in_npageend in  number ,
                out_nrecordcnt out number ,
                out_nstatuscode out number,
                out_cvgenric out pkg_clips_commdefi.gencurtyp,
                out_cerrordesc out varchar2) is

            v_tempstart    number(5) ;
            v_tempend    number(5) ;
begin
        out_nstatuscode := 0;

            select count(tire_trn_number) into out_nrecordcnt
            from    t_clips_tire 
            where     redirect_operation_center_id = in_noperationcenterid
                and    tire_status_id = in_ntirestatuscode
                and    tire_date >= in_dreportfromdt
                and tire_date <= in_dreporttodt
                and wip_account = in_cwipaccount ;

        if in_npagestart =  -1 and in_npageend = -1 then
        
            v_tempstart    := 1;
            v_tempend    := out_nrecordcnt ;
        else
              v_tempstart :=   in_npagestart ;
              v_tempend :=    in_npageend ;

        end if ;
open out_cvgenric for 
'select *
    from (
  select tire.tire_trn_number tiretrnnumber,
                    to_char(tire.tire_date,''mm/dd/yy''),
                    tire.tire_time,
                    tire.direct_submitter_name user_name,
                dense_rank() over ( order by tire.tire_trn_number ) dr
            from    t_clips_tire tire,
                t_clips_afs_transaction transactions,
                t_clips_transaction_code transactionscd
            where
                tire.tire_trn_number = transactions.tire_trn_number and
                transactions.tran_code = transactionscd.tran_code and 
                redirect_operation_center_id = :opp and
                tire.tire_status_id = :stcode  and
                tire.wip_account = :wip and
                tire.tire_date > :reportfromdt and
                tire.tire_date < :reporttodt and
            order by transactions.tire_trn_number,tran_seq
         )
where dr between :start and :end' using 
in_noperationcenterid,in_ntirestatuscode,in_cwipaccount,v_tempstart,v_tempend;

end sp_clips_reports_soandso;
/
show err;
no errors.
sql> var out_cvgenric refcursor;
sql> var out_nstatuscode  number; 
sql> declare
  2  out_cerrordesc varchar2(2000) ;
  3  --var out_nrecordcnt number ;
  4  begin
  5  sp_clips_reports_soandso(4,'16-feb-02', '16-feb-03',null,2,'0293450720',1,10,:out_nrecordcnt, 
:out_nstatuscode ,:out_cvgenric,out_cerrordesc);
  6  dbms_output.put_line(out_cerrordesc);
  7  end ;
  8  /
declare
*
error at line 1:
ora-00936: missing expression
ora-06512: at "CLIPStest2.sp_clips_reports_soandso", line 40
ora-06512: at line 5

In the above code,query is in string,program got compiled.
But while calling it is showing errors.
If I remove "tire.tire_date > :ReportFromDt and tire.tire_date < :ReportToDt" from the WHERE 
clause, the query is working fine and giving results.
If the dates are in query, it is going wrong.

To say, this pagination in SP, will remove much burdens on the application server. But 
unfortunately am not coming with the solution.

Could you please provide me the solution.
Thanks in advance.

 


Followup   March 2, 2003 - 9am Central time zone:

1) see

http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:3027089372477
same issue -- same workaround in 8i and before, native dynamic sql or a view

2) why are you counting -- that is a very very very bad idea.  First -- the answer can and will 
change (your count is a "guess").  Second, it is the best way to make a system CRAWL to a halt.  
"Oh, I think I'll do a bunch of work and then do it all over again".  Time to double up the 
machine.

you have a buggy sql statement -- two things I see straight off:

        tire.tire_date < :reporttodt and
            order by transactions.tire_trn_number,tran_seq
         )

AND ORDER BY -- you are "missing an expression" in there.

Second - you are missing a pair of binds.  I see 5 "using" variables but count 7 binds.


it is not the dates in the query -- it is the invalid query itself.


Suggestion -- this is how I diagnosed this -- cut and paste the query into sqlplus, change '' into 
' globally in the query.  and run it after doing "variable" statements:

ops$tkyte@ORA920> variable opp varchar2(20)
ops$tkyte@ORA920> variable stcode varchar2(20)
ops$tkyte@ORA920> variable wip varchar2(20)
ops$tkyte@ORA920> variable reportfromdt varchar2(20)
ops$tkyte@ORA920> variable reporttodt varchar2(20)
ops$tkyte@ORA920> variable start varchar2(20)
ops$tkyte@ORA920> variable end varchar2(20)
ops$tkyte@ORA920>
ops$tkyte@ORA920> select *
  2      from (
  3    select tire.tire_trn_number tiretrnnumber,
  4                      to_char(tire.tire_date,'mm/dd/yy'),
  5                      tire.tire_time,
  6                      tire.direct_submitter_name user_name,
  7                  dense_rank() over ( order by tire.tire_trn_number ) dr
  8              from    t_clips_tire tire,
  9                  t_clips_afs_transaction transactions,
 10                  t_clips_transaction_code transactionscd
 11              where
 12                  tire.tire_trn_number = transactions.tire_trn_number and
 13                  transactions.tran_code = transactionscd.tran_code and
 14                  redirect_operation_center_id = :opp and
 15                  tire.tire_status_id = :stcode  and
 16                  tire.wip_account = :wip and
 17                  tire.tire_date > :reportfromdt and
 18                  tire.tire_date < :reporttodt and
 19              order by transactions.tire_trn_number,tran_seq
 20           )
 21  where dr between :start and :end
 22  /
            order by transactions.tire_trn_number,tran_seq
            *
ERROR at line 19:
ORA-00936: missing expression


Now it becomes crystal clear where the mistake it. 

5 stars Using dates is giving error.   March 2, 2003 - 9am Central time zone
Reviewer: Chandra S.Reddy from India
Hi Tom,
In my previous question with the same title, USING caluse is wrong. There bind variables for 
'tire_date' fields are missing. It was wrongly pasted. Sorry for that.
Please find the correct one below.
--
USING  in_noperationcenterid,in_ntirestatuscode, in_cwipaccount,in_dreportfromdt, 
in_dreporttodt,v_tempstart, v_tempend ;
----

Thanks you very much. 


Followup   March 2, 2003 - 9am Central time zone:

still -- missing expression -- figure it out, not hard given information I already supplied. 

5 stars Thank you.   March 2, 2003 - 11am Central time zone
Reviewer: A reader from India
Tom,
Thank you for the suggestion.
COUNT is bad idea, but I should return this to application. There application will decide the 
pagination factor depending on the no. of records. So am using count there.

 


5 stars Why does between not work?   March 26, 2003 - 10am Central time zone
Reviewer: Errick from Cincinnati, Oh
Tom,
  I've been reading through this set of posts, and was curious. Why exactly does the between 90 and 
100 not work, whereas just select * from bigtable where rownum < 100 work? Maybe Im missing 
something from the article. Just curious. 


Followup   March 26, 2003 - 3pm Central time zone:

because rownum starts at 1 and is incremented only when a row is output.

so, 

select * from t where rownum between 90 and 100 would be like this:


rownum := 1;
for x in ( select * from t )
loop
   if ( rownum between 90 and 100 ) 
   then
        output
        rownum := rownum+1;
   end if;
end loop;

nothing ever comes out of that loop. 

5 stars Let me understand it better...   April 7, 2003 - 8am Central time zone
Reviewer: David 
Tom,

I am a DBA and I am sometimes a bit confused when it it comes to supporting web applications. 

The web development people have asked me how to implement pagination, since their connection is 
stateless. 

I would like to submit the query one time only, but I ended up creating something like below, which 
"re-parses", "re-executes" and "re-fetches" for each page:

select * from
  (select b.*,rownum rnum
   from (Select * from big_table a order by a.id) b
   where rownum < :max )
where rnum >= :min ;

1) To my knowledge, each time I do this I have to "re-parse", "re-execute" and "re-fetch" the data. 
The bind variable values are kept and incremented for each page in the application. Is this a good 
approach ?

2) Wouldn't it be better if I could return the entire set (with first_rows) ?

3) How would be a mechanism for that (how would I code that) ?

4) Using this last approach, couldn't I do some kind of "pipelining" so the rows are returned to 
the application, submitting the query only once and without having to return the whole set -- since 
the entire table is too large. 

Thanks
 


Followup   April 7, 2003 - 1pm Central time zone:

1) yes, it is what I do.  Short of maintaining a connection and becoming a client server 
application -- there is no real avoiding this.

Me -- I'd rather have to parse (soft) for each page then to keep a physical, private connection 
(and all of the associated resources) open for that user IN CASE they hit page forward.

2) and you have a 500 row result set -- and the user only looks at the first ten -- and never ever 
goes to page 2?  So you do 50 times the LIO you need to?  Probably at least 40 times as much LIO as 
you ever would (pages are like 10 rows and the users NEVER goto page 11).


No, you want to do as little work as possible, save in the knowledge that people get bored and go 
away after hitting page down once or twice.

3) you'd be on your own...

4) that would be back to "i'm client server, I always have a stated connection, I always consume 
massive resources on your machine -- even when I'm not using it" 

4 stars A Belated Defence of Parag   April 7, 2003 - 11am Central time zone
Reviewer: Richard from Oxfordshire, UK
With reference to Parag's use of abbreviations: Parag's meaning was clear; it must have been to 
you, too, or you wouldn't have known to put u = you, ur = your.

Yes, the world IS awash with abbreviations (3-letter and otherwise)and acronyms, but that's because 
they usually (as in Parag's case) make perfect sense and would be likely to confuse or befuddle 
only the elderly and the infirm!

yrs etc.,

Richard 


Followup   April 7, 2003 - 2pm Central time zone:

Elmer Fudd here, 

Weww, I disagwee. You see, I gets wots of qwestions -- some in pewfect engwish, some in bwoken 
engwish, some in foweign wanguages. Oh, dat scwewy wabbit! I twy to pawse these qwestions -- make 
sense of them and evewy woadbwock someone puts in thewe makes it hawd fow me to do that. Just wike 
weading this pawagwaph is hawd fow you now. I do not think it is too much to ask to use pwopew 
wowds in a fowum wike this. Oh, dat scwewy wabbit! Dis is NOT a ceww phone hewe, this is not 
instant messaging. Dis is a discussion pwace, a pwace to wead things. Oh, dat scwewy wabbit! Using 
made up things just makes it hawdew to undewstand. I don't ask fow too many things, this is one 
that I keep asking fow though.

that really hard to read text brought to you by the dialectizer:
http://www.rinkworks.com/dialect/

Well, I disagree.  You see, I gets lots of questions -- some in perfect english, some in broken 
english, some in foreign languages.  I try to parse these questions -- make sense of them and every 
roadblock someone puts in there makes it hard for me to do that.

Just like reading this paragraph is hard for you now.

I do not think it is too much to ask to use proper words in a forum like this.  This is NOT a cell 
phone here, this is not instant messaging.  This is a discussion place, a place to read things.  
Using made up things just makes it harder to understand. 

I don't ask for too many things, this is one that I keep asking for though. 

4 stars Sending results to the Internet application   April 7, 2003 - 12pm Central time zone
Reviewer: B. Robinson from Hartford, CT
DBA David,

It is not just that the connections are stateless, but the connections are pooled and rotated such 
that there may be a different database connection used for every web page request from a given 
user.

So the only way to avoid requerying for every subset of the large result set would be to return the 
whole massive result set to the web app, and the web app would cache the all results in memory, 
reading each subset from memory as needed.  But since this would require the entire result set to 
be read from the database, it would make more sense to use all_rows.

Naturally, that approach uses up gobs of memory on the app server or web server, so it may not be 
feasible for a web app with thousands of users. 


Followup   April 7, 2003 - 2pm Central time zone:

the connection from the client (browser) to the app server is stateless.   

3 stars time   April 7, 2003 - 5pm Central time zone
Reviewer: A reader 
just a note.

on toms site,

 to load first 3-4 pages is very fast about < 2 secs.
when we go to 490-500 of 501 takes 10 sec. to load a very simple page 


Followup   April 7, 2003 - 6pm Central time zone:

and it gets worse the further you go.  my stuff is optimized to get your the first rows fast -- I 
do not give you the ability to goto to "row 3421" -- what meaning would that have in a search like 
this?  


google search for Oracle 


Results 1 - 10 of about 6,840,000. Search took 0.11 seconds.
Results 91 - 100 of about 7,800,000. Search took 0.24 seconds.
Results 181 - 190 of about 6,840,000. Search took 0.49 seconds.
(wow, thats wacky - the counts change too)
Results 811 - 820 of about 6,840,000. Search took 0.91 seconds.
Results 901 - 908 of about 6,840,000. Search took 0.74 seconds.

what?  they cut me off -- I'm sure my answer was 909, I'm just sure of it! 

4 stars Results xxx of about xxxxxx   April 8, 2003 - 6am Central time zone
Reviewer: A reader 
I recently went through a load of code removing every count(*) that there was before the actual 
query that was done by a developer before I came on the project.

It was amazing the argument I had with the (PHP) web developer about it.  I just made the change 
and let the users decide if they liked the improved performance more than the missing bit of fairly 
pointless information.  Guess what they preferred!

The thing that is missing is the "results 1-10 of about 500" (or whatever), which would be useful.  
The user might well want to know if there are just a few more records to look at, in which case it 
might well be worth paging, or whether there are lots, so that they would no to refine the search.

I know Oracle Text can do this sort of thing, but is there anything that can help in "Standard" 
Oracle?  Using Oracle Text would need quite a re-write of the system.

What we could do is have the application ask for 21 rows of data.  If the cursor came back with 
10-20 more rows, the screen would say ">> 7 more rows" (or whatever), and if it hits the 21, then 
display ">> at least 11 more rows".

Have you any comments?

Thanks 


Followup   April 8, 2003 - 7am Central time zone:

...
The thing that is missing is the "results 1-10 of about 500" (or whatever),
.....

if using Oracle Text queries (like I do here) there is an API for that.

if using the CBO in 9i -- you can get the estimated cardinality for the query in v$sql_plan...


 

5 stars For: Srinivas M   April 8, 2003 - 9am Central time zone
Reviewer: A Reader from UK
Hi,

All those fieldx IS LIKE '''' OR fieldX IS NULL .... what is that for ?!!  don't you just want 
fieldX is null ??  Anyway...maybe I missed something...

I'm sure Tom will have lots to say on this, and apologies for 'butting in' but I thought i'd give 
my opinion and if its off base at least I'll learn :)

Do you need that table to be deleted and inserted into each time (looks like a pseudo-temporary 
table) ?  All that looping and fetching - and it looks to me like if you had a llimit of 1000, you 
are going to fetch and do nothing with 1000 rows ??!  Can't you change your query to use the 
constructs Tom has already defined in this article i.e.

SELECT * FROM (YOUR QUERY IN HERE, BUT SELECTING rownum BACK ALSO) WHERE rownum BETWEEN llimit and 
ulimit

??

Then I suspect you don't need your table, your delete, your loops and fetches, you can just open 
this and return the cursor.

Regards,

Paul 


2 stars   April 8, 2003 - 9am Central time zone
Reviewer: A reader 
Hi Srinivas,
Sorry to jump in between but i would like to say one thing. Tom has already given us his views and 
coding tips and tricks. Lets not waste his time by asking him to correct our code. I think this 
site provides us enough knowledge and tools. Only thing required on our part is applying it 
correctly and doing some research.
 


3 stars Screwy Rabbit!   April 8, 2003 - 10am Central time zone
Reviewer: Richard from Oxfordshire, UK
Hi,

Elmer Fudd... priceless! Seldom has an explanation been so funny! Point taken, though.

How about always translating your pages? Daffy Duck's my favourite.

Wegards,

Wichard 


4 stars is this the proc. you are using for your site ?   April 8, 2003 - 3pm Central time zone
Reviewer: A reader 
is this the proc. you are using for your site ?


 if you bind the variable in a session and the 
http connection is state less so how will you 
do it?

pls explain  


Followup   April 8, 2003 - 5pm Central time zone:

yes, this is the procedure I use here...


the "bind variables" are of course passed from page to page -- in my case I use a sessionid (look 
up at that really big number in the URL) and your session "state" is but a row in a table to me.

Hidden fields, cookies -- they work just as well. 

5 stars Thanks   April 8, 2003 - 6pm Central time zone
Reviewer: A reader 


5 stars Want a trick on this   April 28, 2003 - 8am Central time zone
Reviewer: DeeeBeee Crazeee from India
Hi Tom,

I just wanted to know if there is a trick of combining multiple rows into a single row with values 
comma seperated.

For example, I have the department table :

Dept:

Dept_name
---------
ACCOUNTS
HR
MARKETING

I need a query that would return me...

Dept_name
---------
ACCOUNTS, HR, MARKETING

....is there a way with SQL or do we have to use PL/SQL. The number of rows are not fixed.

thanks a lot

PS: Just wanted to check if I can post my questions here (in this section, without asking it 
afresh) ....because, I just happened to come accross a page wherein a reader was apologizing for 
having asked a question in the comments. Do let me know on this, so that I can apologise too when I 
ask you a question in this section the next time ;)

 


Followup   April 28, 2003 - 8am Central time zone:

search this site for 

stragg

 

5 stars What about this ?   May 16, 2003 - 2pm Central time zone
Reviewer: A reader 
I happened to found this in an article on pagination:

select rownum, col1
from foobar
group by rownum, col1
having rownum >= :start and rownum < :end

What do you think ? How does it compare to your solution to the original question ? 


Followup   May 16, 2003 - 5pm Central time zone:

try it, it doesn't work.


set start to 10 and end to 15

you'll never get anything.

the way to do it -- it is above, my method works and is the most efficient method (as of May 16 
2003, maybe some day in the furture there will be another more efficient method) 

4 stars paging result set   May 17, 2003 - 4am Central time zone
Reviewer: lakshmi from Bnaglaore India
Excellent  


5 stars Dynamic order by using rownum   May 27, 2003 - 6am Central time zone
Reviewer: vinodhps from India,chennai
Hi tom ,
our current oracle version is 8.0.4 , i got one query which has to be ordered dynamically ie. if 
Max_ind is X then low_value column has to be order Ascending or if Max_ind is N then low_value 
column has to be ordered Descending. But i could i do that in query.. iam using this in my form
in the below query order by Desc  or Asc is depend on the value passing for the max_ind(N or X).

SELECT insp_dtl.test_value,
              insp_dtl.lab_test_sno,
         purity.low_value, purity.high_value,
         purity.pro_rata_flag, purity.pro_rata_type,
         purity.cumulative_flag, purity.incr,
         purity.prcnt, purity.flat_rate,
         purity.cal_mode, NVL (purity.precision, 1) precision,
              purity.min_max_ind
       FROM   t_las_matl_insp_hdr insp_hdr,
         t_las_matl_insp_dtl insp_dtl,
         t_pur_po_matl_purity_fact_dtl purity
       WHERE  insp_hdr.lab_test_sno = insp_dtl.lab_test_sno
       AND    insp_hdr.cnr_no = 200300905
       AND    purity.po_no = 200200607
--       AND    purity.matl_code = f_matl_code
       AND    purity.para_code = insp_dtl.para_code
  --     AND    purity.para_code = f_para_code
       ORDER BY low_value;




LAB_TEST_SNO LOW_VALUE HIGH_VALUE Max_ind
------------ --------- ---------- ---------
   200300208       1.1        1.5   X
   200300208       1.1          2   N
   200300208       1.6          2   N
   200300208        86       87.9   X
   200300208        88       89.9   N 


Followup   May 27, 2003 - 7am Central time zone:

great, thanks for letting us know?

Not really sure what you are trying to say here. 

4 stars dynamically order by clause   May 27, 2003 - 9am Central time zone
Reviewer: vinodhps from India, Chennai.
Hi Tom,
Thanks for your immediate response,
well i will put my question in this way..

SQL> create table order_by
  2  (low_value number(5),
  3   max_ind   varchar2(1));

Table created.


  1  insert into order_by
  2  select rownum ,'X' from all_objects
  3  where rownum < 10
  4* order by rownum desc
SQL> /

9 rows created.


  1  insert into order_by
  2  select rownum ,'N' from all_objects
  3  where rownum < 10
  4* order by rownum
SQL> /

9 rows created.

Now i would like to select all the values from the table by passing a value for max_ind(a 
indicator) whether its maximum or minimum value here in this query if i pass variable X then the 
query order by clause must be descending or else it should be ascending, actually it is a cursor .

SQL> select low_value  from order_by order by low_value desc;

LOW_VALUE
---------
        9
        9
        8
        8
        7
        7
        6
        6
        5
        5
        4
        4
        3
        3
        2
        2
        1
        1

18 rows selected.

This DESC or ASC will be decided dynamically.

Is it possible to do it dynamically Tom,

I  think above statements are clear. 


Followup   May 27, 2003 - 9am Central time zone:

you would use native dynamic sql to get the optimum query plan.

 l_query := 'select .... order by low_value ' || p_asc_or_desc;

where p_asc_or_desc is a variable you set to ASC or DESC.


that would be best.

you can use decode, but you'll never use an index to sort with if that matters to you


   order by decode( p_input, 'ASC', low_value, 0 ) ASC,
            decode( p_input, 'DESC', low_value, 0 ) DESC


 

5 stars Thank you tom   May 27, 2003 - 10am Central time zone
Reviewer: vinodhps from India, chennai
Thank you tom for your immediate responce...

Hope to see more from you.

Thank you,
 


3 stars Very useful, thanks Tom. One more question.   June 2, 2003 - 3pm Central time zone
Reviewer: Jack Liu from IL USA
1. Did you get total result number by count(*)? this pagin needs to know the count(*) or not, 
because select count take longer time.

2. How to optimize order by, it will use only 3s for below query but 44s with order?
select *   from 
( select qu.*, rownum rnum
  from ( select issn,volume,issue,foa,title,author,subtitle,a.aid,rtype 
     from article a , ec.language l where 1=1 
     AND rtype in ('ART','REV','SER') 
     AND a.aid=l.aid AND l.langcode='eng' 
     AND a.issue is not null ORDER BY a.ayear desc ) qu
   where rownum < 61)
 where rnum >= 31 
 


Followup   June 2, 2003 - 3pm Central time zone:

1) no, i use text's "get me an approximation of what you think the result set size might be" 
function.  (its a text query for me)

2) /*+ FIRST_ROWS */

do you have an index on a.ayear?
is ayear NOT NULL?

if so, it could use the index, with first_rows, to read the data descending. 

5 stars Very, Very...Helpful   June 2, 2003 - 6pm Central time zone
Reviewer: Ralph from USA
Along those lines...How can we get the maximum rows that will be fetched? i.e. to be able to show 
1-10 of 1000 records, how do we know that there are total 1000 records without writing another 
select with count(*) 


Followup   June 2, 2003 - 8pm Central time zone:

you don't -- all you need to show is

"you are seeing 1-10 of more then 10, hit next to see what might be 11-20 (or maybe less"


If you use text, you can approximate the result set size.
If you use the CBO and 9i, you can get the estimated cardinality from v$SQL_PLAN

 

4 stars Very helpful, thanks Tom, follow up with my question.   June 3, 2003 - 1pm Central time zone
Reviewer: Jack from IL USA
Tom,
Thanks for your quick response. This is really a very good place for Oracle Users. Just follow up 
my original question:

1) Is this "get me an approximation of what you think the result set size might be" function only 
in Oracle Text? If I use Oracle Intermedia text, any solution to show total result?

2) a.ayear is indexed but has some null. I know it can not use index to replace order in this 
situation, but why I use /*+ INDEX_ASC (article article_ayear) */, it doesn't work either? The 
optimizer mode is "choose" in svrmgrl> show parameter optimizer_mode; 

Many many thanks.

Jack
I am planing to buy "expert one-on-one". 
  


Followup   June 3, 2003 - 2pm Central time zone:

1) the approximation I'm showing is from text and only works with text.

you can get the estimated cardinality from an explain plan for other queries -- in 9i, that is 
right in v$sql_plan so you do not need to explain the query using explain plan

2) you answered your own question.  The query does not permit the index to be used since using the 
index would miss NULL entries -- resulting in the wrong answer.

can you add "and a.ayear IS NOT NULL"  or "and a.ayear > to_date( '01010001','ddmmyyyy')" to the 
query.  then, an index on ayear alone can be used.


better be quick on the book purchase (bookpool still has it as of jun/3/2003 -- see link on 
homepage)

Publisher went under, book no longer printed ;(   
New book in august though ;) 

4 stars Thank you for your quick response!   June 3, 2003 - 3pm Central time zone
Reviewer: Jack from IL USA
Tom,
Thanks, actually I want the order is Ascending by ayear, since oracle default use descending for 
index, that's the reason I use /*+ INDEX_ASC (article article_ayear) */ hint.
Just don't know why it doesn't work, here is the explain plan with INDEX_ASC hint, I don't know why 
the hint is still choose.
SELECT STATEMENT Hint=CHOOSE 6 K     6911            VIEW        6 K    1 M    6911               
COUNT STOPKEY                                    NESTED LOOPS        6 K    736 K    6911           
       TABLE ACCESS FULL LANGUAGE    6 K    113 K    56  TABLE ACCESS BY INDEX ROWID    ARTICLE    
63 K    5 M    1  INDEX UNIQUE SCAN    SYS_C004334    63 K    

Thanks,

Jack

 


Followup   June 4, 2003 - 7am Central time zone:

Oracle uses indexes ASCENDING by default.

I told you why the index cannot be used -- ayear is nullable, using that index would (could) result 
in missing rows that needed to be processed.

hence, add the predicate I described above to make it so that the index CAN in fact be used. 

3 stars paging and a join   June 5, 2003 - 1pm Central time zone
Reviewer: marc from nyc
Which way would be better with a large table and the user wants to see an average of 500 rows back. 
The query has a main driving table and a 2nd table that will only be used to show a column's data.  
The 2nd table will not be used in the where or the order of the main select.

option 1(all table joined in the main select):

select name,emp_id,salary from ( 
select a.*, rownum rnum from ( 
SELECT emp.name,emp.emp_id,salary.salary FROM EMP,SALARY 
where zip = someting and
EMP.emp_id = salary.emp_id order by name
) a where rownum <= MAX_ROWS )
where rnum >= MIN_ROWS

option 2(only the driving table is in the main select and joins are done at the higher level.  Then 
oracle would only have to join the 2 tables with the data the the user will show. ):
select a.name,a.empid,salary.salary from ( 
select a.*, rownum rnum from ( 
SELECT emp.name,emp.emp_id FROM where zip = someting order by name
) a ,SALARY 
where a.emp_id = salary.emp_id where rownum <= MAX_ROWS )
where rnum >= MIN_ROWS
 


Followup   June 5, 2003 - 1pm Central time zone:

users don't want to see 500 rows.  500 rows -- waayyy too much data, you cannot consume that much 
information...

option 1.  using first_rows hint.


it'll only access the other table as much as it needs to and is much easier to understand.

but benchmark it on your data, that'll tell YOU for sure what is best in YOUR case. 

3 stars   June 6, 2003 - 2pm Central time zone
Reviewer: marc 
My users must be supper users, because they would like to see all that data (page by page of 
course) so they can eyeball a forecast and see a trend.  These people are brokers that look at 1 
column of the dataset, which is price per share (money), So they do look at many rows at a time.  
The extra data is superfluous.  500 rows is nothing for these users.

I was asking your opinion if you think it is better to do the join in the main dataset and not in 
the pagination piece.  My perfmaince tuning told me it putting the data in the main or subset is 
all related on the type of data in I need to show,  Example a join to get the name can be in main 
select, but the get the (select count(trades) from othertable) works better in the pagination 
section.
 


Followup   June 6, 2003 - 2pm Central time zone:

as long as the query is a "first rows" sort of query that can terminate with a COUNT STOPKEY -- the 
join can go anywhere. 

4 stars Using index with order by   July 15, 2003 - 6am Central time zone
Reviewer: Jon from Sydney
Will an index be used with an order by if the table has already been accessed via another index?  I 
thought the CBO would only work with one index per table (except with bitmap indexes).

I'm working on returning search results.  Users want first 2000 rows (I know, I know... what will 
they do with it all - was originally top 500 rows, but that wasn't enough for them).  The main 
table is already being accessed via another index to limit the result set initially.  Explain Plan 
tells me that the index on the order by column is not being used.  How to use the index for 
ordering?

Actually as I'm writing this, I think the answer came to me - concatenated indexes - of form 
(limit_cols, order_by_col), and then include the leading index column(s) in the order by clause.

Secondly, if I work with a union clause on similar, but not identical, queries, can an index be 
used for ordering in this case?

E.g.
select * from (
  select * from (  
    select ... from x,y,z where ...
    union all
    select ... from x,y,w where ...
  ) order by x.col1
) where rownum <= 2000

or would we get better results with this approach:

select * from (
  select * from (
    select * from (
      select ... from x,y,z where ... 
      order by x.col1
    ) where rownum <= 2000
    union all
    select * from (
      select ... from x,y,w where ...
      order by x.col1
    ) where rownum <= 2000
  ) order by x.col1
) where rownum <= 2000

So, if the result is partially sorted, does an order by perform better than if not sorted (this 
brings make memories of sorting algorithms many years ago...). I would think yes - but not sure of 
Oracle's internal sort algorithm? 


Followup   July 15, 2003 - 9am Central time zone:

if you use the "index to sort", how can you use another index "to find"

You can either use an index to sort OR you can use an index to find, but tell me -- how could you 
imagine using both?

Your concatenated index will work in some cases -- yes.


the 2cnd approach -- where you limit all of the subresults - will most likely be the better 
approach.


You cannot go into the "does an order by perform better ....", that is so far out of the realm of 
your control at this point as to be something to not even think about. 

3 stars   July 15, 2003 - 7pm Central time zone
Reviewer: Jon from Sydney
"how could you imagine using both?" - not sure I understand you here.  Wanting to use two indexes 
is a common requirement - so I can easily imagine it:

select *
from emp
where hire_date between to_date('01/01/2002','DD/MM/YYYY')
    and to_date('01/02/2002','DD/MM/YYYY')
order by emp_no

If this was a large table, the ability to use an index to filter and an index to order by would 
seem advantageous.

As for internal sort algorithms  - do you know what Oracle uses - or is it secret squirrel stuff?   


Followup   July 15, 2003 - 7pm Central time zone:

so tell me -- how would it work, give us the "psuedo code", make it real. 

4 stars Hmmm...   July 16, 2003 - 10am Central time zone
Reviewer: Jon from Sydney
I mean Oracle does that fancy index combine operation with bitmap indexes.  I guess I'll just have 
to build it for you.  

Tell you what, if I come up with a way of doing something similar for b*tree's, I'll sell it to 
Oracle... then I'll retire :-) 


Followup   July 16, 2003 - 10am Central time zone:

Oh, we can use more then one index

we have index joins -- for example:


create table t ( x int, y int );

create index t_idx1 on t(x);
create index t_idx2 on t(y);

then select x, y from t where x = 5 and y = 55;

could range scan both t_idx1, t_idx2 and then hash join them together by rowid.


We have bitmaps where by we can AND and OR bitmaps together...



BUT - I want you to explain an algorithm that would permit you to 

a) range scan by index index in order to locate data
b) use another index to "sort it"


None of the multi-index approaches "sort" data, they are used to find data. 

4 stars All this thinking makes my brain hurt.   July 16, 2003 - 11pm Central time zone
Reviewer: Jon from Sydney
Well, since we CAN combine two indexes, how about:

1) Use idx1 to range scan
2) Hash join rowids to idx2 to produce result set
3) Do a sort-merge between 2) result set and idx2 to order

The efficiency of doing 2) & 3) over sort of table data would probably depend on cardinality of 1). 
 

More fun for the CBO team and the Oracle mathematics dept... 


Followup   July 17, 2003 - 10am Central time zone:

it would depend on cardinality of 1 and 2 really.

if card of 1 is small but card of 2 is big and you have to (must) full scan idx2 a block at a time 
to look for matches (we have to inspect every index entry) -- full scanning the index could take a 
really really long time

step 3 would would not be neccesary in this scenario as the full scan of index 2 would be 'sorted' 
and would just probe the hash table you built in 1

 

4 stars To clarify   July 16, 2003 - 11pm Central time zone
Reviewer: Jon from Sydney
By sort-merge in 3) I mean a set intersection operation. 


4 stars getting rows N through M of a result set   July 17, 2003 - 8am Central time zone
Reviewer: Mohan from Bangalore, India
Regarding the discussion regarding pagination of the result set into random chunks and sequencing 
them 

consider the table customer_data

create table customer_data(custno number, invoiceno number);
insert into customer_data(custno, invoiceno) values(1,110);
insert into customer_data(custno, invoiceno) values(1,111);
insert into customer_data(custno, invoiceno) values(1,112);
insert into customer_data(custno, invoiceno) values(2,1150);
insert into customer_data(custno, invoiceno) values(2,1611);
insert into customer_data(custno, invoiceno) values(3,1127);
insert into customer_data(custno, invoiceno) values(2,3150);
insert into customer_data(custno, invoiceno) values(2,3611);
insert into customer_data(custno, invoiceno) values(3,3127);

The following query will break the result sets based on custno and sequences each chunk.

select b.rnum-a.minrnum+1 slno, a.custno, b.invoiceno from(select custno, min(rnum) minrnum 
from(select rownum rnum, custno, invoiceno from (select custno, invoiceno from customer_data order 
by custno, invoiceno)) group by custno) a, (select rownum rnum, custno, invoiceno from (select 
custno, invoiceno from customer_data order by custno, invoiceno)) b where a.custno=b.custno;


Mohan
 


Followup   July 17, 2003 - 10am Central time zone:

ok, put 100,000 rows in there and let us know how it goes... (speed and resource useage wise) 

4 stars It works..   July 17, 2003 - 5pm Central time zone
Reviewer: DD from MN, USA
<quote>
What about this ?  May 16, 2003 
Reviewer:  A reader 

I happened to found this in an article on pagination:

select rownum, col1
from foobar
group by rownum, col1
having rownum >= :start and rownum < :end

What do you think ? How does it compare to your solution to the original 
question ? 


Followup:  
try it, it doesn't work.


set start to 10 and end to 15

you'll never get anything.

the way to do it -- it is above, my method works and is the most efficient 
method (as of May 16 2003, maybe some day in the furture there will be another 
more efficient method) 
</quote>

Tom,
  Your reply above states that this does not work. Infact it does work and it MUST work. The group 
by will be done before the having clause is applied and so we will get the correct result set. 
Please let me know your views and what is it that makes you think this wont work. Here are my 
results.

RKPD01> select rownum, object_id from big_table
  2  group by rownum, object_id
  3  having rownum > 10 and rownum < 15;

    ROWNUM  OBJECT_ID
---------- ----------
        11        911
        12        915
        13       1091
        14       1103


I havent tried to see if it is efficient but I wanted to verify why it wouldnt work when it should. 
Hope to Hear

Thanks
DD
 


Followup   July 17, 2003 - 7pm Central time zone:

oh, i messed up, saw the having and read it as 'where'

totally 100% inefficient, not a good way to do it.  it does the entire result set and then gets 
rows 10-15

as opposed to my method which gets 15 rows, then throws out the first couple.

 

4 stars getting rows N through M of a result set   July 19, 2003 - 3am Central time zone
Reviewer: Mohan K from Bangalore, India
Refer to the review on July 17, 2003 

If the custno column is not indexed, then the performance will be a problem. 

Run the following scripts to test the above query.


create table customer_data(custno number, invoiceno number);

declare
n1 number;
n2 number;
begin
for n1 in 1..2500 LOOP
for n2 in 1..100 LOOP
insert into customer_data(custno, invoiceno) values(n1, n2);
END LOOP;
END LOOP;
end;
/

commit;

create index customer_data_idx on customer_data(custno);


The first sql statement will create the table. The PL/SQL script will populate the table with 
250000 rows. The next statement will create an index.


Now run the query as given below

select b.rnum-a.minrnum+1 slno, a.custno, b.invoiceno from(select custno, min(rnum) minrnum from
(select rownum rnum, custno, invoiceno from (select custno, invoiceno from customer_data order by 
custno, invoiceno)) group by custno) a, 
(select rownum rnum, custno, invoiceno from (select custno, invoiceno from customer_data order by 
custno, invoiceno)) b 
where a.custno=b.custno;

Mohan

 


5 stars Is it Possible?   July 23, 2003 - 12pm Central time zone
Reviewer: A reader from India
Hi Tom,

I have a table like this

Name
Date
Amount

Data will be like 

User1       01-JAN-03       100
User1       22-JUL-03        20
......
User2       23-JUL-03        90

Is there any way, I can get the last 6 (Order by Date desc)records for each user with a Single 
query?

I need get the output like 

User1      22-JUL-03      20
User1      01-JAN-03     100
....
User2      23-JUL-03........

Thank you very much Tom.  (I am using 8.1.7) 


Followup   July 23, 2003 - 7pm Central time zone:

select * 
  from (select name, date, amount, 
               row_number() over (Partition by user order by date DESC ) rn
          from t )
 where rn <= 6; 

5 stars For Query example on CUSTOMER_DATA table posted above...   July 23, 2003 - 10pm Central time zone
Reviewer: Kamal Kishore from New Jersey, USA
It is my understanding that the same output can be produced by using the following query:

SELECT row_number() over(PARTITION BY custno ORDER BY custno, invoiceno) slno,
       custno,
       invoiceno
FROM   customer_data
WHERE  custno IN (1, 2)
ORDER  BY custno,
          invoiceno
/


I may be understanding wrong. Maybe, Tom can verify this.

I ran the two queries on the CUSTOMER_DATA table (with 250000 rows) and below are the statistics. I 
ran both queries several times to remove any benefit of doubt, but results were similar.

I see a huge performance difference on the two queries.

Waiting for inputs/insights from Tom.
Thanks,

==========================================================

SQL> SELECT row_number() over(PARTITION BY custno ORDER BY custno, invoiceno) slno,
  2         custno,
  3         invoiceno
  4  FROM   customer_data
  5  WHERE  custno IN (1, 2)
  6  ORDER  BY custno,
  7            invoiceno
  8  /

200 rows selected.

Elapsed: 00:00:00.02

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   WINDOW (SORT)
   2    1     CONCATENATION
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'CUSTOMER_DATA'
   4    3         INDEX (RANGE SCAN) OF 'CUSTOMER_DATA_IDX' (NON-UNIQU
          E)

   5    2       TABLE ACCESS (BY INDEX ROWID) OF 'CUSTOMER_DATA'
   6    5         INDEX (RANGE SCAN) OF 'CUSTOMER_DATA_IDX' (NON-UNIQU
          E)





Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          8  consistent gets
          0  physical reads
          0  redo size
       2859  bytes sent via SQL*Net to client
        510  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
        200  rows processed

SQL> SELECT b.rnum - a.minrnum + 1 slno,
  2         a.custno,
  3         b.invoiceno
  4  FROM   (SELECT custno,
  5                 MIN(rnum) minrnum
  6          FROM   (SELECT rownum rnum,
  7                         custno,
  8                         invoiceno
  9                  FROM   (SELECT custno,
 10                                 invoiceno
 11                          FROM   customer_data
 12                          ORDER  BY custno,
 13                                    invoiceno))
 14          GROUP  BY custno) a,
 15         (SELECT rownum rnum,
 16                 custno,
 17                 invoiceno
 18          FROM   (SELECT custno,
 19                         invoiceno
 20                  FROM   customer_data
 21                  ORDER  BY custno,
 22                            invoiceno)) b
 23  WHERE  a.custno = b.custno AND a.custno in (1, 2)
 24  ORDER  BY custno,
 25            invoiceno
 26  /

200 rows selected.

Elapsed: 00:00:20.08

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   MERGE JOIN
   2    1     VIEW
   3    2       COUNT
   4    3         VIEW
   5    4           SORT (ORDER BY)
   6    5             TABLE ACCESS (FULL) OF 'CUSTOMER_DATA'
   7    1     SORT (JOIN)
   8    7       VIEW
   9    8         SORT (GROUP BY)
  10    9           VIEW
  11   10             COUNT
  12   11               VIEW
  13   12                 SORT (ORDER BY)
  14   13                   TABLE ACCESS (FULL) OF 'CUSTOMER_DATA'




Statistics
----------------------------------------------------------
          0  recursive calls
         88  db block gets
       1740  consistent gets
       8679  physical reads
          0  redo size
       2859  bytes sent via SQL*Net to client
        510  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          2  sorts (memory)
          2  sorts (disk)
        200  rows processed

SQL>
 


4 stars Example on customer_data table   July 24, 2003 - 4am Central time zone
Reviewer: Mohan K from Bangalore, India
Specify the where clause in the inner query. The same where clause has to be applied twice.

select b.rnum-a.minrnum+1 slno, a.custno, b.invoiceno from(select custno,
min(rnum) minrnum from
(select rownum rnum, custno, invoiceno from (select custno, invoiceno from
customer_data where custno in(2,3) order by custno, invoiceno)) group by custno) a,
(select rownum rnum, custno, invoiceno from (select custno, invoiceno from
customer_data where custno in(2,3) order by custno, invoiceno)) b
where a.custno=b.custno
/


Mohan
 


5 stars tkprof results on CUSTOMER_DATA query...   July 24, 2003 - 8am Central time zone
Reviewer: Kamal Kishore from New Jersey, USA
SELECT row_number() over(PARTITION BY custno ORDER BY custno, invoiceno) slno,
       custno,
       invoiceno
FROM   customer_data
WHERE  custno IN (1, 2)
ORDER  BY custno,
          invoiceno

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        3      0.02       0.01          0          8          0         200
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        5      0.02       0.01          0          8          0         200



SELECT b.rnum - a.minrnum + 1 slno, a.custno, b.invoiceno
FROM   (SELECT custno, MIN(rnum) minrnum
        FROM   (SELECT rownum rnum, custno, invoiceno
                FROM   (SELECT custno, invoiceno
                        FROM   customer_data
                        WHERE  custno IN (1, 2)
                        ORDER  BY custno, invoiceno))
        GROUP  BY custno) a,
       (SELECT rownum rnum, custno, invoiceno
        FROM   (SELECT custno, invoiceno
                FROM   customer_data
                WHERE  custno IN (1, 2)
                ORDER  BY custno, invoiceno)) b
WHERE  a.custno = b.custno

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.04          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        3      0.06       0.05          0         16          0         200
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        5      0.07       0.10          0         16          0         200


**********************************************************
==========================================================
**********************************************************

SELECT row_number() over(PARTITION BY custno ORDER BY custno, invoiceno) slno,
       custno,
       invoiceno
FROM   customer_data
ORDER  BY custno,
          invoiceno

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     2501     16.53      19.38       2080        436         50      250000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2503     16.53      19.38       2080        436         50      250000


SELECT b.rnum - a.minrnum + 1 slno, a.custno, b.invoiceno
FROM   (SELECT custno, MIN(rnum) minrnum
        FROM   (SELECT rownum rnum, custno, invoiceno
                FROM   (SELECT custno, invoiceno
                        FROM   customer_data
                        ORDER  BY custno, invoiceno))
        GROUP  BY custno) a,
       (SELECT rownum rnum, custno, invoiceno
        FROM   (SELECT custno, invoiceno
                FROM   customer_data
                ORDER  BY custno, invoiceno)) b
WHERE  a.custno = b.custno

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.03          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     2501     71.99      82.11       5007        872        100      250000
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2503     71.99      82.14       5007        872        100      250000
 


5 stars ALL_ROWS or FIRST_ROWS ?   August 5, 2003 - 1pm Central time zone
Reviewer: Tatiane 
After all, using your pagination method, what optimization mode (or goal) should we use ? 


Followup   August 5, 2003 - 2pm Central time zone:

FIRST_ROWS definitely 

4 stars   August 5, 2003 - 2pm Central time zone
Reviewer: A reader 
Maybe FIRST_ROWS_1, 10, 100, 1000 ???? 

From the 9.2 Reference:

<q>
first_rows_n 

The optimizer uses a cost-based approach, regardless of the presence of statistics, and optimizes 
with a goal of best response time to return the first n rows (where n = 1, 10, 100, 1000).

first_rows 

The optimizer uses a mix of costs and heuristics to find a best plan for fast delivery of the first 
few rows.
</q>

What is the difference in this case ? 


Followup   August 5, 2003 - 2pm Central time zone:

see

http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:313616750808#11436946922740
for an example 

2 stars I still don't get it   August 11, 2003 - 5pm Central time zone
Reviewer: Sudha Bhagavatula from Buffalo,NY
I'm trying to run this and I get only 25 rows:

select * 
  from (select cl.prov_full_name full_name, 
               cl.spec_desc specialty_dsc, 
               sum(cl.plan_liab_amt) tot_pd, 
               sum(cl.co_ins_amt+cl.ded_amt+cl.copay_amt) patient_resp, 
               count(distinct clm10_id) claims
          from aso.t_medical_claims_detail cl, 
               aso.t_employer_groups_data g, 
               aso.t_categories_data c
         where g.emp_super_grp_id||g.emp_sub_grp_id = cl.emp_grp_id
           and c.cat_dim_id = g.cat_dim_id
           and c.cat_name like 'America%'
           and cl.paid_date between to_date('01/01/2003','mm/dd/yyyy') 
                                and to_date('06/30/2003','mm/dd/yyyy')
           and prov_full_name not like '*%'
           and spec_desc not like '*%'
       group by prov_full_name, 
                spec_desc
       order by count(distinct clm10_id) desc )
where rownum < 26
union
select decode(full_name,null,' ', 'All Other Providers') full_name,decode(specialty_dsc,null,' 
','y') specialty_dsc,tot_pd,patient_resp,claims 
  from (select cl.prov_full_name full_name, 
               cl.spec_desc specialty_dsc, 
               sum(cl.plan_liab_amt) tot_pd, 
               sum(cl.co_ins_amt+cl.ded_amt+cl.copay_amt) patient_resp, 
               count(distinct clm10_id) claims
          from aso.t_medical_claims_detail cl, 
               aso.t_employer_groups_data g, 
               aso.t_categories_data c
         where g.emp_super_grp_id||g.emp_sub_grp_id = cl.emp_grp_id
           and c.cat_dim_id = g.cat_dim_id
           and c.cat_name like 'America%'
           and cl.paid_date between to_date('01/01/2003','mm/dd/yyyy') 
                                and to_date('06/30/2003','mm/dd/yyyy')
           and prov_full_name not like '*%'
           and spec_desc not like '*%'
       group by prov_full_name, 
                spec_desc
       order by count(distinct clm10_id) desc )
where rownum >= 26 


Followup   August 11, 2003 - 6pm Central time zone:

it by very definition will only ever return 25 rows at most.

"where rownum > 26" is assured to return 0 records.

rownum is assigned to a row like this:


rownum = 1
loop over potential records in the result set
    if predicate satisified 
    then
         OUTPUT RECORD
         rownum = rownum + 1
    end if
end loop


So, you see -- rownum is ALWAYS 1 since rownum is never > 26 and rownum never gets incremented. 

3 stars So how do I get the rows   August 12, 2003 - 8am Central time zone
Reviewer: Sudha Bhagavatula from Buffalo,NY USA
So how do I get the result that I'm trying to achieve ? Can it be done ?

Thanks
Sudha 


Followup   August 12, 2003 - 9am Central time zone:

I don't know -- why don't you phrase IN ENGLISH what you are trying to achieve.

The sql parser in my brain doesn't like to parse big queries and try to reverse engineer what you 
MIGHT have wanted (given that the question isn't phrased properly in the first place and all).... 

3 stars This is my question   August 12, 2003 - 9am Central time zone
Reviewer: Sudha Bhagavatula 
I have to create a report showing the top 25 providers based on the number of distinct claims. Get 
the the total for the 25 providers, compute percentages against the total for all the providers, 
and then total the claims for the providers not in the top 25.

This is how the report should be :

provider                #claims   %of total

xxxxxxx                 1234      14%
yyyyyyy                 987       11%
-------


---till the top 25
All other providers     3210      32%  

Thanks
Sudha 


Followup   August 12, 2003 - 9am Central time zone:

ops$tkyte@ORA920> /*
DOC>
DOC>drop table t1;
DOC>drop table t2;
DOC>
DOC>create table t1 ( provider int );
DOC>
DOC>create table t2 ( provider int, claim_no int );
DOC>
DOC>
DOC>-- 100 providers...
DOC>insert into t1 select rownum from all_objects where rownum <= 100;
DOC>
DOC>insert into t2
DOC>select dbms_random.value( 1, 100 ), rownum
DOC>  from all_objects;
DOC>*/
ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select case when rn <= 25
  2              then to_char(provider)
  3              else 'all others'
  4         end provider,
  5         to_char( round(sum( rtr ) * 100 ,2), '999.99' )  || '%'
  6    from (
  7  select provider, cnt, rtr, row_number() over (order by rtr) rn
  8    from (
  9  select provider, cnt, ratio_to_report(cnt) over () rtr
 10    from (
 11  select t1.provider, count(*) cnt
 12    from t1, t2
 13   where t1.provider = t2.provider
 14   group by t1.provider
 15         )
 16         )
 17         )
 18   group by case when rn <= 25
 19                 then to_char(provider)
 20                 else 'all others'
 21             end
 22   order by count(*), sum(rtr) desc
 23  /

PROVIDER                                 TO_CHAR(
---------------------------------------- --------
69                                           .97%
45                                           .97%
14                                           .97%
99                                           .97%
27                                           .97%
43                                           .97%
5                                            .96%
72                                           .96%
2                                            .96%
61                                           .96%
78                                           .96%
29                                           .95%
92                                           .95%
88                                           .95%
63                                           .95%
91                                           .95%
35                                           .94%
67                                           .93%
77                                           .93%
60                                           .91%
76                                           .91%
55                                           .91%
79                                           .88%
1                                            .48%
100                                          .48%
all others                                 77.24%

26 rows selected.
 

5 stars Great solution   August 12, 2003 - 2pm Central time zone
Reviewer: Sudha Bhagavatula 
Tom,

That worked like a charm, thanks !

Sudha

 


5 stars Works great, but bind variables giving bad plan   August 22, 2003 - 4pm Central time zone
Reviewer: Mike Madland from Bellevue, WA
Hi Tom,

Thanks for a great web site and a great book.

I'm using your awesome paginate query and getting great
results but I'm running into issues with the optimizer
giving me a bad plan when I use bind variables for the
beginning and ending row numbers.  I've tried all kinds
of hints but ended up resorting to dynamic sql to get the
fastest plan.

Do you have any ideas on why my query with the bind
variables is insisting on doing a hash join (and thus is
slower) and if there is any fix?  Thanks in advance.

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.3.0 - Production

SQL> create sequence s;

Sequence created.

SQL> create table t as
  2  select s.nextval pk, object_name, created, object_type
  3   from all_objects;

Table created.

SQL> insert /*+ append */
  2    into t (pk, object_name, created, object_type)
  3  select s.nextval,  object_name, created, object_type
  4   from t;

21158 rows created.

SQL> commit;

Commit complete.

SQL> insert /*+ append */
  2    into t (pk, object_name, created, object_type)
  3  select s.nextval,  object_name, created, object_type
  4   from t;

42316 rows created.

SQL> commit;

Commit complete.

SQL> insert /*+ append */
  2    into t (pk, object_name, created, object_type)
  3  select s.nextval,  object_name, created, object_type
  4   from t;

84632 rows created.

SQL>  commit;

Commit complete.

SQL> insert /*+ append */
  2    into t (pk, object_name, created, object_type)
  3  select s.nextval,  object_name, created, object_type
  4   from t;

169264 rows created.

SQL> commit;

Commit complete.

SQL> alter table t add constraint pk_t primary key (pk);

Table altered.

SQL> create index t_u on t (lower(object_name), pk);

Index created.

SQL> analyze table t compute statistics
  2    for table for all indexes for all indexed columns
  3  /

Table analyzed.

SQL> set timing on
SQL> alter session set sql_trace=true;

Session altered.

SQL> SELECT t.pk, t.object_name, t.created, object_type
  2    FROM (SELECT *
  3            FROM (select innermost.*, rownum as rowpos
  4                    from (SELECT pk
  5                            FROM t
  6                           ORDER BY LOWER(object_name)
  7                         ) innermost
  8                   where rownum <= 10 )
  9           where rowpos >= 1) pg
 10         INNER JOIN t ON pg.pk = t.pk
 11   ORDER BY pg.rowpos;

        PK OBJECT_NAME                    CREATED   OBJECT_TYPE
---------- ------------------------------ --------- -------------
         1 /1005bd30_LnkdConstant         13-AUG-03 JAVA CLASS
     10352 /1005bd30_LnkdConstant         13-AUG-03 SYNONYM
     21159 /1005bd30_LnkdConstant         13-AUG-03 JAVA CLASS
     31510 /1005bd30_LnkdConstant         13-AUG-03 SYNONYM
     42317 /1005bd30_LnkdConstant         13-AUG-03 JAVA CLASS
     52668 /1005bd30_LnkdConstant         13-AUG-03 SYNONYM
     63475 /1005bd30_LnkdConstant         13-AUG-03 JAVA CLASS
     73826 /1005bd30_LnkdConstant         13-AUG-03 SYNONYM
     84633 /1005bd30_LnkdConstant         13-AUG-03 JAVA CLASS
     94984 /1005bd30_LnkdConstant         13-AUG-03 SYNONYM

10 rows selected.

Elapsed: 00:00:00.03
SQL>
SQL> variable r            refcursor
SQL>
SQL> declare
  2  i_endrow    integer;
  3  i_startrow  integer;
  4
  5  begin
  6  i_endrow   := 10;
  7  i_startrow := 1;
  8
  9  open :r FOR
 10  SELECT t.pk, t.object_name, t.created, object_type
 11    FROM (SELECT *
 12            FROM (select innermost.*, rownum as rowpos
 13                    from (SELECT pk
 14                            FROM t
 15                           ORDER BY LOWER(object_name)
 16                         ) innermost
 17                   where rownum <= i_endrow )
 18           where rowpos >= i_startrow) pg
 19         INNER JOIN t ON pg.pk = t.pk
 20   ORDER BY pg.rowpos;
 21  END;
 22  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SQL>
SQL> print :r

        PK OBJECT_NAME                    CREATED   OBJECT_TYPE
---------- ------------------------------ --------- -------------
         1 /1005bd30_LnkdConstant         13-AUG-03 JAVA CLASS
     10352 /1005bd30_LnkdConstant         13-AUG-03 SYNONYM
     21159 /1005bd30_LnkdConstant         13-AUG-03 JAVA CLASS
     31510 /1005bd30_LnkdConstant         13-AUG-03 SYNONYM
     42317 /1005bd30_LnkdConstant         13-AUG-03 JAVA CLASS
     52668 /1005bd30_LnkdConstant         13-AUG-03 SYNONYM
     63475 /1005bd30_LnkdConstant         13-AUG-03 JAVA CLASS
     73826 /1005bd30_LnkdConstant         13-AUG-03 SYNONYM
     84633 /1005bd30_LnkdConstant         13-AUG-03 JAVA CLASS
     94984 /1005bd30_LnkdConstant         13-AUG-03 SYNONYM

10 rows selected.

Elapsed: 00:00:02.05

---- From TKPROF ----

SELECT t.pk, t.object_name, t.created, object_type
  FROM (SELECT *
          FROM (select innermost.*, rownum as rowpos
                  from (SELECT pk
                          FROM t
                         ORDER BY LOWER(object_name)
                       ) innermost
                 where rownum <= 10 )
         where rowpos >= 1) pg
       INNER JOIN t ON pg.pk = t.pk
 ORDER BY pg.rowpos

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.36         22         25          0          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.01       0.38         22         25          0          10

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
     10   SORT (ORDER BY)
     10    NESTED LOOPS
     10     VIEW
     10      COUNT (STOPKEY)
     10       VIEW
     10        INDEX   GOAL: ANALYZED (FULL SCAN) OF 'T_U'
                   (NON-UNIQUE)
     10     TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID) OF 'T'
     10      INDEX   GOAL: ANALYZED (UNIQUE SCAN) OF 'PK_T' (UNIQUE)

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

SELECT t.pk, t.object_name, t.created, object_type
  FROM (SELECT *
          FROM (select innermost.*, rownum as rowpos
                  from (SELECT pk
                          FROM t
                         ORDER BY LOWER(object_name)
                       ) innermost
                 where rownum <= :b1 )
         where rowpos >= :b2) pg
       INNER JOIN t ON pg.pk = t.pk
 ORDER BY pg.rowpos

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      2.34       2.55       1152       2492          0          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      2.34       2.56       1152       2492          0          10

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
     10   SORT (ORDER BY)
     10    HASH JOIN
     10     VIEW
     10      COUNT (STOPKEY)
     10       VIEW
     10        INDEX   GOAL: ANALYZED (FULL SCAN) OF 'T_U'
                   (NON-UNIQUE)
 338528     TABLE ACCESS   GOAL: ANALYZED (FULL) OF 'T'
 


Followup   August 23, 2003 - 10am Central time zone:

first_rows all of the subqueries.  that is appropriate for pagination.  I should have put that into 
the original response I guess!


consider:

SELECT t.pk, t.object_name, t.created, object_type
  FROM (SELECT *
          FROM (select innermost.*, rownum as rowpos
                  from (SELECT pk
                          FROM t
                         ORDER BY LOWER(object_name)
                       ) innermost
                 where rownum <= :b1 )
         where rowpos >= :b2) pg
       INNER JOIN t ON pg.pk = t.pk
 ORDER BY pg.rowpos

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      1.92       2.03          2       2491          0          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      1.92       2.03          2       2491          0          10

Rows     Row Source Operation
-------  ---------------------------------------------------
     10  SORT ORDER BY (cr=2491 r=2 w=0 time=2033994 us)
     10   HASH JOIN  (cr=2491 r=2 w=0 time=2033701 us)
     10    VIEW  (cr=3 r=2 w=0 time=1029 us)
     10     COUNT STOPKEY (cr=3 r=2 w=0 time=955 us)
     10      VIEW  (cr=3 r=2 w=0 time=883 us)
     10       INDEX FULL SCAN T_U (cr=3 r=2 w=0 time=848 us)(object id 55317)
 350000    TABLE ACCESS FULL T (cr=2488 r=0 w=0 time=598964 us)


versus:

********************************************************************************
SELECT t.pk, t.object_name, t.created, object_type
  FROM (SELECT /*+ FIRST_ROWS */ *
          FROM (select /*+ FIRST_ROWS */ innermost.*, rownum as rowpos
                  from (SELECT /*+ FIRST_ROWS */ pk
                          FROM t
                         ORDER BY LOWER(object_name)
                       ) innermost
                 where rownum <= :b1 )
         where rowpos >= :b2) pg
       INNER JOIN t ON pg.pk = t.pk
 ORDER BY pg.rowpos

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00          0         20          0          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0         20          0          10

Rows     Row Source Operation
-------  ---------------------------------------------------
     10  SORT ORDER BY (cr=20 r=0 w=0 time=954 us)
     10   TABLE ACCESS BY INDEX ROWID OBJ#(55315) (cr=20 r=0 w=0 time=749 us)
     21    NESTED LOOPS  (cr=15 r=0 w=0 time=589 us)
     10     VIEW  (cr=3 r=0 w=0 time=278 us)
     10      COUNT STOPKEY (cr=3 r=0 w=0 time=210 us)
     10       VIEW  (cr=3 r=0 w=0 time=151 us)
     10        INDEX FULL SCAN OBJ#(55317) (cr=3 r=0 w=0 time=98 us)(object id 55317)
     10     INDEX RANGE SCAN OBJ#(55316) (cr=12 r=0 w=0 time=188 us)(object id 55316) 

5 stars   August 25, 2003 - 4am Central time zone
Reviewer: A reader 


5 stars Perfect   September 3, 2003 - 12pm Central time zone
Reviewer: Mike Madland from Bellevue, WA USA
Tom, thank you so much.  I had tried first_rows, but not on *all* of the subqueries.  This is 
great. 


3 stars how about 8.0.   September 13, 2003 - 3am Central time zone
Reviewer: s devarshi from Bhubaneswar ,India
what if ia want to do the same in 8.0.4 version

plsql ?

i have few other problem and wanted to ask you about it,
'ask your question later ' is blocking me 

devarshi 


Followup   September 13, 2003 - 9am Central time zone:

you cannot use order by in a subquery in 8.0 so this technique doesn't apply.

you have to open the cursor.

fetch the first N rows and ignore them

then fetch the next M rows and keep them

close the cursor



that's it. 

5 stars One question about your approach   September 25, 2003 - 11am Central time zone
Reviewer: julie from Ohio
My java developer is asking me how he will know 
how many rows are in the table for him to pass me
the minimum and maximun number. So that he can
pass  20, 40 and so on on the jsp page.


 


Followup   September 25, 2003 - 11pm Central time zone:

you have a "first page"

you have a "next page"

when "next page" returns less rows then requested -- you know you have hit "last page"

it is the way I do it... works great.  uses least resources. 

5 stars ORDER OF SELECTS   October 8, 2003 - 12pm Central time zone
Reviewer: Tracy from UK
I have a table accounts with a varchar2(50) column accountnumber.

I want to select the row with the highest value in accountnumber where the column contains numbers 
only so I do this:


test> select max(acno)
  2  from
  3  (select to_number(ACCOUNTNUMBER) acno
  4  from ACCOUNTS
  5  where replace(translate(ACCOUNTNUMBER, '1234567890', 'aaaaaaaaaa'),'a','') is null);

   MAX(ACNO)
------------
179976182723

which works fine. (May not be the best way of doing it, but it works.)

I then want to refine it by adding 'only if the number is less than 500000' so I add

where acno < 500000

and then I get ORA-01722: invalid number.

test> l
  1  select max(acno)
  2  from
  3  (select to_number(ACCOUNTNUMBER) acno
  4  from ACCOUNTS
  5* where replace(translate(ACCOUNTNUMBER, '1234567890', 'aaaaaaaaaa'),'a','') is null) where acno 
< 500000
test> /
where replace(translate(ACCOUNTNUMBER, '1234567890', 'aaaaaaaaaa'),'a','') is null) where acno < 
500000
                                                                                          *
ERROR at line 5:
ORA-01722: invalid number

Presumably this is to do with the order in which the selects work, but I thought that because the 
inner select is returning numbers 
only that the outer select would work ok? 


Followup   October 9, 2003 - 3pm Central time zone:

you are ascribing procedural constructs to a non-procedural language!

you are thinking "inline view done AND then outer stuff"

in fact that query is not any different then the query with the inline view removed -- the acno < 
50000 is done "whenever".


you can:

where 
  decode( replace(translate( accountNumber, '1234567890','0000000000'),'0',''), 
          NULL, to_number( accountNumber ),
          NULL ) < 50000


hint: don't use 'a', else a string with 'a' in it would be considered a valid number!
  

5 stars search producing wrong results   October 9, 2003 - 11am Central time zone
Reviewer: Paul Druker from Fairfax, VA USA
Tom, I was looking for from$_subquery$ combination on your site (I saw it in 
dba_audit_trail.obj_name). However, search for from$_subquery$  provides approximately 871 records, 
which is not correct. For example, this page does contain this word, but almost all extracted pages 
don't. It's interesting that search for from_subquery (without underscoire and $ sign) provides the 
same result. Search for "from$_subquery$" provides the same 871 results. I'd understand "special 
treatment" of underscore sign, but why $ sign? 


Followup   October 9, 2003 - 6pm Central time zone:

http://download-west.oracle.com/docs/cd/B10501_01/text.920/a96518/cqoper.htm#14499
$ is a stem search. 

5 stars Implementing dynamic query to suggested pagination query   October 24, 2003 - 8am Central time zone
Reviewer: Stephane Gouin from Ottawa, ON, Canada
Hi Tom

Using owa_util.cellsprint, but wanting to customize the table rows a little (adding a style sheet 
to hilight every other row, as a visual aid to users), so forced to look at the following query, as 
given in this thread:

select *
  from ( select a.*, rownum rnum
           from ( YOUR_QUERY_GOES_HERE -- including the order by ) a
          where rownum <= MAX_ROWS )
 where rnum >= MIN_ROWS
/

My question is however, how do you introduce a dynamic query into the mix, given I want to build a 
re-usable module to others can implement. This is exactly what owa_util.cellsprint with the dynamic 
cursor accomplishes, but I can't get in there to tweak it the layout.

Thanks for your help 


Followup   October 24, 2003 - 9am Central time zone:

cellsprint only supports dynamic sql?  not sure what the issue is here? 

5 stars   October 24, 2003 - 10am Central time zone
Reviewer: Stephane Gouin from Ottawa, ON Canada
Hi Tom,

Sorry, wasn't clear in my question. I was using cellsprint, but I realized I can't insert a style 
in the table row tag for instance (ie <tr class="h1">) The objective is to add a style to the row, 
where via a CSS, I can hilite alternate rows, giving user a little contrast when dealing with long 
lists.

I want to extend the cellsprint function, by allowing further control over the table tags... (ie 
style sheets, alignment, widths etc...)

Using a REF Cursor (or owa_util.bind_variables) for the subquery, how could I implement it using 
the pagination query.

Hope I clarified the question enough.. 


Followup   October 24, 2003 - 11am Central time zone:

you actually have access to the source code for cellsprint (its not wrapped).  just copy it as your 
own and modify as you see fit.

 

5 stars getting rows N through M of a result se   October 30, 2003 - 10am Central time zone
Reviewer: Edward Girard from Dallas, TX
Excelllent thread 


5 stars Very useful for web-based applications   November 1, 2003 - 12am Central time zone
Reviewer: Saminathan Seerangan from NJ,USA


4 stars HAVING can be efficient   November 12, 2003 - 1pm Central time zone
Reviewer: Joe Levy from Cambridge MA
Agreed that this

<quote>
select rownum, col1
from foobar
group by rownum, col1
having rownum >= :start and rownum < :end
</quote>

is inefficient.  But

select rownum, col1
from foobar
where rownum < :end -- added line to improve performance
group by rownum, col1
having rownum >= :start and rownum < :end

is almost as efficient as your preferred method.  And it has the advantage of being usable in a 
scalar subquery.  (The additional nesting required by your preferred method puts columns from 
tables in the outer query out of scope.)

Is there a reason not to use a HAVING clause with ROWNUM when variable scope demands it?
 


Followup   November 12, 2003 - 4pm Central time zone:

why would a scalar subquery need the N'th row?  

but yes, that would work (don't need the second and rownum < :end) 

4 stars Row_Number() or ROWNUM   November 19, 2003 - 5am Central time zone
Reviewer: Ranjit Desai from India
Hi Tom,

We do use row_Number() and other Analytical functions. But recently came across some limitation. 
Oracle 8i standard edition don't support this functions. They are only available in Enterprise 
Edition. Many of our sites are on Standard edition on Oracle 8i.

So current method to use Row_NUmber() to get required output needs to be changed.

SELECT deptno, ename, hiredate, 
ROW_NUMBER() OVER (PARTITION BY deptno ORDER BY hiredate) AS emp_id 
FROM emp 

To get similar output in SELECT query what can we do? Is it possible to use ROWNUM?? or Used 
defined function?

Please help us. As we have already tried some options without success.

Thanks & Regards,

Ranjit Desai  


Followup   November 21, 2003 - 11am Central time zone:

you cannot use rownum to achieve that.  analytics are mandatory for getting those numbers 
"partitioned"

9iR2 SE (standard) offers analytics as a feature. 

5 stars Fetching rows N-M   November 26, 2003 - 5am Central time zone
Reviewer: Stevef from Europe
Can the first N rows optimization feature be used in association with the paging technique to 
enhance the performance these queries ?

SELECT /*+ FIRST_ROWS(N) */ .... 



http://otn.oracle.com/products/oracle9i/daily/jan28.html


Followup   November 26, 2003 - 7am Central time zone:

yes, i usually just use first_rows myself. 

1 stars Fetching rows N-M   November 27, 2003 - 8am Central time zone
Reviewer: Stevef from Europe
Actually, Weird effects. The first query below returns 10 rows as expected but the second returns 
19 rows !!!!
(Oracle9i Enterprise Edition Release 9.2.0.2.1 Win2000)

select*
   from (select a.*,rownum r
           from (select /*+ first_rows */ customerid from customer order by 1) a
   where rownum <= 10+9 )
where r >= 10

select*
   from (select a.*,rownum r
           from (select /*+ first_rows(10) */ customerid from customer order by 1) a
   where rownum <= 10+9 )
where r >= 10

  


Followup   November 27, 2003 - 10am Central time zone:

confirmed -- filed a bug, temporary workaround is to add "order by r"

we can see they lose the filter using dbms_xplan:


ops$tkyte@ORA920> delete from plan_table;
6 rows deleted.
 
ops$tkyte@ORA920> explain plan for
  2  select*
  3     from (select a.*,rownum r
  4             from (select /*+ first_rows(10) */ empno from scott.emp order by 1) a
  5     where rownum <= 19 )
  6  where r >= 10
  7  /
 
Explained.
 
ops$tkyte@ORA920> select * from table(dbms_xplan.display);
 
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
 
-------------------------------------------------------------------------
| Id  | Operation            |  Name       | Rows  | Bytes | Cost (%CPU)|
-------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |             |    14 |   364 |     2  (50)|
|   1 |  VIEW                |             |    14 |   364 |            |
|*  2 |   COUNT STOPKEY      |             |       |       |            |
|   3 |    VIEW              |             |    14 |   182 |            |
|   4 |     INDEX FULL SCAN  | EMP_PK      |    14 |    42 |     2  (50)|
-------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   2 - filter(ROWNUM<=19)
 
15 rows selected.
 
ops$tkyte@ORA920>
ops$tkyte@ORA920> delete from plan_table;
 
5 rows deleted.
 
ops$tkyte@ORA920> explain plan for
  2  select*
  3     from (select a.*,rownum r
  4             from (select /*+ first_rows(10) */ empno from scott.emp order by 1) a
  5     where rownum <= 19 )
  6  where r >= 10
  7  order by r
  8  /
 
Explained.
 
ops$tkyte@ORA920> select * from table(dbms_xplan.display);
 
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
 
-------------------------------------------------------------------------
| Id  | Operation            |  Name       | Rows  | Bytes | Cost (%CPU)|
-------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |             |    14 |   364 |     3  (67)|
|   1 |  SORT ORDER BY       |             |    14 |   364 |     3  (67)|
|*  2 |   VIEW               |             |    14 |   364 |            |
|*  3 |    COUNT STOPKEY     |             |       |       |            |
|   4 |     VIEW             |             |    14 |   182 |            |
|   5 |      INDEX FULL SCAN | EMP_PK      |    14 |    42 |     2  (50)|
-------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   2 - filter("from$_subquery$_001"."R">=10)
   3 - filter(ROWNUM<=19)
 
17 rows selected.
 
ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> select*
  2     from (select a.*,rownum r
  3             from (select /*+ first_rows(10) */ empno from scott.emp order by 1) a
  4     where rownum <= 19 )
  5  where r >= 10
  6  order by r
  7  /
 
     EMPNO          R
---------- ----------
      7844         10
      7876         11
      7900         12
      7902         13
      7934         14
 
ops$tkyte@ORA920>
 

5 stars rows N-M   November 28, 2003 - 6am Central time zone
Reviewer: Stevef 
Gosh Tom, your momma sure raised you smart!
Great detective work! 


4 stars Getting total rows..   December 4, 2003 - 4am Central time zone
Reviewer: Naveen from India
Hi Tom:

The application we are developing should use pagination to show the results. The devlopers want me 
to get the total rows that the query return so that they can display that many pages. (Say, if the 
total rows returned are 100 and the number of results that have to be displayed in each page is 10 
rows, they can set 10 pages to display results sets.) The requirement is that we have to display 
the page number with hyperlink, so when user clicks on page 3, we have to display rows 31-40. 

To do this i have to first find the count of rows that the query returns and then fire the query to 
return the rows N through M. This is two I/O calls to the database and two queries to be parsed to 
display a page. Is there any work around.

Thanks
Nav. 


Followup   December 4, 2003 - 8am Central time zone:

I have a very very very very simple solution to this problem.

DON'T DO IT.

Your developers probably love www.google.com right?
they appreciate its speed, accuracy, usefulness.

All you need to do is tell them "use google as the gold standard for searching.  DO WHAT IT DOES"

Google lies constantly.  the hit count is never real.  It tells you "here are the first 10 pages" 
-- but you'll find if you click on page 10, you'll be on page 7 (there wasnt any page 8, 9 or 10 -- 
they didn't know that)

google guesses.  (i guess -- search on asktom, "approximately")

google is the gold standard -- just remember that.

In order to tell the end user "hey, there are 15 pages" you would have to run the entire query to 
completion on page one

and guess what, by the time page 1 is delivered to them (after waiting and waiting for it) there is 
a good chance their result set won't have 15 pages!!! (it is a database after all, people do write 
to it).  they might have 16 or maybe 14, or maybe NONE or maybe lots more the next time they page 
up or down!!

google is the gold standard.

did you know, you'll never go past page 100 on google - try it, they won't let you.

Here is a short excerpt from my book "Effective Oracle By Design" where I talk about this very 
topic (pagination in a web environment)


<quote>
Keep in mind that people are impatient and have short attention spans. How many times have you gone 
past the tenth page on a search page on the Internet? When I do a Google (www.google.com) search 
that returns more hits than the number of hamburgers sold by McDonald's, I never go to the last 
page; in fact, I never get to page 11. By the time I've looked at the first five pages or so, I 
realize that I need to refine my search because this is too much data. Your end users will, believe 
it or not, do the same. 


Some Advice on Web-based Searches with Pagination

My advice for handling web-based searches that you need to paginate through is to never provide an 
exact hit count. Use an estimate to tell the users about N hits. This is what I do on my asktom web 
site, for example. I use Oracle Text to index the content. Before I run a query, I ask Oracle Text 
for an estimate. You can do the same with your relational queries using EXPLAIN PLAN in Oracle8i 
and earlier, or by querying V$SQL_PLAN in Oracle9i and up. 
You may want to tell the end users they got 1,032,231 hits, but the problem with that is twofold:

o It takes a long time to count that many hits. You need to run that ALL_ROWS type of query to the 
end to find that out! It is really slow.
o By the time you count the hits, in all probability (unless you are on a read-only database), the 
answer has already changed and you do not have that number of hits anymore!


My other advice for this type of application is to never provide a Last Page button or give the 
user more than ten pages at a time from which to choose. Look at the standard, www.google.com, and 
do what it does.

Follow those two pieces of advice, and your pagination worries are over.
</quote>


 

5 stars Thanks Tom..   December 4, 2003 - 10pm Central time zone
Reviewer: Naveen from India
Hi Tom,

Got what you said. I'll try to convince my developers with this information. Day by day the 
admiration for you keeps growing. 

Thank you

Nav.


 


4 stars Displaying Top N rows in 8.0.6   December 9, 2003 - 4am Central time zone
Reviewer: Russell from Melbourne, Australia
G'day Tom,

On September 13, 2003  or there abouts you left the following:
 ----------
you cannot use order by in a subquery in 8.0 so this technique doesn't apply.

you have to open the cursor.

fetch the first N rows and ignore them

then fetch the next M rows and keep them

close the cursor
that's it. 

----------

I have an application, where a set of grouped records is in the vicinity of 800 combinations.  For 
the purposes of analysis, 80% of work is in top 20% of grouped entries.  As such most gains will be 
achieved by analysing these entries with the most reords.  As I am trying to do the majority of the 
grunt work in Oracle, parameters are passed by users, to a procedure with a ref Cursor being OUTput 
to a Crystal report.

One of the parameters I am inputting, is TopN hoping to return grouped entries with the greatest 
Record counts by the grouping needed.

I include this statement in a cursor, and loop through for 1 to TopN, appending the resulting Group 
Names to a varchar2 variable hoping to include the contents of this string in a subsequent where 
statement.  

A possible example:

declare
TopN := 3;  -- return all records matching the group identifer with TopN most records

Counter Number := 0;
vchString varchar2(200);
begin
for I in (Select Dept, count(*) from employees
    where ....
    group by Dept order by count(*) desc)
Loop

if counter > TopN then
    exit;
end if;

if counter > 0 then
    vchString:=vchstring||',';
end if;
vchString:=vchString||i.dept;
 -- or vchstring:=vchstring|| ''''||string||'''' 
 -- for columns containing varchar data....
end loop;

I then have a statement
Cursor is ....
select ......
from employees
where .....
AND  DEPT in ( vchString);

end;

with the hope that the original cursor might return something like
DEPT  COUNT(*)
30    8
20    7
19    6
10    5
15    3
 7    2
 1    1
 4    1


and the returning cursor in the optimum world would therefore become something like 
select ......
from employees
where .....
AND  DEPT in ( 30,20,19);
 
Hence having to select, group, sum, and display the 12(TopN) group entries instead of 800 ish.

The loop works, and populates the varchar2 variable, but the contents of that variable don't seem 
to be transposed or included into the IN statement.  As mentioned above I am using Oracle database 
version 8.0.6, and having read a number of threads on your site, don't think I can use the Analytic 
functions included in 8.1 and above.

Please advise what my problem is, or if there is a better way to try and do what I am after.

Thanks in advance. 


Followup   December 9, 2003 - 6am Central time zone:

http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:110612348061

5 stars Catastrophic Performance Degredation   December 16, 2003 - 11am Central time zone
Reviewer: Doh! 
Any ideas as to why the act of putting an outer "wrapper" on an inner query

select * from ( my_inner_query )

can cause the performance of a query to degrade by a factor of 3000 ?

First the innermost query:

SQL>     ( SELECT a.*, ROWNUM RECORDINDEX FROM
  2      ( SELECT 'Townland Boundary' "FEATURENAME", mME.MinX, mME.MaxX, mME.MinY, mME.MaxY,  
gL.LayerName,
  3        gL.LayerAlias,  TOWNLAND.GEONAME COLUMN1 , COUNTY.GEONAME COLUMN2
  4       FROM COUNTY ,TOWNLAND , MinMaxExtent mME, GeoLayer gL
  5        WHERE COUNTY.GEONAME = 'L123'
  6         AND  TOWNLAND.GEONAME LIKE 'BALL%'
  7         AND TOWNLAND.COUNTYID = COUNTY.COUNTYID
  8         AND mME.ForeignID = TOWNLAND.GEOMETRYID
  9        AND  mME.TableName = 'TOWNLAND'
 10        AND gL.TableName = mME.TableName
 11        AND gL.LayerName = 'TOWNLAND'
 12      ORDER BY  TOWNLAND.GEONAME , COUNTY.GEONAME)
 13      a WHERE ROWNUM <= 10)
 14  /

10 rows selected.

Elapsed: 00:00:00.01
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=169 Card=2 Bytes=3214)
   1    0   COUNT (STOPKEY)
   2    1     VIEW (Cost=169 Card=2 Bytes=3214)
   3    2       SORT (ORDER BY STOPKEY) (Cost=169 Card=2 Bytes=224)
   4    3         NESTED LOOPS (Cost=167 Card=2 Bytes=224)
   5    4           NESTED LOOPS (Cost=163 Card=2 Bytes=158)
   6    5             MERGE JOIN (CARTESIAN) (Cost=3 Card=1 Bytes=57)
   7    6               TABLE ACCESS (BY INDEX ROWID) OF 'GEOLAYER' (Cost=2 Card=1 Bytes=45)
   8    7                 INDEX (RANGE SCAN) OF 'GEOLAYER_LAYERNAME_IDx' (NON-UNIQUE) (Cost=1 
Card=1)
   9    6               BUFFER (SORT) (Cost=1 Card=1 Bytes=12)
  10    9                 TABLE ACCESS (BY INDEX ROWID) OF 'COUNTY' (Cost=1 Card=1 Bytes=12)
  11   10                   INDEX (RANGE SCAN) OF 'COUNTY_GEONAME_IDX'    (NON-UNIQUE)
  12    5             TABLE ACCESS (BY INDEX ROWID) OF 'TOWNLAND' (Cost=163 Card=1 Bytes=22)
  13   12               BITMAP CONVERSION (TO ROWIDS)
  14   13                 BITMAP AND
  15   14                   BITMAP CONVERSION (FROM ROWIDS)
  16   15                     INDEX (RANGE SCAN) OF 'TOWNLAND_COUNTYID_IDX' (NON-UNIQUE) (Cost=4 
Card=1950)
  17   14                   BITMAP CONVERSION (FROM ROWIDS)
  18   17                     SORT (ORDER BY)
  19   18                       INDEX (RANGE SCAN) OF 'TOWNLAND_GEONAME_IDX' (NON-UNIQUE) (Cost=14 
Card=1950)
  20    4           TABLE ACCESS (BY INDEX ROWID) OF 'MINMAXEXTENT' (Cost=2 Card=50698 
Bytes=1673034)
  21   20             INDEX (UNIQUE SCAN) OF 'MINMAXEXT_UK' (UNIQUE) (  Cost=1 Card=4)

Statistics
----------------------------------------------------------
          0  recursive calls
          6  db block gets
        530  consistent gets
         21  physical reads
          0  redo size
       1458  bytes sent via SQL*Net to client
        498  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          2  sorts (memory)
          1  sorts (disk)
         10  rows processed

Now the final outer wrapper:

SQL> SELECT a.* FROM
  2      ( SELECT a.*, ROWNUM RECORDINDEX FROM
  3      ( SELECT 'Townland Boundary' "FEATURENAME", mME.MinX, mME.MaxX, mME.MinY, mME.MaxY,  
gL.LayerName,
  4        gL.LayerAlias,  TOWNLAND.GEONAME COLUMN1 , COUNTY.GEONAME COLUMN2
  5       FROM COUNTY ,TOWNLAND , MinMaxExtent mME, GeoLayer gL
  6        WHERE COUNTY.GEONAME = 'L123'
  7         AND  TOWNLAND.GEONAME LIKE 'BALL%'
  8         AND TOWNLAND.COUNTYID = COUNTY.COUNTYID
  9         AND mME.ForeignID = TOWNLAND.GEOMETRYID
 10        AND  mME.TableName = 'TOWNLAND'
 11        AND gL.TableName = mME.TableName
 12        AND gL.LayerName = 'TOWNLAND'
 13      ORDER BY  TOWNLAND.GEONAME , COUNTY.GEONAME)
 14      a WHERE ROWNUM <= 10) a
 15  /

10 rows selected.

Elapsed: 00:00:32.00

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=466 Card=2 Bytes=3240)

   1    0   VIEW (Cost=466 Card=2 Bytes=3240)
   2    1     COUNT (STOPKEY)
   3    2       VIEW (Cost=466 Card=2 Bytes=3214)
   4    3         SORT (ORDER BY STOPKEY) (Cost=466 Card=2 Bytes=224)
   5    4           TABLE ACCESS (BY INDEX ROWID) OF 'TOWNLAND' (Cost=464 Card=1 Bytes=22)
   6    5             NESTED LOOPS (Cost=464 Card=2 Bytes=224)
   7    6               NESTED LOOPS (Cost=68 Card=722 Bytes=64980)
   8    7                 MERGE JOIN (CARTESIAN) (Cost=3 Card=1 Bytes=57)
   9    8                   TABLE ACCESS (BY INDEX ROWID) OF 'COUNTY'(Cost=2 Card=1 Bytes=12)
  10    9                     INDEX (RANGE SCAN) OF 'COUNTY_GEONAME_IDX' (NON-UNIQUE) (Cost=1 
Card=1)
  11    8                   BUFFER (SORT) (Cost=1 Card=1 Bytes=45)
  12   11                     TABLE ACCESS (BY INDEX ROWID) OF 'GEOLAYER' (Cost=1 Card=1 Bytes=45)
  13   12                       INDEX (RANGE SCAN) OF 'GEOLAYER_LAYERNAME_IDX' (NON-UNIQUE)
  14    7                 TABLE ACCESS (BY INDEX ROWID) OF 'MINMAXEXTENT' (Cost=65 Card=1 Bytes=33)
  15   14                   INDEX (RANGE SCAN) OF 'MINMAXEXT_UK' (UNIQUE) (Cost=43 Card=2112)
  16    6               BITMAP CONVERSION (TO ROWIDS)
  17   16                 BITMAP AND
  18   17                   BITMAP CONVERSION (FROM ROWIDS)
  19   18                     INDEX (RANGE SCAN) OF 'TOWNLAND_PK' (UNIQUE)
  20   17                   BITMAP CONVERSION (FROM ROWIDS)
  21   20                     INDEX (RANGE SCAN) OF 'TOWNLAND_COUNTYID_IDX' (NON-UNIQUE) (Cost=4 
Card=12)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
     254501  consistent gets
        847  physical reads
          0  redo size
       1458  bytes sent via SQL*Net to client
        498  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
         10  rows processed

 


Followup   December 16, 2003 - 1pm Central time zone:

if you push a first_rows hint into the innermost queries -- what happens then (no answer for why 
this is happening -- don't know in this case -- for that, suggest a tar but lets try to find a way 
to workaround the issue here) 

5 stars Improvement   December 17, 2003 - 6am Central time zone
Reviewer: A reader 
Query elapsed time falls to about 1 second. Huge improvement but still not as snappy as the 
original query at 0.01 seconds!

  1    SELECT a.* FROM
  2           ( SELECT  a.*, ROWNUM RECORDINDEX FROM
  3           ( SELECT /*+ FIRST_ROWS */ 'Townland Boundary' "FEATURENAME", mME.MinX, mME.MaxX, 
mME.MinY, mME.MaxY,  gL.LayerName,
  4             gL.LayerAlias,  TOWNLAND.GEONAME COLUMN1 , COUNTY.GEONAME COLUMN2
  5            FROM COUNTY ,TOWNLAND , MinMaxExtent mME, GeoLayer gL
  6             WHERE COUNTY.GEONAME = 'L123'
  7              AND  TOWNLAND.GEONAME LIKE 'BALL%'
  8             AND TOWNLAND.COUNTYID = COUNTY.COUNTYID
  9              AND mME.ForeignID = TOWNLAND.GEOMETRYID
 10            AND  mME.TableName = 'TOWNLAND'
 11           AND gL.TableName = mME.TableName
 12            AND gL.LayerName = 'TOWNLAND'
 13          ORDER BY  TOWNLAND.GEONAME , COUNTY.GEONAME)
 14*         a WHERE ROWNUM <= 10) a
SQL> /

10 rows selected.

Elapsed: 00:00:01.08

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=8578 Card=2 Bytes=3240)
   1    0   VIEW (Cost=8578 Card=2 Bytes=3240)
   2    1     COUNT (STOPKEY)
   3    2       VIEW (Cost=8578 Card=2 Bytes=3214)
   4    3         SORT (ORDER BY STOPKEY) (Cost=8578 Card=2 Bytes=224)
   5    4           NESTED LOOPS (Cost=8576 Card=2 Bytes=224)
   6    5             MERGE JOIN (CARTESIAN) (Cost=8264 Card=156 Bytes=12324)
   7    6               NESTED LOOPS (Cost=8108 Card=156 Bytes=5304)
   8    7                 TABLE ACCESS (BY INDEX ROWID) OF 'TOWNLAND'(Cost=4052 Card=4056 
Bytes=89232)
   9    8                   INDEX (RANGE SCAN) OF 'TOWNLAND_GEONAME_IDX' (NON-UNIQUE) (Cost=15 
Card=4056)
  10    7                 TABLE ACCESS (BY INDEX ROWID) OF 'COUNTY' (Cost=1 Card=1 Bytes=12)
  11   10                   INDEX (UNIQUE SCAN) OF 'COUNTY_UK' (UNIQUE)
  12    6               BUFFER (SORT) (Cost=8263 Card=1 Bytes=45)
  13   12                 TABLE ACCESS (BY INDEX ROWID) OF 'GEOLAYER' (Cost=1 Card=1 Bytes=45)
  14   13                   INDEX (RANGE SCAN) OF 'GEOLAYER_LAYERNAME_IDX' (NON-UNIQUE)
  15    5             TABLE ACCESS (BY INDEX ROWID) OF 'MINMAXEXTENT'(Cost=2 Card=1 Bytes=33)
  16   15               INDEX (UNIQUE SCAN) OF 'MINMAXEXT_UK' (UNIQUE)(Cost=1 Card=1)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      10413  consistent gets
       4174  physical reads
          0  redo size
       1458  bytes sent via SQL*Net to client
        498  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
         10  rows processed
 


Followup   December 17, 2003 - 7am Central time zone:

do you have a tkprof -- are the "estimations" in the autotrace anywhere near the "real numbers" in 
the tkprof -- are the stats current and up to date. 

5 stars   December 17, 2003 - 9am Central time zone
Reviewer: A reader 
Stats are current.

Here we have tkprof first with and then without first_rows hint:

    SELECT a.* FROM
           ( SELECT  a.*, ROWNUM RECORDINDEX FROM
           ( SELECT /*+ FIRST_ROWS */ :"SYS_B_0" "FEATURENAME",
             mME.MinX, mME.MaxX, mME.MinY, mME.MaxY,  gL.LayerName,
             gL.LayerAlias,  TOWNLAND.GEONAME COLUMN1 , COUNTY.GEONAME COLUMN2
           FROM COUNTY ,TOWNLAND , MinMaxExtent mME, GeoLayer gL
            WHERE COUNTY.GEONAME = :"SYS_B_1"
             AND  TOWNLAND.GEONAME LIKE :"SYS_B_2"
             AND TOWNLAND.COUNTYID = COUNTY.COUNTYID
              AND mME.ForeignID = TOWNLAND.GEOMETRYID
           AND  mME.TableName = :"SYS_B_3"
           AND gL.TableName = mME.TableName
            AND gL.LayerName = :"SYS_B_4"
          ORDER BY  TOWNLAND.GEONAME , COUNTY.GEONAME)
          a WHERE ROWNUM <= :"SYS_B_5") a

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        2      0.00       0.00          0          0          0           0
Execute      2      0.00       0.00          0          0          0           0
Fetch        3      0.68      11.24       8694      20432          0          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        7      0.68      11.25       8694      20432          0          10

Misses in library cache during parse: 1
Optimizer goal: FIRST_ROWS
Parsing user id: 89  

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  VIEW  (cr=10019 r=4443 w=0 time=9453637 us)
      0   COUNT STOPKEY (cr=10019 r=4443 w=0 time=9453630 us)
      0    VIEW  (cr=10019 r=4443 w=0 time=9453627 us)
      0     SORT ORDER BY STOPKEY (cr=10019 r=4443 w=0 time=9453621 us)
      0      NESTED LOOPS  (cr=10019 r=4443 w=0 time=9453566 us)
      0       MERGE JOIN CARTESIAN (cr=10019 r=4443 w=0 time=9453562 us)
      0        NESTED LOOPS  (cr=10019 r=4443 w=0 time=9453555 us)
   5011         TABLE ACCESS BY INDEX ROWID TOWNLAND (cr=5006 r=4438 w=0 time=9339026 us)
   5011          INDEX RANGE SCAN TOWNLAND_GEONAME_IDX (cr=21 r=0 w=0 time=30694 us)(object id 
55399)
      0         TABLE ACCESS BY INDEX ROWID COUNTY (cr=5013 r=5 w=0 time=84014 us)
   5011          INDEX UNIQUE SCAN COUNTY_UK (cr=2 r=1 w=0 time=32598 us)(object id 55015)
      0        BUFFER SORT (cr=0 r=0 w=0 time=0 us)
      0         TABLE ACCESS BY INDEX ROWID GEOLAYER (cr=0 r=0 w=0 time=0 us)
      0          INDEX RANGE SCAN GEOLAYER_LAYERNAME_IDX (cr=0 r=0 w=0 time=0 us)(object id 55930)
      0       TABLE ACCESS BY INDEX ROWID MINMAXEXTENT (cr=0 r=0 w=0 time=0 us)
      0        INDEX UNIQUE SCAN MINMAXEXT_UK (cr=0 r=0 w=0 time=0 us)(object id 55602)

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


    SELECT a.* FROM
           ( SELECT  a.*, ROWNUM RECORDINDEX FROM
           ( SELECT  :"SYS_B_0" "FEATURENAME",
             mME.MinX, mME.MaxX, mME.MinY, mME.MaxY,  gL.LayerName,
             gL.LayerAlias,  TOWNLAND.GEONAME COLUMN1 , COUNTY.GEONAME COLUMN2
           FROM COUNTY ,TOWNLAND , MinMaxExtent mME, GeoLayer gL
            WHERE COUNTY.GEONAME = :"SYS_B_1"
             AND  TOWNLAND.GEONAME LIKE :"SYS_B_2"
             AND TOWNLAND.COUNTYID = COUNTY.COUNTYID
              AND mME.ForeignID = TOWNLAND.GEOMETRYID
           AND  mME.TableName = :"SYS_B_3"
           AND gL.TableName = mME.TableName
            AND gL.LayerName = :"SYS_B_4"
          ORDER BY  TOWNLAND.GEONAME , COUNTY.GEONAME)
          a WHERE ROWNUM <= :"SYS_B_5") a

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2    136.84     139.22        851     254502          0          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4    136.84     139.22        851     254502          0          10

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 89  

Rows     Row Source Operation
-------  ---------------------------------------------------
     10  VIEW  (cr=254502 r=851 w=0 time=139222875 us)
     10   COUNT STOPKEY (cr=254502 r=851 w=0 time=139222837 us)
     10    VIEW  (cr=254502 r=851 w=0 time=139222808 us)
     10     SORT ORDER BY STOPKEY (cr=254502 r=851 w=0 time=139222779 us)
    130      TABLE ACCESS BY INDEX ROWID TOWNLAND (cr=254502 r=851 w=0 time=139221108 us)
  51817       NESTED LOOPS  (cr=254226 r=695 w=0 time=138882960 us)
  50698        NESTED LOOPS  (cr=628 r=597 w=0 time=1651025 us)
      1         MERGE JOIN CARTESIAN (cr=5 r=0 w=0 time=319 us)
      1          TABLE ACCESS BY INDEX ROWID COUNTY (cr=3 r=0 w=0 time=127 us)
      1           INDEX RANGE SCAN COUNTY_GEONAME_IDX (cr=1 r=0 w=0 time=56 us)(object id 55401)
      1          BUFFER SORT (cr=2 r=0 w=0 time=96 us)
      1           TABLE ACCESS BY INDEX ROWID GEOLAYER (cr=2 r=0 w=0 time=31 us)
      1            INDEX RANGE SCAN GEOLAYER_LAYERNAME_IDX (cr=1 r=0 w=0 time=18 us)(object id 
55930)
  50698         TABLE ACCESS BY INDEX ROWID MINMAXEXTENT (cr=623 r=597 w=0 time=1548952 us)
  50698          INDEX RANGE SCAN MINMAXEXT_UK (cr=154 r=137 w=0 time=592031 us)(object id 55602)
   1118        BITMAP CONVERSION TO ROWIDS (cr=253598 r=98 w=0 time=136695508 us)
   1118         BITMAP AND  (cr=253598 r=98 w=0 time=136580794 us)
  50698          BITMAP CONVERSION FROM ROWIDS (cr=50804 r=98 w=0 time=1338601 us)
  50698           INDEX RANGE SCAN TOWNLAND_PK (cr=50804 r=98 w=0 time=960672 us)(object id 55131)
  27108          BITMAP CONVERSION FROM ROWIDS (cr=202794 r=0 w=0 time=134967141 us)
56680364           INDEX RANGE SCAN TOWNLAND_COUNTYID_IDX (cr=202794 r=0 w=0 time=76815579 
us)(object id 55416)



 


Followup   December 18, 2003 - 8am Central time zone:

I wanted to compare the first_rows to the one that is "fast", not the slow one. 

5 stars additional tkprof   December 17, 2003 - 10am Central time zone
Reviewer: A reader 
here's the tkprof for the query without the outer wrapper:

 SELECT  a.*, ROWNUM RECORDINDEX FROM
           ( SELECT  :"SYS_B_0" "FEATURENAME",
             mME.MinX, mME.MaxX, mME.MinY, mME.MaxY,  gL.LayerName,
             gL.LayerAlias,  TOWNLAND.GEONAME COLUMN1 , COUNTY.GEONAME COLUMN2
           FROM COUNTY ,TOWNLAND , MinMaxExtent mME, GeoLayer gL
            WHERE COUNTY.GEONAME = :"SYS_B_1"
             AND  TOWNLAND.GEONAME LIKE :"SYS_B_2"
             AND TOWNLAND.COUNTYID = COUNTY.COUNTYID
              AND mME.ForeignID = TOWNLAND.GEOMETRYID
           AND  mME.TableName = :"SYS_B_3"
           AND gL.TableName = mME.TableName
            AND gL.LayerName = :"SYS_B_4"
          ORDER BY  TOWNLAND.GEONAME , COUNTY.GEONAME)
          a WHERE ROWNUM <= :"SYS_B_5"

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.03       0.08         21        530          6          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.03       0.08         21        530          6          10

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 89  

Rows     Row Source Operation
-------  ---------------------------------------------------
     10  COUNT STOPKEY (cr=530 r=21 w=21 time=85530 us)
     10   VIEW  (cr=530 r=21 w=21 time=85489 us)
     10    SORT ORDER BY STOPKEY (cr=530 r=21 w=21 time=85462 us)
    130     NESTED LOOPS  (cr=530 r=21 w=21 time=84925 us)
    130      NESTED LOOPS  (cr=138 r=21 w=21 time=82066 us)
      1       MERGE JOIN CARTESIAN (cr=4 r=0 w=0 time=236 us)
      1        TABLE ACCESS BY INDEX ROWID GEOLAYER (cr=2 r=0 w=0 time=93 us)
      1         INDEX RANGE SCAN GEOLAYER_LAYERNAME_IDX (cr=1 r=0 w=0 time=56 us)(object id 55930)
      1        BUFFER SORT (cr=2 r=0 w=0 time=92 us)
      1         TABLE ACCESS BY INDEX ROWID COUNTY (cr=2 r=0 w=0 time=38 us)
      1          INDEX RANGE SCAN COUNTY_GEONAME_IDX (cr=1 r=0 w=0 time=19 us)(object id 55401)
    130       TABLE ACCESS BY INDEX ROWID TOWNLAND (cr=134 r=21 w=21 time=81527 us)
    130        BITMAP CONVERSION TO ROWIDS (cr=26 r=21 w=21 time=80386 us)
      1         BITMAP AND  (cr=26 r=21 w=21 time=80228 us)
      1          BITMAP CONVERSION FROM ROWIDS (cr=5 r=0 w=0 time=2782 us)
   1118           INDEX RANGE SCAN TOWNLAND_COUNTYID_IDX (cr=5 r=0 w=0 time=1551 us)(object id 
55416)
      1          BITMAP CONVERSION FROM ROWIDS (cr=21 r=21 w=21 time=77315 us)
   5011           SORT ORDER BY (cr=21 r=21 w=21 time=72098 us)
   5011            INDEX RANGE SCAN TOWNLAND_GEONAME_IDX (cr=21 r=0 w=0 time=20016 us)(object id 
55399)
    130      TABLE ACCESS BY INDEX ROWID MINMAXEXTENT (cr=392 r=0 w=0 time=2105 us)
    130       INDEX UNIQUE SCAN MINMAXEXT_UK (cr=262 r=0 w=0 time=1126 us)(object id 55602)

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


Followup   December 18, 2003 - 8am Central time zone:

wonder if it is a side effect of cursor sharing here -- hmm.  The difference between the plans is 
one is using b*tree - bitmap conversions, avoiding the table access by rowid (and that is what is 
causing the "slowdown", all of the IO to read that table a block at a time)

what happens to the plans if you turned off cursor sharing for a minute (alter session set 
cursor_sharing=exact).  just curious at this point. 

5 stars   December 18, 2003 - 11am Central time zone
Reviewer: A reader 
Attached:

First the fastest and then the slower with first_rows hint:



alter session set cursor_sharing=exact

SELECT a.*, ROWNUM RECORDINDEX FROM
             ( SELECT 'Townland Boundary' "FEATURENAME", mME.MinX, mME.MaxX, mME.MinY, mME.MaxY,  
gL.LayerName,
               gL.LayerAlias,  TOWNLAND.GEONAME COLUMN1 , COUNTY.GEONAME COLUMN2
              FROM COUNTY ,TOWNLAND , MinMaxExtent mME, GeoLayer gL
               WHERE COUNTY.GEONAME = 'L123'
                AND  TOWNLAND.GEONAME LIKE 'BALL%'
               AND TOWNLAND.COUNTYID = COUNTY.COUNTYID
                AND mME.ForeignID = TOWNLAND.GEOMETRYID
             AND  mME.TableName = 'TOWNLAND'
            AND gL.TableName = mME.TableName
             AND gL.LayerName = 'TOWNLAND'
           ORDER BY  TOWNLAND.GEONAME , COUNTY.GEONAME)
          a WHERE ROWNUM <= 10

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.40       0.41          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.09       0.28        120        530          6          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.50       0.69        120        530          6          10

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 89  

Rows     Row Source Operation
-------  ---------------------------------------------------
     10  COUNT STOPKEY (cr=530 r=120 w=21 time=284532 us)
     10   VIEW  (cr=530 r=120 w=21 time=284494 us)
     10    SORT ORDER BY STOPKEY (cr=530 r=120 w=21 time=284464 us)
    130     NESTED LOOPS  (cr=530 r=120 w=21 time=283692 us)
    130      NESTED LOOPS  (cr=138 r=119 w=21 time=270656 us)
      1       MERGE JOIN CARTESIAN (cr=4 r=1 w=0 time=11295 us)
      1        TABLE ACCESS BY INDEX ROWID GEOLAYER (cr=2 r=0 w=0 time=89 us)
      1         INDEX RANGE SCAN GEOLAYER_LAYERNAME_IDX (cr=1 r=0 w=0 time=52 us)(object id 55930)
      1        BUFFER SORT (cr=2 r=1 w=0 time=11101 us)
      1         TABLE ACCESS BY INDEX ROWID COUNTY (cr=2 r=1 w=0 time=11002 us)
      1          INDEX RANGE SCAN COUNTY_GEONAME_IDX (cr=1 r=1 w=0 time=10963 us)(object id 55401)
    130       TABLE ACCESS BY INDEX ROWID TOWNLAND (cr=134 r=118 w=21 time=258993 us)
    130        BITMAP CONVERSION TO ROWIDS (cr=26 r=40 w=21 time=138838 us)
      1         BITMAP AND  (cr=26 r=40 w=21 time=138608 us)
      1          BITMAP CONVERSION FROM ROWIDS (cr=5 r=0 w=0 time=2838 us)
   1118           INDEX RANGE SCAN TOWNLAND_COUNTYID_IDX (cr=5 r=0 w=0 time=1601 us)(object id 
55416)
      1          BITMAP CONVERSION FROM ROWIDS (cr=21 r=40 w=21 time=135671 us)
   5011           SORT ORDER BY (cr=21 r=40 w=21 time=130355 us)
   5011            INDEX RANGE SCAN TOWNLAND_GEONAME_IDX (cr=21 r=19 w=0 time=78502 us)(object id 
55399)
    130      TABLE ACCESS BY INDEX ROWID MINMAXEXTENT (cr=392 r=1 w=0 time=11953 us)
    130       INDEX UNIQUE SCAN MINMAXEXT_UK (cr=262 r=0 w=0 time=1625 us)(object id 55602)

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

SELECT a.* FROM
             ( SELECT /*+ first_rows */  a.*, ROWNUM RECORDINDEX FROM
             ( SELECT 'Townland Boundary' "FEATURENAME", mME.MinX, mME.MaxX, mME.MinY, mME.MaxY,  
gL.LayerName,
               gL.LayerAlias,  TOWNLAND.GEONAME COLUMN1 , COUNTY.GEONAME COLUMN2
              FROM COUNTY ,TOWNLAND , MinMaxExtent mME, GeoLayer gL
               WHERE COUNTY.GEONAME = 'L123'
                AND  TOWNLAND.GEONAME LIKE 'BALL%'
               AND TOWNLAND.COUNTYID = COUNTY.COUNTYID
                AND mME.ForeignID = TOWNLAND.GEOMETRYID
             AND  mME.TableName = 'TOWNLAND'
            AND gL.TableName = mME.TableName
             AND gL.LayerName = 'TOWNLAND'
           ORDER BY  TOWNLAND.GEONAME , COUNTY.GEONAME)
          a WHERE ROWNUM <= 10) a

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.03       0.02          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.56      12.38       3889      10413          0          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.59      12.41       3889      10413          0          10

Misses in library cache during parse: 1
Optimizer goal: FIRST_ROWS
Parsing user id: 89  

Rows     Row Source Operation
-------  ---------------------------------------------------
     10  VIEW  (cr=10413 r=3889 w=0 time=12388676 us)
     10   COUNT STOPKEY (cr=10413 r=3889 w=0 time=12388629 us)
     10    VIEW  (cr=10413 r=3889 w=0 time=12388592 us)
     10     SORT ORDER BY STOPKEY (cr=10413 r=3889 w=0 time=12388562 us)
    130      NESTED LOOPS  (cr=10413 r=3889 w=0 time=12387024 us)
    130       MERGE JOIN CARTESIAN (cr=10021 r=3888 w=0 time=12380531 us)
    130        NESTED LOOPS  (cr=10019 r=3888 w=0 time=12377287 us)
   5011         TABLE ACCESS BY INDEX ROWID TOWNLAND (cr=5006 r=3888 w=0 time=12252430 us)
   5011          INDEX RANGE SCAN TOWNLAND_GEONAME_IDX (cr=21 r=15 w=0 time=41073 us)(object id 
55399)
    130         TABLE ACCESS BY INDEX ROWID COUNTY (cr=5013 r=0 w=0 time=88160 us)
   5011          INDEX UNIQUE SCAN COUNTY_UK (cr=2 r=0 w=0 time=28698 us)(object id 55015)
    130        BUFFER SORT (cr=2 r=0 w=0 time=1745 us)
      1         TABLE ACCESS BY INDEX ROWID GEOLAYER (cr=2 r=0 w=0 time=38 us)
      1          INDEX RANGE SCAN GEOLAYER_LAYERNAME_IDX (cr=1 r=0 w=0 time=25 us)(object id 55930)
    130       TABLE ACCESS BY INDEX ROWID MINMAXEXTENT (cr=392 r=1 w=0 time=4649 us)
    130        INDEX UNIQUE SCAN MINMAXEXT_UK (cr=262 r=0 w=0 time=2697 us)(object id 55602)

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


4 stars Re: Catastrophic Performance Degredation   February 11, 2004 - 5pm Central time zone
Reviewer: T Truong from Long Beach, CA
Mr. Kyte,
We are having the same performance problem as with reviewer Doh!

The following query (provided in your first post of this thread) works perfectly prior to our 
database upgrade from 8.1.7 to 9.2.0.

select * 
  from ( select a.*, rownum rnum
           from ( YOUR_QUERY_GOES_HERE -- including the order by ) a
          where rownum <= MAX_ROWS )
 where rnum >= MIN_ROWS
/

After the upgrade to 9.2.0, we have the performance problem with the above query.

Please continue this thread to determine the cause.

Best Regards,
 


Followup   February 11, 2004 - 6pm Central time zone:

hows about your example? your query, your 8i tkprf and your 9i one as well 

4 stars Re: Catastrophic Performance Degredation   February 11, 2004 - 8pm Central time zone
Reviewer: T Truong from Long Beach, CA
Mr. Kyte,
Thank you for your prompt response.

As a developer, don't have access to tkprof utilities to check out the query statistics and we're 
not getting much time from our DBAs so far to run tkprof; though we will be getting some of their 
time soon (hopefully next week).

So far, we know that if we set the initial parameter OPTIMIZER_FEATURES_ENABLE to 8.1.7, then the 
query runs just as fast as it was prior to the database upgrade.

We don't have any other 8i sandbox to regenerate the explan plan for the query, but here's the 
query and its explain plan in our only 9i 9.2.0 sandbox

select
       x.ccbnumber
      ,x.chgtype
      ,x.pkgid
      ,x.project_status
      ,x.title
      ,x.projleadname
 from (
    select a.*, rownum rnum
     from (
           select
                  pkg_info.chgty_code || pkg_info.pkg_seq_id ccbnumber
                 ,pkg_info.chgty_code chgtype
                 ,pkg_info.pkg_seq_id pkgid
                 ,(
                   select decode(pkg_info.sty_code,'INPROCESS','In-Process'
                                                  ,'CREATED','Created'
                                                  ,decode(
                                                          min(
                                                              decode(pvs.sty_code,'COMPLETE',10
                                                                                 ,'CANCEL',20
                                                                                 ,'APPROVE',30
                                                                                 ,'APPROVEDEF',30
                                                                                 ,'INPROCESS',40
                                                                                 ,'DISAPPROVE',50
                                                                                 ,'DISAPPRXA',50
                                                                                 ,'VOID',60
                                                                                 ,'CREATED',70
                                                                                 ,null
                                                                    )
                                                             ),10,'Complete'
                                                              ,20,'Cancelled'
                                                              ,30,'Approved'
                                                              ,40,'In-Process'
                                                              ,50,'Disapproved'
                                                              ,50,'Disapproved XA'
                                                              ,60,'Void'
                                                              ,70,'Created'
                                                         )
                                ) project_status
                     from cms_pkg_ver_statuses pvs
                         ,cms_pkg_vers pv
                    where pv.pkg_seq_id = pkg_info.pkg_seq_id
                      and pv.pkg_seq_id = pvs.pkgver_pkg_seq_id
                      and pv.seq_num = pvs.pkgver_seq_num
                      and pvs.sty_scty_dp_code = 'PKG'
                      and pvs.sty_scty_code = 'STATE'
                      and pvs.create_date =
                             (select max(create_date)
                                from cms_pkg_ver_statuses
                               where pkgver_pkg_seq_id = pvs.pkgver_pkg_seq_id
                                 and pkgver_seq_num = pvs.pkgver_seq_num
                                 and sty_scty_dp_code = 'PKG'
                                 and sty_scty_code = 'STATE'
                             )
                      and pvs.create_date =
                             (select max(create_date)
                                from cms_pkg_ver_statuses a
                               where a.pkgver_pkg_seq_id = pvs.pkgver_pkg_seq_id
                                 and a.pkgver_seq_num = pvs.pkgver_seq_num
                                 and a.sty_scty_dp_code = 'PKG'
                                 and a.sty_scty_code = 'STATE'
                                 and a.create_date =
                                        (select max(create_date)
                                           from cms_pkg_ver_statuses
                                          where pkgver_pkg_seq_id = a.pkgver_pkg_seq_id
                                            and pkgver_seq_num = a.pkgver_seq_num
                                            and sty_scty_dp_code = 'PKG'
                                            and sty_scty_code = 'STATE'
                                        )
                             )
                  ) project_status
                 ,pkg_info.title title
                 ,emp.user_name projleadname
             from pit_pkg_info pkg_info
                 ,emp_person emp
            where pkg_info.projmgr_emp_employee_num = emp.emp_no(+)
              and pkg_info.title like '%AIR%'
            order by pkgid
          ) a
     where rownum <= 100
      ) x
where x.rnum >= 51
;


OPERATION                   OPTIONS         OBJECT_NAME         COST POSITION
--------------------------- --------------- ----------------- ------ --------
SELECT STATEMENT                                                   4        4
  VIEW                                                             4        1
    COUNT                   STOPKEY                                         1
      VIEW                                                         4        1
        NESTED LOOPS        OUTER                                  4        1
          NESTED LOOPS      OUTER                                  3        1
            TABLE ACCESS    BY INDEX ROWID  PIT_PKG_INFO           3        1
              INDEX         RANGE SCAN      PIT_PKG_PKGVER_I       2        1
            INDEX           UNIQUE SCAN     STY_PK                          2
          TABLE ACCESS      BY INDEX ROWID  EMP_PERSON             1        2
            INDEX           UNIQUE SCAN     SYS_C001839                     1

11 rows selected.

Follows are the 9i initial parameters

SQL> show parameters
O7_DICTIONARY_ACCESSIBILITY          boolean     FALSE                          
_trace_files_public                  boolean     TRUE                           
active_instance_count                integer                                    
aq_tm_processes                      integer     0                              
archive_lag_target                   integer     0                              
audit_file_dest                      string      ?/rdbms/audit                  
audit_sys_operations                 boolean     FALSE                          
audit_trail                          string      TRUE                           
background_core_dump                 string      partial                        
background_dump_dest                 string      /u01/app/oracle/admin/U50DAMC/ 
                                                 bdump                          
backup_tape_io_slaves                boolean     FALSE                          
bitmap_merge_area_size               integer     1048576                        
blank_trimming                       boolean     FALSE                          
buffer_pool_keep                     string                                     
buffer_pool_recycle                  string                                     
circuits                             integer     0                              
cluster_database                     boolean     FALSE                          
cluster_database_instances           integer     1                              
cluster_interconnects                string                                     
commit_point_strength                integer     1                              
compatible                           string      9.2.0.0                        
control_file_record_keep_time        integer     3                              
control_files                        string      /np70/oradata/U50DAMC/cr1/cont 
                                                 rol01.ctl, /np70/oradata/U50DA 
                                                 MC/cr2/control02.ctl, /np70/or 
                                                 adata/U50DAMC/cr3/control03.ct 
                                                 l                              
core_dump_dest                       string      /u01/app/oracle/admin/U50DAMC/ 
                                                 cdump                          
cpu_count                            integer     4                              
create_bitmap_area_size              integer     8388608                        
cursor_sharing                       string      EXACT                          
cursor_space_for_time                boolean     FALSE                          
db_16k_cache_size                    big integer 0                              
db_2k_cache_size                     big integer 0                              
db_32k_cache_size                    big integer 0                              
db_4k_cache_size                     big integer 0                              
db_8k_cache_size                     big integer 0                              
db_block_buffers                     integer     6000                           
db_block_checking                    boolean     FALSE                          
db_block_checksum                    boolean     TRUE                           
db_block_size                        integer     8192                           
db_cache_advice                      string      OFF                            
db_cache_size                        big integer 0                              
db_create_file_dest                  string                                     
db_create_online_log_dest_1          string                                     
db_create_online_log_dest_2          string                                     
db_create_online_log_dest_3          string                                     
db_create_online_log_dest_4          string                                     
db_create_online_log_dest_5          string                                     
db_domain                            string      lgb.ams.boeing.com             
db_file_multiblock_read_count        integer     8                              
db_file_name_convert                 string                                     
db_files                             integer     1024                           
db_keep_cache_size                   big integer 0                              
db_name                              string      U50DAMC                        
db_recycle_cache_size                big integer 0                              
db_writer_processes                  integer     4                              
dblink_encrypt_login                 boolean     FALSE                          
dbwr_io_slaves                       integer     0                              
dg_broker_config_file1               string      ?/dbs/dr1@.dat                 
dg_broker_config_file2               string      ?/dbs/dr2@.dat                 
dg_broker_start                      boolean     FALSE                          
disk_asynch_io                       boolean     TRUE                           
dispatchers                          string                                     
distributed_lock_timeout             integer     60                             
dml_locks                            integer     800                            
drs_start                            boolean     FALSE                          
enqueue_resources                    integer     2389                           
event                                string                                     
fal_client                           string                                     
fal_server                           string                                     
fast_start_io_target                 integer     0                              
fast_start_mttr_target               integer     0                              
fast_start_parallel_rollback         string      LOW                            
file_mapping                         boolean     FALSE                          
filesystemio_options                 string      asynch                         
fixed_date                           string                                     
gc_files_to_locks                    string                                     
global_context_pool_size             string                                     
global_names                         boolean     FALSE                          
hash_area_size                       integer     10000000                       
hash_join_enabled                    boolean     TRUE                           
hi_shared_memory_address             integer     0                              
hpux_sched_noage                     integer     0                              
hs_autoregister                      boolean     TRUE                           
ifile                                file                                       
instance_groups                      string                                     
instance_name                        string      U50DAMC                        
instance_number                      integer     0                              
java_max_sessionspace_size           integer     0                              
java_pool_size                       big integer 50331648                       
java_soft_sessionspace_limit         integer     0                              
job_queue_processes                  integer     4                              
large_pool_size                      big integer 16777216                       
license_max_sessions                 integer     0                              
license_max_users                    integer     0                              
license_sessions_warning             integer     0                              
local_listener                       string                                     
lock_name_space                      string                                     
lock_sga                             boolean     FALSE                          
log_archive_dest                     string                                     
log_archive_dest_1                   string      location=/np70/oradata/U50DAMC 
                                                 /arch MANDATORY REOPEN=60      
log_archive_dest_10                  string                                     
log_archive_dest_2                   string      location=/u01/app/oracle/admin 
                                                 /altarch/U50DAMC OPTIONAL      
log_archive_dest_3                   string                                     
log_archive_dest_4                   string                                     
log_archive_dest_5                   string                                     
log_archive_dest_6                   string                                     
log_archive_dest_7                   string                                     
log_archive_dest_8                   string                                     
log_archive_dest_9                   string                                     
log_archive_dest_state_1             string      enable                         
log_archive_dest_state_10            string      enable                         
log_archive_dest_state_2             string      defer                          
log_archive_dest_state_3             string      defer                          
log_archive_dest_state_4             string      defer                          
log_archive_dest_state_5             string      defer                          
log_archive_dest_state_6             string      enable                         
log_archive_dest_state_7             string      enable                         
log_archive_dest_state_8             string      enable                         
log_archive_dest_state_9             string      enable                         
log_archive_duplex_dest              string                                     
log_archive_format                   string      U50DAMC_%T_%S.ARC              
log_archive_max_processes            integer     2                              
log_archive_min_succeed_dest         integer     1                              
log_archive_start                    boolean     TRUE                           
log_archive_trace                    integer     0                              
log_buffer                           integer     1048576                        
log_checkpoint_interval              integer     10000                          
log_checkpoint_timeout               integer     1800                           
log_checkpoints_to_alert             boolean     FALSE                          
log_file_name_convert                string                                     
log_parallelism                      integer     1                              
logmnr_max_persistent_sessions       integer     1                              
max_commit_propagation_delay         integer     700                            
max_dispatchers                      integer     5                              
max_dump_file_size                   string      10240K                         
max_enabled_roles                    integer     148                            
max_rollback_segments                integer     40                             
max_shared_servers                   integer     20                             
mts_circuits                         integer     0                              
mts_dispatchers                      string                                     
mts_listener_address                 string                                     
mts_max_dispatchers                  integer     5                              
mts_max_servers                      integer     20                             
mts_multiple_listeners               boolean     FALSE                          
mts_servers                          integer     0                              
mts_service                          string      U50DAMC                        
mts_sessions                         integer     0                              
nls_calendar                         string                                     
nls_comp                             string                                     
nls_currency                         string                                     
nls_date_format                      string                                     
nls_date_language                    string                                     
nls_dual_currency                    string                                     
nls_iso_currency                     string                                     
nls_language                         string      AMERICAN                       
nls_length_semantics                 string      BYTE                           
nls_nchar_conv_excp                  string      FALSE                          
nls_numeric_characters               string                                     
nls_sort                             string                                     
nls_territory                        string      AMERICA                        
nls_time_format                      string                                     
nls_time_tz_format                   string                                     
nls_timestamp_format                 string                                     
nls_timestamp_tz_format              string                                     
object_cache_max_size_percent        integer     10                             
object_cache_optimal_size            integer     102400                         
olap_page_pool_size                  integer     33554432                       
open_cursors                         integer     500                            
open_links                           integer     100                            
open_links_per_instance              integer     4                              
optimizer_dynamic_sampling           integer     0                              
optimizer_features_enable            string      8.1.7                          
optimizer_index_caching              integer     0                              
optimizer_index_cost_adj             integer     100                            
optimizer_max_permutations           integer     80000                          
optimizer_mode                       string      CHOOSE                         
oracle_trace_collection_name         string                                     
oracle_trace_collection_path         string      ?/otrace/admin/cdf             
oracle_trace_collection_size         integer     5242880                        
oracle_trace_enable                  boolean     FALSE                          
oracle_trace_facility_name           string      oracled                        
oracle_trace_facility_path           string      ?/otrace/admin/fdf             
os_authent_prefix                    string      ops_                           
os_roles                             boolean     FALSE                          
parallel_adaptive_multi_user         boolean     FALSE                          
parallel_automatic_tuning            boolean     FALSE                          
parallel_execution_message_size      integer     2152                           
parallel_instance_group              string                                     
parallel_max_servers                 integer     5                              
parallel_min_percent                 integer     0                              
parallel_min_servers                 integer     0                              
parallel_server                      boolean     FALSE                          
parallel_server_instances            integer     1                              
parallel_threads_per_cpu             integer     2                              
partition_view_enabled               boolean     FALSE                          
pga_aggregate_target                 big integer 25165824                       
plsql_compiler_flags                 string      INTERPRETED                    
plsql_native_c_compiler              string                                     
plsql_native_library_dir             string                                     
plsql_native_library_subdir_count    integer     0                              
plsql_native_linker                  string                                     
plsql_native_make_file_name          string                                     
plsql_native_make_utility            string                                     
plsql_v2_compatibility               boolean     FALSE                          
pre_page_sga                         boolean     FALSE                          
processes                            integer     600                            
query_rewrite_enabled                string      false                          
query_rewrite_integrity              string      enforced                       
rdbms_server_dn                      string                                     
read_only_open_delayed               boolean     FALSE                          
recovery_parallelism                 integer     0                              
remote_archive_enable                string      true                           
remote_dependencies_mode             string      TIMESTAMP                      
remote_listener                      string                                     
remote_login_passwordfile            string      EXCLUSIVE                      
remote_os_authent                    boolean     FALSE                          
remote_os_roles                      boolean     FALSE                          
replication_dependency_tracking      boolean     TRUE                           
resource_limit                       boolean     FALSE                          
resource_manager_plan                string                                     
rollback_segments                    string      r01, r02, r03, r04, r05, r06,  
                                                 r07, r08                       
row_locking                          string      always                         
serial_reuse                         string      DISABLE                        
serializable                         boolean     FALSE                          
service_names                        string      U50DAMC.lgb.ams.boeing.com     
session_cached_cursors               integer     0                              
session_max_open_files               integer     10                             
sessions                             integer     665                            
sga_max_size                         big integer 386756664                      
shadow_core_dump                     string      partial                        
shared_memory_address                integer     0                              
shared_pool_reserved_size            big integer 10066329                       
shared_pool_size                     big integer 201326592                      
shared_server_sessions               integer     0                              
shared_servers                       integer     0                              
sort_area_retained_size              integer     5000000                        
sort_area_size                       integer     5000000                        
spfile                               string                                     
sql92_security                       boolean     FALSE                          
sql_trace                            boolean     FALSE                          
sql_version                          string      NATIVE                         
standby_archive_dest                 string      ?/dbs/arch                     
standby_file_management              string      MANUAL                         
star_transformation_enabled          string      FALSE                          
statistics_level                     string      TYPICAL                        
tape_asynch_io                       boolean     TRUE                           
thread                               integer     0                              
timed_os_statistics                  integer     0                              
timed_statistics                     boolean     TRUE                           
trace_enabled                        boolean     TRUE                           
tracefile_identifier                 string                                     
transaction_auditing                 boolean     TRUE                           
transactions                         integer     200                            
transactions_per_rollback_segment    integer     5                              
undo_management                      string      MANUAL                         
undo_retention                       integer     900                            
undo_suppress_errors                 boolean     FALSE                          
undo_tablespace                      string                                     
use_indirect_data_buffers            boolean     FALSE                          
user_dump_dest                       string      /u01/app/oracle/admin/U50DAMC/ 
                                                 udump                          
utl_file_dir                         string                                     
workarea_size_policy                 string      AUTO                           

Hope you can spot something out of these

Best Regards,
Thomas
 


Followup   February 12, 2004 - 8am Central time zone:

I dont like your DBA's then.   Really, they are preventing everyone from doing *their job*.  
arg..... 


anyway, that plan "looks dandy" -- it looks like it would get first rows first.

We really need to "compare" plans.

Can you at least get an autotrace traceonly explain out of 8i (or at least an explain plan)

can you tell me "how fast it was in 8i", "how slow it is in 9i" and are the machines you are 
testing on even remotely similar. 

5 stars Very handy   March 8, 2004 - 11am Central time zone
Reviewer: Sajid Anwar from London
Hi Tom,
Just simple one about your SPECIAL QUERY for paging. I am using your method for paging.

select *
  from ( select a.*, rownum rnum
           from ( select * from t ) a
          where rownum <= 5
       ) b
 where rnum >= 2;

This gives me everything plus one extra column rnum that I dont want. How do I get rid of it in the 
same query?


Many thanks in advance.

Regards,
Sajid
 


Followup   March 8, 2004 - 2pm Central time zone:

besides just selecting the columns you want in the outer wrapper?  nothing


select a, b, c, d
  from ......

instead of select * 

3 stars Regarding post from "T Truong from Long Beach, CA"   March 8, 2004 - 6pm Central time zone
Reviewer: Matt from Australia
Tom,

There are various ways of getting access to trace files on the server, some of which have been 
documented on this site.

However, once the developer has the raw trace they need to access TKPROF. As far as I am aware this 
is not part of the default Oracle client distribution. Installing the DB on each desktop would be 
nice but an administrators nightmare.

As there any licencing restrictions that might prevent copying the required libraries and 
executables for tkprof and placing these on a desktop for developer use? I tested this (though not 
exhaustively) and it appears to work.

Do you see any problems with this approach?

Cheers,
 


Followup   March 9, 2004 - 10am Central time zone:

why an admins nightmare?  developers cannot install software? 


i'm not aware of any issues with getting people access to the code they have licensed. 

5 stars how do I display rows with out using /* + first_rows */ hint ?   March 9, 2004 - 11am Central time zone
Reviewer: A reader 
Hi tom we use
saveral applications to brows the data from oracle.
one of them is toad. 

 They show a part of the data as soon as it is available on the screen and don't wait for the 
complete result set to be returned. 


  I checked the v$sql, v$sqlarea there is not any stmt with the first rows hint . It show the exact 
sql stmt that we "user" passed. How can I do that in my custom application ? I don't what to wait 
for 4k record and then show it to user, I need first rows hint functinality with out changing the 
stmt ? possible ? how ? is pagging involded ? 

and yes we are using Java 1.4 + classes12.jar 
and to display results, we use JTable 



 


Followup   March 9, 2004 - 3pm Central time zone:

you can alter your session to set the optimizer goal if you like. 

3 stars Response to Tom   March 9, 2004 - 5pm Central time zone
Reviewer: Matt from Australia
>> Why an admins nightmare?  developers cannot install software? 

I intending to create a developer/administrator divide here - software development is a team 
effort. Of course developers can install software however, I would prefer that everyone runs the 
same patched version of the DB and managing this when there are multiple desktop DB's I see as 
being problematic.

>>i'm not aware of any issues with getting people access to the code they have licensed. 

This is the issue, I guess. Is a patched desktop version of the DB that is used for development 
"licenced for development" (ie: free) or is there a licence payment required? 

I understand that the "standalone" tkprof might fall into a different category. But, if a patched 
desktop version may be licenced for development, I don't see an issue.

Ta. 


Followup   March 9, 2004 - 10pm Central time zone:

i don't know what you mean by a patched desktop version? 

5 stars Very Useful   March 16, 2004 - 5am Central time zone
Reviewer: shabana 
I had problems populating large result sets. The Query helped me in fetching the needed rows 
keeping a page count from web tier 


5 stars "order by"   April 1, 2004 - 6pm Central time zone
Reviewer: A reader 
hi tom
"select * 
  from ( select a.*, rownum rnum
           from ( YOUR_QUERY_GOES_HERE -- including the order by ) a
          where rownum <= MAX_ROWS )
 where rnum >= MIN_ROWS
/
"

Does not the order by force you to go through the
entire rows anyways = which is pretty much
the same overhead as a "count(*)" thus defeating
the purpose? And I think in most cases, users
do want to sort their result in some order.
(Also in order by case the FIRST_ROWS hint also
is useless...
thanx! 


Followup   April 2, 2004 - 9am Central time zone:

No it doesn't 


think "index"


also, using rownum with the order by trick above has special top-n optimizations, so in the case 
where it would have to get the entire result set -- it is much more efficient then asking Oracle to 
generate the result set and just fetching the first N rows (using the rownum kicks in a special 
top-n optimization)


This is NOT like count(*).  count(*) is something we can definitely 100% live without and would 
force the entire result set to be computed (most of the times, we don't need to get the entire 
result set here!)

ORDER BY is something you cannot live without -- we definitely 100% need it in many cases. 

5 stars thanx!   April 2, 2004 - 10am Central time zone
Reviewer: A reader 
I tried it out myself and I am getting the results
that you say. I believe the "count stopkey" indicates
the rownum based top n optimization you talked
about (t1 is a copy of all_objects with some 30,000
rows one index on all columns of t1 being selected.)
select * from
(
  select /*+ FIRST_ROWS */ a.*, rownum rnum
  from
  (
    /* OUR QUERY */
    select owner, object_name, object_type, rownum
    from t1
    where owner = 'PUBLIC'
    order by owner, object_name, object_type
  ) a
  where rownum <= 10
)
where rnum >= 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.02       0.02          0          0          0           0
Execute      1      0.01       0.00          0          0          0           0
Fetch        2      0.00       0.00          0          4          0          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.03       0.02          0          4          0          10

Misses in library cache during parse: 1
Optimizer mode: FIRST_ROWS
Parsing user id: 64

Rows     Row Source Operation
-------  ---------------------------------------------------
     10  VIEW  (cr=4 pr=0 pw=0 time=486 us)
     10   COUNT STOPKEY (cr=4 pr=0 pw=0 time=427 us)
     10    VIEW  (cr=4 pr=0 pw=0 time=373 us)
     10     COUNT  (cr=4 pr=0 pw=0 time=309 us)
     10      INDEX RANGE SCAN OBJ#(56907) (cr=4 pr=0 pw=0 time=270 us)(object id 56907)

Thanx!!!!

 


Followup   April 2, 2004 - 1pm Central time zone:

it also applies in sorting unindexed stuff as well (full details in expert one on one)

basically if you 

select * 
  from ( select * from really_huge_table order by something_not_indexed )
 where rownum < 10

oracle will get the first record and put it into slot 1 in a result set

it'll get the second record and if that is less than the one in one, it'll push it down to two and 
put this in one, else this goes into two

and so on for the first 10 records -- we now have 10 sorted records -- now it'll get the 11th and 
either

a) the 11th exceeds the one in the 10th slot -- this new record is discarded
b) the 11th is less than one of the existing 10 -- the current 10th goes away and this gets stuffed 
in there.


lots more efficient to sort the top N, than it would be to sort the entire result set into temp, 
merge it all back together -- just to fetch the first 10... 

5 stars wow!   April 2, 2004 - 1pm Central time zone
Reviewer: A reader 
awesome - thanx a lot!!! ( not sure if you are on 
vacation or is this your idea of vacation?;))

Regards 


5 stars i think   April 2, 2004 - 2pm Central time zone
Reviewer: A reader 
"(full details in expert one 
on one)"
You meant effective oracle by design (page 502)
thanx!

 


Followup   April 2, 2004 - 3pm Central time zone:

doh, you are right. 

5 stars so here is the second test (without indexes)   April 2, 2004 - 2pm Central time zone
Reviewer: A reader 
thought I would share with others since I anyways 
ran it.
------schema 
spool s3
set echo on
drop table t2;
create table t2
as select owner, object_name, object_type
from all_objects;
insert into t2
select * from t2;
commit;
 
analyze table t2 compute statistics for table for all indexes for all
indexed columns;
-------------------
notice we have no indexes created
--------- selects ran - one with rownum and one without 

set termout off
alter session set timed_statistics=true;
alter session set events '10046 trace name context forever, level 12';
select * from
(
  select /*+ FIRST_ROWS */ a.*, rownum rnum
  from
  (  
    /* OUR QUERY  ROWNUM ABSENT */
    select owner, object_name, object_type, rownum
    from t2
    where owner = 'PUBLIC'
    order by object_name
  ) a
);
  
select * from
(
  select /*+ FIRST_ROWS */ a.*, rownum rnum
  from
  (  
    /* OUR QUERY  ROWNUM PRESENT */
    select owner, object_name, object_type, rownum
    from t2
    where owner = 'PUBLIC'
    order by object_name
  ) a
  where rownum <= 10
)
where rnum >= 1;

  --------tkprof results----
-- FIRST CASE - ROWNUM ABSENT
select * from
(
  select /*+ FIRST_ROWS */ a.*, rownum rnum
  from
  (
    /* OUR QUERY  ROWNUM ABSENT */
    select owner, object_name, object_type, rownum
    from t2
    where owner = 'PUBLIC'
    order by object_name
  ) a
)
 
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch     2528      2.02       4.10        515        500          7       37904
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     2530      2.02       4.10        515        500          7       37904
 
Misses in library cache during parse: 0
Optimizer mode: FIRST_ROWS
Parsing user id: 64  
 
Rows     Row Source Operation
-------  ---------------------------------------------------
  37904  VIEW  (cr=500 pr=515 pw=515 time=3587213 us)
  37904   COUNT  (cr=500 pr=515 pw=515 time=3413040 us)
  37904    VIEW  (cr=500 pr=515 pw=515 time=3295049 us)
  37904     SORT ORDER BY (cr=500 pr=515 pw=515 time=3144606 us)
  37904      COUNT  (cr=500 pr=0 pw=0 time=613868 us)
  37904       TABLE ACCESS FULL T2 (cr=500 pr=0 pw=0 time=281939 us)

--- second case ROWNUM present
select * from
(
  select /*+ FIRST_ROWS */ a.*, rownum rnum
  from
  (
    /* OUR QUERY  ROWNUM PRESENT */
    select owner, object_name, object_type, rownum
    from t2
    where owner = 'PUBLIC'
    order by object_name
  ) a
  where rownum <= 10
)
where rnum >= 1
 
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.33       0.49          0        500          0          10
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.33       0.49          0        500          0          10
 
Misses in library cache during parse: 0
Optimizer mode: FIRST_ROWS
Parsing user id: 64  
 
Rows     Row Source Operation
-------  ---------------------------------------------------
     10  VIEW  (cr=500 pr=0 pw=0 time=495126 us)
     10   COUNT STOPKEY (cr=500 pr=0 pw=0 time=494955 us)
     10    VIEW  (cr=500 pr=0 pw=0 time=494864 us)
     10     SORT ORDER BY STOPKEY (cr=500 pr=0 pw=0 time=494817 us)
  37904      COUNT  (cr=500 pr=0 pw=0 time=262446 us)
  37904       TABLE ACCESS FULL OBJ#(56928) (cr=500 pr=0 pw=0 time=129898 us)
 
 
Elapsed time in first case: 4.10 seconds
Elapsed time in second case (what would be our query) : 0.49 seconds
 
the second option is 8 times faster.
 


5 stars   April 5, 2004 - 1pm Central time zone
Reviewer: A reader from Marlboro, MA
Invaluable information.

thank you Tom. 


2 stars Different question   April 14, 2004 - 6pm Central time zone
Reviewer: Roughing it from Sunnyvale, CA
I have a table with time and place,
  where the place is a single string with city,stateAbbrev
  like SeattleWA
It is indexed by time and has about 10M records.

These queries take no time at all as expected:
    select min(time) from Time_Place;
    select max(time) from Time_Place;

But if I do:
    select min(time), max(time) from Time_Place;
it takes a looooooong time...

What I really want is:
    select max(time) from Time_Place
       where place like '%CA';

If it started searching at the end, it would find it very quickly.  It's not finding it quickly.  
It's appearing to search all the records.

Is there a way to speed this up?
Or must I keep a list of last times per state and do
  select max(time) from Time_Place
  where time>=(select last time from Last_per_state
               where state='CA')
        and place like '%CA';

Thanks,
-r 


Followup   April 15, 2004 - 8am Central time zone:

Ok, two things here -- select min/max and how to make that query on data stored "not correctly" (it 
should have been two fields!!!) go fast.

max/min first.  big_table is 1,000,000 rows on my system, if we:

big_table@ORA9IR2> set autotrace on
big_table@ORA9IR2> select min(created) from big_table;
 
MIN(CREAT
---------
12-MAY-02
 
 
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=7)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (FULL SCAN (MIN/MAX)) OF 'BT_IDX_CREATED' (NON-UNIQUE) (Cost=3 Card=1000000 
Bytes=7000000)
 
that used index full scan (min/max) -- it knew it could read the index head or tail and be done, 
very efficient:
 
 
Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          3  consistent gets
          0  physical reads
          0  redo size
        388  bytes sent via SQL*Net to client
        499  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed
 
big_table@ORA9IR2>
big_table@ORA9IR2> select max(created) from big_table;
 
MAX(CREAT
---------
28-NOV-03
 
 
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=7)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (FULL SCAN (MIN/MAX)) OF 'BT_IDX_CREATED' (NON-UNIQUE) (Cost=3 Card=1000000 
Bytes=7000000)
 
 
 
 
Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          3  consistent gets
          0  physical reads
          0  redo size
        388  bytes sent via SQL*Net to client
        499  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed
 
same there, but:

big_table@ORA9IR2>
big_table@ORA9IR2> select min(created), max(created) from big_table;
 
MIN(CREAT MAX(CREAT
--------- ---------
12-MAY-02 28-NOV-03
 
 
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=257 Card=1 Bytes=7)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (FAST FULL SCAN) OF 'BT_IDX_CREATED' (NON-UNIQUE) (Cost=257 Card=1000000 
Bytes=7000000)
 
 
 
 
Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       2671  consistent gets
       2656  physical reads
          0  redo size
        456  bytes sent via SQL*Net to client
        499  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

it cannot read the head and the tail 'in the general sense' -- i'll concede in this case, it 
could but in a query with a group by, it could not really.  So -- can we do something?
 
big_table@ORA9IR2>
big_table@ORA9IR2>
big_table@ORA9IR2> select min(created), max(created)
  2    from (
  3  select min(created) created from big_table
  4  union all
  5  select max(created) created from big_table
  6         )
  7  /
 
MIN(CREAT MAX(CREAT
--------- ---------
12-MAY-02 28-NOV-03
 
 
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=1 Bytes=9)
   1    0   SORT (AGGREGATE)
   2    1     VIEW (Cost=6 Card=2 Bytes=18)
   3    2       UNION-ALL
   4    3         SORT (AGGREGATE)
   5    4           INDEX (FULL SCAN (MIN/MAX)) OF 'BT_IDX_CREATED' (NON-UNIQUE) (Cost=3 
Card=1000000 Bytes=70
          00000)
 
   6    3         SORT (AGGREGATE)
   7    6           INDEX (FULL SCAN (MIN/MAX)) OF 'BT_IDX_CREATED' (NON-UNIQUE) (Cost=3 
Card=1000000 Bytes=70
          00000)
 
 
 
 
 
Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          6  consistent gets
          0  physical reads
          0  redo size
        456  bytes sent via SQL*Net to client
        499  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

that shows how to do that.  Now onto that state field stuffed onto the end -- here we have to 
full scan the table (or full scan an index on object_name,created) since EACH ROW must be 
inspected:

big_table@ORA9IR2>
big_table@ORA9IR2> select max(created)
  2    from big_table
  3   where object_name like '%WI';
 
MAX(CREAT
---------
12-MAY-02
 
 
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1379 Card=1 Bytes=24)
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=1379 Card=50000 Bytes=1200000)
 
 
 
 
Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      14338  consistent gets
      14327  physical reads
          0  redo size
        388  bytes sent via SQL*Net to client
        499  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

we can begin by observing that is the same as this, substr() -- get the last two characters:

 
big_table@ORA9IR2>
big_table@ORA9IR2> select max(created)
  2    from big_table
  3   where substr(object_name,length(object_name)-1) = 'WI';
 
MAX(CREAT
---------
12-MAY-02
 
 
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=1379 Card=1 Bytes=24)
   1    0   SORT (AGGREGATE)
   2    1     TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=1379 Card=10000 Bytes=240000)
 
 
 
 
Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      14338  consistent gets
      13030  physical reads
          0  redo size
        388  bytes sent via SQL*Net to client
        499  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed
 
Now we have something to INDEX!

big_table@ORA9IR2>
big_table@ORA9IR2> create index fbi on big_table( substr(object_name,length(object_name)-1), 
created )
  2  compute statistics;
 
Index created.
 
big_table@ORA9IR2>
big_table@ORA9IR2> select max(created)
  2    from big_table
  3   where substr(object_name,length(object_name)-1) = 'WI';
 
MAX(CREAT
---------
12-MAY-02
 
 
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=24)
   1    0   SORT (AGGREGATE)
   2    1     INDEX (RANGE SCAN) OF 'FBI' (NON-UNIQUE) (Cost=3 Card=10000 Bytes=240000)
 
 
 
 
Statistics
----------------------------------------------------------
         29  recursive calls
          0  db block gets
          7  consistent gets
          2  physical reads
          0  redo size
        388  bytes sent via SQL*Net to client
        499  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed
 
big_table@ORA9IR2>
big_table@ORA9IR2> set autotrace off
 

5 stars getting rows N through M of a result set   April 15, 2004 - 5pm Central time zone
Reviewer: Ben Kafka from Provo, UT
Really appreciate finding this info... wish oracle did it
like postgresql but this would have been some work coming up with myself.  Thanks! 


3 stars getting N rows out of 1 row   April 21, 2004 - 2pm Central time zone
Reviewer: Deepak Gulrajani from Sunnyvale, California, USA
The above feedback was useful to return less number of rows from the resultset of the Query.

Tom, Can we write a SQL Statement and return 2 or 3(dynamic)rows into a PL/SQL Table for every 
single row? 


Followup   April 21, 2004 - 9pm Central time zone:

Can we write a SQL Statement and return 2 or 3(dynamic)rows into a PL/SQL 
Table for every single row? 

that doesn't "make sense" to me.  not sure what you mean. 

5 stars getting N rows out of 1 row   April 23, 2004 - 2pm Central time zone
Reviewer: Deepak Gulrajani from Sunnyvale, CA
Tom, Can we achive this with a single SQL in bulk rather than row by row. Sorry my question in the 
previous update was a little vague.Here is the example--
I would like to create rows in table B(2 or 3 depending on value of col3 for every row in Table A, 
i.e if value of col3 = F-2 then I need to create 2 rows in Table B, if value in col3 = F-3 then I 
need to create 3 rows in table B). For Example----

ROW IN TABLE A
-----------------------
col1  col2  col3   col4   col5   col6
----  ----  ----   ----   ----   ----
1    ITEM   F-2   XXX    YYY      15

ROWS IN TABLE B(if col3= F-2)
--------------------------
col1  col2  col3   col4   col5   col6
----  ----  ----   ----   ----   ----
1    ITEM   F-2   XXX    YYY      -15
2    IPV    F-2   XXX    YYY       15

ROWS IN TABLE B(if col3= F-3 then basically the col6 is further split)
--------------------------
col1  col2  col3   col4   col5   col6
----  ----  ----   ----   ----   ----
1    ITEM   F-3   XXX    YYY      -15
2    IPV    F-3   XXX    YYY       12
3    ERV    F-3   XXX    YYY        3

 


Followup   April 23, 2004 - 3pm Central time zone:

if substr( col3,3,1 ) is always a number then:


select a.*
  from a, 
      (select rownum r from all_objects where r <= 10) x
 where x.r <= to_number(substr(a.col3,3,1))
/



(adjust r <= 10 to your needs, if 10 isn't "big enough", make it big enough)
 

5 stars getting N rows out of 1 row   April 23, 2004 - 4pm Central time zone
Reviewer: Deepak Gulrajani from Sunnyvale, CA
Tom, Thanks for the prompt and precise reply. --deepak 


5 stars just a tiny fix   April 23, 2004 - 7pm Central time zone
Reviewer: Marcio from Brazil
select a.*
  from a, 
      (select rownum r from all_objects where r <= 10) x
                                        ^^^^^^^^^^^^^
 where x.r <= to_number(substr(a.col3,3,1))
/

instead of where r <= 10 you have where rownum <= 10

ops$marcio@MRP920> select rownum r from all_objects where r <= 10;
select rownum r from all_objects where r <= 10
                                       *
ERROR at line 1:
ORA-00904: "R": invalid identifier

so, you have:

select a.*
  from a, 
      (select rownum r from all_objects where rownum <= 10) x
 where x.r <= to_number(substr(a.col3,3,1))
/
 


Followup   April 23, 2004 - 7pm Central time zone:

thanks, that is correct (every time I put a query up without actually running the darn thing that 
happens :) 

3 stars Selecting nth Row from table by IDNumber   April 26, 2004 - 8am Central time zone
Reviewer: denni50 from na
Hi Tom

I'm developing a Second Gift Analysis Report.
(mgt wants to see the activity of first time donors
 who give second gifts).

The dilemma is I have to go back and start with
donors who gave their 1st gift in November 2003...then
generate a report when they gave their second gift.
Some of the donors may have went on to give 3rd and 4th
gifts through April 2004...however all subsequent gifts
after the second gift need to be excluded from the query.

On my home computer(Oracle 9i) I was able to use:
ROW_NUMBER() OVER(PARTITION BY idnumber ORDER BY giftdate)
as rn....etc to get the results using test data.

At work we don't AF.
Below is the query to find the first
time donors in November 2003. I then inserted those records
into a temp table called SecondGift:

FirstGift Query:

select idnumber,giftdate,giftamount
from gift where idnumber in(select g.idnumber
                from gift g
                where g.usercode1='ACGA'
                and g.giftdate < to_date('01-NOV-2003','DD-MON-YYYY')
                having sum(g.giftamount)=0
                group by g.idnumber)
and giftamount>0
and giftdate between to_date('01-NOV-2003','DD-MON-YYYY')
and to_date('30-NOV-2003','DD-MON-YYYY')

here is the query trying to select second gift donors:
(however it's only selecting idnumbers with count=2.
 A donor may have nth records even though I'm only searching
 for the second giftamount>0)

Second Gift Query
select idnumber,giftdate,giftamount
from gift where idnumber in(select g.idnumber
                 from gift g where g.idnumber in(select s.idnumber
                                  from secondgift s
                                    where s.firstgiftcode='Nov2003')
                 and g.giftamount>0
                 having count(g.giftdate)=2
                 group by g.idnumber)
and giftamount>0

tried using rownum(typically used for TOP n analysis)with that returning only the 2nd row from 8mil 
records.


thanks for any feedback





 


Followup   April 26, 2004 - 9am Central time zone:

perhaps this'll help:

ops$tkyte@ORA9IR2> select * from t;
 
  IDNUMBER GIFTDATE  GIFTAMOUNT
---------- --------- ----------
         1 01-OCT-03         55
         1 01-APR-04         65
         1 02-APR-04         65
         2 01-DEC-03         55
         2 01-APR-04         65
         3 01-OCT-03         55
         3 21-OCT-03         65
 
7 rows selected.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select idnumber,
  2         gf1,
  3         to_date(substr(gf2,1,14),'yyyymmddhh24miss') gd2,
  4         to_number(substr(gf2,15)) ga2
  5    from (
  6  select idnumber,
  7         max(giftdate1) gf1,
  8         min(giftdate2) gf2
  9    from (
 10  select idnumber,
 11         case when giftdate <= to_date('01-nov-2003','dd-mon-yyyy')
 12              then giftdate
 13          end giftdate1,
 14         case when giftdate  > to_date('01-nov-2003','dd-mon-yyyy')
 15              then to_char(giftdate,'yyyymmddhh24miss') || to_char(giftamount)
 16          end giftdate2
 17    from t
 18         )
 19   group by idnumber
 20  having max(giftdate1) is not null and min(giftdate2) is not null
 21         )
 22  /
 
  IDNUMBER GF1       GD2              GA2
---------- --------- --------- ----------
         1 01-OCT-03 01-APR-04         65
 
ops$tkyte@ORA9IR2>
 

5 stars set of rows at a time   April 28, 2004 - 11am Central time zone
Reviewer: pushparaj arulappan from nj,usa
Tom,

In our web application we need to retrieve data from
the database in portion and present to the user by piece meal.

For example, for a search if the query retrieves 100000 
rows , initially we only want to present the user the first 10000 rows and then pick the next 10000 
rows and so on..

The query may be joined with multiple tables.

We use the connection pool and hence we do not want to hold on to the connection for that 
particular user until the user reviews all the 100000 rows. We probably want to disconnect the 
user's connection from the database after 
fetching the the first 10000 rows.

Can you please guide us.

Our database is Oracle9i and weblogic is the application server.

Thanks
Pushparaj 


Followup   April 28, 2004 - 6pm Central time zone:

10,000!!!!!! out of 100,000!!!!! 

are you *kidding*???

google = gold standard for searching

google = 10 hits per page
google = if you try to go to page 100, we'll laugh at you and then say "no"
google = "got it so right"


you need to back off by at least an order of 2 to 3 magnitudes here -- at least.

and then use this query (above) 

5 stars Selecting n rows from tables   May 6, 2004 - 3am Central time zone
Reviewer: Graeme Whitfield from Canberra, Australia
Thanks, this saved me a bucket to time!!! 


3 stars Selecting N rows for each Group   May 21, 2004 - 3pm Central time zone
Reviewer: Mir from Houston, TX - USA
Hi Tom,

How will i write a SQL Query to fetch N rows of every Group. If we take the DEPT, EMP example i 
want to retrive say first 5 rows of EVERY dept.

 


Followup   May 22, 2004 - 11am Central time zone:

select * 
  from ( select ..., ROW_NUMBER() over (PARTITION BY DEPT order by whatever )rn
           from emp )
 where rn <= 5; 

5 stars Thanks Tom, * * * * * invaluable * * * * *   May 24, 2004 - 11am Central time zone
Reviewer: A reader 


5 stars Thanks Tom, * * * * * invaluable * * * * *   May 24, 2004 - 11am Central time zone
Reviewer: A reader 


4 stars I need help about how to paginate   May 30, 2004 - 4pm Central time zone
Reviewer: Fernando Sanchez from Madrid, Spain
I had never had to work with these kind of things and I quite lost.

An application is asking me any page of any size from a table an it is taking too long. I think the 
problem is because of the pagination.

This is an example of what they ask me, returns 10 rows out of 279368 (it is taking 00:01:117.09)

  select *
    from (select a.*, rownum rnum
            from (select env.CO_MSDN_V, env.CO_IMSI_V, sms.CO_TEXT_V, env.CO_MSC_V, 
per.DS_PER_CLT_V, env.CO_REIN_N, TO_CHAR(env.FX_FECH_ENV_D, 'DD/MM/YYYY HH24:MI:SS'), 
TO_CHAR(env.FX_RCP_IAS_D, 'DD/MM/YYYY HH24:MI:SS')
               from ir_tb_env_clts env, IR_CT_SMS sms, IR_CT_PER_CLT per
                   where env.CO_SMS_N = sms.CO_SMS_N(+)
                and env.CO_PER_CLT_N = per.CO_PER_CLT_N(+)
                   order by env.SQ_ECL_01, env.CO_MSDN_V, env.CO_SMS_N, env.FX_FECH_ENV_D) a
      where rownum <= 100510)
   where rnum >= 100501;



Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=28552 Card=136815 Bytes=28867965)

   1    0   VIEW (Cost=28552 Card=136815 Bytes=28867965)
   2    1     COUNT (STOPKEY)
   3    2       VIEW (Cost=28552 Card=136815 Bytes=27089370)
   4    3         SORT (ORDER BY STOPKEY) (Cost=28552 Card=136815 Bytes=35435085)

   5    4           HASH JOIN (OUTER) (Cost=3016 Card=136815 Bytes=35435085)

   6    5             MERGE JOIN (OUTER) (Cost=833 Card=136815 Bytes=26542110)

   7    6               SORT (JOIN)
   8    7                 PARTITION RANGE (ALL)
   9    8                   TABLE ACCESS (FULL) OF 'IR_TB_ENV_CLTS' (Cost=829 Card=136815 
Bytes=13544685)

  10    6               SORT (JOIN) (Cost=3 Card=82 Bytes=7790)
  11   10                 TABLE ACCESS (FULL) OF 'IR_CT_SMS' (Cost=1 Card=82 Bytes=7790)

  12    5             TABLE ACCESS (FULL) OF 'IR_CT_PER_CLT' (Cost=1 Card=82 Bytes=5330)





Statistics
----------------------------------------------------------
       5481  recursive calls
       2443  db block gets
       3548  consistent gets
      57938  physical reads
     107572  redo size
       1580  bytes sent via SQL*Net to client
        426  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          1  sorts (memory)
          2  sorts (disk)
         10  rows processed


Any advice will be helful for me.


Thanks in advance.

 


Followup   May 31, 2004 - 12pm Central time zone:

think about what has to take place here -- 

either: 

at least the first 100,000 plus rows would have to retrieve via an index (very painfully slow to go 
row by row) and then the 10 you want would be returned

or

the entire result is gotten as fast as possible and sorted and then the 10 you want are returned.

there will be nothing "speedy" about this.  Ask the developer to give you the business case that 
would actually necessitate going beyond say the first 100 rows (first 10 pages of a result set).  
Ask them to find a single search engine on the web (say like google) that lets you goto "row 
100,000 out of lots of rows".   You won't find one.

I believe the application has got it "wrong" here.  Who would have the 

a) patience to hit next page 1,000 times to get to this page?
b) the *NEED* to goto page 1,000



 

3 stars partially solved   May 30, 2004 - 5pm Central time zone
Reviewer: Fernando Sanchez from Madrid, Spain
The biggest problem was the joins in the most inside query 

select env.CO_MSDN_V, sms.CO_TEXT_V, env.CO_MSC_V,  per.DS_PER_CLT_V, env.CO_REIN_N, 
TO_CHAR(env.FX_FECH_ENV_D, 'DD/MM/YYYY HH24:MI:SS'), TO_CHAR(env.FX_RCP_IAS_D, 'DD/MM/YYYY 
HH24:MI:SS')
  from (select a.*, rownum rnum
        from (select *
               from ir_tb_env_clts
              order by SQ_ECL_01, CO_MSDN_V, CO_SMS_N, FX_FECH_ENV_D) a
              where rownum <= 100510) env, IR_CT_SMS sms, IR_CT_PER_CLT per
 where env.CO_SMS_N = sms.CO_SMS_N(+)
   and env.CO_PER_CLT_N = per.CO_PER_CLT_N(+)
   and env.rnum >= 100501

takes only about 11 seconds, I sure there are more things I could do,

Appart from that, isn't there a more standard way of returning pages of a table to an application ?

Thanks again.
 


Followup   May 31, 2004 - 12pm Central time zone:

guess what -- rownum is assigned BEFORE order by is done.

what you have done is:

a) gotten the first 100510 rows
b) sorted them
c) joined them  (possibly destroying the sorted order, most likely)
d) returned the "last ten" in some random order.

In short -- you have not returned "rows N thru M", so fast=true this is *not*

You can try something like this.  the goal with "X" is to get the 10 rowids *after sorting* (so 
there better be an index on the order by columns AND one of the columns better be NOT NULL in the 
data dictionary).

Once we get those 10 rows (and that'll take as long as it takes to range scan that index from the 
START to the 100,000+ plus row -- that'll be some time), we'll join to the table again to pick up 
the rows we want and outer join to SMS and PER.  

select /*+ FIRST_ROWS */
       env.CO_MSDN_V,
       sms.CO_TEXT_V,
       env.CO_MSC_V,
       per.DS_PER_CLT_V,
       env.CO_REIN_N,
       TO_CHAR(env.FX_FECH_ENV_D, 'DD/MM/YYYY HH24:MI:SS'),
       TO_CHAR(env.FX_RCP_IAS_D, 'DD/MM/YYYY HH24:MI:SS')
  from (select /*+ FIRST_ROWS */ rid
          from (select /*+ FIRST_ROWS */ a.*, rownum rnum
                  from (select /*+ FIRST_ROWS */ rowid rid
                          from ir_tb_env_clts
                         order by SQ_ECL_01, CO_MSDN_V, CO_SMS_N, FX_FECH_ENV_D
                       ) a
                 where rownum <= :n
               )
         where r >= :m
       ) X,
       ir_tb_env_clts env
       IR_CT_SMS sms,
       IR_CT_PER_CLT per
 where env.rowid = x.rid
   and env.CO_SMS_N = sms.CO_SMS_N(+)
   and env.CO_PER_CLT_N = per.CO_PER_CLT_N(+)
 order by env.SQ_ECL_01, env.CO_MSDN_V, env.CO_SMS_N, env.FX_FECH_ENV_D
/

and if the outer join is causing us issues we can:

select /*+ FIRST_ROWS */
       env.CO_MSDN_V,
       (select CO_TEXT_V
          from ir_ct_sms sms
         where env.CO_SMS_N = sms.CO_SMS_N),
       env.CO_MSC_V,
       (select DS_PER_CLT_V
          from IR_CT_PER_CLT
         where  env.CO_PER_CLT_N = per.CO_PER_CLT_N ),
       env.CO_REIN_N,
       TO_CHAR(env.FX_FECH_ENV_D, 'DD/MM/YYYY HH24:MI:SS'),
       TO_CHAR(env.FX_RCP_IAS_D, 'DD/MM/YYYY HH24:MI:SS')
  from (select /*+ FIRST_ROWS */ rid
          from (select /*+ FIRST_ROWS */ a.*, rownum rnum
                  from (select /*+ FIRST_ROWS */ rowid rid
                          from ir_tb_env_clts
                         order by SQ_ECL_01, CO_MSDN_V, CO_SMS_N, FX_FECH_ENV_D
                       ) a
                 where rownum <= :n
               )
         where r >= :m
       ) X,
       ir_tb_env_clts env
 where env.rowid = x.rid
 order by env.SQ_ECL_01, env.CO_MSDN_V, env.CO_SMS_N, env.FX_FECH_ENV_D
/


assuming SMS and PER are "optional 1 to 1 relations with ENV" -- if they are not -- then your query 
above really returns "randomness" since it would get 10 random rows -- and then turn them into N 
random rows....
 

4 stars Insert to a file   June 10, 2004 - 10am Central time zone
Reviewer: A reader 
I have a partitioned table. Each Partition has around 5 million rows.I need to load a single 
partition data to a file but in batches of say 10.So each set will be of around 500,000 rows.
What is the best most efficient way to do that.
I was thinking of using your query to get m thru n,parametize it and in a loop use utl file 
package.
Any suggestions or any alternative approach ? 


Followup   June 10, 2004 - 5pm Central time zone:

no, you would have a single query:

select * from table t partition(p);

and array fetch from it 10 rows at a time.  do not even consider "paging" thru it, do not even 
CONSIDER it.



sqlplus can do this.
see 
http://asktom.oracle.com/~tkyte/flat

4 stars Insert to a file DB version 9.2   June 10, 2004 - 10am Central time zone
Reviewer: A reader 
forgot the DB version.
Thanx 


4 stars Insert to a file DB version 9.2 some Clarification   June 10, 2004 - 5pm Central time zone
Reviewer: A reader 
Thanx for your response.
When you say"select * from table t partition(p);

and array fetch from it 10 rows at a time.  do not even consider "paging" thru 
it, do not even CONSIDER it.
"

1) by array fetch Do you mean a bulk collect with limit clause ?
Will a cursor be able to  handle 2 million row set with the limit set to 500,000 so it will be 10 
such sets.

2) Can I load these sets of 500,000 to a different external table each time instead of using 
utl_file.
Will it be better.
3) Is it possible to use insert /*+append*/ into external table like insert /*+append*/  select 
..batch of 500,000 for each set.

Thanx

 


Followup   June 10, 2004 - 8pm Central time zone:

1) if you were to do this in plsql - yes, but i would recommend either sqlplus or proc (see that 
url)


2) in 10g, yes, in 9i -- no, you cannot "create" an external table as select.

3) you cannot insert into an external table. 

4 stars Insert to a file DB version 9.2 some Clarification :to add   June 10, 2004 - 5pm Central time zone
Reviewer: A reader 
As I need data in 10 different files of 500,000 rows each 


Followup   June 10, 2004 - 8pm Central time zone:

I'd use C if you could (code is written pretty much if you are on unix -- the code is written as 
you could array_flat | split ) 

4 stars Breaks in Dates   June 13, 2004 - 11pm Central time zone
Reviewer: Saar from UAE
Tom,

I have 2 tables and the structure is enclosed...


Table 1 : COST_BREAKS

Structure :

from_date    date,
to_date        date,
cost        number(13,2)

Data :

From_Date    To_Date        Cost
01/04/2004    19/06/2004    800
20/06/2004    31/07/2004    1100
01/08/2004    31/03/2005    900


Table 2 : PRICE_BREAKS

Structure :

From_Date    date,
To_Date        date,
Price        Number(13,2)


Data

From_Date    To_Date        Price
02/05/2004    22/06/2004    1450
01/06/2004    15/07/2004    1750
16/07/2004    31/03/2005    1650



Output after combining the two table values with date breaks...

The breaks up from_date(01/04/2004) & To_Date(31/10/2004) will be passed as 
parameter and should get the datebreaks along with cost and price like this.

Output :-


From_Date    To_Date        Price    Cost
---------    -------        ----    -----
01/04/2004    01/05/2004    Nil    800
02/05/2004    31/05/2004    1450    800
01/06/2004    19/06/2004    1450    800
20/06/2004    22/06/2004    1450    1100
23/06/2004    15/07/2004    1750    1100
16/07/2004    31/07/2004    1650    1100
01/08/2004    31/08/2004    1650    900


Ur advice will be valuable 


Followup   June 14, 2004 - 7am Central time zone:

I ignored this on the other page (what this had to do with export, I'll never figure out)

but since you put it here as well, I feel compelled to point out something.

Maybe anyone else reading this can help *me* out and let me know how this could be more clear:

http://asktom.oracle.com/pls/ask/f?p=4950:9:::NO:9:F4950_P9_DISPLAYID:127412348064
this "followup" neither 

a) applies to the original question
b) supplies the basic information required (create table, inserts)

I'm at a loss as to how to make it "more clear"? 

3 stars   June 14, 2004 - 9am Central time zone
Reviewer: Saar from uae
Create Table cost_breaks
( cost_id      Number,
  from_date    date,
  to_date      date,
  cost         number(13,2)
);


Insert Into cost_breaks Values (120,to_date('01-APR-04'),to_date('19-JUN-04'),800);
Insert Into cost_breaks Values (121,to_date('20-JUN-04'),to_date('31-JUL-04'),1100);
Insert Into cost_breaks Values (122,to_date('01-AUG-04'),to_date('31-MAR-05'),900);

Create Table price_breaks
( price_id     Number,
  from_date    date,
  to_date      date,
  cost         number(13,2)
);

Insert Into price_breaks Values (131,to_date('02-MAY-04'),to_date('22-JUN-04'),1450);
Insert Into price_breaks Values (132,to_date('01-JUN-04'),to_date('15-JUL-04'),750);
Insert Into price_breaks Values (133,to_date('16-JUL-04'),to_date('31-MAR-05'),1650);


COMMIT;

------------------------------------------------------------------------------------

SQL> SELECT * FROM COST_BREAKS;

   COST_ID FROM_DATE   TO_DATE                COST
---------- ----------- ----------- ---------------
       120 01/04/2004  19/06/2004           800.00
       121 20/06/2004  31/07/2004          1100.00
       122 01/08/2004  31/03/2005           900.00

SQL> SQL> SELECT * FROM PRICE_BREAKS;

  PRICE_ID FROM_DATE   TO_DATE                COST
---------- ----------- ----------- ---------------
       131 02/05/2004  22/06/2004          1450.00
       132 01/06/2004  15/07/2004           750.00
       133 16/07/2004  31/03/2005          1650.00
       

I have To pass 2 dateband. One Is '01-MAR-04' And The other one Is '31-OCT-04'. Now I have To 
produce a output
With datebreaks In both The tables....Like this..       

From_Date    To_Date        Price    Cost
---------    -------        ----    -----
01/04/2004    01/05/2004              800
02/05/2004    31/05/2004    1450      800
01/06/2004    19/06/2004    1450      800
20/06/2004    22/06/2004    1450      1100
23/06/2004    15/07/2004    1750      1100
16/07/2004    31/07/2004    1650      1100
01/08/2004    31/08/2004    1650      900

Rgrd 


Followup   June 14, 2004 - 10am Central time zone:

cool -- unfortunately you understand what you want, but it is not clear to me what you want.  but 
it looks alot like "a procedural output in a report", not a SQL query.

Also, still not sure what this has to do with "getting rows N thru M from a result set"?

but you will want to write some code to generate this, I think I see what you want (maybe), and 
it's not going to be done via a simple query. 

3 stars how to get a fixed no of rows   June 21, 2004 - 8am Central time zone
Reviewer: s devarshi from India
Tom 
    I have a table t1(name,marks). a name can appear many times. i want to select 2 names with 
their top ten marks arranged in descending order.can it be done by sql.
I can get all the rows (select name,mark from t1 where name in (a ,b) order by a||b;)

Devarshi
 


Followup   June 21, 2004 - 9am Central time zone:

select name, mark, rn
  from (select name, mark, row_number() over (partition by name order by mark) rn
          from t
         where name in ( a,b )
       )
 where rn <= 10;


also, -- read about the difference between row_number, rank and dense_rank.

suppose name=a has 100 rows with the "top" mark

row_number will assign 1, 2, 3, 4, .... to these 100 rows and you'll get 10 "random" ones.

rank will assign 1 to the first 100 rows (they are all the same rank) and 101 to the second and 102 
and so on.  so, you'll get 100 rows using rank.

dense_rank will assign 1 to the first 100 rows, 2 to the second highest and so on.  with dense_rank 
you'll get 100+ rows.... 

4 stars Pseudo code   July 13, 2004 - 5pm Central time zone
Reviewer: User 
Hi Tom,
I have received a pseudo code from an non-oracle user and wanted to convert this code to sql 
query.Pls see below

Get from table research_personnel list of all center= cder co-pi 
loop over list: 
if emp_profile_key listed -> check in skills db for center 
 if from cder -> delete from research_personnel 
 else -> move to table resform_collab 
 else -> check if center specified in research_personnel 
 if center is cder -> delete from research_personnel 
 else -> move to table resform_collab  
move to resform_collab: 
insert into resform_collab:
resform_basic_id (same) 
 collab_name (research_personnel.fname + " " +
 research_personnel.lname) collab_center ("FDA/" + research_personnel.center + "/" + 
research_personnel.office + "/" + research_personnel.division + "/" + research_personnel.lab) 
delete from research_personnel

Any guidence would be appreciated.
  


Followup   July 13, 2004 - 8pm Central time zone:

logic does not make sense.

you have else/else with no if's

if from cder -> ...
else -> move ... (ok
else ?????? how do you get here. 

3 stars SQL query   July 14, 2004 - 9am Central time zone
Reviewer: A reader 
Tom,
Please see this.


get all CDER co-pi:

List1= 
Select * from researchnew.research_personnel,researchnew.resform_basic 
where researchnew.pi_type=2 
and researchnew.resp_center='CDER'
and researchnew.resform_basic.resform_basic_id=researchnew.research_personnel.resform_basic_id

Loop over List1:
_________________
if we have List1.empprofkey:

    level1 = 
    Select level1 from expertise.fda_aries_data 
    Where expertise.fda_aries_data.emp_profile_key = List1.empprofkey

    if level1 is CDER:
    select *  from researchnew.research_personnel 
    where researchnew.pi_type=2 and researchnew.resp_center='CDER'and     
researchnew.resform_basic.resform_basic_id=researchnew.research_personnel.resform_basic_id and      
       expertise.fda_aries_data.emp_profile_key = research_personnel.empprofkey


List1.id  

    else: insert into resform_collab:
    collab_name= emp_first_name + " " + emp_last_name
    collab_center = "FDA/" + org_level_1_code + "/"+ org_level_2_code + "/"+ org_level_3_code + 
"/"+ org_level_4_code 
else:
    if researchnew.research_personnelcenter is CDER:
    delete from researchnew.research_personnel 
    where List1.id  

    else: insert to resform_collab:
    collab_name= lname + " " + fname
    collab_center = "FDA/" + center + "/"+ office + "/"+ division + "/"+ lab  


Followup   July 14, 2004 - 11am Central time zone:

don't under the need or use of the second select in there?  seems to be the same as the first ?

level1 assignment could be a join in the main driving query (join 3 tables together)

now, once 3 tables are joined, you can easily:

insert into resform_collab
select decode( level1, cder, then format data one way, else format it another way) * from these 
three tables;

and then

delete from researchnew where key in (select * from these three tables where level1 = cder);


you are doing a three table join, if level1 = cder then format columns one way and insert into 
resform_collab, else format another way and do insert.  then delete rows where level1=cder. 

4 stars SQL Query   July 14, 2004 - 12pm Central time zone
Reviewer: A reader 
Tom,
Your answer clearup little bit.Could you pls put them as a sql query ?.I haven't done much complex 
sql stuff but am in the learing process.

Thanks for all your help. 


Followup   July 14, 2004 - 9pm Central time zone:

i sort of did?  just join -- two sql statements... 

4 stars rows current before and after when ordered by date   July 14, 2004 - 3pm Central time zone
Reviewer: A reader 
Hello Sir.

Given an ID ,type and a start date 
Need to get all rows ( after arranging in ascending order of start date )
 having
 1) The above id ,type ,start date
 
 and
 2) row or set of rows with start date earlier to the one given above( just one date closest to)
 
 and
 3) row or set of rows with start date after the one given above ( just one date closest to)
 
 example 
 
 for id = 1 type = A and start date = 1/17/1995
 
out put must be
  ID              TYPE START_DATE            END_DATE             
--------------- ---- --------------------- ---------------------
   1               A    2/11/1993             1/16/1995            
   1               A    2/11/1993             1/16/1995            
   1               A    1/17/1995             1/19/1996            
   1               A    1/17/1995             1/19/1996            
   1               A    1/20/1996             1/16/1997            

Mt soln works but i think its terrible.

Can we have a complete view and then just give this id ,type and date and get the above result.
My soln needs to generate a dynamic query so I cant just use by giving a where clause to a view.
Any better soln
   
I tried using dense_rank
SELECT *
  FROM (SELECT DENSE_RANK () OVER (PARTITION BY ID, TYPE ORDER BY start_date)
                                                                           rn,
               t.*
          FROM td t) p
 WHERE ID = 1
   AND TYPE = 'A'
   AND EXISTS (
          SELECT NULL
            FROM (SELECT DENSE_RANK () OVER (PARTITION BY ID, TYPE ORDER BY start_date)
                                                                           rn,
                         s.*
                    FROM td s) q
           WHERE q.start_date = TO_DATE ('1/17/1995', 'MM/DD/YYYY')
             AND q.ID = p.ID
             AND q.TYPE = p.TYPE
             AND q.rn BETWEEN (p.rn - 1) AND (p.rn + 1))
ORDER BY ID, TYPE, rn         
        RN ID              TYPE START_DATE            END_DATE             
---------- --------------- ---- --------------------- ---------------------
         3 1               A    2/11/1993             1/16/1995            
         3 1               A    2/11/1993             1/16/1995            
         4 1               A    1/17/1995             1/19/1996            
         4 1               A    1/17/1995             1/19/1996            
         5 1               A    1/20/1996             1/16/1997       


CREATE TABLE TD
(
  ID          VARCHAR2(15 BYTE)                 NOT NULL,
  TYPE        VARCHAR2(1 BYTE),
  START_DATE  DATE,
  END_DATE    DATE
)
LOGGING 
NOCACHE
NOPARALLEL;

INSERT INTO TD ( ID, TYPE, START_DATE, END_DATE ) VALUES ( 
'1', 'A',  TO_Date( '09/11/1987 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'),  TO_Date( '09/07/1991 
12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM')); 
INSERT INTO TD ( ID, TYPE, START_DATE, END_DATE ) VALUES ( 
'1', 'A',  TO_Date( '09/08/1991 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'),  TO_Date( '02/10/1993 
12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM')); 
INSERT INTO TD ( ID, TYPE, START_DATE, END_DATE ) VALUES ( 
'1', 'A',  TO_Date( '02/11/1993 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'),  TO_Date( '01/16/1995 
12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM')); 
INSERT INTO TD ( ID, TYPE, START_DATE, END_DATE ) VALUES ( 
'1', 'A',  TO_Date( '01/17/1995 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'),  TO_Date( '01/19/1996 
12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM')); 
INSERT INTO TD ( ID, TYPE, START_DATE, END_DATE ) VALUES ( 
'1', 'A',  TO_Date( '01/20/1996 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'),  TO_Date( '01/16/1997 
12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM')); 
INSERT INTO TD ( ID, TYPE, START_DATE, END_DATE ) VALUES ( 
'1', 'A',  TO_Date( '01/17/1997 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'),  TO_Date( '01/15/1998 
12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM')); 
INSERT INTO TD ( ID, TYPE, START_DATE, END_DATE ) VALUES ( 
'1', 'A',  TO_Date( '01/12/2004 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'), NULL); 
INSERT INTO TD ( ID, TYPE, START_DATE, END_DATE ) VALUES ( 
'2', 'B',  TO_Date( '01/13/1997 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'),  TO_Date( '10/30/1997 
12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM')); 
INSERT INTO TD ( ID, TYPE, START_DATE, END_DATE ) VALUES ( 
'2', 'B',  TO_Date( '04/06/1996 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'),  TO_Date( '09/12/1996 
12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM')); 
INSERT INTO TD ( ID, TYPE, START_DATE, END_DATE ) VALUES ( 
'2', 'B',  TO_Date( '09/13/1996 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'),  TO_Date( '01/12/1997 
12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM')); 
INSERT INTO TD ( ID, TYPE, START_DATE, END_DATE ) VALUES ( 
'2', 'A',  TO_Date( '04/06/1996 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'),  TO_Date( '09/12/1996 
12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM')); 
INSERT INTO TD ( ID, TYPE, START_DATE, END_DATE ) VALUES ( 
'2', 'A',  TO_Date( '09/13/1996 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'),  TO_Date( '01/12/1997 
12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM')); 
INSERT INTO TD ( ID, TYPE, START_DATE, END_DATE ) VALUES ( 
'2', 'A',  TO_Date( '01/13/1997 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'),  TO_Date( '10/30/1997 
12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM')); 
INSERT INTO TD ( ID, TYPE, START_DATE, END_DATE ) VALUES ( 
'2', 'B',  TO_Date( '01/13/1997 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'),  TO_Date( '10/30/1997 
12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM')); 
INSERT INTO TD ( ID, TYPE, START_DATE, END_DATE ) VALUES ( 
'2', 'B',  TO_Date( '04/06/1996 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'),  TO_Date( '09/12/1996 
12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM')); 
INSERT INTO TD ( ID, TYPE, START_DATE, END_DATE ) VALUES ( 
'2', 'B',  TO_Date( '09/13/1996 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'),  TO_Date( '01/12/1997 
12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM')); 
INSERT INTO TD ( ID, TYPE, START_DATE, END_DATE ) VALUES ( 
'1', 'A',  TO_Date( '09/11/1987 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'),  TO_Date( '09/07/1991 
12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM')); 
INSERT INTO TD ( ID, TYPE, START_DATE, END_DATE ) VALUES ( 
'1', 'A',  TO_Date( '09/08/1991 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'),  TO_Date( '02/10/1993 
12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM')); 
INSERT INTO TD ( ID, TYPE, START_DATE, END_DATE ) VALUES ( 
'1', 'A',  TO_Date( '02/11/1993 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'),  TO_Date( '01/16/1995 
12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM')); 
INSERT INTO TD ( ID, TYPE, START_DATE, END_DATE ) VALUES ( 
'1', 'A',  TO_Date( '01/17/1995 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'),  TO_Date( '01/19/1996 
12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM')); 
INSERT INTO TD ( ID, TYPE, START_DATE, END_DATE ) VALUES ( 
'1', 'A', NULL, NULL); 
INSERT INTO TD ( ID, TYPE, START_DATE, END_DATE ) VALUES ( 
'1', 'A', NULL, NULL); 
COMMIT; 


Followup   July 15, 2004 - 11am Central time zone:

ops$tkyte@ORA9IR2> select * from td
  2  where id = '1'
  3    and type = 'A'
  4    and start_date in
  5    ( to_date( '1/17/1995', 'mm/dd/yyyy' ),
  6      (select min(start_date)
  7         from td
  8        where id = '1' and type = 'A'
  9          and start_date > to_date( '1/17/1995', 'mm/dd/yyyy' )),
 10      (select max(start_date)
 11         from td
 12        where id = '1' and type = 'A'
 13          and start_date < to_date( '1/17/1995', 'mm/dd/yyyy' )) )
 14   order by start_date
 15  /
 
ID              T START_DAT END_DATE
--------------- - --------- ---------
1               A 11-FEB-93 16-JAN-95
1               A 11-FEB-93 16-JAN-95
1               A 17-JAN-95 19-JAN-96
1               A 17-JAN-95 19-JAN-96
1               A 20-JAN-96 16-JAN-97


is one way... 

4 stars What if   July 15, 2004 - 11am Central time zone
Reviewer: A reader 
Thanx Sir for your answer.
What if I were to extend this to say 2 dates prior and after the given date ?
Or N dates prior and after given date.

In my Bad analytic soln I would just change 

rn between rn - N and rn + N
Any suggestions ?
 


Followup   July 15, 2004 - 1pm Central time zone:

in ( select to_date( ... ) from dual
     union all
     select start_date
       from (select distinct start_date 
               from td where id = .. and type = ... 
                and start_date <= your_date order by start_date desc )
     where rownum <= 2 )
     union all .....

just generate the sets of dates you are interested in. 

3 stars But Nulls   July 17, 2004 - 9pm Central time zone
Reviewer: A reader 
Thanx Sir for your Help.
Soln will not work for Nulls may I need to nvl with sysdate.As there are few in the test data.

Also if we want to return ranges for sample
     start_date
      1/1/1990
       null
       1/1/1991
       null
       null
       1/1/1992
       1/1/1992
       1/1/1993.

Nulls will be grouped together.
Example for 1992 
it should return
      null
      1/1/1992
      1/1/1992
      1/1/1993.
How to get that
 


Followup   July 18, 2004 - 12pm Central time zone:

huh?

does not compute, not understanding what you are asking.  


you seem to be presuming that the null row "has some position in the table that is meaningful".


that null doesn't sort after 1991 and before 1992 -- rows have no "positions" in a table.   You 
seem to be prescribing attributes of a flat file to rows in a table and you cannot. 

3 stars SQL Query   July 19, 2004 - 2pm Central time zone
Reviewer: A reader 
Tom,
I tried to come with a sql query to perform the insert and delete as you outlined here.But not able 
to succeed.
=================================================
level1 assignment could be a join in the main driving query (join 3 tables 
together)

now, once 3 tables are joined, you can easily:

insert into resform_collab
select decode( level1, cder, then format data one way, else format it another 
way) * from these three tables;

and then

delete from researchnew where key in (select * from these three tables where 
level1 = cder);


you are doing a three table join, if level1 = cder then format columns one way 
and insert into resform_collab, else format another way and do insert.  then 
delete rows where level1=cder
=======================================

Could you please explaion this using emp ,dept tables or ur own example tables so that I can 
duplicate that.
Thanks a lot. 


Followup   July 19, 2004 - 4pm Central time zone:

you have a three table join here.  can you get that far?  if not, no example against emp/dept is 
going to help you. 

4 stars Mr Parag - "Mutual Respect" - You should learn how to?   July 28, 2004 - 6pm Central time zone
Reviewer: Reji from NJ
You might change this to "MR" - Tom is 100% right. I don't understand why you got really upset with 
his response. You should check your BP - not Bharat Petroleum, Blood Pressure. 

You could have taken his response in a very light way but at the same time you should have 
understood why he said that. 

Please behave properly Sir.

Tom:

Thanks for spending your personal time to help 100s of software engineers around the globe. We all 
really appreciated your time and effort. 


3 stars limiting takes longer   August 3, 2004 - 8pm Central time zone
Reviewer: v 
My original query takes about 1 second to execute.  It involves joining 3 tables and a lot of 
conditions must be met.  When I ran the same query with your example to limit the range of records 
to N through M, it took 50 seconds to execute.

I noticed a few other users have posted here concerning a performance issue when limiting rows.  
Obviously there is something misconfigured on our end because the majority of users are happy here. 
:)

I noticed when I take out the last WHERE clause, "where rnum >= MIN_ROWS", the query executes in 1 
second.  I also tried changing the clause to "where rnum = 1000", and that also takes tremendously 
long.

Any pointers?
 


Followup   August 3, 2004 - 8pm Central time zone:

show us the queries and explain plans (autotrace traceonly explain is sufficient)

and a tkprof of the same (that actually fetched the data) 

4 stars thanks   August 5, 2004 - 4am Central time zone
Reviewer: sriram from Chennai,India
Hei..its was petty useful...Not only this...I have cleared many things in this site. This one is 
really gr8 


4 stars thanks   August 5, 2004 - 4am Central time zone
Reviewer: sriram from Chennai,India
Hei..its was pretty useful...Not only this...I have cleared many things in this site. This site is 
really gr8 


4 stars Does "rownum <= MAX_ROWS" give any performance improvment?   August 5, 2004 - 9am Central time zone
Reviewer: A reader 
Dear Tom,

In terms of performance, is there any difference between Query (A) and (B)?

A)
select a.*, rownum rnum
from ( YOUR_QUERY_GOES_HERE -- including the order by ) a
where rownum <= MAX_ROWS
/


B)
select a.*, rownum rnum
from ( YOUR_QUERY_GOES_HERE -- including the order by ) a
/
 


Followup   August 5, 2004 - 1pm Central time zone:

sure, if the (b) returns a billion rows and (a) returns 5 -- (a) will be faster :)

but we call that a top-n query and yes there are top-n optimizations that makes (a) faster and less 
expensive to perform

http://asktom.oracle.com/pls/ask/f?p=4950:8:12308262164558077111::NO::F4950_P8_DISPLAYID,F4950_P8_CR
ITERIA:127412348064,#PAGEBOTTOM
 

4 stars Is the URL correct?   August 6, 2004 - 10am Central time zone
Reviewer: Sami 
Tom,
The URL which you have given is pointing to the same page. 


Followup   August 6, 2004 - 10am Central time zone:

http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:127412348064#3282021148551
thanks -- should be pointing UP in this page :) 

4 stars In out web application,paging sql is strange   August 7, 2004 - 1am Central time zone
Reviewer: Steven from BeiJing.China
Hello,I have a question about sql paging --- get top Max --Min value from an order by inner sql;

We have a table app_AssetBasicInfo(ID Number Primary key,Title varchar2(255),CategoryID number not 
null,Del_tag not null,CreateDate Date not null,...);

CategoryID has 3 distinct value,del_tag has 2 distinct value ,they are very skewed. and I gathered 
statistics using method_opt=>'for columns CategoryID&#65292;del_tag size skewonly');
And I have a index CATEGORYDELTAGCDATEID  on app_Assetbasicinfo(CategoryID,del_tag,CreateDate 
desc,ID desc) and physical table storage is also sorted by categoryID,del_tag,CreateDate desc,ID 
desc.

paging sql like these:

select * from (select table_a.*,rownum as my_rownum from (select
       title FROM app_AssetBasicInfo  WHERE
      app_AssetBasicInfo.CategoryID=1 AND  Del_tag=0  And
CreateDate between &Date1 and &Date2 order by CreateDate DESC,app_AssetBasicInfo.ID DESC )
        table_a where rownum<&Max_Value) where  
 my_rownum>=&Min_Value;

but it is confused me very much.Please see these sql_trace result:
[code]
********************************************************************************

select table_a.*,rownum as my_rownum from (select
       title FROM app_AssetBasicInfo  WHERE
      app_AssetBasicInfo.CategoryID=2 AND  Del_tag=0  order by CreateDate 
DESC,app_AssetBasicInfo.ID DESC )
        table_a where rownum<20

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.03       0.44          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        3      0.00       0.00          0          8          0          19
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        5      0.03       0.44          0          8          0          19

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 63  

Rows     Row Source Operation
-------  ---------------------------------------------------
     19  COUNT STOPKEY 
     19   VIEW  
     19    TABLE ACCESS BY INDEX ROWID APP_ASSETBASICINFO 
     19     INDEX RANGE SCAN CATEGORYDELTAGCDATEID (object id 33935)

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

select * from (select table_a.*,rownum as my_rownum from (select
       title FROM app_AssetBasicInfo  WHERE
      app_AssetBasicInfo.CategoryID=1 AND  Del_tag=0   order by CreateDate 
DESC,app_AssetBasicInfo.ID DESC )
        table_a where rownum<20) where   my_rownum>=0

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.26       0.49          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        3      1.81       1.90          0      19523          0          19
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        5      2.07       2.40          0      19523          0          19

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 63  

Rows     Row Source Operation
-------  ---------------------------------------------------
     19  VIEW  
     19   COUNT STOPKEY 
     19    VIEW  
     19     SORT ORDER BY STOPKEY 
482147      TABLE ACCESS BY INDEX ROWID APP_ASSETBASICINFO 
482147       INDEX RANGE SCAN CATEGORYDELTAGCDATEID (object id 33935)

INDEX RANGE SCAN return 482147 rows ,seems equal with full index scan.
[/code]


I discovered that when i wrap a outer select ,it gets slow and comsume much consistent gets.

I also alter index CATEGORYDELTAGCDATEID with compress 2 and /*+ first_rows */ hint;but result is 
same;

but when i use RowID to paging sql,it runs good.but it can not support  tables join.
[code]
************************************************************
select title     from app_AssetBasicInfo
      where rowid in
        ( select rid from
           ( select rownum rno,rowid rid from
              (select  rowid FROM app_AssetBasicInfo  WHERE
app_AssetBasicInfo.CategoryID=1 AND  Del_tag=0
 order by CreateDate desc,app_AssetBasicInfo.ID DESC
                 ) where rownum <= 20
           ) where rno >= 0
      )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.15       0.15          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        3      0.01       0.00          0         23          0          20
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        5      0.17       0.15          0         23          0          20

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 63  

Rows     Row Source Operation
-------  ---------------------------------------------------
     20  NESTED LOOPS  
     20   VIEW  
     20    SORT UNIQUE 
     20     COUNT STOPKEY 
     20      VIEW  
     20       INDEX RANGE SCAN CATEGORYDELTAGCDATEID (object id 33971)
     20   TABLE ACCESS BY USER ROWID APP_ASSETBASICINFO 

************************************************************
[/code]

I want to know why sql with outer select get more index range scamed  than sql with no outer 
wapper. 


I am looking forward for your reply.

Thank you very much !

  Steven

 


Followup   August 7, 2004 - 10am Central time zone:

for paging queries, recommend you use first_rows -- you always want the index pretty much, since 
you want to get rows 1..10 ASAP.  11..20 should take just a tad longer and so on.

/*+ FIRST_ROWS */ 

5 stars How would you go about this?   August 15, 2004 - 3pm Central time zone
Reviewer: Brian McGinity from USA
Suppose SCOTT.EMP had 300,000 rows and you needed to show do this type pagination from a search:

1.User inputs an ename to search.
2.If ename is found in EMP then show result (see result set description below).
3.If ename is not found than chop off the last letter in the search criteria and try again.

Once found, the result set needs to show the 20 enames sorted alphabetically before the match and 
20 enames after the match.  The result has a total of 41 names sorted descending with the closest 
matching record in the middle.  



 


Followup   August 16, 2004 - 8am Central time zone:

"closest matching record" in this case is ambigous since the equality could return thousands of 
records to begin with.  that'd be my first problem - what means 'closest'

it'd be something like:

with q
as
(select ename
   from (select ename 
           from emp
          where ename in ( :ename, 
                           case when :l > 1 then substr( :ename, :l-1 ) end,
                           case when :l > 2 then substr( :ename, :l-2 ) end,
                           case when :l > 3 then substr( :ename, :l-3 ) end,
                           ...
                           case when :l > N then substr( :ename, :l-3 ) end )
          order by length(ename) desc )
  where rownum = 1 )
( select * 
    from (select * from emps 
           where ename <= (select ename from q) order by ename desc )
  where rownum <= 21 )
union 
( select * 
    from ( select * from emps 
            where ename >= (select ename from q) order by ename asc)
  where rownum <= 21 )
order by ename;


subquery q gets the "ename of interest"
first union all gets it and 20 before
second gets it and 20 after

union does sort distinct which removes the duplicate. 

5 stars BAGUS SEKALI (PERFECT)   September 1, 2004 - 11pm Central time zone
Reviewer: David, Raymond from Jakarta, Indonesia
I have been looking solution to my problem
and finally I got the solution...

select * 
  from ( select a.*, rownum rnum
           from ( YOUR_QUERY_GOES_HERE -- including the order by ) a
          where rownum <= MAX_ROWS )
 where rnum >= MIN_ROWS

Absolutely...this is one of the most useful query 
in ORACLE SQL

again...thanks TOM... 


5 stars Pagination ordered by a varchar2 column ?   October 13, 2004 - 7am Central time zone
Reviewer: Kim Berg Hansen from Middelfart, Denmark
Hi, Tom

I'm trying to use your pagination methods for a log-system I'm developing (Oracle 8.1.7.4.)

But I can't always get Oracle to use the trick with index scanning to make this speedy. Seems to me 
it only works with dates/numbers and not with varchar2s?


I have this test-table :

SQL> create table testlog
  2  (
  3      logdate        date          not null,
  4      logseq           integer          not null,
  5      logdmltype     varchar2(1)    not null,
  6      loguser        varchar2(10)   not null,
  7      logdept        varchar2(10)   not null,
  8      logip           raw(4)          not null,
  9      recordid       integer          not null,
 10      keyfield       varchar2(10)   not null,
 11      col1_old       varchar2(10),
 12      col1_new       varchar2(10),
 13      col2_old       number(32,16),
 14      col2_new       number(32,16)
 15  );

With these test-data :

SQL> insert into testlog
  2  select
  3  last_ddl_time logdate,
  4  rownum logseq,
  5  'U' logdmltype,
  6  substr(owner,1,10) loguser,
  7  substr(object_type,1,10) logdept,
  8  hextoraw('AABBCCDD') logip,
  9  ceil(object_id/100) recordid,
 10  substr(object_name,1,10) keyfield,
 11  substr(subobject_name,1,10) col1_old,
 12  substr(subobject_name,2,10) col1_new,
 13  data_object_id col2_old,
 14  object_id col2_new
 15  from all_objects
 16  where rownum <= 40000;

40000 rows created.


Typical ways to find data would be "by date", "by user", "by recordid", "by keyfield" :

SQL> create index testlog_date on testlog (
  2      logdate, logseq
  3  );

SQL> create index testlog_user on testlog (
  2      loguser, logdate, logseq
  3  );

SQL> create index testlog_recordid on testlog (
  2      recordid, logdate, logseq
  3  );

SQL> create index testlog_keyfield on testlog (
  2      keyfield, logdate, logseq
  3  );

(Note all indexes are on "not null" columns - that's a requirement for the trick to work, right?)


Gather statistics :

SQL> begin dbms_stats.gather_table_stats('XAL_SUPERVISOR','TESTLOG',method_opt=>'FOR ALL INDEXED 
COLUMNS SIZE 1',cascade=>true); end;
  2  /


And then fire some test statements for pagination :

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

Try "by date" :

select /*+ FIRST_ROWS */ * from (
   select /*+ FIRST_ROWS */ p.*, rownum r from (
      select /*+ FIRST_ROWS */ t.*
      from testlog t
      order by logdate, logseq
   ) p
   where rownum <= 5
) where r >= 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.02       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0          5          0           5
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.02       0.01          0          5          0           5

Rows     Row Source Operation
-------  ---------------------------------------------------
      5  VIEW 
      5   COUNT STOPKEY 
      5    VIEW 
      5     TABLE ACCESS BY INDEX ROWID TESTLOG 
      5      INDEX FULL SCAN (object id 190604)   <--TESTLOG_DATE

Works dandy.

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

Try "by date" backwards :

select /*+ FIRST_ROWS */ * from (
   select /*+ FIRST_ROWS */ p.*, rownum r from (
      select /*+ FIRST_ROWS */ t.*
      from testlog t
      order by logdate desc, logseq desc
   ) p
   where rownum <= 5
) where r >= 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.01       0.01          1          5          0           5
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.01       0.01          1          5          0           5

Rows     Row Source Operation
-------  ---------------------------------------------------
      5  VIEW 
      5   COUNT STOPKEY 
      5    VIEW 
      5     TABLE ACCESS BY INDEX ROWID TESTLOG 
      5      INDEX FULL SCAN DESCENDING (object id 190604)   <--TESTLOG_DATE

Works dandy backwards too.

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

Try "by user" :

select /*+ FIRST_ROWS */ * from (
   select /*+ FIRST_ROWS */ p.*, rownum r from (
      select /*+ FIRST_ROWS */ t.*
      from testlog t
      order by loguser, logdate, logseq
   ) p
   where rownum <= 5
) where r >= 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.15       0.24        161        361          6           5
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.15       0.25        161        361          6           5

Rows     Row Source Operation
-------  ---------------------------------------------------
      5  VIEW 
      5   COUNT STOPKEY 
      5    VIEW 
      5     SORT ORDER BY STOPKEY 
  40000      TABLE ACCESS FULL TESTLOG 

Hmmm... Not so dandy with varchar2 column ?

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

Try "by recordid" :

select /*+ FIRST_ROWS */ * from (
   select /*+ FIRST_ROWS */ p.*, rownum r from (
      select /*+ FIRST_ROWS */ t.*
      from testlog t
      order by recordid, logdate, logseq
   ) p
   where rownum <= 5
) where r >= 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0          5          0           5
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          0          5          0           5

Rows     Row Source Operation
-------  ---------------------------------------------------
      5  VIEW 
      5   COUNT STOPKEY 
      5    VIEW 
      5     TABLE ACCESS BY INDEX ROWID TESTLOG 
      5      INDEX FULL SCAN (object id 190606)   <--TESTLOG_RECORDID

Works dandy with a number column.

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

Try "last 5 for a particular recordid" :

select /*+ FIRST_ROWS */ * from (
   select /*+ FIRST_ROWS */ p.*, rownum r from (
      select /*+ FIRST_ROWS */ t.*
      from testlog t
      where recordid = 1000
      order by recordid desc, logdate desc, logseq desc
   ) p
   where rownum <= 5
) where r >= 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.02          2          6          0           5
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.02          2          6          0           5

Rows     Row Source Operation
-------  ---------------------------------------------------
      5  VIEW 
      5   COUNT STOPKEY 
      5    VIEW 
      5     TABLE ACCESS BY INDEX ROWID TESTLOG 
      5      INDEX RANGE SCAN DESCENDING (object id 190606)   <--TESTLOG_RECORDID

Number column again rocks - it does a descending range scan and stops when it has 5 records.

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

Try "last 5 for a particular user" :

select /*+ FIRST_ROWS */ * from (
   select /*+ FIRST_ROWS */ p.*, rownum r from (
      select /*+ FIRST_ROWS */ t.*
      from testlog t
      where loguser = 'SYS'
      order by loguser desc, logdate desc, logseq desc
   ) p
   where rownum <= 5
) where r >= 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.08       0.12          5       2373          0           5
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.09       0.13          5       2373          0           5

Rows     Row Source Operation
-------  ---------------------------------------------------
      5  VIEW 
      5   COUNT STOPKEY 
      5    VIEW 
      5     SORT ORDER BY STOPKEY 
   8706      TABLE ACCESS BY INDEX ROWID TESTLOG 
   8707       INDEX RANGE SCAN (object id 190605)   <--TESTLOG_USER

Again the varchar2 column makes it not so perfect :-(

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

One thing I notice is this :

Rows     Row Source Operation
-------  ---------------------------------------------------
      5  VIEW 
      5   COUNT STOPKEY 
      5    VIEW 
      5     SORT ORDER BY STOPKEY   <-- This linie only appears using varchar2 column?
         (table access one way or the other)

For numbers/dates the COUNT STOPKEY can halt the table/index access when enough records have been 
found.
For varchar2s the SORT ORDER BY STOPKEY seems to disable that trick?

Why does it want to SORT ORDER BY STOPKEY when it's a varchar2?
It's already sorted in the index (same as with the numbers/dates)?
What am I doing wrong?


As always - profound thanks for all your help to all of us.


Kim Berg Hansen

Senior System Developer
T.Hansen Gruppen A/S
 


Followup   October 13, 2004 - 8am Central time zone:

what is your characterset? 

817 for me does:

select /*+ FIRST_ROWS */ * from (
   select /*+ FIRST_ROWS */ p.*, rownum r from (
      select /*+ FIRST_ROWS */ t.*
      from testlog t
      order by loguser, logdate, logseq
   ) p
   where rownum <= 5
) where r >= 1

with WE8ISO8859P1


is this an "nls_sort()" issue? (eg: the binary sort isn't 'sorted' in your character set and we'd 
need an FBI perhaps?) 

5 stars Yes !!!   October 13, 2004 - 9am Central time zone
Reviewer: Kim Berg Hansen from Middelfart, Denmark
I'm amazed as usual.

You have a rare gift for immediately noticing those details that should have been obvious to us 
blind folks raving in the dark ;-)

My character set is WE8ISO8859P1 - no problem there.

My database has NLS_SORT=BINARY.

The client I used for testing/development had NLS_SORT=DANISH.

When I change the client to NLS_SORT=BINARY - everything works as it's supposed to do...

Thanks a million, Tom.

 


Followup   October 13, 2004 - 9am Central time zone:

a function based index could work for them.....  creating the index on nls_sort(....) and ordering 
by that. 

5 stars No need...   October 13, 2004 - 9am Central time zone
Reviewer: Kim Berg Hansen from Middelfart, Denmark
I just checked - the production clients (the ERP system) does have NLS_SORT=BINARY.

It was simply the registry settings here on my development PC that wasn't correct... so the 
solution was very simple :-)
 


5 stars   October 14, 2004 - 1am Central time zone
Reviewer: A reader 


5 stars Continued pagination troubles...   October 15, 2004 - 8am Central time zone
Reviewer: Kim Berg Hansen from Middelfart, Denmark
Hi again, Tom

Continuation of my question from a couple of days ago...

I'm still working on the best way of getting Oracle to use index scans in pagination queries.
I have no problem anymore with the simpler queries from my last question to you.

But suppose I wish to start the pagination from a particular point in a composite index.
A good example is this index :

SQL> create index testlog_user on testlog (
  2      loguser, logdate, logseq
  3  );

(Table, indexes and data for these tests are identical to the last question I gave you.)

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

Example 1:

It works fine if I do pagination from a starting point in the index in which I only use first 
column of the index. For example start the pagination a point where loguser = 'SYS' :


SQL> select /*+ FIRST_ROWS */ loguser, logdate, logseq, keyfield from (
  2      select /*+ FIRST_ROWS */ p.*, rownum r from (
  3         select /*+ FIRST_ROWS */ t.*
  4         from testlog t
  5         where loguser >= 'SYS'
  6         order by loguser, logdate, logseq
  7      ) p
  8      where rownum <= 5
  9  ) where r >= 1;

LOGUSER    LOGDATE      LOGSEQ KEYFIELD                                         
---------- -------- ---------- ----------                                       
SYS        01-03-27       4133 BOOTSTRAP$                                       
SYS        01-03-27       5044 I_CCOL1                                          
SYS        01-03-27       5045 I_CCOL2                                          
SYS        01-03-27       5046 I_CDEF1                                          
SYS        01-03-27       5047 I_CDEF2                                          


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0          5          0           5
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.01       0.01          0          5          0           5

Rows     Row Source Operation
-------  ---------------------------------------------------
      5  VIEW 
      5   COUNT STOPKEY 
      5    VIEW 
      5     TABLE ACCESS BY INDEX ROWID TESTLOG 
      5      INDEX RANGE SCAN (object id 191422) <--Index TESTLOG_USER

Perfect scan of the index and stop at the number of rows I wish to paginate.

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

Example 2:

Consider then when I wish to use a starting poing with all three columns in the composite index. 
For example start the pagination at the point where loguser = 'SYS', logdate = '31-08-2004 
11:22:33' and logseq = 5799, and then just scan the index forward 5 records from that point.
Best SQL I can come up with is something like this :


SQL> select /*+ FIRST_ROWS */ loguser, logdate, logseq, keyfield from (
  2      select /*+ FIRST_ROWS */ p.*, rownum r from (
  3         select /*+ FIRST_ROWS */ t.*
  4         from testlog t
  5         where ( loguser = 'SYS' and
                   logdate = to_date('31-08-2004 11:22:33','DD-MM-YYYY HH24:MI:SS') and
                   logseq >= 5799
                 )
  6            or ( loguser = 'SYS' and
                   logdate > to_date('31-08-2004 11:22:33','DD-MM-YYYY HH24:MI:SS')
                 )
  7            or ( loguser > 'SYS' )
  8         order by loguser, logdate, logseq
  9      ) p
 10      where rownum <= 5
 11  ) where r >= 1;

LOGUSER    LOGDATE      LOGSEQ KEYFIELD                                         
---------- -------- ---------- ----------                                       
SYS        04-08-31       5799 V_$PROCESS                                       
SYS        04-08-31       5827 V_$SESSION                                       
SYS        04-08-31       5857 V_$STATNAM                                       
SYSTEM     01-03-27      16877 AQ$_QUEUES                                       
SYSTEM     01-03-27      16878 AQ$_QUEUES                                       


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.08       0.10          0       6153          0           5
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.08       0.10          0       6153          0           5

Rows     Row Source Operation
-------  ---------------------------------------------------
      5  VIEW 
      5   COUNT STOPKEY 
      5    VIEW 
      5     TABLE ACCESS BY INDEX ROWID TESTLOG 
  16848      INDEX FULL SCAN (object id 191422) <--Index TESTLOG_USER


Do you know another way to phrase that SQL in a manner so that Oracle understands, that I'm 
pinpointing a particular spot in the index and wants it to scan forward from that point ?

Conceptually I think it should be no different for Oracle to start the range scan at a point 
defined by 3 column values in example 2 as it starts the range scan at a point defined by only the 
first column value in example 1?

The trouble is how to express in the SQL language what I want done :-)

In "pseudo-code" I might be tempted to express it somewhat like :

   "where (loguser, logdate, logseq) >= ('SYS', '31-08-2004 11:22:33', 5799)"

...but that syntax is not recognized in SQL, alas ;-)

What do you think? Can I do anything to make the example 2 be as perfectly efficient as example 1?
 


Followup   October 15, 2004 - 11am Central time zone:

For example start the pagination at the point where loguser = 
'SYS', logdate = '31-08-2004 11:22:33' and logseq = 5799, and then just scan the 
index forward 5 records from that point.

I don't understand the concept of "start the pagination at the point"?


are you saying "ordered by loguser, logdate, logseq", starting with :x/:y/:z?

in which case, we'd need an index on those 3 columns in order to avoid getting ALL rows and sorting 
before giving you the first row. 

5 stars Paraphrase of my previous review...   October 18, 2004 - 4am Central time zone
Reviewer: Kim Berg Hansen from Middelfart, Denmark
Hi again, Tom

Sorry if I don't "conceptualize" clearly - english ain't my native language :-) I'll try to 
paraphrase to make it more clear.

Test table, indexes and data used for this is taken from my review of october 13th to this 
question. 

Specifically the index I'm trying to use (abuse? ;-) is this index:

SQL> create index testlog_user on testlog (
  2      loguser, logdate, logseq
  3  );

All three columns are NOT NULL, so all the 40.000 records will be in the index.
Here's part of the data ordered by that index:

SQL> select p.*, rownum from (
  2      select loguser, logdate, logseq, keyfield
  3      from testlog t
  4      order by loguser, logdate, logseq
  5  ) p;

LOGUSER    LOGDATE      LOGSEQ KEYFIELD       ROWNUM
---------- -------- ---------- ---------- ----------
OUTLN      01-03-27      17121 OL$                 1
OUTLN      01-03-27      17122 OL$HINTS            2

(... lots of rows ...)

PUBLIC     03-07-23      13812 TOAD_TABLE       8139
PUBLIC     03-07-23      13810 TOAD_SPACE       8140
SYS        01-03-27       4133 BOOTSTRAP$       8141 <== POINT A
SYS        01-03-27       5044 I_CCOL1          8142
SYS        01-03-27       5045 I_CCOL2          8143
SYS        01-03-27       5046 I_CDEF1          8144
SYS        01-03-27       5047 I_CDEF2          8145
SYS        01-03-27       5048 I_CDEF3          8146
SYS        01-03-27       5049 I_CDEF4          8147
SYS        01-03-27       5050 I_COBJ#          8148
SYS        01-03-27       5051 I_COL1           8149
SYS        01-03-27       5052 I_COL2           8150
SYS        01-03-27       5053 I_COL3           8151
SYS        01-03-27       5057 I_CON1           8152
SYS        01-03-27       5058 I_CON2           8153

(... lots of rows ...)

SYS        04-08-31       5799 V_$PROCESS      16844 <== POINT B
SYS        04-08-31       5827 V_$SESSION      16845
SYS        04-08-31       5857 V_$STATNAM      16846
SYSTEM     01-03-27      16877 AQ$_QUEUES      16847
SYSTEM     01-03-27      16878 AQ$_QUEUES      16848
SYSTEM     01-03-27      16879 AQ$_QUEUES      16849
SYSTEM     01-03-27      16880 AQ$_QUEUE_      16850
SYSTEM     01-03-27      16881 AQ$_QUEUE_      16851
SYSTEM     01-03-27      16882 AQ$_SCHEDU      16852
SYSTEM     01-03-27      16883 AQ$_SCHEDU      16853
SYSTEM     01-03-27      16884 AQ$_SCHEDU      16854
SYSTEM     01-03-27      16910 DEF$_TRANO      16855
SYSTEM     01-03-27      17113 SYS_C00745      16856
SYSTEM     01-03-27      17114 SYS_C00748      16857
SYSTEM     01-03-27      16891 DEF$_AQERR      16858
SYSTEM     01-03-27      16893 DEF$_CALLD      16859
SYSTEM     01-03-27      16894 DEF$_CALLD      16860
SYSTEM     01-03-27      16896 DEF$_DEFAU      16861
SYSTEM     01-03-27      16898 DEF$_DESTI      16862
SYSTEM     01-03-27      16900 DEF$_ERROR      16863

(... lots of rows ...)

XAL_TRYKSA 04-09-21      31307 ORDREPOSTI      39999
XAL_TRYKSA 04-09-30      31220 LAGERINDGA      40000

40000 rows selected.

I've marked two records - point A and point B - that I'll explain further down.


Now for the tricky part of the explanation...

The original pagination code that utilizes my index well calls for using something like this 
construct:

SQL> select /*+ FIRST_ROWS */ loguser, logdate, logseq, keyfield from (
  2      select /*+ FIRST_ROWS */ p.*, rownum r from (
  3         select /*+ FIRST_ROWS */ t.*
  4         from testlog t
  5         order by loguser, logdate, logseq
  6      ) p
  7      where rownum <= :hirow
  8  ) where r >= :lorow;

(Which works perfectly after I corrected NLS_SORT on my development PC ;-)


When a user asks for seeing the records starting with "loguser = 'SYS'" and then enable him to 
paginate with 5 rows at a time forward "from that point on" - thats what I mean with "starting the 
pagination at point A".

I can not use the statement from above with :lorow = 8141 and :hirow = 8145 because that would 
require that I somehow first find those two numbers. To avoid that I instead use this:

SQL> select /*+ FIRST_ROWS */ loguser, logdate, logseq, keyfield from (
  2      select /*+ FIRST_ROWS */ p.*, rownum r from (
  3         select /*+ FIRST_ROWS */ t.*
  4         from testlog t
  5         where loguser >= 'SYS'
  6         order by loguser, logdate, logseq
  7      ) p
  8      where rownum <= 5
  9  ) where r >= 1;

LOGUSER    LOGDATE      LOGSEQ KEYFIELD                                         
---------- -------- ---------- ----------                                       
SYS        01-03-27       4133 BOOTSTRAP$                                       
SYS        01-03-27       5044 I_CCOL1                                          
SYS        01-03-27       5045 I_CCOL2                                          
SYS        01-03-27       5046 I_CDEF1                                          
SYS        01-03-27       5047 I_CDEF2                                          

This statement gives me "Page 1" in a "five rows at a time" pagination "starting at the point where 
loguser = 'SYS'" (point A). And this statement utilizes the index very efficiently indeed:

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0          5          0           5
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.01          0          5          0           5

Rows     Row Source Operation
-------  ---------------------------------------------------
      5  VIEW
      5   COUNT STOPKEY
      5    VIEW
      5     TABLE ACCESS BY INDEX ROWID TESTLOG
      5      INDEX RANGE SCAN (object id 191688)

When the user clicks to see "Page 2" (paginates forward), this statement is used:

SQL> select /*+ FIRST_ROWS */ loguser, logdate, logseq, keyfield from (
  2      select /*+ FIRST_ROWS */ p.*, rownum r from (
  3         select /*+ FIRST_ROWS */ t.*
  4         from testlog t
  5         where loguser >= 'SYS'
  6         order by loguser, logdate, logseq
  7      ) p
  8      where rownum <= 10
  9  ) where r >= 6;

LOGUSER    LOGDATE      LOGSEQ KEYFIELD                                         
---------- -------- ---------- ----------                                       
SYS        01-03-27       5048 I_CDEF3                                          
SYS        01-03-27       5049 I_CDEF4                                          
SYS        01-03-27       5050 I_COBJ#                                          
SYS        01-03-27       5051 I_COL1                                           
SYS        01-03-27       5052 I_COL2                                           

And it is quite efficient as well:

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0          6          0           5
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.01          0          6          0           5

Rows     Row Source Operation
-------  ---------------------------------------------------
      5  VIEW
     10   COUNT STOPKEY
     10    VIEW
     10     TABLE ACCESS BY INDEX ROWID TESTLOG
     10      INDEX RANGE SCAN (object id 191688)

So by this method I "pinpoint Point A in the index" and paginate forward from that point... (Hope 
this is clear what I mean.)

The tricky part is when I wish to do exactly the same thing at point B !!!

This time I want to start at the point in the index (in the "order by" if you wish, but that's the 
same in this case) defined not just by the first column but by three columns. I want to start at 
the point where loguser = 'SYS' and logdate = '31-08-2004 11:22:33' and logseq = 5799 (point B) and 
paginate "forward in the index/order by".

I can come up with one way of defining a where-clause that will give me the rows from "point B" and 
forward using that order by:

SQL> select /*+ FIRST_ROWS */ loguser, logdate, logseq, keyfield from (
  2      select /*+ FIRST_ROWS */ p.*, rownum r from (
  3         select /*+ FIRST_ROWS */ t.*
  4         from testlog t
  5         where (loguser = 'SYS' and logdate = to_date('31-08-2004 11:22:33','DD-MM-YYYY 
HH24:MI:SS') and logseq >= 5799)
  6            or (loguser = 'SYS' and logdate > to_date('31-08-2004 11:22:33','DD-MM-YYYY 
HH24:MI:SS'))
  7            or (loguser > 'SYS')
  8         order by loguser, logdate, logseq
  9      ) p
 10      where rownum <= 5
 11  ) where r >= 1;

LOGUSER    LOGDATE      LOGSEQ KEYFIELD                                         
---------- -------- ---------- ----------                                       
SYS        04-08-31       5799 V_$PROCESS                                       
SYS        04-08-31       5827 V_$SESSION                                       
SYS        04-08-31       5857 V_$STATNAM                                       
SYSTEM     01-03-27      16877 AQ$_QUEUES                                       
SYSTEM     01-03-27      16878 AQ$_QUEUES                                       

It gives me the correct 5 rows (page 1 of the pagination starting at point B), but it does not use 
the index efficiently - it full scans the index rather that a range scan:

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.08       0.07          0       6153          0           5
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.09       0.08          0       6153          0           5

Rows     Row Source Operation
-------  ---------------------------------------------------
      5  VIEW
      5   COUNT STOPKEY
      5    VIEW
      5     TABLE ACCESS BY INDEX ROWID TESTLOG
  16848      INDEX FULL SCAN (object id 191688)

And when the user clicks "Page 2" this is what I try:

SQL> select /*+ FIRST_ROWS */ loguser, logdate, logseq, keyfield from (
  2      select /*+ FIRST_ROWS */ p.*, rownum r from (
  3         select /*+ FIRST_ROWS */ t.*
  4         from testlog t
  5         where (loguser = 'SYS' and logdate = to_date('31-08-2004 11:22:33','DD-MM-YYYY 
HH24:MI:SS') and logseq >= 5799)
  6            or (loguser = 'SYS' and logdate > to_date('31-08-2004 11:22:33','DD-MM-YYYY 
HH24:MI:SS'))
  7            or (loguser > 'SYS')
  8         order by loguser, logdate, logseq
  9      ) p
 10      where rownum <= 10
 11  ) where r >= 6;

LOGUSER    LOGDATE      LOGSEQ KEYFIELD                                         
---------- -------- ---------- ----------                                       
SYSTEM     01-03-27      16879 AQ$_QUEUES                                       
SYSTEM     01-03-27      16880 AQ$_QUEUE_                                       
SYSTEM     01-03-27      16881 AQ$_QUEUE_                                       
SYSTEM     01-03-27      16882 AQ$_SCHEDU                                       
SYSTEM     01-03-27      16883 AQ$_SCHEDU                                       

Which again is the correct rows but inefficient:

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.08       0.10          0       6153          0           5
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.09       0.11          0       6153          0           5

Rows     Row Source Operation
-------  ---------------------------------------------------
      5  VIEW
     10   COUNT STOPKEY
     10    VIEW
     10     TABLE ACCESS BY INDEX ROWID TESTLOG
  16853      INDEX FULL SCAN (object id 191688)


So my problem is:

When I "start my pagination at point A" - Oracle intelligently realizes that it can go to the index 
"at point A" and give me five rows by scanning the index from that point forward (or in the case of 
pagination to "Page 2": 10 rows forward and then only return the last 5 of those 10.) That is very 
efficient and rocks!

When I "start my pagination at point B"... I don't have a clear way of defining my where-clause, so 
that Oracle can realize "hey, this is the same as before, I can go to point B in the index and give 
him 5 rows by scanning forward from that point".


How can I write my where-clause in a way, so that Oracle has a chance to realize that it can do 
exactly the same thing with "point B" as it did with "point A"?


I'm sorry I write such long novels that you probably get bored reading through them :-) ... but 
that's the only way I can be clear about it.

I hope you can figure out some way to work around this full index scan and get a range scan 
instead...?!? I'm kinda stumped here :-)
 


Followup   October 18, 2004 - 8am Central time zone:

"I want to start at the point where loguser = 'SYS' and logdate 
= '31-08-2004 11:22:33' and logseq = 5799"


that is 'hard' -- if you just used the simple predicate, that would "skip around" in the table as 
it went from loguser value to loguser value.  Hence your really complex predicate:

  5         where (loguser = 'SYS' and logdate = to_date('31-08-2004 
11:22:33','DD-MM-YYYY HH24:MI:SS') and logseq >= 5799)
  6            or (loguser = 'SYS' and logdate > to_date('31-08-2004 
11:22:33','DD-MM-YYYY HH24:MI:SS'))
  7            or (loguser > 'SYS')

(as soon as you see an OR -- abandon all hope :)



so, basically, you are trying to treat the table as if it was a VSAM/ISAM file -- seek to key and 
read forward from key.  Concept that is vaguely orthogonal to relational technology...

but what about this:


ops$tkyte@ORA9IR2> update testlog set logseq = rownum, logdate = add_months(sysdate,-12) where 
loguser = 'XDB';
 
270 rows updated.
 
I wanted some data "after loguser=SYS logdate=31-aug-2004 logseq=5799" with smaller logdates and 
logseqs


ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create index user_date_seq on testlog
  2  ( rpad(loguser,10) || to_char(logdate,'yyyymmddhh24miss') || to_char(logseq,'fm9999999999') );
 
Index created.

we encode the columns you want to "seek through" in a single column.  numbers that are POSITIVE 
are easy -- you have to work a little harder to get negative numbers to encode "sortable"

 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace view v
  2  as
  3  select testlog.*,
  4         rpad(loguser,10) || to_char(logdate,'yyyymmddhh24miss') || 
to_char(logseq,'fm9999999999') user_date_seq
  5    from testlog
  6  /
 
View created.

I like the view, cuts down on typos in the query later...
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec dbms_stats.gather_table_stats( user, 'T', cascade=>true );
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> variable min number
ops$tkyte@ORA9IR2> variable max number
ops$tkyte@ORA9IR2> variable u varchar2(10)
ops$tkyte@ORA9IR2> variable d varchar2(15)
ops$tkyte@ORA9IR2> variable s number
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> set linesize 121
ops$tkyte@ORA9IR2> set autotrace on explain
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec :min := 1; :max := 5; :u := 'SYS'; :d := '20040831112233'; :s := 5799
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select /*+ FIRST_ROWS */ loguser, logdate, logseq, keyfield from (
  2      select /*+ FIRST_ROWS */ p.*, rownum r from (
  3         select /*+ FIRST_ROWS */ t.*
  4         from v t
  5         where user_date_seq >= rpad(:u,10) || :d || to_char(:s,'fm9999999999')
  6         order by user_date_seq
  7      ) p
  8      where rownum <= :max
  9  ) where r >= :min
 10  /
 
LOGUSER    LOGDATE       LOGSEQ KEYFIELD
---------- --------- ---------- ----------
SYS        02-SEP-04       6126 BOOTSTRAP$
SYS        02-SEP-04       6129 CCOL$
SYS        02-SEP-04       6144 CDEF$
SYS        02-SEP-04       6152 CLU$
SYS        02-SEP-04       6162 CON$
 
 
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=10 Card=997 Bytes=48853)
   1    0   VIEW (Cost=10 Card=997 Bytes=48853)
   2    1     COUNT (STOPKEY)
   3    2       VIEW (Cost=10 Card=997 Bytes=35892)
   4    3         TABLE ACCESS (BY INDEX ROWID) OF 'TESTLOG' (Cost=10 Card=997 Bytes=101694)
   5    4           INDEX (RANGE SCAN) OF 'USER_DATE_SEQ' (NON-UNIQUE) (Cost=2 Card=179)
 
 
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec :min := 14200; :max := 14205;
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA9IR2> /
 
LOGUSER    LOGDATE       LOGSEQ KEYFIELD
---------- --------- ---------- ----------
XDB        18-OCT-03        115 XDB$ENUM2_
XDB        18-OCT-03        116 XDB$ENUM_T
XDB        18-OCT-03        117 XDB$ENUM_V
XDB        18-OCT-03        118 XDB$EXTNAM
XDB        18-OCT-03        119 XDB$EXTRA_
XDB        18-OCT-03         12 DBMS_XDBZ
 
6 rows selected.
 
 
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=10 Card=997 Bytes=48853)
   1    0   VIEW (Cost=10 Card=997 Bytes=48853)
   2    1     COUNT (STOPKEY)
   3    2       VIEW (Cost=10 Card=997 Bytes=35892)
   4    3         TABLE ACCESS (BY INDEX ROWID) OF 'TESTLOG' (Cost=10 Card=997 Bytes=101694)
   5    4           INDEX (RANGE SCAN) OF 'USER_DATE_SEQ' (NON-UNIQUE) (Cost=2 Card=179)
 
 

 

5 stars Viable approach   October 18, 2004 - 9am Central time zone
Reviewer: Kim Berg Hansen from Middelfart, Denmark
Yes, I can use that method - particularly when wrapped in a view like that.

Probably I'll wrap the sorting encoding some more...

- function: sortkey(user, date, seq) return varchar2 (returning concatenated sorting string)
- index on sortkey(loguser, logdate, logseq)
- view: select t.*, sortkey(loguser, logdate, logseq) sortedkey from testlog t
- where clauses: select * from v where sortedkey = sortkey(:user, :date, :seq)

...or something like that.

Thanks for pointing me in the direction of another approach.

Often my trouble is that I'm used to thinking in terms of the Concorde XAL ERP system that sits "on 
top of" that Oracle base. In the XAL world there's nothing but simple tables and indexes are only 
on columns.

But then again in the XAL programming language one continually uses an index as a key and scans 
forward in this fashion.

I'm beginning (slowly but surely) to see the strengths of the "set-based" thinking I need to do in 
order to write good SQL (instead of the very much record-based thinking needed to write good XAL 
:-)...
...but one of the things that has always puzzled me is why the SQL language does NOT allow for 
using the composite indexes as key lookups in where clauses somehow... I mean those indexes are 
there and could profitably be used - the language just doesn't support it...

Oh, well - it's just one of those things that "just is", I guess. Perhaps I should try modifying a 
MySql to include that functionality :-)

Anyway, making the index a non-composite index is a viable approach - I can live with that.
 


3 stars   October 19, 2004 - 2pm Central time zone
Reviewer: anto 
In oracle , for my current session I want to retrieve say only the first 10 rows of the select SQL 
each time.(I dont want to give 'where rownum <=10 each time' in the query). Is there any way I can 
do this at the session level in oracle, instead of giving where rownum <= 10 'condition each time  
in the query ? 


Followup   October 19, 2004 - 4pm Central time zone:

no, we always return what you query for - the client would either have to fetch the first ten and 
stop or you add "where rownum <= 10" 

4 stars   October 19, 2004 - 4pm Central time zone
Reviewer: A reader 
Thanks,Tom for confirming this 


4 stars fga   October 20, 2004 - 12am Central time zone
Reviewer: Michael from Germany
I think you can always use fine grained access control (fga)
to append "automatically" a where clause to any table.
In your case "where rownum < 10". Search this site for fga or "fine grained access control". 
That'll do it.

Cheers 


Followup   October 20, 2004 - 7am Central time zone:

whoa -- think about it.

sure, if all you do is "select * from t" -- something that simple (yet so very very very very very 
drastic) would work.

but -- 

select * from emp, dept where...

would become:

select * from ( select * from emp where rownum <= 10 ) emp, 
              ( select * from dept where rownum <= 10 ) dept


suppose that finds 10 emps in deptno = 1000
and 10 depts 10, 20, 30, .... 100


no data found 

 

4 stars Use Views?   October 21, 2004 - 3am Central time zone
Reviewer: Michael from Germany
In that case why not simply create a view and predicate the view? Wouldn't you then have something 
like

select * from 
  (select * from emp,dept where emp.deptno=dept.deptno) <==  View
where rownum < 100?

I think if you allow access to the data only through views (and not through) tables you overcome 
your mentioned problem? 


Followup   October 21, 2004 - 6am Central time zone:

What if you wanted deptno=10

select * from your_view where deptno=10

would have the where rownum done FIRST (get 100 random rows) and then return the ones from that 100 
that are deptno=10 (perhaps NO rows)

no, views / FGA -- they are not solutions to this. 

3 stars it seems there is bug in 9.2.0.1.0 when get rows from M to N   November 1, 2004 - 9pm Central time zone
Reviewer: Steven from BeijIng .China
I think it's a bug.
[code]
SQL> select *from v$version;
BANNER
---------
Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
PL/SQL Release 9.2.0.1.0 - Production
CORE    9.2.0.1.0       Production
TNS for 32-bit Windows: Version 9.2.0.1.0 - Production
NLSRTL Version 9.2.0.1.0 - Production

SQL> create table tt nologging as select rownum rn,b.*from dba_objects b;
SQL> alter table tt  add primary key(rn) nologging;
SQL> create index ttidx on tt(objecT_type,created) nologging;
SQL> analyze table tt compute statistics;

SQL> select /*+ first_rows */*from  (select a.*,rownum as rr from (select *from
tt where object_type='TABLE' order by created) a where rownum<20)where rr>0;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=485 Card=1
          9 Bytes=3857)

   1    0   VIEW (Cost=485 Card=19 Bytes=3857)
   2    1     COUNT (STOPKEY)
   3    2       VIEW (Cost=485 Card=1005 Bytes=190950)
   4    3         SORT (ORDER BY STOPKEY) (Cost=485 Card=1005 Bytes=89
          445)

   5    4           TABLE ACCESS (BY INDEX ROWID) OF 'TT' (Cost=484 Ca
          rd=1005 Bytes=89445)

   6    5             INDEX (RANGE SCAN) OF 'TTIDX' (NON-UNIQUE) (Cost
          =6 Card=1005)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        852  consistent gets
          0  physical reads
          0  redo size
       1928  bytes sent via SQL*Net to client
        514  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
         19  rows processed

SQL> select /*+ first_rows */ a.*,rownum as rr from (select *from tt where objec
t_type='TABLE' order by created) a where rownum<20;
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=484 Card=1
          9 Bytes=190950)

   1    0   COUNT (STOPKEY)
   2    1     VIEW (Cost=484 Card=1005 Bytes=190950)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'TT' (Cost=484 Card=1
          005 Bytes=89445)

   4    3         INDEX (RANGE SCAN) OF 'TTIDX' (NON-UNIQUE) (Cost=6 C
          ard=1005)

Statistics
----------------------------------------------------------
          3  recursive calls
          0  db block gets
         16  consistent gets
          0  physical reads
          0  redo size
       1928  bytes sent via SQL*Net to client
        514  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
         19  rows processed

[/code]

but in  9.2.0.5.0  it's correct

[code]
SQL> select *from v$version;

BANNER
----------------------------------------------------------------
Oracle9i Enterprise Edition Release 9.2.0.5.0 - Production
PL/SQL Release 9.2.0.5.0 - Production
CORE    9.2.0.6.0       Production
TNS for 32-bit Windows: Version 9.2.0.5.0 - Production
NLSRTL Version 9.2.0.5.0 - Production

SQL> create table tt nologging as select rownum rn,b.*from dba_objects b;

SQL>  alter table tt  add primary key(rn) nologging;

SQL> create index ttidx on tt(objecT_type,created) nologging;

SQL> analyze table tt compute statistics;

SQL> select /*+ first_rows */ *from (select a.*,rownum as rr from (select *from
  2  tt where object_type='TABLE' order by created) a where rownum<20)where rr>0;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=64 Card=19
           Bytes=3857)
   1    0   VIEW (Cost=64 Card=19 Bytes=3857)
   2    1     COUNT (STOPKEY)
   3    2       VIEW (Cost=64 Card=395 Bytes=75050)
   4    3         TABLE ACCESS (BY INDEX ROWID) OF 'TT' (Cost=64 Card=
          395 Bytes=31995)

   5    4           INDEX (RANGE SCAN) OF 'TTIDX' (NON-UNIQUE) (Cost=3
           Card=395)
Statistics
----------------------------------------------------------
          8  recursive calls
          0  db block gets
         19  consistent gets
          0  physical reads
          0  redo size
       1907  bytes sent via SQL*Net to client
        514  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          3  sorts (memory)
          0  sorts (disk)
         19  rows processed

SQL> select/*+ first_rows */ a.*,rownum as rr from (select *from
  2  tt where object_type='TABLE' order by created) a where rownum<20;

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=64 Card=19
           Bytes=3610)
   1    0   COUNT (STOPKEY)
   2    1     VIEW (Cost=64 Card=395 Bytes=75050)
   3    2       TABLE ACCESS (BY INDEX ROWID) OF 'TT' (Cost=64 Card=39
          5 Bytes=31995)
   4    3         INDEX (RANGE SCAN) OF 'TTIDX' (NON-UNIQUE) (Cost=3 C
          ard=395)

Statistics
----------------------------------------------------------
          3  recursive calls
          0  db block gets
         17  consistent gets
          0  physical reads
          0  redo size
       1907  bytes sent via SQL*Net to client
        514  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
         19  rows processed
[/code]

but i could not search any infomation from metalink.
hope this can help many people. 


5 stars old question and DBMS_XMLQuery   November 18, 2004 - 1pm Central time zone
Reviewer: A reader 
Tom,
first, thanks a lot as usual!!!
DBMS_XMLQuery package has two interesting procedures: setSkipRows and setMaxRows that look like 
perfect tool for getting specific "data window" from the whole recordset while forming XML. Just 
wondering - is it using the same technic, that you provided in the very beginning of this thread, 
or there is some additional magic behind the scene? In other words - will it also be more slow with 
setSkipRows increasing? 


Followup   November 18, 2004 - 2pm Central time zone:

alter session set sql-trace=true;
do it
tkprof it

and see :)

it is what I would have to do... 

5 stars Thanks for the wonderful solution   December 10, 2004 - 7am Central time zone
Reviewer: san_mat from UK
Hi Tom,
as always you are Great !!
I got what i was looking for..everytime i have some problem..i come to this site & you never 
disappoints for any queries...

Thanks again
 


3 stars Total rows   January 3, 2005 - 5pm Central time zone
Reviewer: Rula from Buenos Aires, Argentina
Naveen from India said

"The devlopers want me to get the total rows that the query return so that they can display that 
many pages. [...] This is two I/O calls to the database and two queries to be parsed to display a 
page. Is there any work around."

And you answered

"I have a very very very very simple solution to this problem. DON'T DO IT."

It is a funny answer, but we had a similar problem here, and that kind of answer was not possible 
(if we meant to keep our jobs...), so we found a work around. Here it is:

select * from
(SELECT COUNT(1) OVER (PARTITION BY 1) total_rows,
        row_number() OVER (ORDER BY <some criteria>) r_number
        <the rest of your query>
) WHERE r_number BETWEEN :1 AND :2

So you may get the first page simultaneously to the total number of rows. It is better than 
executing 2 queries and worse than ignoring the total like google.

I hope it is useful for you.

Regards.
Rula.

 


Followup   January 3, 2005 - 10pm Central time zone:

I did not mean to be funny

I was *DEADLY* serious.

I wrote it in my book.

that 

count(*) over () 

(slightly more meaningful and less confusing than "count(1) over (partition by 1)"  what is UP with 
count(1) -- what is count(1)?  count(*) has meaning -- but I digress)

That trick is something hardware vendors will love you for, but not too many DBA's or people who 
care about performance.


You get the FIRST page for that query after ONLY resolving and getting to the VERY LAST PAGE

(think about it -- how can you get the total row count without- well -- counting the rows!!!!!) 

5 stars Row estimates   January 21, 2005 - 4am Central time zone
Reviewer: Andy from Germany
Hi Tom,

Apologies if this is deemed off-topic! I'm trying to return the estimated cardinality of a query to 
the user (for use with pagination etc.) From what I've read in this thread, there are only two ways 
to do this: either with EXPLAIN PLAN, or by querying the v$ tables directly. I've decided to do the 
latter, so as to be able to take advantage of bind peeking and - if I've understood correctly - as 
it's a bit more efficient. So, I'm using a query like this:

select * from
  (select b.*,rownum rnum
   from (<main query>) b
   where rownum < :max )
where rnum >= :min ;

starting with, say, : max = 51 and :min = 0 if I'm fetching 50 rows at a time. To get the card 
value using EXPLAIN PLAN I would, when I get the first batch of rows, strip away the "batch" stuff 
and send this: 

explain plan for <main query>

The card value is then straightforward as I simply take the value from plan_table where id = 0. But 
I'm not so sure how I get the *right* card value when using v$sql_plan. Because I'm querying 
v$sql_plan for a plan that already exists, how can I get the card value that refers to what would 
have been selected had there been no batching? Example:

mires@WS2TEST> var x varchar2(10)
mires@WS2TEST> exec :x := '1.01.01'

PL/SQL-Prozedur wurde erfolgreich abgeschlossen.

mires@WS2TEST> select * from (select rownumber from fulltext where az = :x) where rownum < 11;

 ROWNUMBER
----------
     37845
     37846
     37847
     37848
     37849
     37850
     37851
     37852
     37853
     37855

10 Zeilen ausgewählt.

mires@WS2TEST> explain plan set statement_id ='my_test_no_batch' for select rownumber from fulltext 
where az = :x;

EXPLAIN PLAN ausgeführt.

mires@WS2TEST> select id, operation, cardinality from plan_table where statement_id = 
'my_test_no_batch';

        ID OPERATION                      CARDINALITY
---------- ------------------------------ -----------
         0 SELECT STATEMENT                       100
         1 TABLE ACCESS                           100
         2 INDEX                                  100


(So with EXPLAIN PLAN I just take the value where id = 0).

mires@WS2TEST> select /* find me */ * from (select rownumber from fulltext where az = :x) where 
rownum < 11;

 ROWNUMBER
----------
     37845
     37846
     37847
     37848
     37849
     37850
     37851
     37852
     37853
     37855

10 Zeilen ausgewählt.

mires@WS2TEST> select id, operation, cardinality from v$sql_plan where (address, child_number) in 
(select address, child_number from v$sql where sql_text like '%find me%' and sql_text not like 
'%sql_text%') order by id;

        ID OPERATION                      CARDINALITY
---------- ------------------------------ -----------
         0 SELECT STATEMENT
         1 COUNT
         2 TABLE ACCESS                           100
         3 INDEX                                  100

In v$sql_plan, card values are not shown for each step. Here it's obvious which card value refers 
to my inner query, but how can I be sure with a more complex query. Does v$sql_plan never display a 
card value for the step which filters out my 10 rows (in which case I can just take the "last" card 
value - i.e. the card value for the lowest id value that has a non-null card value)?

 


Followup   January 21, 2005 - 8am Central time zone:

you want the first one you hit by ID.  it is the "top of the stack", it'll be as close as you 
appear to be able to get from v$sql_plan.


ops$tkyte@ORA9IR2> create table emp as select * from scott.emp;
 
Table created.
 
ops$tkyte@ORA9IR2> create index emp_ename_idx on emp(ename);
 
Index created.
 
ops$tkyte@ORA9IR2> exec dbms_stats.gather_table_stats( user, 'EMP', cascade=>true );
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA9IR2> variable x varchar2(25)
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace view dynamic_plan_table
  2  as
  3  select
  4   rawtohex(address) || '_' || child_number statement_id,
  5   sysdate timestamp, operation, options, object_node,
  6   object_owner, object_name, 0 object_instance,
  7   optimizer,  search_columns, id, parent_id, position,
  8   cost, cardinality, bytes, other_tag, partition_start,
  9   partition_stop, partition_id, other, distribution,
 10   cpu_cost, io_cost, temp_space, access_predicates,
 11   filter_predicates
 12   from v$sql_plan;
 
View created.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> define Q='select * from (select emp.empno, e2.ename from emp, emp e2 where 
e2.ename = :x ) where rownum < 11';
ops$tkyte@ORA9IR2> select * from (select emp.empno, e2.ename from emp, emp e2 where e2.ename = :x ) 
where rownum < 11;
 
no rows selected
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> delete from plan_table;
 
6 rows deleted.
 
ops$tkyte@ORA9IR2> explain plan for &Q;
old   1: explain plan for &Q
new   1: explain plan for select * from (select emp.empno, e2.ename from emp, emp e2 where e2.ename 
= :x ) where rownum < 11
 
Explained.
 
ops$tkyte@ORA9IR2> select * from table(dbms_xplan.display);
 
PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------
 
------------------------------------------------------------------------
| Id  | Operation             |  Name          | Rows  | Bytes | Cost  |
------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |                |    10 |   100 |     3 |
|*  1 |  COUNT STOPKEY        |                |       |       |       |
|   2 |   MERGE JOIN CARTESIAN|                |    14 |   140 |     3 |
|   3 |    TABLE ACCESS FULL  | EMP            |    14 |    56 |     3 |
|   4 |    BUFFER SORT        |                |     1 |     6 |       |
|*  5 |     INDEX RANGE SCAN  | EMP_ENAME_IDX  |     1 |     6 |       |
------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - filter(ROWNUM<11)
   5 - access("E2"."ENAME"=:Z)
 
Note: cpu costing is off
 
19 rows selected.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select * from table( dbms_xplan.display
  2  ( 'dynamic_plan_table',
  3      (select rawtohex(address)||'_'||child_number x
  4         from v$sql
  5        where sql_text='&Q' ),
  6     'serial' ) )
  7  /
old   5:       where sql_text='&Q' ),
new   5:       where sql_text='select * from (select emp.empno, e2.ename from emp, emp e2 where 
e2.ename = :x ) where rownum < 11' ),
 
PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------
 
------------------------------------------------------------------------
| Id  | Operation             |  Name          | Rows  | Bytes | Cost  |
------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |                |       |       |     3 |
|*  1 |  COUNT STOPKEY        |                |       |       |       |
|   2 |   MERGE JOIN CARTESIAN|                |    14 |   140 |     3 |
|   3 |    TABLE ACCESS FULL  | EMP            |    14 |    56 |     3 |
|   4 |    BUFFER SORT        |                |     1 |     6 |       |
|*  5 |     INDEX RANGE SCAN  | EMP_ENAME_IDX  |     1 |     6 |       |
------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - filter(ROWNUM<11)
   5 - access("ENAME"=:X)
 
Note: cpu costing is off
 
and it won't be precisely the same as explain plan gives (but in this case -- 11 would not really 
be in the query, so it would not "know" -- and 11 would actually be wrong for you!  14 is right if 
you think about it, you want to know the estimated size of the entire set, not the set after the 
stopkey processing)

 

5 stars first_rows vs. order by   March 10, 2005 - 4pm Central time zone
Reviewer: VA from New Jersey, USA
In a pagination style query like

select /*+ first_rows */ ... 
from ...
where ...
order by ...

Dont the first_rows and the order by cancel each other out? ORDER BY implies that you need to fetch 
everything to start spitting out the first row. Which is contradictory with first_rows.

So, if I have a resultset that returns 1000 rows and I want to see the first 10 rows ordered by 
something, how would I go about doing this most efficiently knowing that users are going to go away 
after paging down couple of times?

Thanks 


Followup   March 10, 2005 - 7pm Central time zone:

no it doesn't.  think "index" and think "top n processing"

if you can use an index, we can get there pretty fast.

if you cannot - we still can use a top-n optimization to avoid sorting 1000 rows (just have to grab 
the first n rows -- sort them, then every row that comes after that -- just compare to last row in 
array of N sorted rows -- if greater than the last row, ignore it, else put it in the array and 
bump out the last one) 

3 stars Pls explain how it will work with example   March 11, 2005 - 5am Central time zone
Reviewer: Kiran from India
sql>select * 
  2    from ( select   rownum rnum, a.*
  3             from ( select * from emp order by 1 ) a
  4            where rownum <= 15 )
  5   where rnum >= 1
  6  ;

      RNUM      EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM     DEPTNO    
    LOC
---------- ---------- ---------- --------- ---------- --------- ---------- ---------- ---------- 
---
         1       7369 aaat&&maa  CLERK           7902 17-DEC-80       8000        250
         2       7566 NJS        MANAGER         7839 02-APR-81       2975        100
         3       7782 CLARK      MANAGER         7839 09-JUN-81       2450                    10
         4       7788 SCOTT      ANALYST         7566 09-DEC-82       3000
         5       7839 KING       PRESIDENT            17-NOV-81       5000                    10
         6       7876 ADAMS      CLERK           7788 12-JAN-83       1100
         7       7902 FORD       ANALYST         7566 03-DEC-81       3000
         8       7934 MILLER     CLERK           7782 23-JAN-82       1300                    10
         9       7965 AKV        CLERK           7566 20-DEC-83       1020        400         20

9 rows selected.


it looks like normal query, it is not resetting the rownum value, pls explain ur query with example 


Followup   March 11, 2005 - 6am Central time zone:

run the query from the inside out (don't know what you mean by "not resetting the rownum value")

a) take the query select * from emp order by 1
b) then get the first 15 rows (where rownum <= 15) and assign rownum as rnum to each of them
c) then keep only rnum >= 1

to get rows 1..15 of the result set

you should try perhaps 5 .. 10 since emp only has 14 rows. 

5 stars   March 11, 2005 - 10am Central time zone
Reviewer: A reader 
So, if I have a resultset that returns 1000 rows and I want to see the first 10 rows ordered by 
something, how would I go about doing this most efficiently knowing that users are going to go away 
after paging down couple of times?

How would I do this?

Thanks

 


Followup   March 11, 2005 - 10am Central time zone:

i use the pagination style query we were discussing right above.  right on my home page I use this 
query (not again emp of course :) 

5 stars   March 11, 2005 - 11pm Central time zone
Reviewer: salee from chennai india
i wnat tetrive some records out of 3 million records(ie i want to retrive records between 322222 
and322232).using rownum how can i do this 


Followup   March 12, 2005 - 10am Central time zone:

there is no such thing as "record 32222" you know.  You have to have an order by.  but to get rows 
n thru m, see above?  I showed how to do that. 

3 stars delete from table - easiest way   March 22, 2005 - 4pm Central time zone
Reviewer: A reader 
Hi Tom,

I have a table sample as

create table sample
(
num number,
str varchar2(255),
method varchar2(255)
id1 number,
id2 number
);

I have about 32 million rows in this table out of which some rows are duplicated like for eg

num   str      method       id1       id2
1     1        2            201       202
2     1                     201       202

that is, the id1 and id2 of two rows might be duplicated. if that is the case, i want to find out 
such rows and keep one and delete another. is there an easiest way to achieve this?

Thanks.
 


Followup   March 22, 2005 - 6pm Central time zone:

search this site for

duplicates

we've done this one a couple of ways, many times. 

5 stars analytics and index use   April 7, 2005 - 8am Central time zone
Reviewer: bob from PA
Tom,

The query you mentioned above:

select *
  from ( select t.*, row_number() over (partition by store order by customer) rn
           from t
       )
 where rn <= 2;

I can't understand why this requires a full scan for a large table on our system. (9.0.1.4)
If I had a (store,customer) index and the cardinality of store was 5 or so, couldn't the optimizer 
read the first two rows in the index for each store?  That may be overly simplistic, but I don't 
understand why I have to do 2000 consistent gets for a 25k row table and a full scan to accomplish 
this for 10 total rows.

stats are calculated using "analyze table t compute statistics for table for all indexes for all 
indexed columns"

On a related note: while trying to hint with FIRST_ROWS, I noticed the hint changed the answer.

select * from (
select /*+ FIRST_ROWS(5) */ dt, rownum r from t order by dt) 
where r <= 5;

returns all 25k rows, if I dropped the (5) out of the hint, it returned just 5.


 


Followup   April 7, 2005 - 10am Central time zone:

well, you have 10 stores

covering 25,000 rows

so that is 2,500 not "5 or so" all of a sudden.... 

You would need to combine a skip scan of the index with an index range scan and count stopkey.  
Meaning, we'd have to see "oh, there are only about 10 stores, you want the first two after sorting 
by customer.  we'll skip around in the index and just get two from each".  I believe some day it'll 
be that sophisticated, but remember in general these queries are much more complex.

And in your example, you would have had 5,000 stores -- and skip scanning would have be a really 
bad idea.

If a hint changes the results, there is a bug, please contact support (but 9014..) 

5 stars CBO vs. me   April 7, 2005 - 10am Central time zone
Reviewer: bob from PA
Tom,

I always remember you suggesting we should think about how the optimizer might approach a query 
using the methods it has available to it.  I assumed that if my simple mind could think of an 
approach than surely the CBO could implement it.  :)  I understand your point that it might be much 
more complicated in general.

If in this example, I know the stores (and there are 5), I might be better off, writing a union of 
5 queries that each get the first two customers for that store using that concatenated index than 
this analytic approach.  I'll have to test that theory to see.

Thanks for the verification.  I thought I was missing something. With regards to 9014, for some 
reason metalink de-certification notices don't phase the customer.
 


4 stars getting rows N through M of a result set   May 4, 2005 - 7am Central time zone
Reviewer: Hossein Alaei Bavil from Ir. Iran
excellent!
 I think you are in Oracle core !!
but i think why oracle dosen't produce a built in feature for doing this?
 


4 stars usin join or cursors   May 15, 2005 - 1pm Central time zone
Reviewer: mohannad from jordan
i have four tables and i want to use the information from the four tables so what is the most 
effient way , 
1.to create a view joining the four tables 
2.or to create only one database data block using oracle developer and use the post query trigger 
to retrieve the information from the others tables by using cursors or select into. 

my point is that by using post query the cursors or select into is performed only on the records 
fetched from the database (10 recors for example) and when you show more records by moving the 
scroll bar down for example the post query then fires again, but using the join between the tables 
means that oracle should join all the records at once which means taking more time. so which choice 
is better since i am working with a huge tables so time is very important to me.
thanks alot

 


Followup   May 15, 2005 - 1pm Central time zone:

database were born to join
and be written to

a join does not mean that Oracle has to retrieve the last row before it can give you the first at 
all.

use FIRST_ROWS (session setting or hint) if getting the first rows is the most important thing to 
you. 

4 stars join or cursors   May 15, 2005 - 2pm Central time zone
Reviewer: mohannad from jordan
thank you for your quick respond,
but i think that i have  bit conflict in understanding the meaning of paging,as i understand that 
paging means displaying the result of the join only when the user want to display more results 
which can be done by joining the tables at a higher level (using oracle forms for example) an the 
advantege of not joining the tables in the database is that if the user get what he want without 
the need to display all the record so joing the tables at a higher level means that computation 
only occurs when the user wants more results to be displayed, i am right or no????  


Followup   May 15, 2005 - 3pm Central time zone:

you are not correct.

select /*+ FIRST_ROWS */ * 
 from t1, t2, t3, t4, t5, t6, ......
 where t1.key = t2.key
   and .....


you open that query and fetch the first row and only that first row.

Well, the database is going to read a teeny bit of t1, t2, t3, t4, .... and so on.  It is NOT going 
to process the entire thing!

joining does not mean "gotta get all the rows before you get the last".  joins can be done on the 
fly.

say you have:

drop table t1;
drop table t2;
drop table t3;
drop table t4;
                                                                                              
create table t1 as select * from all_objects;
alter table t1 add constraint t1_pk primary key(object_id);
exec dbms_stats.gather_table_stats(user,'T1',cascade=>true);
                                                                                              
create table t2 as select * from all_objects;
alter table t2 add constraint t2_pk primary key(object_id);
exec dbms_stats.gather_table_stats(user,'t2',cascade=>true);
                                                                                              
create table t3 as select * from all_objects;
alter table t3 add constraint t3_pk primary key(object_id);
exec dbms_stats.gather_table_stats(user,'t3',cascade=>true);
                                                                                              
create table t4 as select * from all_objects;
alter table t4 add constraint t4_pk primary key(object_id);
exec dbms_stats.gather_table_stats(user,'t4',cascade=>true);


and you query:

 select /*+ first_rows */  t1.object_name, t2.owner, t3.created, t4.temporary
                 from t1, t2, t3, t4
                where t1.object_id = t2.object_id
                  and t2.object_id = t3.object_id
                  and t3.object_id = t4.object_id

and fetch 100 rows, or you do it yourself:

declare
    cnt number := 0;
begin
    for x in ( select t1.object_name, t1.object_id from t1 )
    loop
        for y in ( select t2.owner, t2.object_id from t2 where object_id = x.object_id)
        loop
            for z in ( select t3.created, object_id from t3 where object_id = y.object_id)
            loop
                for a in ( select t4.temporary from t4 where t4.object_id = z.object_id )
                loop
                    cnt := cnt+1;
                    exit when cnt >= 100;
                end loop;
                exit when cnt >= 100;
            end loop;
            exit when cnt >= 100;
        end loop;
        exit when cnt >= 100;
    end loop;
end;
/


well, tkprof shows:

SELECT /*+ first_rows */ T1.OBJECT_NAME, T2.OWNER, T3.CREATED, T4.TEMPORARY
FROM
 T1, T2, T3, T4 WHERE T1.OBJECT_ID = T2.OBJECT_ID AND T2.OBJECT_ID =
  T3.OBJECT_ID AND T3.OBJECT_ID = T4.OBJECT_ID
                                                                                              
                                                                                              
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.01       0.00          0        611          0         100
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.01       0.00          0        611          0         100
                                                                                              
Misses in library cache during parse: 0
Optimizer mode: FIRST_ROWS
Parsing user id: 108     (recursive depth: 1)
                                                                                              
Rows     Row Source Operation
-------  ---------------------------------------------------
    100  NESTED LOOPS  (cr=611 pr=0 pw=0 time=8091 us)
    100   NESTED LOOPS  (cr=409 pr=0 pw=0 time=5346 us)
    100    NESTED LOOPS  (cr=207 pr=0 pw=0 time=3194 us)
    100     TABLE ACCESS FULL T1 (cr=5 pr=0 pw=0 time=503 us)
    100     TABLE ACCESS BY INDEX ROWID T2 (cr=202 pr=0 pw=0 time=1647 us)
    100      INDEX UNIQUE SCAN T2_PK (cr=102 pr=0 pw=0 time=801 us)(object id 67372)
    100    TABLE ACCESS BY INDEX ROWID T3 (cr=202 pr=0 pw=0 time=1464 us)
    100     INDEX UNIQUE SCAN T3_PK (cr=102 pr=0 pw=0 time=659 us)(object id 67374)
    100   TABLE ACCESS BY INDEX ROWID T4 (cr=202 pr=0 pw=0 time=1433 us)
    100    INDEX UNIQUE SCAN T4_PK (cr=102 pr=0 pw=0 time=637 us)(object id 67376)


we only do the WORK WE NEED to do, as you ask us.  And if you compare the work done here with the 
work you would make us do:


                                                                                              
SELECT T1.OBJECT_NAME, T1.OBJECT_ID FROM T1
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00          0          5          0         100
********************************************************************************
SELECT T2.OWNER, T2.OBJECT_ID FROM T2 WHERE OBJECT_ID = :B1
                                                                                              
call     count       cpu    elapsed       disk      query    current        rows ------- ------  
-------- ---------- ---------- ---------- ----------  ----------
total      201      0.01       0.01          0        300          0         100
********************************************************************************
SELECT T3.CREATED, OBJECT_ID FROM T3 WHERE OBJECT_ID = :B1
                                                                                              
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      201      0.00       0.04          0        300          0         100
********************************************************************************
SELECT T4.TEMPORARY FROM T4 WHERE T4.OBJECT_ID = :B1
                                                                                              
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      201      0.06       0.01          0        300          0         100



You would do in 905 IO's what we did in 611, you would have many back and forths, binds an 
executes, where as we would have one.


IF you can do it in a single query, please -- do it.
 

5 stars select /*+ FIRST_ROWS */ *   May 15, 2005 - 4pm Central time zone
Reviewer: mohannad from jordan
i can not understand what is the differnce between 

1.select * from items,invoice_d
where items.itemno=invoice_d.itemno;

and
2.select  /*+ FIRST_ROWS */ * 
from items,invoice_d
where items.itemno=invoice_d.itemno;

they give me the same result and the same number of records fetched each time (as i understand that 
using /*+ FIRST_ROWS */ * means fetching certain number of records but i can understand how i did 
not find any difference between the query with first_rows or without it)

Thanks a lot.. 


Followup   May 15, 2005 - 8pm Central time zone:

if it gave you different answers, that would be a bug.

The plans should be different.  The first query will optimize to find ALL ROWS as efficiently as 
possible, the second to return the first rows as soon as it can.

the first optimizes for throughput.
the second for intial response time:


ops$tkyte@ORA10G> create table items( itemno number primary key, data char(80) );
 
Table created.
 
ops$tkyte@ORA10G> create table invoice( id number, itemno references items, data char(80),
  2  constraint invoice_pk primary key(id,itemno) );
 
Table created.
 
ops$tkyte@ORA10G>
ops$tkyte@ORA10G> exec dbms_stats.set_table_stats( user, 'ITEMS', numrows => 100000 );
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA10G> exec dbms_stats.set_table_stats( user, 'INVOICE', numrows => 1000000 );
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA10G>
ops$tkyte@ORA10G> set autotrace traceonly explain
ops$tkyte@ORA10G> select *
  2    from items, invoice
  3   where items.itemno = invoice.itemno;
 
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=ALL_ROWS (Cost=5925 Card=1000000 Bytes=195000000)
   1    0   HASH JOIN (Cost=5925 Card=1000000 Bytes=195000000)
   2    1     TABLE ACCESS (FULL) OF 'ITEMS' (TABLE) (Cost=31 Card=100000 Bytes=9500000)
   3    1     TABLE ACCESS (FULL) OF 'INVOICE' (TABLE) (Cost=50 Card=1000000 Bytes=100000000)
 
 
 
ops$tkyte@ORA10G> select /*+ FIRST_ROWS */ *
  2    from items, invoice
  3   where items.itemno = invoice.itemno;
 
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=HINT: FIRST_ROWS (Cost=101481 Card=1000000 Bytes=195000000)
   1    0   NESTED LOOPS (Cost=101481 Card=1000000 Bytes=195000000)
   2    1     TABLE ACCESS (FULL) OF 'INVOICE' (TABLE) (Cost=50 Card=1000000 Bytes=100000000)
   3    1     TABLE ACCESS (BY INDEX ROWID) OF 'ITEMS' (TABLE) (Cost=1 Card=1 Bytes=95)
   4    3       INDEX (UNIQUE SCAN) OF 'SYS_C0010764' (INDEX (UNIQUE)) (Cost=0 Card=1)
 
 
 
ops$tkyte@ORA10G> set autotrace off


the hash join will wait until it reads the one table fully, hashes it -- once it does that, you'll 
start getting rows.

The second one returns rows IMMEDIATELY, but will take longer to return the last row. 

5 stars dffernce between first_rows(n) and all_rows   May 15, 2005 - 7pm Central time zone
Reviewer: mohannad from jordan
what is the main difference between  first_rows(n) and all_rows,as i understand that first_rows(10) 
for example retrive the first 10 rows very fast,but as i understand that if i want to retrive all 
the records then i should avoid using first_rows and instead i should use all_rows what does 
all_rows do???.  


Followup   May 15, 2005 - 8pm Central time zone:

all rows = optimize to be able to get the last row as fast as possible. you might wait for the 
first row for a while, but all rows will be returned faster.

first rows = get first row as fast as possible.  getting to the last row might take lots longer 
than with all rows, but we have an end user waiting to see data so get the first rows fast


use all rows for non-interactive things (eg: print this report)
use first rows for things that end users sit and wait for (paging through a query on the web for 
example) 

5 stars performance issue   May 16, 2005 - 9am Central time zone
Reviewer: mohannad from Jordan
i have two tables with large amount of records

>>desc working
empno
start_date
.
.
.
with empno as the promary key

>>desc working_hestory
empno
hestory_date
.
.
. 
with empno & hestory_date as the primary key

and i want all empno from working table where their stardate < max of their hestory_date i write 
the following two queries
but i found the second is two time faster than the first i want to know what is the reasons???,some 
people told me that the optimizer will optimize the two queries so that they will have the same 
speed but when i use the two queries i found the second query faster than the first so what is the 
reson ,and is there any general rule about that

1.select * from working where start_date<(select max(hestory_date) from working_hestory
where working.empno=working_hestory.empno)


2.select * from working , (select empno,max(hestory_date) from working_hestory where empno 
in(select empno from working) a
where 
a.empno=working.empno
and
start_date<hestory_date;

Thanks A lot



  


Followup   May 16, 2005 - 12pm Central time zone:

read the plans.  they will be very different.

you would probably make it even faster with

select * 
  from working, (select empno, max(history_date) 
                   from working_history
                  group by empno) a
 where working.empno = a.empno
   and working.start_date > a.history_date;

 

5 stars performance issue   May 16, 2005 - 1pm Central time zone
Reviewer: mohannad from Jordan
but what is the reasons behind the difference between the two quires????
is their any general guidline for this difference. 


Followup   May 16, 2005 - 1pm Central time zone:

I cannot see the plans, you can....

 

5 stars performance issue   May 16, 2005 - 1pm Central time zone
Reviewer: mohannad from Jordan
i mean if there is any guidline that i can use when i write any sql quuery without the use of plans 
,if there a rule to use join rather than subquery for example. 


Followup   May 16, 2005 - 1pm Central time zone:

if you gain a conceptual understanding of how the query will likely be processed, that would be 
good -- understand what happens, how it happens (access paths are discussed in the performance 
guide, I wrote about them in effective oracle by design as well)

but if you use the CBO, it'll try rewriting them as much as it can -- making the difference between 
the two less and less.  No idea what optimizer you were using however.

Also, knowledge of the features of sql available to you (like analytic functions) is key to being 
successful. 

5 stars Best of the Best   May 20, 2005 - 8am Central time zone
Reviewer: A Reader from UK
Howdy,

Thanks for sharing your knowledge with us.

Cheers 


5 stars Partition   May 20, 2005 - 8am Central time zone
Reviewer: Mohit from UK
Hi Tom,

Hope you are in good spirits!

Tom, where I can read some more stuff like the one below:

--------------------------------
select *
  from ( select t.*, row_number() over (partition by store order by customer) rn
           from t
       )
 where rn <= 2;
---------------------------------

I have never seen this kind of the logic in any of the SQL books i have read so far. Can you 
suggest any book or documentation for learning/reading knowledgable thinhs like the above please?

Thanks Tom,
Mohit
 


Followup   May 20, 2005 - 10am Central time zone:

Expert One on One Oracle - chapter on analytics.
Effective Oracle by Design ....

On otn.oracle.com -> data warehousing guide.

been in the database since 8.1.6 

4 stars question about paging   June 9, 2005 - 10am Central time zone
Reviewer: James Su from toronto
hi Tom,

We have a large transactions table with indexes on trans_id (primary key) and trans_time, now I am 
trying to display the transactions page by page. The startdate and enddate is specified by user and 
passed from front end (usually the first and last day of the month). The front end will also 
remember the trans_id of the last row of the page and pass it to the database in order to fetch the 
next page.

main logic:

...........

begin

  if p_direction='pagedown' then   -- going to next page
     v_sql := 'select trans_id,trans_time,trans_amount from mytransactions where trans_time between 
:1 and :2 and trans_id<=:3 order by trans_id desc';
  else                             -- going to last page
     v_sql := 'select trans_id,trans_time,trans_amount from mytransactions where trans_time between 
:1 and :2 and trans_id>=:3 order by trans_id';
  end if;
  
  open c_var for v_sql using p_startdate,p_enddate,p_last_trans_id;

  i :=0;
  
  loop
     FETCH c_var INTO v_row;

     i := i + 1;

     EXIT WHEN c_var%NOTFOUND or i>30;    -- 30: rows per page

     -- add v_row into the array
     
  end loop;
    
  close c_var;
  
  -- return array to the front end
  ...........
end;
/

in this way, if the user can input a trans_id then we can help him to locate to that page.

Can you tell me whether there's a better approach? The performance seems not good. Thank you very 
much. 


Followup   June 9, 2005 - 11am Central time zone:

first rows hint it AND use rownum to get the number of rows you want

select *
  from ( select /*+ FIRST_ROWS */ ..... order by trans_id )
 where rownum <= 30;


that'll be the query you want -- use static SQL (no need for dynamic) here.  Bulk collect the rows 
and be done


if ( pagedown )
then
   select .... BULK COLLECT into ....
     from ( select ... )
   where rownum <= 30;
else
   select .......
end if;

 

5 stars first_rows hint works!   June 9, 2005 - 11am Central time zone
Reviewer: James Su from toronto
hi Tom,
It's amazing, thank you so much!! 


4 stars first_row hint on views   June 9, 2005 - 12pm Central time zone
Reviewer: James Su from toronto
sorry tom, I forgot to mention that mytransactions is actually a view, which is the union all of 
current table and archived table. Now the problem is:
If I have the trans_id =1 in the archived table, then:
select /*+ FIRST_ROWS */ trans_id from mytransactions where rownum<=30 and trans_id>=1 order by 
trans_id;

it will return the trans_id in the current table, which is greater than 1.

What can I do with this situation? Thank you. 


Followup   June 9, 2005 - 6pm Central time zone:

you cannot do that regardless.

to "top-n" an ordered set, you MUST:

select *
  from ( select /*+ first_rows */ .... ORDER BY .... )
 where rownum <= 30;

and if it is in a union all view -- it isn't going to be excessively "first rows friendly" 

3 stars When is Rownum applied   July 7, 2005 - 5pm Central time zone
Reviewer: A reader 
Hello,
     Is rownum applied after order by clause or as the rows are fetched 

select * from (
select deptno ,rownum r from dept order by deptno )
where r = 1


 


Followup   July 7, 2005 - 6pm Central time zone:

that assigns rownum to the data from dept AND THEN sorts it AND THEN keeps the first row that 
happend to come from dept before it was sorted.

eg:

select deptno from dept where rownum=1;

would be the same but faster.

if the want the first after sorting

select * from (select deptno from dept order by deptno) where rownum = 1;

(in this case, actually, select min(deptno) from dept :) 

5 stars Please help me with a query   August 9, 2005 - 3am Central time zone
Reviewer: reader from india
Hi Tom,

I have a table "xyz" where TDATE and BOOKNAME are the columns in it .

The output of the table is like this when i do a "select * from xyz". 


                                       
TDATE           BOOKNAME
--------------- ----------             
16-MAY-05         kk6 
16-MAY-05         kk6
16-MAY-05         kk6


17-MAY-05         kk7           
17-MAY-05         kk7           
17-MAY-05         kk7           
17-MAY-05         kk7           
17-MAY-05         kk7           
17-MAY-05         kk7           



I would like to have the output like the below.Please help me with a sql  which will give me the 
number of times a distinct BOOKNAME value is present per TDATE value



TDATE           BOOKNAME        count(*)
--------------- ----------      ----------       
16-MAY-05         kk7           3
17-MAY-05         kk7           6


Thanks in advance 


Followup   August 9, 2005 - 9am Central time zone:

homework?  (sorry, this looks pretty basic)

look up trunc in the sql reference manual, you'll probably have to trucn the TDATE to the day 
level:

ops$tkyte@ORA10G> alter session set nls_date_format = 'dd-mon-yyyy hh24:mi:ss';
                                                                                                    
                                                                  
Session altered.
                                                                                                    
                                                                  
ops$tkyte@ORA10G> select sysdate, trunc(sysdate) from dual;
                                                                                                    
                                                                  
SYSDATE              TRUNC(SYSDATE)
-------------------- --------------------
09-aug-2005 09:38:08 09-aug-2005 00:00:00
                                                                                                    
                                                                  


In order to lose the time component.  And then read up on group by and count(*).

Now, I don't know why your output has kk7 twice, I'll assume that is a typo. But this is a very 
simple group by on the trunc of tdate and bookname with a count. 

5 stars ROWNUM performance   August 22, 2005 - 4pm Central time zone
Reviewer: Tony from Canada
Tom,
     Thanks a lot for your help and valueable time. I have a very simple query (looks simple) but 
it takes more than 4 mins to execute, 

select  * from (select L.LEG_ID from leg_t L WHERE
   L.STAT_ID = 300 AND 
   L.LEG_CAT = 2 AND 
   L.D_CD = 'CIS' AND
   L.P_ID is null order by L.LEG_ID desc)
 where rownum <= 16;

LEG_ID is the primary key(PK_LEG), I also have index(leg_i1) on (STAT_ID,LEG_CAT,D_CD,P_ID,leg_id 
desc).

Now if I run this query as is it takes about 4-5 mins and the plan is:

SELECT STATEMENT   Cost = 90
  COUNT STOPKEY
    VIEW
      TABLE ACCESS BY INDEX ROWID LEG_T
        INDEX FULL SCAN DESCENDING PK_LEG
The query doesn't use the leg_i1 index..shouldn't it?

Secondly if I run the internal query:

select L.LEG_ID from leg_t L WHERE
   L.STAT_ID = 300 AND 
   L.LEG_CAT = 2 AND 
   L.D_CD = 'CIS' AND
   L.P_ID is null order by L.LEG_ID desc

it uses the index leg_i1 and comes back in milli-seconds.

I tried the rule hint on the query and it come back in milliseconds again instead of 4-5 minutes.( 
I can't use hints in the application.)

Please guide. 


Followup   August 24, 2005 - 3am Central time zone:

it is trying to do first rows here (because of the top-n, the rownum) and the path to first rows is 
to use that index to "sort" the data, read the data sorted.

But apparently, you have to search LOTS of rows to find the ones of interest - hence it takes 
longer.

either try all_rows optimization OR put leg_id on the leading edge of that index instead of way at 
the end 

5 stars ROWNUM performance   August 29, 2005 - 4pm Central time zone
Reviewer: Tony from Canada
Tom,
     Thanks a lot for your valuable time, I tried the index as you suggested but still the 
optimizer doesn't pick it,(default optimizer mode is all_rows). 

This table(leg_t) contains about one million rows, and STAT_ID(not null) column contains just 8 
distinct values, 
LEG_CAT(not null) column contains just 2 distinct value
D_CD   )not null) column contains just 1 distinct value

I can't use bitmap index, what other option do you recommend so that the optimizer pick up the 
index (as it does when the mode is RULE) please help.

 


Followup   August 30, 2005 - 1am Central time zone:

hint it all rows in the inline view. (it sees the rownum..) 

5 stars ROWNUM performance   August 30, 2005 - 10am Central time zone
Reviewer: Tony from Canada
Tom,
     Thanks again, I tried all_rows as you suggested but still it doesn't pick the index, it still 
goes for the primary key index, which takes 5 minutes. Here is the plan with all_rows:

SELECT STATEMENT   Cost = 10
  COUNT STOPKEY
    VIEW
      TABLE ACCESS BY INDEX ROWID LEG_T
        INDEX FULL SCAN DESCENDING PK_LEG

Do you suggest histograms for such columns, which columns are the nest candidates for histograms 
(if you think that can help) 

Please help, I even tried to play with optimizer_index_caching, optimizer_index_cost_adj  
parameters but couldn't get better results.  


Followup   August 30, 2005 - 12pm Central time zone:

select * 
  from (
select * 
  from (select /*+ no_merge */ L.LEG_ID  
          from leg_t L 
         WHERE L.STAT_ID = 300 
           AND L.LEG_CAT = 2 
           AND L.D_CD = 'CIS'  
           AND L.P_ID is null
        ) 
  order by L.LEG_ID desc
        )
 where rownum <= 16;

 

5 stars FIRST_ROWS   September 7, 2005 - 11am Central time zone
Reviewer: Jon Roberts 
I had implemented the suggested solution some time back and when it finally got to production it 
was rather slow when using an order by in the inner most query.  
We allow users to sort by a number of columns and when sorting, it would run much slower. Using 
autotrace, I could see that I had the same plan but with the larger production table, it had more 
data to search and it took longer to do the full table scan.

I created indexes on the columns people sort by but it wouldn't use the indexes. I just re-read 
this discussion and found your suggestion of using the first_rows hint.  That did the trick. It 
uses the indexes now and everything is nice and fast.

Thanks for the great article! 


5 stars Excellent Thread   November 3, 2005 - 1pm Central time zone
Reviewer: Manas from India
Thanks Tom.
Before going through this thread, I was thinking to implement the pagination using ref cursor 
(dynamic) and Bulk collect. 


4 stars How to find the record count of a ref cursor ?   December 5, 2005 - 6pm Central time zone
Reviewer: VKOUL from WA, USA
Hi Tom,

Is it possible ? (kind of collection.count)

procedure (par1 in number, par2 out refcursor, par3 out number) is
begin
    . . .
    open par2 for select . . .;
    at this point how can I get the number of records in par2.
    par3 := number of records;
end;
/
 


Followup   December 6, 2005 - 5am Central time zone:

you cannot, no one KNOWS what the record count is until.....

you've fetch the last row.


consider this:


   open rc for select * from ten_billion_row_table;


it is not as if we copy the entire result set someplace, in fact, we typically do no work to open a 
cursor (no IO is performed), it is not until you actually start asking for data that we start 
getting it and we have no idea how many rows will be returned until they are actually returned.

No use in doing work that you might well never be asked to do. 

5 stars   December 23, 2005 - 4am Central time zone
Reviewer: A reader 
Awesome !!! 


4 stars Page through a ref cursor using bulk collect   January 13, 2006 - 6am Central time zone
Reviewer: Barry Chase from Shelbyville, KY USA
Can I use your bulkcollect and first rows logic while building a refcursor that I pass back to a 
front end which permits them to page through a large dataset 10,25,50 records at a time while still 
maintaining performance and the end as well as the first part of the query ? 


Followup   January 13, 2006 - 11am Central time zone:

don't use bulk collect - just return the ref cursor and let the front end array fetch as it needs 
rows. 

4 stars Follow up question   January 14, 2006 - 10am Central time zone
Reviewer: Barry C from Shelbyville, KY USA
Okay,no on bulk collecting. Our frontend is pulling back several thousand records potentially. I 
would prefer that they apply more criteria, but our administrative users have decided that they 
feel differently. Needless to say, for a webpage, the performance is far from 5-10 seconds return 
for all of the records. They say this is unacceptable. I tried the min max row thing and it works 
great at the early part of the query, but performance progressively gets worse as I go down the 
result set... say...show me recs 900-950. 

So I am supposed to come up with a solution for which I am not sure there is a solution. Any 
thoughts or commentary ?
 


Followup   January 15, 2006 - 3pm Central time zone:

only give them a NEXT button and no "page 55" button.

Do it like google.  Ask your end users to go to page 101 of this search:

http://www.google.com/search?q=oracle&start=0&ie=utf-8&oe=utf-8&client=firefox-a&rls=org.mozilla:en-
US:official
also, ask them to read the time to develop each page as well as they try to get there.

tell them "google = gold standard, if they don't do it, neither will I"

I give you a next button, nothing more.  
Google lets you hit 10 at a time, nothing more.

And google will say this:
Sorry, Google does not serve more than 1000 results for any query.

if you try to go page page 100. 

4 stars Further enhancement   January 16, 2006 - 5am Central time zone
Reviewer: A reader from London, UK
Hi Tom,

excellent thread. In addition to getting M..N rows I would also like to add column sorting (the 
column header will be a link). How can I do this efficiently?

Thanks

RP 


Followup   January 16, 2006 - 9am Central time zone:

read original answer?  I had "including order by...."?? 

3 stars   January 16, 2006 - 12pm Central time zone
Reviewer: A reader from London, UK
..with the potential to use any of the columns in the table that means either i create a set of sql 
statements, one for each column (plus ASC or DESC) or I generate the sql statement dynamically.

If i do it dynamically would i not lose the magic of *bind variables*?

Apologies, that was what i meant to ask.

R

 


Followup   January 16, 2006 - 12pm Central time zone:

you would not lose the magic of bind variables, you would however have a copy of the sql statement 
for each unique sort combination (which is not horrible, unless you have hundreds/thousands of such 
sort orders) 

3 stars   January 16, 2006 - 1pm Central time zone
Reviewer: A reader 
and if the number of static statements got too large, could i do it dynamically like this:

http://asktom.oracle.com/pls/ask/f?p=4950:8:14563657761463309193::NO::F4950_P8_DISPLAYID,F4950_P8_CR
ITERIA:1288401763279
?? Is it relevent in this context(No pun intended)?

R 


Followup   January 16, 2006 - 1pm Central time zone:

Yes, there are many ways to bind

a) static sql in plsql does it nicely
b) sys_context with open refcursor for....
c) dbms_sql - with dbms_sql.bind_variable
d) open refcursor for .... USING <when you know the number of binds>


you could also:


   order by decode( p_input, 1, c1 ) ASC, decode( p_input, -1, c1 ) DESC, 
            decode( p_input, 2, c2 ) ASC, decode( p_input, -2, c2 ) DESC,
            ....

in order to have one order by statement - you would never be able to use an index to retrieve the 
data "sorted" (but you might not be able to anyway in many cases)...
 

5 stars what if your query get info back from more than one table?   February 9, 2006 - 10pm Central time zone
Reviewer: Michelle from Lehigh Valley, PA
What would the syntax look like?
Thank you! 


Followup   February 10, 2006 - 12pm Central time zone:

I assume you are referring to the query right above this?

select a.c1, a.c2, b.c3, b.c4, ....
  from a,b....
 where ....
 order by decode( p_input, 1, a.c1 ) ASC, decode( p_input, -1, a.c1 ) DESC, 
            decode( p_input, 2, a.c2 ) ASC, decode( p_input, -2, a.c2 ) DESC,
            decode( p_input, 3, b.c3 ) ASC, decode( p_input, -3, b.c3 ) DESC,
            ....


not any different than if there was one table really. 

5 stars Get the total   March 1, 2006 - 11am Central time zone
Reviewer: Nitai from Switzerland
Hi Tom

How can I get the total of all found records with this query:

SELECT rn, id
FROM (
     SELECT ROWNUM AS rn, id
     FROM (
           SELECT id
           FROM test
           )
          WHERE ROWNUM <= 30
     )
WHERE rn > 0

I tried to put count(rn) in there but that only returns me the 30 records (of course) but what I 
need is the total records this query found. Is this even possible within the same query? Thank you 
for your kind help.
 


Followup   March 1, 2006 - 1pm Central time zone:

why?

goto google, search for oracle, tell me if you think their count is accurate.  then, goto page 101 
of the search results and tell me what the first link is. 

2 stars   March 1, 2006 - 4pm Central time zone
Reviewer: nitai from Switzerland
Call me stupid, but what is your point. When I go to Google and enter Oracle I get this:

Results 411 - 411 of about 113,000,000 for oracle

I get to go until page 43 and that's it. Ok, that means it is not possible?

All I really need is how many total found records there are (meaning the 113,000,00 in the case of 
the google search) :-) 


Followup   March 2, 2006 - 9am Central time zone:

do you think that google actually counted the results?

No, they don't 

There is no page 101 on google.


They don't let you go that far.


My point - made many times - counting the exact number of hits to paginate through a result set on 
the web is "not smart"

I refuse to do it.

I won't show how.

It is just a way to burn CPU like mad, make everything really really really slow.

 

3 stars Got your point   March 2, 2006 - 9am Central time zone
Reviewer: Nitai from Switzerland
Ok Tom, got your point. Ok, but what about if I have a ecommerce site and customers are searching 
for a product. They would want to know how many products they found, thus I would need that number 
of the overall found records.

At the moment I would have to run the query two times, one that gets me the total number and one 
with the rownum > 50 and so on. I don't think that is very performant either.

What else to do? 


Followup   March 2, 2006 - 12pm Central time zone:

Just tell them "you are looking at 1 thru 10 of more than 10"

Or guess - just like I do, google does.  Give them a google interface - look at google as the gold 
standard here.  Google ran out of pages and didn't get upset or anything - if you tried to goto 
page 50, it just put you on the last page.


You DO NOT EVER need to tell them

you are looking at 1 through 10 of 153,531 items

Just tell them, here is 1 through 10, there are more, next will get you to them.

Or give them links to the first 10 pages (like google) and if they click on page 10 but there isn't 
a page 10, show them the last page and then only show them pages 1..N in the click links.

Be like google.

Sorry, not going to tell you how to burn cpu like mad, this is one of my pet peeves - this counting 
stuff. 

5 stars 10gR2 optimizer problem   March 10, 2006 - 3am Central time zone
Reviewer: A reader 
Hi Tom,

I hardly believed when I've seen it. This is a tkprof of same query second time it has select * 
from (<original_query) arround. Can you give us hint what might be happening here.

SELECT
               a.*, ROWNUM AS rnum
          FROM (SELECT   /*+first_rows*/
                         s.userid, s.username, s.client_ip, s.timestamp_,
                         s.DURATION, s.calling_station_id,
                         s.called_station_id, s.acct_terminate_cause,
                         s.nas_port_type
                    FROM dialin_sessions s
                   WHERE s.client_ip LIKE '213.240.3.%'
                     AND s.username LIKE 'c%'
                     AND s.timestamp_end >= 1136070000
                     AND s.timestamp_ <= 1138751940
                ORDER BY timestamp_ DESC) a
         WHERE ROWNUM <= 26

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.06       0.06          0       6801          0          25
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.06       0.07          0       6801          0          25

Misses in library cache during parse: 1
Optimizer mode: FIRST_ROWS
Parsing user id: 63

Rows     Row Source Operation
-------  ---------------------------------------------------
     25  COUNT STOPKEY (cr=6801 pr=0 pw=0 time=74387 us)
     25   VIEW  (cr=6801 pr=0 pw=0 time=74303 us)
     25    TABLE ACCESS BY INDEX ROWID DIALIN_SESSIONS (cr=6801 pr=0 pw=0 time=74221 us)
   7050     INDEX RANGE SCAN DESCENDING DIALIN_SESSIONS_TIMESTAMP (cr=21 pr=0 pw=0 time=14187 
us)(object id 53272)


Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                       2        0.00          0.00
  SQL*Net message from client                     2        6.39          6.39
********************************************************************************


SELECT *
  FROM (SELECT
               a.*, ROWNUM AS rnum
          FROM (SELECT   /*+first_rows*/
                         s.userid, s.username, s.client_ip, s.timestamp_,
                         s.DURATION, s.calling_station_id,
                         s.called_station_id, s.acct_terminate_cause,
                         s.nas_port_type
                    FROM dialin_sessions s
                   WHERE s.client_ip LIKE '213.240.3.%'
                     AND s.username LIKE 'c%'
                     AND s.timestamp_end >= 1136070000
                     AND s.timestamp_ <= 1138751940
                ORDER BY timestamp_ DESC) a
         WHERE ROWNUM <= 26)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1     34.45      68.05     267097     325479          0          25
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3     34.45      68.06     267097     325479          0          25

Misses in library cache during parse: 1
Optimizer mode: FIRST_ROWS
Parsing user id: 63

Rows     Row Source Operation
-------  ---------------------------------------------------
     25  VIEW  (cr=325479 pr=267097 pw=0 time=68055294 us)
     25   COUNT STOPKEY (cr=325479 pr=267097 pw=0 time=68055230 us)
     25    VIEW  (cr=325479 pr=267097 pw=0 time=68055196 us)
     25     SORT ORDER BY STOPKEY (cr=325479 pr=267097 pw=0 time=68055118 us)
  12268      TABLE ACCESS FULL DIALIN_SESSIONS (cr=325479 pr=267097 pw=0 time=23052374 us)


Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                       2        0.00          0.00
  SQL*Net message from client                     2       17.54         17.55
  db file sequential read                      6768        0.02          3.62
  db file scattered read                      28978        0.07         41.07
  latch: cache buffers lru chain                  1        0.00          0.00
******************************************************************************** 


Followup   March 10, 2006 - 12pm Central time zone:

you would have to provide a little more context.


what happened in between these two tkprofs that I assume were taken at different times. 

5 stars Optimizer problem   March 13, 2006 - 4am Central time zone
Reviewer: Zeljko Vracaric from Belgrade, Serbia and Montenegro
No, it was one session. Queries are only one in that database session. I was trying to optimize one 
of ours most used php scripts (migrating to oracle from sybase). I analyzed 10053 trace that day. 
But the only thing I've spotted is that in final section optimizer goal for second query was all 
rows not first_rows. I tried to change optimizer mode by alter session and I got same results. It 
is the first_rows that is essential for query taking plan with index that enables stop key to stop 
processing after 25 rows that match criteria.
It is very complicated script because it has to answer lot of really different questions. For 
instance give me all sessions that were active in some point in time and on the other hand give me 
all sessions in long period of time matching some criteria. We have to detect intersection of two 
intervals and avoid FTS or index scan on millions of rows, finding criteria to limit number of rows 
processed. Optimizing it is of course subject for another thread. But this problem with simple 
inline view was unexpected.
 


5 stars date java question   March 24, 2006 - 8pm Central time zone
Reviewer: winny from Hongkong
Create a Date class with the following capabilities:
     a) Output the date in multiple formats such as
            DDD YYYY
      MM/DD/YY
      June 14, 1992
     b) Use overloaded constructors to create Date objects initialized with dates of the formats in 
part (a).
Hint : you can compare strings using method equals. Suppose you have two string references s1 and 
s2. if those strings are equal s1. equals (s2) returns true. Otherwise, it returns false
 


4 stars 10gR2 linux another similar problem   March 27, 2006 - 3am Central time zone
Reviewer: Zeljko Vracaric from Belgrade, Serbia
Hi Tom,

I've found another similar problem with select * from (<query>). This time I used autotrace to 
document it. It looks like bug in optimizer using wrong cardinalities, or we are doing something 
very wrong in out php project.

BILLING@dev> select * from ecm_invoiceitems;

253884 rows selected.


Execution Plan
----------------------------------------------------------
Plan hash value: 4279212659

--------------------------------------------------------------------------------------
| Id  | Operation         | Name             | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |                  |   253K|    49M|   542   (8)| 00:00:03 |
|   1 |  TABLE ACCESS FULL| ECM_INVOICEITEMS |   253K|    49M|   542   (8)| 00:00:03 |
--------------------------------------------------------------------------------------


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
     253920  consistent gets
          0  physical reads
          0  redo size
  188764863  bytes sent via SQL*Net to client
  107899015  bytes received via SQL*Net from client
     761644  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
     253884  rows processed

BILLING@dev> select a.*,rownum as rnum from(
  2          select  /*+first_rows 
*/i.invoiceid,i.invoice_number,ii.product_name,p.amount,co.company,i.time_invoice, c.code as 
customer_code, i.customerid, i.statusid,i.cost_total
  3  from ecm_invoiceitems ii,cm_customers c, cm_contacts co,ecm_invoices i
  4  left join ecm_payments_invoices ip on ( i.invoiceid=ip.invoiceid)
  5  left join ecm_payments p on ( p.paymentid=ip.paymentid )
  6  where
  7  i.invoiceid=ii.invoiceid and i.customerid = c.customerid and c.contactid = co.contactid and 
co.type_ = 'PERSON' and ((p.paymentid is null and i.cost_total between 200-1 and 200+1) or 
p.amount=200)
  8  and (p.paymentid is null or p.is_success in ('U', 'S'))
  9  and i.statusid not in (0,303) and time_invoice>to_date('2005-11-01','yyyy-mm-dd')
 10  order by i.statusid desc, p.amount,i.time_invoice,i.invoiceid
 11  ) a where rownum<25;


Execution Plan
----------------------------------------------------------
Plan hash value: 440181276

----------------------------------------------------------------------------------------------------
------------------------
| Id  | Operation                             | Name                       | Rows  | Bytes 
|TempSpc| Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------------------
------------------------
|   0 | SELECT STATEMENT                      |                            |    24 |  6936 |       
| 35762   (1)| 00:02:27 |
|*  1 |  COUNT STOPKEY                        |                            |       |       |       
|            |          |
|   2 |   VIEW                                |                            | 33450 |  9440K|       
| 35762   (1)| 00:02:27 |
|*  3 |    SORT ORDER BY STOPKEY              |                            | 33450 |  5749K|    
11M| 35762   (1)| 00:02:27 |
|   4 |     TABLE ACCESS BY INDEX ROWID       | ECM_INVOICEITEMS           |     1 |    56 |       
|     1   (0)| 00:00:01 |
|   5 |      NESTED LOOPS                     |                            | 33450 |  5749K|       
| 34658   (1)| 00:02:23 |
|*  6 |       FILTER                          |                            |       |       |       
|            |          |
|   7 |        NESTED LOOPS OUTER             |                            | 24819 |  2908K|       
| 28438   (1)| 00:01:57 |
|   8 |         NESTED LOOPS OUTER            |                            | 24819 |  2593K|       
| 22219   (1)| 00:01:32 |
|   9 |          NESTED LOOPS                 |                            | 24751 |  2296K|       
| 16019   (1)| 00:01:06 |
|  10 |           NESTED LOOPS                |                            | 24751 |  1571K|       
|  9817   (1)| 00:00:41 |
|* 11 |            TABLE ACCESS BY INDEX ROWID| ECM_INVOICES               | 24751 |   966K|       
|  3615   (1)| 00:00:15 |
|* 12 |             INDEX RANGE SCAN          | INVOICES_TIME_INVOICE      | 24856 |       |       
|    17   (0)| 00:00:01 |
|  13 |            TABLE ACCESS BY INDEX ROWID| CM_CUSTOMERS               |     1 |    25 |       
|     1   (0)| 00:00:01 |
|* 14 |             INDEX UNIQUE SCAN         | CM_CUSTOME_5955332052      |     1 |       |       
|     1   (0)| 00:00:01 |
|* 15 |           TABLE ACCESS BY INDEX ROWID | CM_CONTACTS                |     1 |    30 |       
|     1   (0)| 00:00:01 |
|* 16 |            INDEX UNIQUE SCAN          | CM_CONTACT_17544893292     |     1 |       |       
|     1   (0)| 00:00:01 |
|* 17 |          INDEX RANGE SCAN             | ECM_PAYMEN_800553712       |     1 |    12 |       
|     1   (0)| 00:00:01 |
|  18 |         TABLE ACCESS BY INDEX ROWID   | ECM_PAYMENTS               |     1 |    13 |       
|     1   (0)| 00:00:01 |
|* 19 |          INDEX UNIQUE SCAN            | ECM_PAYMEN_9475344592      |     1 |       |       
|     1   (0)| 00:00:01 |
|* 20 |       INDEX RANGE SCAN                | ECM_INVOICEITEMS_INVOICEID |     1 |       |       
|     1   (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------
------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter(ROWNUM<25)
   3 - filter(ROWNUM<25)
   6 - filter(("P"."PAYMENTID" IS NULL AND "I"."COST_TOTAL">=199 AND "I"."COST_TOTAL"<=201 OR 
"P"."AMOUNT"=200) AND
              ("P"."PAYMENTID" IS NULL OR ("P"."IS_SUCCESS"='S' OR "P"."IS_SUCCESS"='U')))
  11 - filter("I"."STATUSID"<>0 AND "I"."STATUSID"<>303)
  12 - access("I"."TIME_INVOICE">TO_DATE('2005-11-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss'))
  14 - access("I"."CUSTOMERID"="C"."CUSTOMERID")
  15 - filter("CO"."TYPE_"='PERSON')
  16 - access("C"."CONTACTID"="CO"."CONTACTID")
  17 - access("I"."INVOICEID"="IP"."INVOICEID"(+))
  19 - access("P"."PAYMENTID"(+)="IP"."PAYMENTID")
  20 - access("I"."INVOICEID"="II"."INVOICEID")


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
     131721  consistent gets
          0  physical reads
          0  redo size
       1134  bytes sent via SQL*Net to client
        385  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
          1  rows processed

BILLING@dev> select * from
  2  (select a.*,rownum as rnum from(
  3          select  /*+first_rows 
*/i.invoiceid,i.invoice_number,ii.product_name,p.amount,co.company,i.time_invoice, c.code as 
customer_code, i.customerid, i.statusid,i.cost_total
  4  from ecm_invoiceitems ii,cm_customers c, cm_contacts co,ecm_invoices i
  5  left join ecm_payments_invoices ip on ( i.invoiceid=ip.invoiceid)
  6  left join ecm_payments p on ( p.paymentid=ip.paymentid )
  7  where
  8  i.invoiceid=ii.invoiceid and i.customerid = c.customerid and c.contactid = co.contactid and 
co.type_ = 'PERSON' and ((p.paymentid is null and i.cost_total between 200-1 and 200+1) or 
p.amount=200)
  9  and (p.paymentid is null or p.is_success in ('U', 'S'))
 10  and i.statusid not in (0,303) and time_invoice>to_date('2005-11-01','yyyy-mm-dd')
 11  order by i.statusid desc, p.amount,i.time_invoice,i.invoiceid
 12  ) a where rownum<25
 13  );


Execution Plan
----------------------------------------------------------
Plan hash value: 1216454693

----------------------------------------------------------------------------------------------------
-------------
| Id  | Operation                              | Name                   | Rows  | Bytes | Cost 
(%CPU)| Time     |
----------------------------------------------------------------------------------------------------
-------------
|   0 | SELECT STATEMENT                       |                        |     2 |   604 |  1147   
(2)| 00:00:05 |
|   1 |  VIEW                                  |                        |     2 |   604 |  1147   
(2)| 00:00:05 |
|*  2 |   COUNT STOPKEY                        |                        |       |       |           
 |          |
|   3 |    VIEW                                |                        |     2 |   578 |  1147   
(2)| 00:00:05 |
|*  4 |     SORT ORDER BY STOPKEY              |                        |     2 |   352 |  1147   
(2)| 00:00:05 |
|   5 |      CONCATENATION                     |                        |       |       |           
 |          |
|*  6 |       FILTER                           |                        |       |       |           
 |          |
|   7 |        NESTED LOOPS OUTER              |                        |     1 |   176 |     7   
(0)| 00:00:01 |
|   8 |         NESTED LOOPS                   |                        |     1 |   163 |     6   
(0)| 00:00:01 |
|   9 |          NESTED LOOPS OUTER            |                        |     2 |   266 |     5   
(0)| 00:00:01 |
|  10 |           NESTED LOOPS                 |                        |     2 |   242 |     4   
(0)| 00:00:01 |
|  11 |            NESTED LOOPS                |                        |     1 |    96 |     3   
(0)| 00:00:01 |
|  12 |             TABLE ACCESS FULL          | ECM_INVOICEITEMS       |   198 | 11088 |     2   
(0)| 00:00:01 |
|* 13 |             TABLE ACCESS BY INDEX ROWID| ECM_INVOICES           |     1 |    40 |     1   
(0)| 00:00:01 |
|* 14 |              INDEX UNIQUE SCAN         | ECM_INVOIC_14275361692 |     1 |       |     1   
(0)| 00:00:01 |
|  15 |            TABLE ACCESS BY INDEX ROWID | CM_CUSTOMERS           |     1 |    25 |     1   
(0)| 00:00:01 |
|* 16 |             INDEX UNIQUE SCAN          | CM_CUSTOME_5955332052  |     1 |       |     1   
(0)| 00:00:01 |
|* 17 |           INDEX RANGE SCAN             | ECM_PAYMEN_800553712   |     1 |    12 |     1   
(0)| 00:00:01 |
|* 18 |          TABLE ACCESS BY INDEX ROWID   | CM_CONTACTS            |     1 |    30 |     1   
(0)| 00:00:01 |
|* 19 |           INDEX UNIQUE SCAN            | CM_CONTACT_17544893292 |     1 |       |     1   
(0)| 00:00:01 |
|  20 |         TABLE ACCESS BY INDEX ROWID    | ECM_PAYMENTS           |     1 |    13 |     1   
(0)| 00:00:01 |
|* 21 |          INDEX UNIQUE SCAN             | ECM_PAYMEN_9475344592  |     1 |       |     1   
(0)| 00:00:01 |
|* 22 |       FILTER                           |                        |       |       |           
 |          |
|  23 |        NESTED LOOPS OUTER              |                        |     1 |   176 |    35   
(0)| 00:00:01 |
|  24 |         NESTED LOOPS                   |                        |     1 |   163 |    34   
(0)| 00:00:01 |
|  25 |          NESTED LOOPS                  |                        |     2 |   266 |    33   
(0)| 00:00:01 |
|  26 |           NESTED LOOPS OUTER           |                        |     2 |   216 |    32   
(0)| 00:00:01 |
|  27 |            NESTED LOOPS                |                        |     2 |   192 |    31   
(0)| 00:00:01 |
|  28 |             TABLE ACCESS FULL          | ECM_INVOICEITEMS       |   198 | 11088 |     2   
(0)| 00:00:01 |
|* 29 |             TABLE ACCESS BY INDEX ROWID| ECM_INVOICES           |     1 |    40 |     1   
(0)| 00:00:01 |
|* 30 |              INDEX UNIQUE SCAN         | ECM_INVOIC_14275361692 |     1 |       |     1   
(0)| 00:00:01 |
|* 31 |            INDEX RANGE SCAN            | ECM_PAYMEN_800553712   |     1 |    12 |     1   
(0)| 00:00:01 |
|  32 |           TABLE ACCESS BY INDEX ROWID  | CM_CUSTOMERS           |     1 |    25 |     1   
(0)| 00:00:01 |
|* 33 |            INDEX UNIQUE SCAN           | CM_CUSTOME_5955332052  |     1 |       |     1   
(0)| 00:00:01 |
|* 34 |          TABLE ACCESS BY INDEX ROWID   | CM_CONTACTS            |     1 |    30 |     1   
(0)| 00:00:01 |
|* 35 |           INDEX UNIQUE SCAN            | CM_CONTACT_17544893292 |     1 |       |     1   
(0)| 00:00:01 |
|  36 |         TABLE ACCESS BY INDEX ROWID    | ECM_PAYMENTS           |     1 |    13 |     1   
(0)| 00:00:01 |
|* 37 |          INDEX UNIQUE SCAN             | ECM_PAYMEN_9475344592  |     1 |       |     1   
(0)| 00:00:01 |
----------------------------------------------------------------------------------------------------
-------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter(ROWNUM<25)
   4 - filter(ROWNUM<25)
   6 - filter("P"."AMOUNT"=200 AND ("P"."PAYMENTID" IS NULL OR ("P"."IS_SUCCESS"='S' OR
              "P"."IS_SUCCESS"='U')))
  13 - filter("I"."TIME_INVOICE">TO_DATE('2005-11-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss') AND
              "I"."STATUSID"<>0 AND "I"."STATUSID"<>303)
  14 - access("I"."INVOICEID"="II"."INVOICEID")
  16 - access("I"."CUSTOMERID"="C"."CUSTOMERID")
  17 - access("I"."INVOICEID"="IP"."INVOICEID"(+))
  18 - filter("CO"."TYPE_"='PERSON')
  19 - access("C"."CONTACTID"="CO"."CONTACTID")
  21 - access("P"."PAYMENTID"(+)="IP"."PAYMENTID")
  22 - filter(("P"."PAYMENTID" IS NULL OR ("P"."IS_SUCCESS"='S' OR "P"."IS_SUCCESS"='U')) AND
              "P"."PAYMENTID" IS NULL AND LNNVL("P"."AMOUNT"=200))
  29 - filter("I"."COST_TOTAL"<=201 AND "I"."TIME_INVOICE">TO_DATE('2005-11-01 00:00:00', 
'yyyy-mm-dd
              hh24:mi:ss') AND "I"."COST_TOTAL">=199 AND "I"."STATUSID"<>0 AND "I"."STATUSID"<>303)
  30 - access("I"."INVOICEID"="II"."INVOICEID")
  31 - access("I"."INVOICEID"="IP"."INVOICEID"(+))
  33 - access("I"."CUSTOMERID"="C"."CUSTOMERID")
  34 - filter("CO"."TYPE_"='PERSON')
  35 - access("C"."CONTACTID"="CO"."CONTACTID")
  37 - access("P"."PAYMENTID"(+)="IP"."PAYMENTID")


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
    1214653  consistent gets
          0  physical reads
          0  redo size
       1134  bytes sent via SQL*Net to client
        385  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
          1  rows processed

BILLING@dev>


first query plan is ok but second is very wrong. We have a lot queries like this in out web 
application we try to migrate from sybase. I'd hate to hint queries like this, is there any other 
solution? 


Followup   March 27, 2006 - 9am Central time zone:

can you tell me what exactly is wrong - given that I spend seconds looking at review/followups and 
only look at them once.

 

4 stars select * from (<query>)   March 28, 2006 - 2am Central time zone
Reviewer: Zeljko Vracaric from Belgrade, Serbia
Hello Tom,

Problem is that optimizer changes query plan when we put select * from () around it. I'm sorry I 
didn't point it clearly.

 I can not reproduce it on small and simple example. So I sent real examples from out application 
in previous posts. We use a lot construction like this you recommended.

select * 
  from ( select a.*, rownum rnum
           from ( YOUR_QUERY_GOES_HERE -- including the order by ) a
          where rownum <= MAX_ROWS )
 where rnum >= MIN_ROWS

but, 


select a.*, rownum rnum
           from ( YOUR_QUERY_GOES_HERE -- including the order by ) a
          where rownum <= MAX_ROWS 

has good query plan and it's performance is ok. However putting select * from () with or without 
where rnum>1 query plan is changed and I believe it is very wrong.

 Tracing 10053 on query in my first post (one with tkprof) I found that probably optimizer goal was 
changed from first_rows to all_rows. I'm not sure because I'm not expert in debugging 10053 trace. 
In my later post (autotrace) I found another query with similar problem and looking at the plan I 
believe that cardinality for full scan of ecm_invoiceitems table was wrong but statistics were good 
and I included select * from ecm_invoiceitems in post to proof that. So basicly in my previous post 
I have 3 queries with autotrace.

select * from ecm_invoiceitem to show that optimizer knows cardinality.

select ...(complex query with ecm_invoiceitems in from) with correct plan for first_rows hint

select * from (select ...(complex query with ecm_invoiceitems in from)) this has wrong plan, plan 
is different than previous.

I’m surprised with this third query plan. I expected to be same as plan without select * from () 
around.

 So trying to be short I wrote another large post. Keeping short and explain things in simple 
manner is a talent, I think you have that gift and that’s why your book and site is very popular.

Zeljko
 


3 stars Is it possible in SQL or PL/SQL ?   April 4, 2006 - 2am Central time zone
Reviewer: Parag Jayant Patankar from India
Hi Tom,

I am using Oracle 9.2 database. I am having following data

drop table toto;
create table toto
(
r         char(10)
)
organization external
(
type oracle_loader
default directory data_dir
access parameters
(
    records delimited by newline
    logfile data_dir:'toto.log'
)
location ('pp.sysout')
)
reject limit unlimited
/

In pp.sysout I am having following data

A
B
C
D=10
E
F
G
A
B
C
D=20
E
F
G
H
I
A
B
C
D=20
E
F
G
H
A
B
C
D=30
E
F
G
H

I want set of results in a different spool file starting from 'A' upto next 'A' where value of 'D' 
is unique.

For e.g. 
1. spool file xxx.1 will contain
A
B
C
D=10
E
F
G

2. spool file xxx.2 will contain ( it will have two sets because D=20 appearing twice in data )

A
B
C
D=20
E
F
G
H
I
A
B
C
D=20
E
F
G
H

3. spool file xxx.3 will contain

A
B
C
D=30
E
F
G
H

Kindly let me know is it possible to do that ? if yes pl show me how.

thanks & regards
pjp 


Followup   April 4, 2006 - 9am Central time zone:

I don't know of a way to do that in sqlplus - not with the multiple spools.

 

4 stars It is possible   April 6, 2006 - 3am Central time zone
Reviewer: Michel Cadot from France
Hi,

Put the following in a file and execute it.

col sp fold_after
break on sp
set head off
set feed off
set pages 0
set recsep off
set colsep off
spool t
with
  t2 as ( 
    select col,
           case 
             when instr(col, '=') != 0
               then to_number(substr(col,instr(col,'=')+1))
           end value,
           rownum rn,
           max(case when col = 'A' then rownum end)
             over (order by rownum) grp
    from t 
  ),
  t3 as (
    select col,
           max(value) over (partition by grp) value,
           rn, grp
    from t2
  ),
  t4 as (
    select col, value,
           max(grp) 
             over (partition by value order by rn
                   rows between unbounded preceding and unbounded following)
             grp
    from t3
  )
select 'spool file'||value sp,
       'prompt '||col
from t4
/
prompt spool off
spool off
@t.lst

It does not work if you have the same D value but in non consecutive groups.
Spool file names contain D value instead of consecutive number.

Regards
Michel
 


Followup   April 6, 2006 - 10am Central time zone:

interesting workaround - write a single spool that is itself a sqlplus script that does a spool and 
echo for each file :) 

3 stars The whole world   April 6, 2006 - 10am Central time zone
Reviewer: Michel Cadot from France
Give us SQL*Plus, case expression, instr, substr and analytic functions, connect by and we can 
handle the SQL world with the help of the model clause from time to time. :))

Generating SQL or SQL*Plus script with SQL in SQL*Plus is one of my favorite tools with "new_value" 
on column to generate polymorphic queries.

Cheers
Michel
 


4 stars   April 21, 2006 - 1pm Central time zone
Reviewer: A reader 
Hi Tom,

In your reply to the initial post in this thread, for paging results you suggested the query

select * 
  from ( select a.*, rownum rnum
           from ( YOUR_QUERY_GOES_HERE -- including the order by ) a
          where rownum <= MAX_ROWS )
 where rnum >= MIN_ROWS
/

I have something like this for one of our web applications. It works fine but the only problem I am 
facing is when MAX_ROWS = 20 and MIN_ROWS= 1 the query returns almost instantaneously (~2 secs). 
But if I want to browse to the last page in the web page then my MAX_ROWS = 37612 and MIN_ROWS = 
37601 then the query is taking some time (~18 secs). Is this expected behaviour?

Thanks for your help.
 


Followup   April 21, 2006 - 3pm Central time zone:

sure - just like on google - google "oracle" and then look at the time to return each page.

first - tell us how long for page 1, and then for page 99.

and tell us how long for page 101 :)


If you want the "last page" - to me you really mean "i want the first page, after sorting the data 
properly"


No one - NO ONE is going to hit page down that many times (and if you give them a last page button 
- that is YOUR FAULT - stop doing that, have them sort the opposite way and get the FIRST PAGE).  
Look at google - do what they do. 

5 stars ORDER BY in inner query   May 10, 2006 - 12pm Central time zone
Reviewer: Viorel Hobinca from Toronto, Canada
In 

select * 
  from ( select a.*, rownum rnum
           from ( YOUR_QUERY_GOES_HERE -- including the order by ) a
          where rownum <= MAX_ROWS )
 where rnum >= MIN_ROWS

does the ORDER BY in the inner query have to include a primary key or some unique field? 

We ran into a problem  where subsequent pages returned the same result set when the ORDER BY clause 
had only one field with low distribution. We plan on adding a primary key or rowid to the ORDER BY 
but I'm wondering if there are other ways. We use Oracle 10g. 


Followup   May 11, 2006 - 8am Central time zone:

the order by should have something "unique" about it - good to point that out.

Else - the order of the rows with the same order by key would be indeterminate and could vary! 

4 stars ORDER BY ROWID   May 11, 2006 - 11am Central time zone
Reviewer: A reader 
If we use "order by rowid" are we going to get the same result s each time we run the query (even 
if the table has no primary key)? 


Followup   May 11, 2006 - 7pm Central time zone:

as long as the rowid is unique sure. 

4 stars Ref: ORDER BY in inner query   May 12, 2006 - 10am Central time zone
Reviewer: A reader 
Is ORDER BY *required* in the inner query? I'm wondering if Oracle can guarantee the order of the 
result set if no order is specified. With no such guarantee the paging will produce indeterminate 
results ... 


Followup   May 12, 2006 - 9pm Central time zone:

if you don't use an order by (and one that says "this is definitely row 42, no other row can be 
42"), then rows "100-110" could change everytime you ask for them.

And - it would be "correct"  

5 stars FIRST_ROWS(n)   May 16, 2006 - 3pm Central time zone
Reviewer: Su Baba 
Does the "n" in FIRST_ROWS(n) hint represent the number of records I want to have returned. If the 
following query always returns 50 records, should n be set to 50?

SELECT *
FROM (
   SELECT /*+ FIRST_ROWS(50) */ a.*, rownum r
   FROM  (YOUR QUERY GOES HERE) a
   WHERE  rownum < :max_row
   )
WHERE r >= :min_row;

 


Followup   May 16, 2006 - 3pm Central time zone:

it represents the number of records to be returned. 

5 stars Row Count   June 6, 2006 - 7pm Central time zone
Reviewer: Su Baba 
You had mentioned above that to get the estimated count of a "result set M through N" SQL, you can 
do one of the following:

"If you use text, you can approximate the result set size.
If you use the CBO and 9i, you can get the estimated cardinality from v$SQL_PLAN"

How does an application use v$sql_plan to get the row count?  How is this actually implemented?

thanks
 


Followup   June 6, 2006 - 9pm Central time zone:

you query it? 

4 stars Thanks   June 16, 2006 - 2pm Central time zone
Reviewer: Rakesh Sreenivasa from USA
great !

Rakesh 


5 stars Very Impressed!!!   July 6, 2006 - 4pm Central time zone
Reviewer: Tom Persaud from Queens, NY USA
Tom,
Your solution was simple and useful.  You seem to enjoy solving problems and helping others.  I am 
sure this takes a lot of your time.  Like a good listener, you seem to evaluate each question and 
comment.  Your directness is also admirable.  In my opinion, you have a God-given gift and you 
share it freely and openly.  I envy you in that I wish I could be more like you - in skill and 
attitude. 


3 stars 8.0.5 Solution   August 4, 2006 - 9am Central time zone
Reviewer: Mal from New York, NY USA
This code works for me in 8.0.5

select * from 
( select rownum rx, title, id from TABLE where rownum < 5 ) 
where rx > 1  


Followup   August 4, 2006 - 12pm Central time zone:

sure, but add "order by" which is typically used in this construct (doesn't make so much sense to 
paginate through unordered data) 

3 stars Previous example inaccurate   August 4, 2006 - 10am Central time zone
Reviewer: Mal from New York, NY USA
I posted a little too quickly, 8.0.x doesn't support order clauses in subselect, so while the above 
example is true, it's not very helpful. 


5 stars different (weird) results when used in stored function   August 15, 2006 - 6am Central time zone
Reviewer: Johann Tagle from Manila, Philippines
Hi Tom,

I'm developing a search program on 8.1.7.  When I execute the following:

select ID, list_name from
   (select ID, list_name, rownum as number_row from
      (select distinct b.id ID,
decode(b.preferred_name,null,b.default_name,b.preferred_name) list_name
from bizinfo b, bizlookup l
where contains(l.keywords, 'computer equipment and supplies')>0
        and b.id = l.id
        order by list_name)
    where rownum <= 5)
where number_row >= 1;

I get something like:
        ID LIST_NAME
---------- --------------------------------------------
     63411 2A Info
     65480 ABACIST
       269 ABC COMPUTER
     97285 ACCENT MICRO
     97286 ACCENT MICRO - SM CITY NORTH

However, if I put the same SQL to a stored function:

CREATE Function GETSAMPLEBIZ ( v_search IN varchar2, startpage IN number, endpage IN number)
  RETURN  MYTYPES.REF_CURSOR IS
  RET MYTYPES.REF_CURSOR;
BEGIN
  OPEN RET FOR
    select ID, list_name from
    (select ID, list_name, rownum as number_row from
        (select distinct b.id as ID, decode(b.preferred_name,null,b.default_name,b.preferred_name) 
list_name
        from bizinfo b, bizlookup l
        where contains(l.keywords, v_search)>0
        and b.id = l.id
        order by list_name
        )
    where rownum <= endpage
    )
    where number_row >= startpage;

   return RET;
END;

(MYTYPES.REF_CURSOR defined elsewhere)

then run:
SQL> var ref refcursor;
SQL> exec :ref := getsamplebiz('computer equipment and supplies',1,5);
SQL> print ref;

I get:

        ID :B1
---------- --------------------------------
     63411 computer equipment and supplies
     65480 computer equipment and supplies
       269 computer equipment and supplies
     97285 computer equipment and supplies
     97286 computer equipment and supplies

Based on the ID column, the result set is the same, but what's supposed to be list_name is replaced 
by my search parameter.

I can't figure out what's wrong with this.  Would appreciate any suggestion.  

Thanks!

Johann 


Followup   August 15, 2006 - 8am Central time zone:

I'd use support for that one.  it is obviously "not right" 

4 stars a case to upgrade to 10g?   August 15, 2006 - 10am Central time zone
Reviewer: Johann Tagle from Manila, Philippines
Hi Tom,

Thanks for the response.  However, 8.1.7 is no longer supported, right?  Tried it on my development 
copy of 10g and its working well there.  Hmmm, this might be a good addition to the case for 
upgrading to 10g I'm helping my client develop.  Without this I either have give up the benefits of 
using a stored function or have the front end application go through every row until it gets to the 
relevant "page", which would be inefficient.

Thanks again,

Johann 


5 stars Performance trade-off?   September 11, 2006 - 2pm Central time zone
Reviewer: Mahmood Lebbai from Bothell,WA
Tom,

In the query you gave us for the initial question, 

select *  from ( select a.*, rownum rnum
from ( YOUR_QUERY_GOES_HERE -- including the order by ) a
where rownum <= MAX_ROWS ) where rnum >= MIN_ROWS

You said the inner query would fetch the maximum records we would be interested in and afterwards 
it would cut off the required records from the result set.

But consider this situation where,say, we got 3 million records and I would like to fetch some 
records in some order and take out some range of records say 2999975 to 2999979 (just five 
records). According to your query, the inner query will select 2999979 records (it looks quite 
unnecessary) and then select the five records.It looks some what odd. What is your justification on 
this? 

I was wondering on this whether there might be any performance trade off on this.

Thanks.
 


Followup   September 11, 2006 - 2pm Central time zone:

this is for pagination through a result set on the web.

goto google.

search for Oracle.

Now, goto page 101.  Tell me what you see on that page?

Nothing, there is no page 101 - google tells you "don't be SILLY, stop it, search better, get with 
the program, I am NOT going to waste MY resources on such a question"

We should do the same.

I would seriously ask you "what possible business reason could you justify gettnig those five 
records - and do you possibly thing you really mean to order by something DESC so that instead of 
getting the last five, you get the first five???"


This is optimized to produce an answer on screen as soon as possible.  No one would hit the page 
down button that many times. 

Look to google, they are the "gold standard" for searching, they got this pagination thing down 
right. 

5 stars   September 26, 2006 - 11pm Central time zone
Reviewer: Wayne Khan from Singapore, SG SG
Hi Tom,
At first I got bamboozled by the subqueries, but this is  great, it worked.

:) 


4 stars your query worked with a small problem   October 18, 2006 - 2pm Central time zone
Reviewer: Soumak from Mumbai India
What the fetching bit did as I understood was that it executed the entire query and then selected 
rows N to M (M>N)from the resultset. Howeever, is there any way that the query stops execution and 
returns me the resultset when the limit M has been reached ? 

I do not thing I can use rownum in such case. Any alternate suggestions? 

I was using a HQL (Hibernate) where two methods setMaxResults() and setFirstResult() did that for 
me. Any equivalent in SQL? 

 


Followup   October 18, 2006 - 3pm Central time zone:

the query DOES stop when it gets M-N+1 rows??? not sure at all what you mean. 

5 stars Excellent, but be aware   October 19, 2006 - 7am Central time zone
Reviewer: Keith Jamieson from Dublin, Ireland
Hi Tom

(ORACLE 10g release 2) 

I'm trying to convince the Java Team here that this is the correct approach to use, to page through 
a result set. 

They like this solution, with one small exception.
If they insert a record, or remove a record, or if the column value that is being ordered by 
changes, then potentially the results of their previous/next pagination may change.  (I'm assuming 
the changes were committed in another session, though the example below is all in one session).

So essentially, they are saying 'What happened to my user'
SQL> select *
  2    from ( select a.*, rownum rnum
  3             from ( select ename,hiredate  from scott.emp order by ename ) a
  4            where rownum <= 5 ) -- max rows
  5   where rnum >= 1-- min_rows
  6  /

ENAME      HIREDATE        RNUM
---------- --------- ----------
ADAMS      23-MAY-87          1
ALLEN      20-FEB-81          2
BLAKE      01-MAY-81