web-dev-qa-db-fra.com

Comment utiliser Oracle ORDER BY et ROWNUM correctement?

J'ai de la difficulté à convertir les procédures stockées de SQL Server en Oracle pour que notre produit soit compatible avec celui-ci.

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

SQL Server:

SELECT TOP 1 *
FROM RACEWAY_INPUT_LABO
ORDER BY t_stamp DESC

=> Cela me rendra le dernier enregistrement

Mais Oracle:

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

=> Cela me retournera l'enregistrement le plus ancien (probablement en fonction de l'index), quelle que soit l'instruction ORDER BY!

J'ai encapsulé la requête Oracle de cette manière pour répondre à mes exigences:

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

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

Quel est le meilleur moyen d'y parvenir?

106
Larry

L'instruction where est exécutée avant le order by. Ainsi, votre requête souhaitée dit "prenez la première ligne puis commandez-la part_stampdesc". Et ce n'est pas ce que vous avez l'intention.

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

Si vous voulez 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

Le * extérieur renverra "1" dans la dernière colonne. Pour éviter cela, vous devez répertorier les colonnes individuellement.

107
Gordon Linoff

Utilisez ROW_NUMBER() à la place. ROWNUM est une pseudo-colonne et ROW_NUMBER() est une fonction. Vous pouvez lire la différence entre eux et voir la différence dans le résultat 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
30
Art

Une autre solution que je suggérerais dans ce cas d'utilisation consiste à utiliser 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 mieux ou mieux que d'essayer de sélectionner la 1ère ligne dans une liste triée - l'intention est également plus explicitement lisible.
J'espère que cela t'aides ...

SQLer

1
SQLer

Documenté quelques problèmes de conception avec cela dans un commentaire ci-dessus. Bref récit, sous Oracle, vous devez limiter les résultats manuellement lorsque vous disposez de tables volumineuses et/ou de tables portant le même nom de colonne (et que vous ne souhaitez pas les taper explicitement et les renommer tous). La solution facile 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 la contrainte de noms de colonnes en conflit n’est pas présente. 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')  

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

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

0
maxweber