投放DATE返回“无效月份”错误

时间:2018-09-20 13:20:02

标签: sql oracle

我的会计年度开始于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)

使用强制转换时,我会收到“无效月份”,但是当我取消转换时,默认为数字。无论哪种方式,我都无法获得想要的结果。

5 个答案:

答案 0 :(得分:4)

只需从日期中减去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()设置为年/月/日/小时/分钟/周日等月份的开始,这对于将事件记录为毫秒精度但您要进行总结或工作的报表很有帮助。与他们一起讨论“本周/月发生的事情数量”等

答案 1 :(得分:1)

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

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

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

答案 2 :(得分:0)

我尝试了以下代码,它对我有用。 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。

答案 3 :(得分:0)

如果您要保留 CASE 语句

,这应该可行。
select CASE WHEN                               
MONTH(CURRENT_DATE) < 5 THEN                   
YEAR(CURRENT_DATE)-1    ELSE YEAR(CURRENT_DATE)
end                                            

答案 4 :(得分:0)

扩展@CaiusJard的答案,您可以使用以下方法来开始会计年度:

add_months(trunc(add_months(get_date, -4), 'YYYY'), 4)
  • add_months(get_date, -4)从开始日期减去四个月,因此将一月到四月的值调整为上一年 的九月到十二月的日期。因此,例如,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


如注释中其他地方所述,您的原始代码有误,因为cast(<string> as date)使用了会话的NLS设置,并且您正在构造的字符串与该设置不匹配。您可以使用to_date()代替case,以便提供期望的格式掩码(请参阅@alvalongo的答案!)。