Siempre entendí que la CASE
declaración funcionaba según un principio de "cortocircuito" en que la evaluación de los pasos posteriores no tiene lugar si un paso anterior se evalúa como verdadero. (Esta respuesta ¿La declaración CASE de SQL Server evalúa todas las condiciones o sale en la primera condición VERDADERA? Está relacionada pero no parece cubrir esta situación y se relaciona con SQL Server).
En el siguiente ejemplo, deseo calcular MAX(amount)
entre un rango de meses que difiere en función de cuántos meses hay entre las fechas de inicio y de pago.
(Este es obviamente un ejemplo construido, pero la lógica tiene un razonamiento comercial válido en el código real donde veo el problema).
Si hay <5 meses entre las fechas de inicio y de pago , se utilizará la Expresión 1; de lo contrario, se utilizará la Expresión 2 .
Esto produce el error "ORA-01428: el argumento '-1' está fuera de rango" porque 1 registro tiene una condición de datos no válida que da como resultado un valor negativo para el inicio de la cláusula ENTRE de ORDER BY.
Consulta 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
Así que fui por esta segunda consulta para eliminar primero cualquier lugar donde esto pueda ocurrir:
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
Desafortunadamente, hay un comportamiento inesperado que significa que los valores que la Expresión 1 DEBERÍA USAR SERÍAN validados, aunque la declaración no se ejecutará porque la condición negativa ahora está atrapada por el exterior CASE
.
Puedo solucionar el problema usando ABS
el MONTHS_BETWEEN
en la Expresión 1 , pero siento que esto debería ser innecesario.
¿Es este comportamiento como se esperaba? Si es así, ¿por qué me parece ilógico y más como un error?
Esto creará una tabla y datos de prueba. La consulta es simplemente yo comprobando que CASE
se está tomando la ruta correcta en el .
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)