Oracle PL/SQL 案例语句未按预期执行

Oracle PL/SQL case statements not executing as expected

我是 PL/SQL 的新手,但我正在尝试将其用于我工作任务的解决方案。本质上,我想 运行 通过 case 语句输出上一个会计期间的值。这是我整理的内容,我将解释下面发生的事情:

select
case
     when substr(sysdate, 1, 6) >= '01-JAN' and substr(sysdate, 1, 6) <= '31-JAN' then '03'
     when substr(sysdate, 1, 6) >= '01-FEB' and substr(sysdate, 1, 6) <= '29-FEB' then '04'
     when substr(sysdate, 1, 6) >= '01-MAR' and substr(sysdate, 1, 6) <= '31-MAR' then '05'
     when substr(sysdate, 1, 6) >= '01-APR' and substr(sysdate, 1, 6) <= '30-APR' then '06'
     when substr(sysdate, 1, 6) >= '01-MAY' and substr(sysdate, 1, 6) <= '31-MAY' then '07'
     when substr(sysdate, 1, 6) >= '01-JUN' and substr(sysdate, 1, 6) <= '30-JUN' then '08'
     when substr(sysdate, 1, 6) >= '01-JUL' and substr(sysdate, 1, 6) <= '31-JUL' then '09'
     when substr(sysdate, 1, 6) >= '01-AUG' and substr(sysdate, 1, 6) <= '31-AUG' then '10'
     when substr(sysdate, 1, 6) >= '01-SEP' and substr(sysdate, 1, 6) <= '30-SEP' then '11'
     when substr(sysdate, 1, 6) >= '01-NOV' and substr(sysdate, 1, 6) <= '30-NOV' then '01'
     when substr(sysdate, 1, 6) >= '01-DEC' and substr(sysdate, 1, 6) <= '31-DEC' then '02'
end actg_period
from table_name

我排除了 10 月,因为那是一个单独的条件。上面发生的事情是今天的 sysdate 值(我已经在我们的数据库中验证了这一点)是“01-JUN”并且应该净输出值为“08”。但是,它所做的是净值“03”。我昨天(5 月 31 日)运行 使用相同的代码,它按预期获得了一个值或“07”。鉴于我对案例陈述的经验不足,我无法确定问题所在。

如有任何建议,我们将不胜感激。

为什么要对 DATE 进行子字符串化?只比较日期:

SELECT
CASE
     WHEN SYSDATE >= TO_DATE('01-JAN','DD-MON') AND SYSDATE <= TO_DATE('31-JAN','DD-MON') THEN '03'
     WHEN SYSDATE >= TO_DATE('01-FEB','DD-MON') AND SYSDATE <= ADD_MONTHS(TO_DATE('01-FEB','DD-MON'),1) -1 THEN '04'
     WHEN SYSDATE >= TO_DATE('01-MAR','DD-MON') AND SYSDATE <= TO_DATE('31-MAR','DD-MON') THEN '05'
     WHEN SYSDATE >= TO_DATE('01-APR','DD-MON') AND SYSDATE <= TO_DATE('30-APR','DD-MON') THEN '06'
     WHEN SYSDATE >= TO_DATE('01-MAY','DD-MON') AND SYSDATE <= TO_DATE('31-MAY','DD-MON') THEN '07'
     WHEN SYSDATE >= TO_DATE('01-JUN','DD-MON') AND SYSDATE <= TO_DATE('30-JUN','DD-MON') THEN '08'
     WHEN SYSDATE >= TO_DATE('01-JUL','DD-MON') AND SYSDATE <= TO_DATE('31-JUL','DD-MON') THEN '09'
     WHEN SYSDATE >= TO_DATE('01-AUG','DD-MON') AND SYSDATE <= TO_DATE('31-AUG','DD-MON') THEN '10'
     WHEN SYSDATE >= TO_DATE('01-SEP','DD-MON') AND SYSDATE <= TO_DATE('30-SEP','DD-MON') THEN '11'
     WHEN SYSDATE >= TO_DATE('01-NOV','DD-MON') AND SYSDATE <= TO_DATE('30-NOV','DD-MON') THEN '01'
     WHEN SYSDATE >= TO_DATE('01-DEC','DD-MON') AND SYSDATE <= TO_DATE('31-DEC','DD-MON') THEN '02'
 END actg_period
FROM table_name ;

您可以只使用 EXTRACT 查看月份:

SELECT CASE EXTRACT(MONTH FROM sysdate) 
WHEN 1 THEN '03'
WHEN 2 THEN '04'
...
WHEN 12 THEN '02'
END AS actg_period
FROM table_name;

这会使您的查询更短且更易于阅读。

将日期转换和比较为字符串通常会导致问题。

你说今天(2022 年 6 月 1 日)你得到 SUBSTR(SYSDATE, 1, 6) 的“01-JUN”。这是绝对正确的(如果我们假设您的系统日期格式类似于 'DD-MON-YYYY')。

然后您说您很惊讶从 CASE 表达式中得到 03。要问的问题是,“字符串‘01-JUN’是否在字符串‘01-JAN’和‘31-JAN`之间”?答案是“是的,是的”。那么,为什么会混淆?

嗯,我们明白为什么了。你真正想说的是:

SYSDATE 的 day-of-month 部分 ('01') 是否在 '01' 和 '31' 之间并且是 SYSDATE 的月份部分 ('JUN') = 'JAN'?如果这样问,答案是否定的,不是,但这不是您的代码所做的。

这就是为什么我们不将日期转换为字符串来比较它们的原因 - 因为我们得到的结果总是正确的,但往往令人惊讶。

有更好的方法可以做到这一点,而且每一种方法都不涉及将日期转换为字符串。

其他人提出了很好的建议。

但我还有一个,它只用了一点数学和最后的字符转换:

ACCTG_PERIOD := TO_CHAR(MOD(EXTRACT(MONTH FROM SYSDATE) + 2, 12), '09');

问题是您比较的是字符串,而不是日期。昨天,substr(sysdate, 1, 6) 给了你 - 假设你的会话 NLS 设置仍然是默认设置,这对你来说不是一个安全的假设,特别是关于其他任何人 运行 这段代码 - 字符串 '31-MAY' .

当比较作为一个字符串时,恰好不适合任何前面的括号。

作为演示,如果您订购每个月的前三天,格式为 DD-MON-YY,按字母顺序排列,您将得到:

01-APR-22
01-AUG-22
01-DEC-22
01-FEB-22
01-JAN-22
01-JUL-22
01-JUN-22
01-MAR-22
01-MAY-22
01-NOV-22
01-OCT-22
01-SEP-22
02-APR-22
02-AUG-22
02-DEC-22
...
02-SEP-22
03-APR-22
03-AUG-22
...
03-SEP-22

仅使用每个月的第一天,您的第一个括号“01-JAN”到“31-JAN”包括 字符串(不是日期):

01-JAN-22
01-JUL-22
01-JUN-22
01-MAR-22
01-MAY-22
01-NOV-22
01-OCT-22
01-SEP-22

以及那些月份中的所有其他日期。

第二个括号“01-FEB”到“29-FEB”包括:

01-FEB-22
01-JAN-22
01-JUL-22
01-JUN-22
01-MAR-22
01-MAY-22
01-NOV-22
01-OCT-22

以及那些月份中的几乎所有其他日期 - 每个月 29 日之前的所有日期,因此排除了 30 日和 31 日。

第三个“01-MAR”到“31-MAR”包含:

01-MAR-22
01-MAY-22
01-NOV-22
01-OCT-22

以及那些月份中的所有其他日期。

第四个“01-APR”到“30-APR”包含所有这些:

01-APR-22
01-AUG-22
01-DEC-22
01-FEB-22
01-JAN-22
01-JUL-22
01-JUN-22
01-MAR-22
01-MAY-22
01-NOV-22
01-OCT-22

以及那些月份中除 31 日以外的所有其他日期。

第五个“01-MAY”到“31-MAY”包含:

01-MAY-22
01-NOV-22
01-OCT-22

以及那些月份中的所有其他日期。

以此类推

当您比较昨天日期 '31-MAY' 的字符串版本时,它不适合第一个括号,因为按字母顺序,'31-M' 不在 '01-J' 和 ' 31-J',因为 'M' 在字母表中位于 'J' 之后。对于第二个括号,它甚至不需要查看月份,它可以在第一个字符之后停止比较——“3”出现在“2”之后,所以它不适合那个括号。第三个括号稍微更令人兴奋,因为它必须检查整个字符串——但是,“31-MAY”仍然不在“01-MAR”和“31-MAR”之间,因为 'Y' 在 [= 之后62=]。两个字符后的第四个括号可能会被拒绝,因为“1”出现在“0”之后。终于可以匹配到第五个括号了,如你所料

但是当您比较今天日期的字符串版本“01-JUN”时,它与您的第一个括号匹配,因为“01-JUN”包含在第一个列表中。


你还没有说你想如何处理十月,但对于其余的转换你不需要 case 表达式;你可以这样做:

TO_CHAR(ADD_MONTHS(TRUNC(SYSDATE, 'MM'), 2), 'MM')

今天给出'08'。您可以修改它,使 October 成为一个特例——完全取决于您要如何对待它;添加不同的月数,或具有特定值的 case 表达式。

我想你可以使用下面这样的东西:

select 
case 
when extract(MONTH from sysdate) < 10 then LPAD(extract(MONTH from sysdate) + 2,2,'0')
when extract(MONTH from sysdate) > 10 then LPAD(extract(MONTH from sysdate) - 10,2,'0')
end actg_period
from table_name;