CASE
前のステップが真であると評価された場合、後続のステップの評価は行われないという点で、ステートメントは「短絡」原理に基づいて機能することを常に理解していました。(この回答は、SQL Server CASEステートメントがすべての条件を評価するか、最初のTRUE条件で終了しますか?は関連していますが、この状況をカバーしておらず、SQL Serverに関連しています)。
次の例でMAX(amount)
は、開始日と支払い日の間の月数に基づいて異なる月の範囲の間を計算したいと思います。
(これは明らかに構築された例ですが、ロジックは、私が問題を見る実際のコードで有効なビジネス推論を持っています)。
開始日と支払い日の間の期間が5か月未満の場合は、式1が使用されます。それ以外の場合は、式2が使用されます。
これにより、「ORA-01428:引数 '-1'は範囲外です」というエラーが発生します。これは、1つのレコードに無効なデータ条件があり、ORDER BYのBETWEEN句の開始が負の値になるためです。
クエリ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
そこで、この2番目のクエリを実行して、これが発生する可能性のある場所を最初に排除しました。
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
残念ながら、予期しない動作が発生し、式1が使用する値が検証されますが、負の条件が外側でトラップされるため、ステートメントは実行されませんCASE
。
式1のを使用ABS
することMONTHS_BETWEEN
で問題を回避できますが、これは不要なはずです。
この動作は期待どおりですか?もしそうなら「なぜ」それは私にとって非論理的で、バグのように見えますか?
これにより、テーブルとテストデータが作成されます。クエリは、の正しいパスCASE
が使用されていることを確認するだけです。
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)