web-dev-qa-db-ja.com

OracleのORDER BYとROWNUMを正しく使用する方法

ストアドプロシージャをSQL ServerからOracleに変換して、互換性があるようにするのに苦労しています。

タイムスタンプに基づいて、いくつかのテーブルの最新のレコードを返すクエリがあります。

SQL Server:

SELECT TOP 1 *
FROM RACEWAY_INPUT_LABO
ORDER BY t_stamp DESC

=>それは私に最新の記録を返します

しかしOracle:

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

=> ORDER BYステートメントに関係なく、これは私に最も古いレコードを(おそらくインデックスに依存して)返します。

私の要件に合わせて、このようにOracleクエリをカプセル化しました。

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

そしてそれはうまくいきます。しかし、それは私にとって恐ろしいハックのように思えます。特に、関連するテーブルにたくさんのレコードがある場合はそうです。

これを達成するための最良の方法は何ですか?

102
Larry

whereステートメントが実行されますorder by。だから、あなたの希望するクエリは "最初の行を取り、それからそれを順序付けるt_stampdesc"と言っています。そしてそれはあなたが意図するものではありません。

サブクエリメソッドは、Oracleでこれを実行するための適切な方法です。

両方のサーバーで機能するバージョンが必要な場合は、次のものを使用できます。

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

外側の*は最後の列に "1"を返します。これを避けるには、列を個別にリストする必要があります。

104
Gordon Linoff

代わりにROW_NUMBER()を使用してください。 ROWNUMは疑似列、ROW_NUMBER()は関数です。あなたはそれらの違いについて読み、以下のクエリの出力の違いを見ることができます。

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
29
Art

このユースケースで私が提案する代替案は、最新の行を取得するためにMAX(t_stamp)を使用することです。

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

私のコーディングパターンの好みは(おそらく)信頼でき、一般的にソートされたリストから最初の行を選択しようとするかそれ以上のパフォーマンスを発揮します - また意図はより明示的に読めます。
お役に立てれば ...

SQLer

1
SQLer

上記のコメントで、これに関する設計上の問題点を文書化しました。短い話ですが、Oracleでは、大きなテーブルや同じカラム名を持つテーブルがある場合は、結果を手動で制限する必要があります(それらすべてを明示的に入力して名前を変更したくない場合)。簡単な解決策は、ブレークポイントを見つけ出し、それをクエリで制限することです。競合する列名の制約がない場合は、内部クエリでこれを行うこともできます。例えば。

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

結果を大幅に削減します。それから、ORDER BYまたは外側のクエリを実行して行を制限することもできます。

また、TOADには行を制限する機能があると思います。しかし、それがOracleの実際のクエリ内で制限されているかどうかはわかりません。わからない。

0
maxweber