我一直都明白,该CASE
语句遵循“短路”原则,因为如果先前步骤被评估为真,则不会对后续步骤进行评估。(此答案是否 SQL Server CASE 语句评估所有条件或在第一个 TRUE 条件时退出?相关但似乎并未涵盖这种情况并且与 SQL Server 相关)。
在以下示例中,我希望MAX(amount)
根据开始日期和支付日期之间的月份数计算不同月份之间的月份范围。
(这显然是一个构建的示例,但该逻辑在我看到问题的实际代码中具有有效的业务推理)。
如果开始日期和支付日期之间的时间小于 5 个月,则将使用表达式 1,否则将使用表达式 2。
这会导致错误“ORA-01428:参数‘-1’超出范围”,因为 1 条记录的数据条件无效,导致 ORDER BY 的 BETWEEN 子句的开头为负值。
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
Run Code Online (Sandbox Code Playgroud)
所以我进行了第二个查询,首先消除任何可能发生的地方:
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
Run Code Online (Sandbox Code Playgroud)
不幸的是,有一些意外行为意味着表达式 1使用的值会被验证,即使语句不会被执行,因为否定条件现在被外部CASE
.
我可以通过在表达式 1ABS
上使用来解决这个问题,但我觉得这应该是不必要的。MONTHS_BETWEEN
这种行为是否符合预期?如果是这样,“为什么”对我来说似乎不合逻辑,更像是一个错误?
这将创建一个表和测试数据。查询只是我检查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
Run Code Online (Sandbox Code Playgroud)
小智 2
因此,我很难从帖子中确定您的实际问题是什么,但我认为当您执行时:
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
Run Code Online (Sandbox Code Playgroud)
您仍然收到ORA-01428: argument '-1' is out of range 吗?
我不认为这是一个错误。我认为这是一个操作顺序的问题。Oracle 需要对结果集返回的所有行进行分析。然后就可以深入了解转换输出的实质内容。
解决这个问题的一些其他方法是使用 where 子句排除该行:
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
-- this excludes the row from being processed
where MONTHS_BETWEEN(paid_date, start_date) > 0
Run Code Online (Sandbox Code Playgroud)
或者您可以在分析中嵌入一个案例,例如:
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
-- This case will be evaluated when the analytic is evaluated
CASE WHEN MONTHS_BETWEEN(paid_date, start_date) < 0
THEN 0
ELSE MONTHS_BETWEEN(paid_date, start_date)
END
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
Run Code Online (Sandbox Code Playgroud)
我希望我能找到一些文档来支持操作顺序,但我还没有找到任何东西......
短路CASE
评估发生在评估解析函数之后。相关查询的操作顺序为:
因此,由于该max over()
情况发生在该情况之前,因此查询失败。
Oracle 的分析函数将被视为行源。如果您对查询执行解释计划,您应该看到一个“窗口排序”,它是分析生成的行,这些行由前一个行源(付款表)提供给它。case 语句是针对行源中的每一行进行计算的表达式。因此,这种情况发生在分析之后是有道理的(至少对我来说)。
归档时间: |
|
查看次数: |
858 次 |
最近记录: |