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)
Respuestas:
Así que fue difícil para mí determinar cuál era su pregunta real de la publicación, pero supongo que es eso cuando ejecuta:
¿Todavía obtiene ORA-01428: el argumento '-1' está fuera de rango ?
No creo que esto sea un error. Creo que es una cuestión de orden de operación. Oracle necesita hacer el análisis en todas las filas devueltas por el conjunto de resultados. Entonces puede llegar al meollo de la cuestión de transformar la salida.
Un par de formas adicionales de evitar esto sería excluir la fila con una cláusula where:
O podría insertar un caso en su analítica como:
Explicación
Desearía poder encontrar documentación para respaldar el orden de operación, pero no he podido encontrar nada ... todavía.
La
CASE
evaluación de cortocircuito ocurre después de evaluar la función analítica. El orden de las operaciones para la consulta en cuestión sería:Entonces, como
max over()
sucede antes del caso, la consulta falla.Las funciones analíticas de Oracle se considerarían una fuente de fila . Si ejecuta un plan de explicación en su consulta, debería ver un "orden de ventana" que es el analítico, que genera filas, que le son alimentadas por la fuente de fila anterior, la tabla de pago. Una declaración de caso es una expresión que se evalúa para cada fila en el origen de la fila. Entonces tiene sentido (al menos para mí), que el caso ocurre después de la analítica.
fuente
SQL define qué hacer, no cómo hacerlo. Aunque normalmente Oracle hará un cortocircuito en la evaluación de casos, esta es una optimización y, por lo tanto, se evitará si el optimizador cree que una ruta de ejecución diferente proporciona un rendimiento superior. Tal diferencia de optimización se esperaría cuando los análisis están involucrados.
La diferencia de optimización no se limita al caso. Su error puede reproducirse usando la fusión, que normalmente también provocaría un cortocircuito.
No parece haber ninguna documentación que diga explícitamente que el optimizador pueda ignorar la evaluación de cortocircuito. Lo más cercano (aunque no lo suficientemente cerca) que puedo encontrar es esto :
Esta pregunta muestra que la evaluación de cortocircuito se ignora incluso sin análisis (aunque hay agrupación).
Tom Kyte menciona que los cortocircuitos pueden ignorarse en su respuesta a una pregunta sobre el orden de evaluación de predicados .
Debe abrir un SR con Oracle. Sospecho que lo aceptarán como un error de documentación y mejorarán la documentación en la próxima versión para incluir una advertencia sobre el optimizador.
fuente
Parece que es una ventana lo que hace que Oracle comience a evaluar todas las expresiones en CASE. Ver
Las primeras dos consultas funcionan bien.
fuente