Ho sempre capito che l' CASEaffermazione ha funzionato secondo un principio di "cortocircuito" in quanto la valutazione delle fasi successive non ha luogo se una fase precedente è valutata come vera. (Questa risposta L'istruzione CASE di SQL Server valuta tutte le condizioni o esce alla prima condizione VERA? È correlata ma non sembra coprire questa situazione e si riferisce a SQL Server).
Nel seguente esempio, desidero calcolare l' MAX(amount)intervallo tra un intervallo di mesi che differisce in base al numero di mesi tra l'inizio e le date pagate.
(Questo è ovviamente un esempio costruito ma la logica ha un valido ragionamento commerciale nel codice attuale in cui vedo il problema).
Se ci sono <5 mesi tra l'inizio e le date di pagamento, verrà utilizzata l' Espressione 1 , altrimenti verrà utilizzata l' Espressione 2 .
Ciò provoca l'errore "ORA-01428: l'argomento '-1' non è compreso nell'intervallo" perché 1 record presenta una condizione dati non valida che determina un valore negativo per l'inizio della clausola BETWEEN di ORDER BY.
Query 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
Quindi sono andato per questa seconda query per eliminare prima ovunque ciò possa accadere:
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
Sfortunatamente, c'è un comportamento inaspettato che significa che i valori di Expression 1 DEVONO essere validati, anche se l'istruzione non verrà eseguita perché la condizione negativa è ora intrappolata dall'esterno CASE.
Posso aggirare il problema utilizzando ABSil MONTHS_BETWEENdi Espressione 1 , ma mi sento come questo dovrebbe essere inutile.
Questo comportamento è come previsto? In tal caso "perché" come mi sembra illogico e più simile a un bug?
Ciò creerà una tabella e testerà i dati. La query è semplicemente che sto controllando che CASEvenga preso il percorso corretto in .
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)