142

I am having a hard time converting stored procedures from SQL Server to Oracle to have our product compatible with it.

I have queries which returns the most recent record of some tables, based on a timestamp :

SQL Server:

SELECT TOP 1 *
FROM RACEWAY_INPUT_LABO
ORDER BY t_stamp DESC

=> That will returns me the most recent record

But Oracle:

SELECT *
FROM raceway_input_labo 
WHERE  rownum <= 1
ORDER BY t_stamp DESC

=> That will returns me the oldest record (probably depending on the index), regardless the ORDER BY statement!

I encapsulated the Oracle query this way to match my requirements:

SELECT * 
FROM 
    (SELECT *
     FROM raceway_input_labo 
     ORDER BY t_stamp DESC)
WHERE  rownum <= 1

and it works. But it sounds like a horrible hack to me, especially if I have a lot of records in the involved tables.

What is the best way to achieve this ?

shA.t
  • 16,580
  • 5
  • 54
  • 111
Larry
  • 17,605
  • 9
  • 77
  • 106
  • 2
    [On ROWNUM and Limiting Results](http://www.oracle.com/technetwork/issue-archive/2006/06-sep/o56asktom-086197.html) – John Woo Feb 26 '13 at 14:41
  • 5
    What you have done in your last Query is correct. You select the 1st row of an ordered list of records. Simply Query encapsulation. – araknoid Feb 26 '13 at 14:49
  • 2
    This is clearly documented in the manual: http://docs.oracle.com/cd/E11882_01/server.112/e26088/pseudocolumns009.htm#i1006297 –  Feb 26 '13 at 15:06
  • 7
    @a_horse_with_no_name You mean clearly documented in this 404 error. – imperfectgrist Jan 28 '15 at 20:14
  • 5
    @anthonybrice: thanks. Oracle changed all their URLs to the manual. The up-to-date link is: https://docs.oracle.com/cd/E11882_01/server.112/e41084/pseudocolumns009.htm#SQLRF00255 –  Jan 28 '15 at 20:15
  • 1
    Bad design of Oracle SQL UI. Can't select * on multiple tables with overlapping column names, etc. Can't select like OP does as run out fo segment space to complete query. Two design issues with this approach. Ends up making something which is easy in SQL Server a bit of work for Oracle. I know, "Oracle is Industrial Strength". Been hearing this excuse for two decades now. – maxweber Oct 23 '15 at 15:56
  • 1
    @a_horse_with_no_name The manual is *not* clear. It does not say how rownum works. It does give some examples of defined use. [Ask Tom](http://www.oracle.com/technetwork/issue-archive/2006/06-sep/o56asktom-086197.html) shows we must resort to a row-by-row algorithm for FROM/WHERE output--which only ever involves order if the FROM has a single subquery (SELECT but *presumably* also UNION) with ORDER BY. (If a WHERE for a FROM with single ordered subquery only tests rownum =, < or <= a deterministic value then we can take rownum to be per the ordering.) – philipxy Sep 30 '17 at 02:56
  • 2
    Anyway. Those who claims Oracle is great are plaggued by the same kind of Stockholm syndrom as those who praise SAP, Windev, Caché... ;) – Larry Sep 30 '17 at 13:18
  • 1
    Yeah, oracle always have something special. Thats why you need experts! – Ondřej Stašek Aug 18 '20 at 09:20

5 Answers5

140

The where statement gets executed before the order by. So, your desired query is saying "take the first row and then order it by t_stamp desc". And that is not what you intend.

The subquery method is the proper method for doing this in Oracle.

If you want a version that works in both servers, you can use:

select ril.*
from (select ril.*, row_number() over (order by t_stamp desc) as seqnum
      from raceway_input_labo ril
     ) ril
where seqnum = 1

The outer * will return "1" in the last column. You would need to list the columns individually to avoid this.

Gordon Linoff
  • 1,242,037
  • 58
  • 646
  • 786
  • Doesn't this traverse all `raceway_input_labo` table and assign row numbers, then filter? If so, isn't this going to cause problems with large tables? – Hasan Can Saral Mar 02 '22 at 14:25
47

Use ROW_NUMBER() instead. ROWNUM is a pseudocolumn and ROW_NUMBER() is a function. You can read about difference between them and see the difference in output of below queries:

SELECT * FROM (SELECT rownum, deptno, ename
           FROM scott.emp
        ORDER BY deptno
       )
 WHERE rownum <= 3
 /

ROWNUM    DEPTNO    ENAME
---------------------------
 7        10    CLARK
 14       10    MILLER
 9        10    KING


 SELECT * FROM 
 (
  SELECT deptno, ename
       , ROW_NUMBER() OVER (ORDER BY deptno) rno
  FROM scott.emp
 ORDER BY deptno
 )
WHERE rno <= 3
/

DEPTNO    ENAME    RNO
-------------------------
10    CLARK        1
10    MILLER       2
10    KING         3
felipe.zkn
  • 2,012
  • 7
  • 31
  • 63
Art
  • 5,616
  • 1
  • 20
  • 22
  • 4
    `ROWNUM` could be faster than `ROW_NUMBER()` so whether or not one should use one over the other depends on a number of factors. – David Faber Feb 06 '15 at 22:26
  • Apologies for the downvote it was by mistake! Unfortunately I cannot take it back now. – Athafoud May 18 '18 at 12:45
12

Since Oracle 12c we now have row limiting clauses which do exactly this.

SELECT *
FROM raceway_input_labo 
ORDER BY t_stamp DESC
FETCH FIRST ROW ONLY

Or many alternatives for different scenarios (first n rows, tie handling, etc.).

user3067860
  • 456
  • 4
  • 11
0

Documented couple of design issues with this in a comment above. Short story, in Oracle, you need to limit the results manually when you have large tables and/or tables with same column names (and you don't want to explicit type them all out and rename them all). Easy solution is to figure out your breakpoint and limit that in your query. Or you could also do this in the inner query if you don't have the conflicting column names constraint. E.g.

WHERE m_api_log.created_date BETWEEN TO_DATE('10/23/2015 05:00', 'MM/DD/YYYY HH24:MI') 
                                 AND TO_DATE('10/30/2015 23:59', 'MM/DD/YYYY HH24:MI')  

will cut down the results substantially. Then you can ORDER BY or even do the outer query to limit rows.

Also, I think TOAD has a feature to limit rows; but, not sure that does limiting within the actual query on Oracle. Not sure.

APC
  • 144,005
  • 19
  • 170
  • 281
maxweber
  • 576
  • 1
  • 5
  • 12
-1

An alternate I would suggest in this use case is to use the MAX(t_stamp) to get the latest row ... e.g.

select t.* from raceway_input_labo t
where t.t_stamp = (select max(t_stamp) from raceway_input_labo) 
limit 1

My coding pattern preference (perhaps) - reliable, generally performs at or better than trying to select the 1st row from a sorted list - also the intent is more explicitly readable.
Hope this helps ...

SQLer

SQLer
  • 7
  • 1