Eu sempre entendi que a CASE
declaração trabalhava em um 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 VERDADEIRA? Está relacionada, mas parece não cobrir essa situação e está relacionada ao SQL Server).
No exemplo a seguir, desejo calcular o MAX(amount)
intervalo de meses que difere com base em quantos meses estão entre as datas de início e de pagamento.
(Este é obviamente um exemplo construído, mas a lógica tem um raciocínio comercial válido no código real em que vejo o problema).
Se houver menos de 5 meses entre as datas de início e de 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 possui uma condição de dados inválida que resulta em um valor negativo para o início da cláusula BETWEEN da 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, eu fui para esta segunda consulta para eliminar primeiro em 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 usados pela Expressão 1 são validados, mesmo que a instrução não seja executada porque a condição negativa agora está interceptada pelo externo CASE
.
I pode contornar o problema usando ABS
no MONTHS_BETWEEN
no Expression 1 , mas eu sinto que este deve ser desnecessário.
Esse comportamento é o esperado? Se sim, 'por que', como 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 CASE
está sendo usado.
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)
Respostas:
Portanto, foi difícil para mim determinar qual era sua pergunta real no post, mas presumo que seja quando você executa:
Você ainda recebe ORA-01428: o argumento '-1' está fora do intervalo ?
Eu não acho que isso seja um bug. Eu acho que é uma coisa de ordem de operação. A Oracle precisa fazer a análise em todas as linhas retornadas pelo conjunto de resultados. Então, pode-se 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
Gostaria de encontrar alguma documentação para fazer backup da ordem de operação, mas ainda não consegui encontrar nada ...
A
CASE
avaliação de 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
max over()
acontece antes do caso, a consulta falha.As funções analíticas da Oracle seriam consideradas uma fonte de linha . Se você executar um plano de explicação em sua consulta, verá uma "classificação de janela" que é a analítica, gerando linhas, que são alimentadas pela fonte de linha anterior, a tabela de pagamento. Uma instrução de caso é uma expressão que é avaliada para cada linha na origem da linha. Portanto, faz sentido (pelo menos para mim) que o caso ocorra após a análise.
fonte
SQL define o que fazer, não como fazê-lo. Embora normalmente a Oracle faça um curto-circuito na avaliação de caso, essa é uma otimização e, portanto, será evitada se o otimizador acreditar que um caminho de execução diferente fornece 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, o que normalmente também causa um curto-circuito.
Parece não haver nenhuma documentação explicitamente dizendo que a avaliação de curto-circuito pode ser ignorada pelo otimizador. A coisa mais próxima (embora não suficientemente próxima) que eu possa encontrar é esta :
Essa pergunta mostra a avaliação de curto-circuito sendo ignorada mesmo sem análises (embora exista um agrupamento).
Tom Kyte menciona que o curto-circuito pode ser ignorado em sua resposta a uma pergunta da ordem de avaliação de predicados .
Você deve abrir um SR com Oracle. Suspeito que eles o aceitem como um erro de documentação e aprimore a documentação na próxima versão para incluir uma ressalva sobre o otimizador.
fonte
Parece que está dando certo o que faz com que o Oracle comece a avaliar todas as expressões no CASE. Vejo
As duas primeiras consultas são executadas OK.
fonte