【问题标题】:Casting DATE returns "Invalid Month" error转换 DATE 返回“无效月份”错误
【发布时间】:2023-03-27 03:37:01
【问题描述】:

我的会计年度从 5 月 1 日开始,到 4 月 30 日结束。我正在尝试使用 CASE 语句返回会计年度的开始日期。

CAST(
         CASE
             WHEN TO_NUMBER (TO_CHAR (GET_DATE, 'MM')) IN (11,
                                                           12,
                                                           5,
                                                           6,
                                                           7,
                                                           8,
                                                           9,
                                                           10)
             THEN
                   '05/01/'
                || TO_NUMBER (TO_CHAR (TRUNC (get_date, 'year'), 'YYYY'))
             WHEN TO_NUMBER (TO_CHAR (GET_DATE, 'MM')) IN (1,
                                                           2,
                                                           3,
                                                           4)
             THEN
                   '05/01/'
                || TO_NUMBER (TO_CHAR (TRUNC (get_date, 'year'), 'YYYY') - 1)
          END AS DATE)

当我使用演员表时,我得到“无效月份”,但当我取消它时,它默认为数字。无论哪种方式,我都没有得到我想要的结果。

【问题讨论】:

  • ADD_MONTHS(TRUNC(ADD_MONTHS(GET_DATE, -4), 'YYYY'), 4) 也应该这样做
  • 您不能真正将字符串转换为日期。如果字符串恰好在会话的当前nls_date_format 中,您几乎可以摆脱它,但最好使用带有显式格式掩码的to_date(或以其他方式解决问题,这样您就不必这样做了) .
  • @WernfriedDomscheit 谢谢,效果很好!但是,如果您不介意,对于结束日期(试图获得 4/30/'next_year'),我将获得与开始日期相同的年份:(

标签: sql oracle


【解决方案1】:

只需从您的日期减去 4(四)个月并从中提取年份(使用 trunc 将其减少为年份),然后再添加 4(四)个月

SELECT 
  ADD_MONTHS(
    TRUNC(
      ADD_MONTHS( <yourdate> ,-4),
      'YEAR'),
  4) 
FROM DUAL;

为什么会这样:

We have some example dates:              25-04-2009 13-07-2009
These are in the fiscal years beginning: 01-05-2008 01-05-2009
We subtract 4 months from the date:      25-12-2008 13-03-2009
We trunc down to the year start:         01-01-2008 01-01-2009
We add 4 months back on to get to May:   01-05-2009 01-05-2009

为什么它比转换为字符串并返回更好/更好?嗯,这就是原因所在。日期表示为一个数字,这种方法将其保留为一个数字,并且完全依赖于数学;加法、舍入和减法。避免不必要的数据类型转换总是更好,因为这很慢,会占用大量资源并且会引入意外的转换错误

对日期使用 TRUNC 是 oracle 对日期所做的最酷的事情之一,而其他数据库无法处理。能够将任何日期和 TRUNC() 取到年/月/日/小时/分钟/工作日等月份的开始,对于将事件记录到毫秒精度但您想要总结或工作的报告有很大帮助在“本周/月发生的事情的数量”等方面与他们联系

【讨论】:

  • 这是最好的方法。
  • @Caius Jard,问题是关于任何一年 5 月 1 日至明年 4 月 30 日之间任何日期的财政年度开始,所以你不能不减去 5(五)跨度>
  • 方法是对的,但是给出的是六月而不是五月;您需要减去和加上 4 个月,而不是 5 个月?
  • 是的,上面的一个好人告诉我 4 个月,效果很好!现在,我遇到了财政年度结束日期不返回下一年而是给我同一年的问题。所以 2009 年 4 月 30 日结束,2009 年 5 月 1 日开始。
  • @FatimaH。 - 你还没有展示你是如何找到结束日期的。但是您可以使用相同的方法,只需前进一年并后退一天:ADD_MONTHS(TRUNC(ADD_MONTHS(GET_DATE, -4), 'YEAR'), 16) - 1Demo.
【解决方案2】:

对于 Oracle,这将获取任何日期的会计年度开始。

只需将“sysdate”函数替换为 DATE 类型的变量或 DATE 类型的列名即可:

select /* for Oracle */
       to_date(CASE 
                 WHEN extract(month from sysdate)<5 then
                        extract(year from sysdate)-1
                 ELSE 
                   extract(year from sysdate)
               end||'-05-01',
               'yyyy-mm-dd') as start_fiscal_year
from dual;

使用匿名块对 Oracle 数据库进行测试:

Declare
 /*=========================================================================================
  --  objective: calculate Start fiscal date  
  --             Fisacal year starts on May 1 and ends April 30
  --
  --  https://stackoverflow.com/questions/52426117/casting-date-returns-invalid-month-error-in-plsql
  --
  --  Database: Oracle
  --
  --  2018-09-20 alvalongo
  ==========================================================================================*/
  dtStart_date         date:=to_date('2018-01-10','yyyy-mm-dd');
  dtAny_date           date;
  dtStart_fiscal_date  date;
Begin
    dbms_output.put_line('I |ANY_DATE  |START_FISCAL_YEAR');
   for nuI in 0..24 loop
       dtAny_date:=add_months(dtStart_date,nuI);
       --
       select to_date(CASE 
                        WHEN extract(month from dtAny_date)<5 then
                             extract(year from dtAny_date)-1
                        ELSE 
                          extract(year from dtAny_date)
                      end||'-05-01','yyyy-mm-dd') as start_fiscal_year
       into dtStart_fiscal_date
       from dual;
       if extract(month from dtAny_date)=5 then
          dbms_output.put_line('--|----------|----------');
       end if;
       dbms_output.put_line(lpad(nuI,2)
                            ||'|'||to_char(dtAny_date         ,'yyyy-mm-dd')
                            ||'|'||to_char(dtStart_fiscal_date,'yyyy-mm-dd')
                           );
   End loop;
End;
/

使用 dbms_output 缓冲区输出:

I |ANY_DATE  |START_FISCAL_YEAR
 0|2018-01-10|2017-05-01
 1|2018-02-10|2017-05-01
 2|2018-03-10|2017-05-01
 3|2018-04-10|2017-05-01
--|----------|----------
 4|2018-05-10|2018-05-01
 5|2018-06-10|2018-05-01
 6|2018-07-10|2018-05-01
 7|2018-08-10|2018-05-01
 8|2018-09-10|2018-05-01
 9|2018-10-10|2018-05-01
10|2018-11-10|2018-05-01
11|2018-12-10|2018-05-01
12|2019-01-10|2018-05-01
13|2019-02-10|2018-05-01
14|2019-03-10|2018-05-01
15|2019-04-10|2018-05-01
--|----------|----------
16|2019-05-10|2019-05-01
17|2019-06-10|2019-05-01
18|2019-07-10|2019-05-01
19|2019-08-10|2019-05-01
20|2019-09-10|2019-05-01
21|2019-10-10|2019-05-01
22|2019-11-10|2019-05-01
23|2019-12-10|2019-05-01
24|2020-01-10|2019-05-01
Total execution time 517 ms

【讨论】:

  • 这是对问题方法的极大改进和简化;但 Caius 的方法似乎仍然更简单。
【解决方案3】:

扩展@CaiusJard 的回答,您可以通过以下方式开始财政年度:

add_months(trunc(add_months(get_date, -4), 'YYYY'), 4)
  • add_months(get_date, -4) 从开始日期减去四个月,因此 1 月至 4 月的值将调整为上一年的 9 月至 12 月的日期。例如,2018-03-11 将变为 2017-11-11。但是从 5 月开始的日期将保持在同一年,因此例如 2018-07-04 变为 2018-03-04。

  • 然后trunc(..., 'YYYY') 将调整后的值截断为一年的第一天。所以 2018-03-11 变成 2017-11-11 变成 2017-01-01;和 2018-07-04 变成 2018-03-04 变成 2018-01-01。

  • 然后,外部add_months(..., 4) 将四个月加到 调整后的值上。所以 2018-03-11 变成 2017-11-11 变成 2017-01-01 最终变成 2017-05-01; 2018-07-04 变成 2018-03-04 变成 2018-01-01 最终变成 2018-05-01。

要获得财政年度的最后一天,您可以做同样的事情,但在最终计算中增加额外的 12 个月 - 这为您提供下一个财政年度的开始 - 然后减去一天:

add_months(trunc(add_months(get_date, -4), 'YYYY'), 16) - 1

在文档中详细了解add_months()trunc() 函数以及date arithmetic

在 CTE 中带有虚拟日期的演示以显示调整的步骤:

with your_table (get_date) as (
  select add_months(date '2018-01-15', level)
  from dual
  connect by level <= 30
)
select get_date,
  add_months(get_date, -4) as adjusted_month,
  trunc(add_months(get_date, -4), 'YYYY') as adjusted_year,
  add_months(trunc(add_months(get_date, -4), 'YYYY'), 4) as start_date,
  add_months(trunc(add_months(get_date, -4), 'YYYY'), 16) - 1 as end_date
from your_table
order by get_date;

GET_DATE   ADJUSTED_M ADJUSTED_Y START_DATE END_DATE  
---------- ---------- ---------- ---------- ----------
2018-02-15 2017-10-15 2017-01-01 2017-05-01 2018-04-30
2018-03-15 2017-11-15 2017-01-01 2017-05-01 2018-04-30
2018-04-15 2017-12-15 2017-01-01 2017-05-01 2018-04-30
2018-05-15 2018-01-15 2018-01-01 2018-05-01 2019-04-30
2018-06-15 2018-02-15 2018-01-01 2018-05-01 2019-04-30
2018-07-15 2018-03-15 2018-01-01 2018-05-01 2019-04-30
...
2019-01-15 2018-09-15 2018-01-01 2018-05-01 2019-04-30
2019-02-15 2018-10-15 2018-01-01 2018-05-01 2019-04-30
2019-03-15 2018-11-15 2018-01-01 2018-05-01 2019-04-30
...
2020-02-15 2019-10-15 2019-01-01 2019-05-01 2020-04-30
2020-03-15 2019-11-15 2019-01-01 2019-05-01 2020-04-30
2020-04-15 2019-12-15 2019-01-01 2019-05-01 2020-04-30
2020-05-15 2020-01-15 2020-01-01 2020-05-01 2021-04-30
2020-06-15 2020-02-15 2020-01-01 2020-05-01 2021-04-30
2020-07-15 2020-03-15 2020-01-01 2020-05-01 2021-04-30

db<>fiddle


正如 cmets 中其他地方所指出的,您的原始代码出错了,因为 cast(&lt;string&gt; as date) 使用您会话的 NLS 设置,而您正在构建的字符串与该设置不匹配。您可以使用to_date() 而不是case,这样您就可以提供预期的格式掩码(请参阅@alvalongo 的答案!)。

【讨论】:

  • 一切顺利!我不再有这个问题:)
【解决方案4】:

如果您想维护 CASE 声明,这应该可以工作

select CASE WHEN                               
MONTH(CURRENT_DATE) < 5 THEN                   
YEAR(CURRENT_DATE)-1    ELSE YEAR(CURRENT_DATE)
end                                            

【讨论】:

  • 该问题已标记为 Oracle,它没有 month()year() 函数。您可以使用extract() 来做到这一点,但 alvalongo 的回答已经表明了这一点。
【解决方案5】:

我尝试了以下代码,它对我有用。 DATE 是一个内置关键字,因此我们不能将其用作列别名。我将其更改为 FS_START_DATE。

 SELECT SYSDATE,  TO_DATE(CASE
     WHEN TO_NUMBER (TO_CHAR (SYSDATE, 'MM')) IN (11, 12, 5,6,7, 8,9, 10)
     THEN '05/01/' || TO_NUMBER (TO_CHAR (TRUNC (SYSDATE, 'year'), 'YYYY'))
     WHEN TO_NUMBER (TO_CHAR (SYSDATE, 'MM')) IN (1, 2, 3,4)
     THEN  '05/01/' || TO_NUMBER (TO_CHAR (TRUNC (SYSDATE, 'year'), 'YYYY') - 1)
  END,'MM/DD/YYYY') AS FS_START_DATE FROM DUAL;

我正在使用 TO_DATE 函数,这将返回 01-MAY-2018。

【讨论】:

  • 很抱歉给您带来了困惑。我确实有一个类似于 FS_START_DATE 的变量,“as DATE”用于 CAST 将整个代码转换为 DATE。
  • 我编辑了代码以包含 TO_DATE 函数。希望这会有所帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-02-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-16
相关资源
最近更新 更多