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
Portanto, foi difícil para mim determinar qual era sua pergunta real na postagem, mas presumo que quando você executa:
Você ainda obtém ORA-01428: o argumento '-1' está fora do intervalo ?
Eu não acho que isso é um bug. Eu acho que é uma coisa de ordem de operação. A Oracle precisa fazer análises em todas as linhas retornadas pelo conjunto de resultados. Em seguida, pode chegar ao âmago da questão de transformar a saída.
Algumas maneiras adicionais de contornar isso seriam excluir a linha com uma cláusula where:
Ou você pode incorporar um caso em sua análise como:
Explicação
Eu gostaria de encontrar alguma documentação para fazer backup da ordem de operação, mas não consegui encontrar nada... ainda.
A
CASE
avaliação do curto-circuito ocorre após a avaliação da função analítica. A ordem das operações para a consulta em questão seria:Portanto, como isso
max over()
acontece antes do caso, a consulta falha.As funções analíticas do Oracle seriam consideradas uma fonte de linha . Se você executar um plano de explicação em sua consulta, deverá ver uma "classificação de janela" que é a análise, gerando linhas, que são alimentadas pela fonte de linha anterior, a tabela de pagamento. Uma instrução case é uma expressão avaliada para cada linha na origem da linha. Então faz sentido (pelo menos para mim) que o caso aconteça depois da analítica.
SQL define o que fazer, não como fazer. Embora normalmente o Oracle interrompa a avaliação do caso, isso é uma otimização e, portanto, será evitada se o otimizador acreditar que um caminho de execução diferente oferece desempenho superior. Essa diferença de otimização seria esperada quando a análise estiver envolvida.
A diferença de otimização não se limita ao caso. Seu erro pode ser reproduzido usando coalescência, que normalmente também causaria um curto-circuito.
Não parece haver nenhuma documentação dizendo explicitamente que a avaliação de curto-circuito pode ser ignorada pelo otimizador. A coisa mais próxima (embora não perto o suficiente) que posso encontrar é esta :
Esta questão mostra a avaliação de curto-circuito sendo ignorada mesmo sem análise (embora haja agrupamento).
Tom Kyte menciona que o curto-circuito pode ser ignorado em sua resposta a uma pergunta sobre Ordem de avaliação de predicados .
Você deve abrir um SR com Oracle. Suspeito que eles aceitarão isso como um bug de documentação e aprimorarão a documentação na próxima versão para incluir uma ressalva sobre o otimizador.
Parece que é o janelamento que faz o Oracle começar a avaliar todas as expressões em CASE. Ver
As duas primeiras consultas são executadas corretamente.