Sempre entendi que a CASE
declaração funcionava com base no princípio de 'curto-circuito', pois a avaliação das etapas subsequentes não ocorre se uma etapa anterior for avaliada como verdadeira. (Esta resposta A instrução CASE do SQL Server avalia todas as condições ou sai na primeira condição TRUE? está relacionada, mas não parece cobrir esta situação e está relacionada ao SQL Server).
No exemplo a seguir, desejo calcular MAX(amount)
entre um intervalo de meses que difere com base em quantos meses existem entre as datas de início e pagamento.
(Este é obviamente um exemplo construído, mas a lógica tem raciocínio de negócios válido no código real onde vejo o problema).
Se houver < 5 meses entre as datas de início e pagamento, a Expressão 1 será usada, caso contrário, a Expressão 2 será usada.
Isso resulta no erro "ORA-01428: argumento '-1' está fora do intervalo" porque 1 registro tem uma condição de dados inválido que resulta em um valor negativo para o início da cláusula BETWEEN do 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
Então, optei por esta segunda consulta para eliminar primeiro qualquer lugar que isso possa ocorrer:
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
Infelizmente, há algum comportamento inesperado que significa que os valores que a Expressão 1 usaria são validados, mesmo que a instrução não seja executada porque a condição negativa agora é capturada pelo CASE
.
Posso contornar o problema usando ABS
on the MONTHS_BETWEEN
em Expression 1 , mas acho que isso deve ser desnecessário.
Esse comportamento é o esperado? Em caso afirmativo, 'por que', pois parece ilógico para mim e mais como um bug?
Isso criará uma tabela e dados de teste. A consulta é simplesmente eu verificando se o caminho correto no CASE
está sendo seguido.
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