Comment utiliser correctement Oracle ORDER BY et ROWNUM?

126

J'ai du mal à convertir des procédures stockées de SQL Server vers Oracle pour que notre produit soit compatible avec celui-ci.

J'ai des requêtes qui renvoie l'enregistrement le plus récent de certaines tables, basé sur un horodatage:

Serveur SQL:

SELECT TOP 1 *
FROM RACEWAY_INPUT_LABO
ORDER BY t_stamp DESC

=> Cela me retournera l'enregistrement le plus récent

Mais Oracle:

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

=> Cela me rendra l'enregistrement le plus ancien (probablement en fonction de l'index), quelle que soit la ORDER BYdéclaration!

J'ai encapsulé la requête Oracle de cette façon pour répondre à mes besoins:

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

et il fonctionne. Mais cela me semble un horrible hack, surtout si j'ai beaucoup de disques dans les tables concernées.

Quelle est la meilleure façon d'y parvenir?

Larry
la source
4
Ce que vous avez fait dans votre dernière requête est correct. Vous sélectionnez la première ligne d'une liste triée d'enregistrements. Encapsulation simplement de requête.
araknoid
1
Ceci est clairement documenté dans le manuel: docs.oracle.com/cd/E11882_01/server.112/e26088/…
a_horse_with_no_name
5
@a_horse_with_no_name Vous voulez dire clairement documenté dans cette erreur 404.
anthonybrice
3
@anthonybrice: merci. Oracle a changé toutes ses URL dans le manuel. Le lien à jour est: docs.oracle.com/cd/E11882_01/server.112/e41084/…
a_horse_with_no_name

Réponses:

120

L' whereinstruction est exécutée avant le order by. Ainsi, votre requête désirée dit " prenez la première ligne et triez-la ensuite par t_stamp desc ". Et ce n'est pas ce que vous entendez.

La méthode de sous-requête est la méthode appropriée pour ce faire dans Oracle.

Si vous souhaitez une version qui fonctionne sur les deux serveurs, vous pouvez utiliser:

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'extérieur *renverra "1" dans la dernière colonne. Vous auriez besoin de lister les colonnes individuellement pour éviter cela.

Gordon Linoff
la source
40

Utilisez ROW_NUMBER()plutôt. ROWNUMest une pseudo-colonne et ROW_NUMBER()est une fonction. Vous pouvez en savoir plus sur la différence entre eux et voir la différence de sortie des requêtes ci-dessous:

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
Art
la source
3
ROWNUMpourrait être plus rapide que ROW_NUMBER()si l'un doit ou non utiliser l'un sur l'autre dépend d'un certain nombre de facteurs.
David Faber
Toutes mes excuses pour le vote négatif, c'était par erreur! Malheureusement, je ne peux pas le reprendre maintenant.
Athafoud
0

Une alternative que je suggérerais dans ce cas d'utilisation est d'utiliser le MAX (t_stamp) pour obtenir la dernière ligne ... par exemple

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

Ma préférence de modèle de codage (peut-être) - fiable, fonctionne généralement à ou mieux que d'essayer de sélectionner la 1ère ligne dans une liste triée - également l'intention est plus explicitement lisible.
J'espère que cela t'aides ...

SQLer

SQLer
la source
3
Il n'y a pas de LIMITE dans Oracle. Vous mendiez la question.
philipxy
0

Quelques problèmes de conception documentés avec cela dans un commentaire ci-dessus. Petite histoire, dans Oracle, vous devez limiter les résultats manuellement lorsque vous avez de grandes tables et / ou des tables avec les mêmes noms de colonne (et que vous ne voulez pas les taper explicitement et les renommer toutes). La solution simple consiste à déterminer votre point d'arrêt et à le limiter dans votre requête. Vous pouvez également le faire dans la requête interne si vous n'avez pas la contrainte de noms de colonnes en conflit. Par exemple

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

réduira considérablement les résultats. Ensuite, vous pouvez ORDER BY ou même effectuer la requête externe pour limiter les lignes.

De plus, je pense que TOAD a une fonctionnalité pour limiter les lignes; mais, pas sûr que cela limite la requête réelle sur Oracle. Pas certain.

Max Weber
la source