Como usar o Oracle ORDER BY e ROWNUM corretamente?

126

Estou com dificuldade para converter procedimentos armazenados do SQL Server para Oracle para ter nosso produto compatível com ele.

Eu tenho consultas que retornam o registro mais recente de algumas tabelas, com base em um carimbo de data / hora:

Servidor SQL:

SELECT TOP 1 *
FROM RACEWAY_INPUT_LABO
ORDER BY t_stamp DESC

=> Isso me devolve o registro mais recente

Mas a Oracle:

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

=> Isso retornará o registro mais antigo (provavelmente dependendo do índice), independentemente da ORDER BYdeclaração!

Encapsulei a consulta Oracle desta maneira para corresponder aos meus requisitos:

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

e funciona. Mas me parece um truque horrível, principalmente se eu tiver muitos registros nas tabelas envolvidas.

Qual a melhor maneira de alcançar isto ?

Larry
fonte
4
O que você fez na sua última consulta está correto. Você seleciona a primeira linha de uma lista ordenada de registros. Simplesmente consulta encapsulamento.
Araknoid
1
Isso está claramente documentado no manual: docs.oracle.com/cd/E11882_01/server.112/e26088/…
a_horse_with_no_name
5
@a_horse_with_no_name Você quer dizer claramente documentado neste erro 404.
Anthonybrice 28/01
3
@anthonybrice: obrigado. A Oracle alterou todos os seus URLs para o manual. O link atualizado é: docs.oracle.com/cd/E11882_01/server.112/e41084/…
a_horse_with_no_name

Respostas:

120

A whereinstrução é executada antes do order by. Portanto, sua consulta desejada está dizendo " pegue a primeira linha e ordene-a por t_stamp desc ". E não é isso que você pretende.

O método de subconsulta é o método apropriado para fazer isso no Oracle.

Se você deseja uma versão que funcione nos dois servidores, você pode 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

O externo *retornará "1" na última coluna. Você precisaria listar as colunas individualmente para evitar isso.

Gordon Linoff
fonte
40

Use em ROW_NUMBER()vez disso. ROWNUMé uma pseudocoluna e ROW_NUMBER()é uma função. Você pode ler sobre a diferença entre eles e ver a diferença na saída das consultas abaixo:

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
fonte
3
ROWNUMpode ser mais rápido que ROW_NUMBER()isso, se um deve ou não usar um sobre o outro depende de vários fatores.
David Faber
Desculpas pelo voto negativo foi por engano! Infelizmente não posso voltar atrás agora.
Athafoud 18/05/19
0

Uma alternativa que eu sugeriria neste caso de uso é usar o MAX (t_stamp) para obter a última linha ... por exemplo

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

Minha preferência de padrão de codificação (talvez) - confiável, geralmente tem desempenho igual ou superior a tentar selecionar a primeira linha de uma lista classificada - também a intenção é mais explicitamente legível.
Espero que isto ajude ...

SQLer

SQLer
fonte
3
Não há LIMITE no Oracle. Você está implorando a pergunta.
philipxy
0

Documentado alguns problemas de design com isso em um comentário acima. Em resumo, no Oracle, você precisa limitar os resultados manualmente quando tiver tabelas grandes e / ou tabelas com os mesmos nomes de coluna (e não desejar explicitamente digitar todas elas e renomeá-las todas). A solução fácil é descobrir seu ponto de interrupção e limitar isso na sua consulta. Ou você também pode fazer isso na consulta interna se não tiver a restrição de nomes de colunas conflitantes. Por exemplo

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

reduzirá substancialmente os resultados. Em seguida, você pode ORDER BY ou até fazer a consulta externa para limitar as linhas.

Além disso, acho que o TOAD tem um recurso para limitar linhas; mas não sei se isso limita a consulta real no Oracle. Não tenho certeza.

maxweber
fonte