Я всегда понимал, что 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)