¿Cómo utilizar Oracle ORDER BY y ROWNUM correctamente?

126

Me resulta difícil convertir los procedimientos almacenados de SQL Server a Oracle para que nuestro producto sea compatible con él.

Tengo consultas que devuelven el registro más reciente de algunas tablas, basado en una marca de tiempo:

Servidor SQL:

SELECT TOP 1 *
FROM RACEWAY_INPUT_LABO
ORDER BY t_stamp DESC

=> Eso me devolverá el registro más reciente

Pero Oracle:

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

=> ¡Eso me devolverá el registro más antiguo (probablemente según el índice), independientemente de la ORDER BYdeclaración!

Encapsulé la consulta de Oracle de esta manera para que coincida con mis requisitos:

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

y funciona. Pero me parece un truco horrible, especialmente si tengo muchos registros en las tablas involucradas.

Cuál es la mejor manera de lograr esto ?

Larry
fuente
44
Lo que has hecho en tu última consulta es correcto. Selecciona la primera fila de una lista ordenada de registros. Simplemente consulta encapsulación.
aracnoideo
1
Esto está claramente documentado en el manual: docs.oracle.com/cd/E11882_01/server.112/e26088/…
a_horse_with_no_name
55
@a_horse_with_no_name Quiere decir claramente documentado en este error 404.
anthonybrice
3
@anthonybrice: gracias. Oracle cambió todas sus URL al manual. El enlace actualizado es: docs.oracle.com/cd/E11882_01/server.112/e41084/…
a_horse_with_no_name

Respuestas:

120

La wheredeclaración se ejecuta antes de la order by. Entonces, su consulta deseada dice " tome la primera fila y luego t_stamp ordénela por desc ". Y eso no es lo que pretendes.

El método de subconsulta es el método adecuado para hacer esto en Oracle.

Si desea una versión que funcione en ambos servidores, puede usar:

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

El exterior *devolverá "1" en la última columna. Debería enumerar las columnas individualmente para evitar esto.

Gordon Linoff
fuente
40

Usar en su ROW_NUMBER()lugar. ROWNUMes una pseudocolumna y ROW_NUMBER()es una función. Puede leer sobre la diferencia entre ellos y ver la diferencia en la salida de las consultas a continuación:

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
Arte
fuente
3
ROWNUMpodría ser más rápido que ROW_NUMBER()eso si uno debe usar uno sobre el otro depende de una serie de factores.
David Faber
Disculpas por el voto negativo fue por error! Lamentablemente no puedo recuperarlo ahora.
Athafoud
0

Una alternativa que sugeriría en este caso de uso es usar el MAX (t_stamp) para obtener la última fila ... por ejemplo

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

Mi preferencia de patrón de codificación (tal vez), confiable, generalmente funciona mejor o mejor que tratar de seleccionar la primera fila de una lista ordenada, también la intención es más explícitamente legible.
Espero que esto ayude ...

SQLer

SQLer
fuente
3
No hay LÍMITE en Oracle. Estás rogando la pregunta.
philipxy
0

Se documentaron algunos problemas de diseño con esto en un comentario anterior. En pocas palabras, en Oracle, debe limitar los resultados manualmente cuando tiene tablas grandes y / o tablas con los mismos nombres de columna (y no desea escribirlas explícitamente y cambiarles el nombre). La solución fácil es averiguar su punto de interrupción y limitarlo en su consulta. O también puede hacer esto en la consulta interna si no tiene la restricción de nombres de columna en conflicto. P.ej

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

reducirá sustancialmente los resultados. Luego puede ORDER BY o incluso hacer la consulta externa para limitar las filas.

Además, creo que TOAD tiene una función para limitar las filas; pero, no estoy seguro de que eso limite dentro de la consulta real en Oracle. No estoy seguro.

Max Weber
fuente