Bagaimana cara menggunakan Oracle ORDER BY dan ROWNUM dengan benar?

126

Saya mengalami kesulitan saat mengubah prosedur yang tersimpan dari SQL Server ke Oracle agar produk kami kompatibel dengannya.

Saya memiliki pertanyaan yang mengembalikan catatan terbaru dari beberapa tabel, berdasarkan stempel waktu:

SQL Server:

SELECT TOP 1 *
FROM RACEWAY_INPUT_LABO
ORDER BY t_stamp DESC

=> Itu akan mengembalikan saya rekor terbaru

Tapi Oracle:

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

=> Itu akan mengembalikan saya catatan tertua (mungkin tergantung pada indeks), terlepas dari ORDER BYpernyataannya!

Saya merangkum kueri Oracle dengan cara ini agar sesuai dengan kebutuhan saya:

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

dan berhasil. Tapi kedengarannya seperti retasan yang mengerikan bagi saya, terutama jika saya memiliki banyak catatan di tabel yang terlibat.

Apa cara terbaik untuk mencapai ini?

Larry
sumber
4
Apa yang telah Anda lakukan di Kueri terakhir Anda sudah benar. Anda memilih baris pertama dari daftar rekaman yang diurutkan. Cukup Query enkapsulasi.
araknoid
1
Ini dengan jelas didokumentasikan di manual: docs.oracle.com/cd/E11882_01/server.112/e26088/…
a_horse_with_no_name
5
@a_horse_with_no_name Maksud Anda didokumentasikan dengan jelas dalam kesalahan 404 ini.
anthonybrice
3
@anthonybrice: terima kasih. Oracle mengubah semua URL mereka ke manual. Tautan terbaru adalah: docs.oracle.com/cd/E11882_01/server.112/e41084/…
a_horse_with_no_name

Jawaban:

120

The wherepernyataan dijalankan sebelum ini order by. Jadi, kueri yang Anda inginkan mengatakan " ambil baris pertama lalu t_stamp urutkan menurut desc ". Dan bukan itu yang Anda inginkan.

Metode subquery adalah metode yang tepat untuk melakukan ini di Oracle.

Jika Anda menginginkan versi yang berfungsi di kedua server, Anda dapat menggunakan:

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

Bagian luar *akan mengembalikan "1" di kolom terakhir. Anda perlu membuat daftar kolom satu per satu untuk menghindari ini.

Gordon Linoff
sumber
40

Gunakan ROW_NUMBER()sebagai gantinya. ROWNUMadalah pseudocolumn dan ROW_NUMBER()merupakan sebuah fungsi. Anda dapat membaca tentang perbedaan di antara mereka dan melihat perbedaan output dari kueri di bawah ini:

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
Seni
sumber
3
ROWNUMbisa lebih cepat daripada ROW_NUMBER()jadi apakah seseorang harus menggunakan salah satu dari yang lain tergantung pada sejumlah faktor.
David Faber
Maaf atas downvote itu karena kesalahan! Sayangnya saya tidak bisa mengambilnya kembali sekarang.
Athafoud
0

Alternatif yang saya sarankan dalam kasus penggunaan ini adalah menggunakan MAX (t_stamp) untuk mendapatkan baris terbaru ... mis

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

Preferensi pola pengkodean saya (mungkin) - dapat diandalkan, umumnya berkinerja pada atau lebih baik daripada mencoba memilih baris pertama dari daftar yang diurutkan - juga maksudnya lebih dapat dibaca secara eksplisit.
Semoga ini membantu ...

SQLer

SQLer
sumber
3
Tidak ada BATAS di Oracle. Anda memohon pertanyaan.
philipxy
0

Beberapa masalah desain yang didokumentasikan dengan ini dalam komentar di atas. Singkatnya, di Oracle, Anda perlu membatasi hasil secara manual saat Anda memiliki tabel besar dan / atau tabel dengan nama kolom yang sama (dan Anda tidak ingin mengetikkan semuanya secara eksplisit dan mengganti nama semuanya). Solusi mudahnya adalah mencari tahu breakpoint Anda dan membatasinya dalam kueri Anda. Atau Anda juga bisa melakukan ini di kueri bagian dalam jika Anda tidak memiliki batasan nama kolom yang bentrok. Misalnya

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

akan mengurangi hasil secara substansial. Kemudian Anda dapat ORDER BY atau bahkan melakukan kueri luar untuk membatasi baris.

Juga, menurut saya TOAD memiliki fitur untuk membatasi baris; tetapi, tidak yakin bahwa tidak membatasi dalam kueri aktual di Oracle. Tidak yakin.

maxweber.dll
sumber