6

Table has surrogate primary key generated from sequence. Unfortunately, this sequence is used for generating keys for some other tables (I did not designed it and I cannot change it).

What is the fastest way to select last n inserted records in Oracle, ordered by id in descending order (last inserted on top)?

n is some relatively small number - number of records to display on page - probably not bigger than 50.

Table now has 30.000.000 records with 10-15 thousands of new records daily.

Database is Oracle 10g.

Edit:
In answer to one comment: This question was motivated with execution plan for query:

  select * from MyTable order by primarykeyfield desc

Execution plan was:

--------------------------------------------- 
| Id  | Operation          | Name        |     
---------------------------------------------  
|   0 | SELECT STATEMENT   |             |
|   1 |  SORT ORDER BY     |             |
|   2 |   TABLE ACCESS FULL| MyTable     |
---------------------------------------------  

I was surprised that Oracle wants to perform full table scan and sorting when it has index on sort field.

Query from accepted answer uses index and avoids sort.

Edit 2:
Re. APC's comment: Sorting was part that surprised me. I expected that Oracle would use index to retrieve rows in expected order. Execution plan for query:

select * from (select * from arh_promjene order by promjena_id desc) x 
   where rownum < 50000000

uses index instead of full table access and sort (notice condition rownum < 50.000.000 - this is way more than number of records in table and Oracle knows that it should retrieve all records from table). This query returns all rows as first query, but with following execution plan:

| Id  | Operation                     | Name         | 
-------------------------------------------------------
|   0 | SELECT STATEMENT              |              | 
|*  1 |  COUNT STOPKEY                |              | 
|   2 |   VIEW                        |              | 
|   3 |    TABLE ACCESS BY INDEX ROWID| MyTable      | 
|   4 |     INDEX FULL SCAN DESCENDING| SYS_C008809  | 

Predicate Information (identified by operation id):    
---------------------------------------------------    

   1 - filter(ROWNUM<50000000)                         

It was unusual to me that Oracle is creating different execution plans for these two queries that essentially return same result set.

Edit 3: Re Amoq's comment:

Oracle doesn't know that 50M is greater than the number of rows. Sure, it has statistics, but they could be old and wrong - and Oracle would never allow itself to deliver an incorrect result only because the statistics are wrong.

Are you sure? In Oracle versions up to 9 it was recommended to manually refresh statistics from time to time. Since version 10 Oracle automatically updates statistics. What's the use of statistics data if Oracle does not use it for query optimization?

zendar
  • 13,384
  • 14
  • 59
  • 75
  • Why is it unfortunate that the sequence is also used for other tables? It doesn't matter for the query to retrieve the last n inserted records because a sequence is never garantueed to be gapless by Oracle. So you can't do a simple where id between max-n and max anyway. – tuinstoel Dec 01 '09 at 19:00
  • Your query selects *all* columns for *all* rows. Why are you surprised that Oracle does a ful table scan? How else would it get the data to satisfy that query? – APC Dec 02 '09 at 11:11
  • Oracle doesn't *know* that 50M is greater than the number of rows. Sure, it has statistics, but they could be old and wrong - and Oracle would *never* allow itself to deliver an incorrect result only because the statistics are wrong. – Erich Kitzmueller Dec 02 '09 at 12:19

5 Answers5

15

Use ROWNUM:

select
  *
from
  (
    select
      *
    from
      foo
    order by
      bork
   ) x
where
  ROWNUM <= n

Note that rownum is applied before sorting for a subquery, that's why you need the two nested queries, otherwise you'll just get n random rows.

Donnie
  • 45,732
  • 10
  • 64
  • 86
  • This implies that bork is sortable... Is that compatible with the OP? It very well could be but if so it would be odd that the OP would ask such a simple question. – Mark Canlas Dec 01 '09 at 18:55
  • 1
    If we take bork to be the sequence-generated primary key the OP mentioned, and assuming it's monotonically increasing, then this query will work. – Dan Dec 01 '09 at 19:41
  • it works, but if the data is big, it selects and orders millions of records, and just takes top 10, it is totally dissappointing that I can't get just last 10 records of them in a short time like mssql. is there no other way for it? – mrTurkay Oct 31 '16 at 09:31
  • @TMoon - For most tables, you can just sort by the PK or an indexed date column and use the `top` clause. Being forced to use rownum in this way is an artifact of some very bad design decisions from the original question. – Donnie Nov 02 '16 at 17:11
4

In cases where you don't have a strictly increasing field, you could also use ORA_ROWSCN (system change number) as an approximation of this.

select * from (select * from student order by ORA_ROWSCN desc) where rownum<10

Caution: this is not exact, since Oracle records only one SCN per block, not per row. Also it seems to do a full table scan - probably oracle is not smart enough to optimize this kind of sort. So this might not be a good idea for production use.

Dr. Hans-Peter Störr
  • 25,298
  • 30
  • 102
  • 139
4

Will it be viewed many more times than it is updated? How about keeping another table of the IDs of the last N inserted rows (use a trigger to delete the smallest ID from this table and add a new row with the current-inserted).

You now have a table that records the IDs of the last N inserted rows. Any time you want the N, just join it to the main table. If N changes, pick the max it can be, and then filter it after... of course you may find it not so fast for your app (maintenance of this table may negate any performance gain)

debracey
  • 6,517
  • 1
  • 30
  • 56
cjard
  • 41
  • 1
3

This may help you if you don't know the name of the fields or anything other than table name....

select * from (
  select * from(
    select rownum r,student.* from student where rownum<=(
      select max(rownum) from student
    )
  ) order by r desc
 ) where r<=10;
Grambot
  • 4,370
  • 5
  • 28
  • 43
3

Try doing a index_desc hint

select /*+ index_desc(MyTable,<PK_index>) */ * from MyTable order by primarykeyfield desc
shank
  • 31
  • 1