Saya selalu mengerti bahwa CASE
pernyataan tersebut bekerja pada prinsip 'hubungan pendek' dalam evaluasi bahwa langkah-langkah selanjutnya tidak terjadi jika langkah sebelumnya dievaluasi menjadi benar. (Jawaban ini Apakah pernyataan KASUS SQL Server mengevaluasi semua kondisi atau keluar pada kondisi BENAR pertama? Terkait tetapi tampaknya tidak mencakup situasi ini dan terkait dengan SQL Server).
Dalam contoh berikut, saya ingin menghitung MAX(amount)
rentang bulan yang berbeda berdasarkan berapa bulan antara tanggal mulai dan tanggal berbayar.
(Ini jelas merupakan contoh yang dibangun tetapi logika memiliki alasan bisnis yang valid dalam kode aktual di mana saya melihat masalah).
Jika ada <5 bulan antara tanggal mulai dan tanggal pembayaran maka Ekspresi 1 akan digunakan jika tidak, Ekspresi 2 akan digunakan.
Ini menghasilkan kesalahan "ORA-01428: argumen '-1' berada di luar kisaran" karena 1 catatan memiliki kondisi data yang tidak valid yang menghasilkan nilai negatif untuk dimulainya klausa BETWEEN dari ORDER BY.
Pertanyaan 1
SELECT ref_no,
CASE WHEN MONTHS_BETWEEN(paid_date, start_date) < 5 THEN
-- Expression 1
MAX(amount)
OVER (PARTITION BY ref_no ORDER BY paid_date ASC
ROWS BETWEEN MONTHS_BETWEEN(paid_date, start_date) PRECEDING
AND CURRENT ROW)
ELSE
-- Expression 2
MAX(amount)
OVER (PARTITION BY ref_no ORDER BY paid_date ASC
ROWS BETWEEN 5 PRECEDING AND CURRENT ROW)
END
END
FROM payment
Jadi saya pergi untuk kueri kedua ini untuk menghilangkan dulu di mana saja hal ini dapat terjadi:
SELECT ref_no,
CASE WHEN MONTHS_BETWEEN(paid_date, start_date) < 0 THEN 0
ELSE
CASE WHEN MONTHS_BETWEEN(paid_date, start_date) < 5 THEN
MAX(amount)
OVER (PARTITION BY ref_no ORDER BY paid_date ASC
ROWS BETWEEN MONTHS_BETWEEN(paid_date, start_date) PRECEDING
AND CURRENT ROW)
ELSE
MAX(amount)
OVER (PARTITION BY ref_no ORDER BY paid_date ASC
ROWS BETWEEN 5 PRECEDING AND CURRENT ROW)
END
END
FROM payment
Sayangnya, ada beberapa perilaku tak terduga yang berarti bahwa nilai-nilai Ekspresi 1 AKAN digunakan divalidasi, meskipun pernyataan itu tidak akan dieksekusi karena kondisi negatif sekarang terjebak oleh luar CASE
.
Saya bisa mendapatkan sekitar masalah dengan menggunakan ABS
pada MONTHS_BETWEEN
di Expression 1 , tapi aku merasa seperti ini harus tidak perlu.
Apakah perilaku ini seperti yang diharapkan? Jika demikian 'mengapa' karena tampaknya tidak masuk akal bagi saya dan lebih seperti bug?
Ini akan membuat tabel dan menguji data. Pertanyaannya hanyalah saya memeriksa bahwa jalur yang benar CASE
sedang diambil.
CREATE TABLE payment
(ref_no NUMBER,
start_date DATE,
paid_date DATE,
amount NUMBER)
INSERT INTO payment
VALUES (1001,TO_DATE('01-11-2015','DD-MM-YYYY'),TO_DATE('01-01-2016','DD-MM-YYYY'),3000)
INSERT INTO payment
VALUES (1001,TO_DATE('01-11-2015','DD-MM-YYYY'),TO_DATE('12-12-2015','DD-MM-YYYY'),5000)
INSERT INTO payment
VALUES (1001,TO_DATE('10-03-2016','DD-MM-YYYY'),TO_DATE('10-02-2016','DD-MM-YYYY'),2000)
INSERT INTO payment
VALUES (1001,TO_DATE('01-11-2015','DD-MM-YYYY'),TO_DATE('03-03-2016','DD-MM-YYYY'),6000)
INSERT INTO payment
VALUES (1001,TO_DATE('01-11-2015','DD-MM-YYYY'),TO_DATE('28-11-2015','DD-MM-YYYY'),10000)
SELECT ref_no,
CASE WHEN MONTHS_BETWEEN(paid_date, start_date) < 0 THEN '<0'
ELSE
CASE WHEN MONTHS_BETWEEN(paid_date, start_date) < 5 THEN
'<5'
-- MAX(amount)
-- OVER (PARTITION BY ref_no ORDER BY paid_date ASC ROWS
-- BETWEEN MONTHS_BETWEEN(paid_date, start_date) PRECEDING
-- AND CURRENT ROW)
ELSE
'>=5'
-- MAX(amount)
-- OVER (PARTITION BY ref_no ORDER BY paid_date ASC ROWS
-- BETWEEN 5 PRECEDING AND CURRENT ROW)
END
END
FROM payment
MAX(amount) OVER (PARTITION BY ref_no ORDER BY paid_date ASC ROWS BETWEEN GREATEST(0, LEAST(5, MONTHS_BETWEEN(paid_date, start_date))) PRECEDING AND CURRENT ROW)
Jawaban:
Jadi sulit bagi saya untuk menentukan apa pertanyaan Anda yang sebenarnya dari pos, tetapi saya berasumsi bahwa ketika Anda mengeksekusi:
Anda masih mendapatkan ORA-01428: argumen '-1' di luar jangkauan ?
Saya rasa ini bukan bug. Saya pikir ini adalah urutan operasi. Oracle perlu melakukan analisis pada semua baris yang dikembalikan oleh resultset. Maka itu bisa turun ke seluk-beluk dari mengubah output.
Beberapa cara tambahan untuk ini adalah dengan mengecualikan baris dengan klausa di mana:
Atau Anda dapat menyematkan sebuah kasus dalam analitik Anda seperti:
Penjelasan
Saya berharap saya dapat menemukan beberapa dokumentasi untuk mendukung urutan operasi, tetapi saya belum dapat menemukan apa pun ... belum.
The
CASE
arus pendek evaluasi yang terjadi setelah fungsi analitik dievaluasi. Urutan operasi untuk kueri yang dimaksud adalah:Jadi sejak
max over()
terjadi sebelum kasus, permintaan gagal.Fungsi analitik Oracle akan dianggap sebagai sumber baris . Jika Anda menjalankan rencana penjelasan pada kueri Anda, Anda akan melihat "jenis jendela" yang merupakan analitik, menghasilkan baris, yang diumpankan kepadanya oleh sumber baris sebelumnya, tabel pembayaran. Pernyataan kasus adalah ekspresi yang dievaluasi untuk setiap baris di sumber baris. Jadi masuk akal (setidaknya bagi saya), bahwa kasus terjadi setelah analitik.
sumber
SQL mendefinisikan apa yang harus dilakukan, bukan bagaimana melakukannya. Sementara biasanya Oracle akan melakukan evaluasi kasus hubungan pendek, ini adalah optimasi dan karenanya akan dihindari jika optimizer percaya jalur eksekusi yang berbeda memberikan kinerja yang unggul. Perbedaan optimasi seperti itu akan diharapkan ketika analitik terlibat.
Perbedaan optimasi tidak terbatas pada kasus. Kesalahan Anda dapat direproduksi menggunakan coalesce, yang biasanya juga mengalami hubungan pendek.
Tampaknya tidak ada dokumentasi yang secara eksplisit mengatakan bahwa evaluasi arus pendek dapat diabaikan oleh pengoptimal. Hal terdekat (walaupun tidak cukup dekat) yang dapat saya temukan adalah ini :
Pertanyaan ini menunjukkan evaluasi hubungan pendek diabaikan bahkan tanpa analitik (meskipun ada pengelompokan).
Tom Kyte menyebutkan bahwa hubungan arus pendek dapat diabaikan dalam tanggapannya terhadap pertanyaan tentang urutan penilaian predikat .
Anda harus membuka SR dengan Oracle. Saya menduga mereka akan menerimanya sebagai bug dokumentasi, dan meningkatkan dokumentasi di versi berikutnya untuk memasukkan peringatan tentang pengoptimal.
sumber
Sepertinya itu windowing yang membuat Oracle mulai mengevaluasi semua ekspresi dalam CASE. Lihat
Dua kueri pertama dijalankan OK.
sumber