Come utilizzare correttamente Oracle ORDER BY e ROWNUM?


126

Sto avendo difficoltà a convertire le stored procedure da SQL Server a Oracle per avere il nostro prodotto compatibile con esso.

Ho query che restituiscono il record più recente di alcune tabelle, in base a un timestamp:

Server SQL:

SELECT TOP 1 *
FROM RACEWAY_INPUT_LABO
ORDER BY t_stamp DESC

=> Questo mi restituirà il record più recente

Ma Oracle:

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

=> Questo mi restituirà il record più vecchio (probabilmente a seconda dell'indice), indipendentemente ORDER BYdall'istruzione!

Ho incapsulato la query Oracle in questo modo per soddisfare i miei requisiti:

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

e funziona. Ma a me sembra un orribile trucco, soprattutto se ho molti record nelle tabelle coinvolte.

Qual è il modo migliore per ottenere questo risultato?



4
Quello che hai fatto nella tua ultima Query è corretto. Seleziona la prima riga di un elenco ordinato di record. Semplicemente interroga l'incapsulamento.
araknoid

1
Questo è chiaramente documentato nel manuale: docs.oracle.com/cd/E11882_01/server.112/e26088/…
a_horse_with_no_name

5
@a_horse_with_no_name Vuoi dire chiaramente documentato in questo errore 404.
anthonybrice

3
@anthonybrice: grazie. Oracle ha modificato tutti i loro URL nel manuale. Il collegamento aggiornato è: docs.oracle.com/cd/E11882_01/server.112/e41084/…
a_horse_with_no_name

Risposte:


120

L' whereistruzione viene eseguita prima del order by. Quindi, la tua query desiderata sta dicendo " prendi la prima riga e poi t_stamp ordinala per desc ". E non è quello che intendi.

Il metodo subquery è il metodo corretto per eseguire questa operazione in Oracle.

Se vuoi una versione che funzioni su entrambi i server, puoi usare:

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

L'esterno *restituirà "1" nell'ultima colonna. Dovresti elencare le colonne individualmente per evitarlo.


40

Usa ROW_NUMBER()invece. ROWNUMè una pseudocolonna ed ROW_NUMBER()è una funzione. Puoi leggere la differenza tra loro e vedere la differenza nell'output delle query seguenti:

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

3
ROWNUMpotrebbe essere più veloce di ROW_NUMBER()così se uno dovrebbe o meno usarne uno sull'altro dipende da una serie di fattori.
David Faber

Mi scuso per il voto negativo è stato per errore! Purtroppo non posso riprenderlo adesso.
Athafoud

0

Un'alternativa che suggerirei in questo caso d'uso è di utilizzare MAX (t_stamp) per ottenere l'ultima riga ... ad es.

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

La mia preferenza per il pattern di codifica (forse) - affidabile, generalmente funziona ao meglio che provare a selezionare la prima riga da un elenco ordinato - anche l'intento è più chiaramente leggibile.
Spero che questo ti aiuti ...

SQLer


3
Non ci sono LIMITI in Oracle. Stai implorando la domanda.
philipxy

0

Ho documentato un paio di problemi di progettazione con questo in un commento sopra. Breve storia, in Oracle, è necessario limitare i risultati manualmente quando si hanno tabelle di grandi dimensioni e / o tabelle con gli stessi nomi di colonna (e non si desidera scriverle esplicitamente e rinominarle tutte). Una soluzione semplice è individuare il punto di interruzione e limitarlo nella query. Oppure puoi farlo anche nella query interna se non hai il vincolo dei nomi di colonna in conflitto. Per esempio

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')  

ridurrà sostanzialmente i risultati. Quindi puoi ORDER BY o persino eseguire la query esterna per limitare le righe.

Inoltre, penso che TOAD abbia una funzione per limitare le righe; ma non sono sicuro che ciò limiti all'interno della query effettiva su Oracle. Non sono sicuro.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.