Я всегда понимал, что CASE
утверждение работает по принципу «короткого замыкания» в том смысле, что оценка последующих шагов не происходит, если предыдущий шаг оценивается как истинный. (Этот ответ Оценивает ли оператор SQL Server CASE все условия или выход при первом ИСТИННОМ условии? Связан, но, по-видимому, не охватывает эту ситуацию и относится к SQL Server).
В следующем примере я хочу рассчитать MAX(amount)
разницу между месяцами, которая зависит от количества месяцев между датами начала и оплаты.
(Это, очевидно, сконструированный пример, но логика имеет обоснованные бизнес-аргументы в реальном коде, где я вижу проблему).
Если между датами начала и оплаты осталось менее 5 месяцев, то будет использовано выражение 1 , в противном случае будет использовано выражение 2 .
Это приводит к ошибке «ORA-01428: аргумент« -1 »выходит за пределы диапазона», потому что 1 запись имеет недопустимое условие данных, что приводит к отрицательному значению для начала предложения BETWEEN в ORDER BY.
Запрос 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
Итак, я пошел на этот второй запрос, чтобы сначала устранить все, где это может произойти:
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
К сожалению, есть непредвиденное поведение, которое означает, что значения, которые Выражение 1 будет использовать, будут проверены, даже если оператор не будет выполнен, потому что отрицательное условие теперь перехватывается внешним CASE
.
Я могу обойти проблему, используя ABS
на MONTHS_BETWEEN
в Expression 1 , но я чувствую , что это должно быть ненужным.
Это поведение, как ожидалось? Если так, то почему, поскольку это кажется мне нелогичным и больше похоже на ошибку?
Это создаст таблицу и тестовые данные. Запрос просто я проверяю, что выбран правильный путь в CASE
.
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)
Ответы:
Поэтому мне было трудно определить, какой у вас был настоящий вопрос по почте, но я предполагаю, что это так, когда вы выполняете:
Вы все еще получаете ORA-01428: аргумент '-1' находится вне диапазона ?
Я не думаю, что это ошибка. Я думаю, что это порядок работы вещь. Oracle необходимо выполнить аналитику для всех строк, возвращаемых набором результатов. Тогда это может перейти к мельчайшим изменениям производительности.
Пара дополнительных способов обойти это - исключить строку с предложением where:
Или вы можете встроить кейс в свою аналитику, например:
объяснение
Хотел бы я найти какую-то документацию для резервного копирования порядка операций, но я не смог ничего найти ... пока.
Оценка
CASE
короткого замыкания происходит после оценки аналитической функции. Порядок операций для рассматриваемого запроса будет следующим:Таким образом, так как
max over()
происходит раньше, запрос не выполняется.Аналитические функции Oracle будут считаться источником строк . Если вы выполняете план объяснения для своего запроса, вы должны увидеть «сортировку окна», которая является аналитической, генерирующей строки, которые передаются ему предыдущим источником строки, таблицей платежей. Оператор case - это выражение, которое оценивается для каждой строки в источнике строки. Так что имеет смысл (по крайней мере для меня), что случай происходит после аналитического.
источник
SQL определяет, что делать, а не как это делать. В то время как Oracle обычно выполняет оценку короткого замыкания, это является оптимизацией, и поэтому ее следует избегать, если оптимизатор полагает, что другой путь выполнения обеспечивает превосходную производительность. Такой разницы в оптимизации можно ожидать, когда аналитика участвует.
Разница в оптимизации не ограничивается случаем. Ваша ошибка может быть воспроизведена с помощью coalesce, которая обычно также приводит к короткому замыканию.
Кажется, нет какой-либо документации, в которой прямо говорится, что оптимизатор может игнорировать оценку короткого замыкания. Самая близкая вещь (хотя не достаточно близко), которую я могу найти, - это :
Этот вопрос показывает, что оценка короткого замыкания игнорируется даже без аналитики (хотя есть группировка).
Том Кайт упоминает, что короткое замыкание может быть проигнорировано в его ответе на вопрос о порядке оценки предикатов .
Вы должны открыть SR с Oracle. Я подозреваю, что они примут это как ошибку документации и улучшат документацию в следующей версии, чтобы включить предупреждение об оптимизаторе.
источник
Похоже, именно из-за окон Oracle начинает оценивать все выражения в CASE. Видеть
Первые два запроса выполняются нормально.
источник