1

我有一个案例,我YEAR TO MONTH为一个值添加了间隔TIMESTAMP&为了实现这一点,我以这种方式使用它

SELECT (END_DATE + NUMTOYMINTERVAL(2, 'MONTH')) FROM DUAL

除了某些值之外,上面的代码几乎适用于所有 END_DATE 值。

例如,when END_DATE = 31-JULY-2013,上述代码的预期结果是,30-SEPT-2013但它会引发错误

ORA-01839: date not valid for month specified

这是因为上面的代码返回31-SEPT-2013了一个无效的日期。

有没有其他方法可以实现这一目标?

(我可以使用ADD_MONTHS但这个函数有问题,它只返回DATE值并且我需要TIMESTAMP作为返回值)

我错过了什么吗?

4

3 回答 3

3

由于end_date没有小数秒,或者实际上没有任何时间组件,您可以使用add_months并将其转换为timestamp

select cast(add_months(end_date, 2) as timestamp) from ...

add_months也有自己的怪癖。如果原始日期是该月的最后一天,您将获得调整后月份的最后一天 - 在这种情况下,如果您打算缩短月份,这就是您想要的,但如果您要去,则可能不是另一种方法:

with t as (
select to_timestamp('2013-07-31', 'YYYY-MM-DD') as end_date from dual
union all select to_timestamp('2013-06-30', 'YYYY-MM-DD') from dual
union all select to_timestamp('2013-02-28', 'YYYY-MM-DD') from dual
union all select to_timestamp('2012-02-29', 'YYYY-MM-DD') from dual
)
select end_date, cast(add_months(end_date, 2) as timestamp)
from t;

END_DATE                       CAST(ADD_MONTHS(END_DATE,2)AST
------------------------------ ------------------------------
2013-07-31 00:00:00.000000     2013-09-30 00:00:00.000000
2013-06-30 00:00:00.000000     2013-08-31 00:00:00.000000
2013-02-28 00:00:00.000000     2013-04-30 00:00:00.000000
2012-02-29 00:00:00.000000     2012-04-30 00:00:00.000000

或者您可以创建自己的函数来处理错误日期,然后向后调整直到找到有效日期:

create or replace function adjust_timestamp(orig_ts in timestamp,
  months in number)
return timestamp is
  new_ts timestamp;
  offset number := 0;
  bad_adjustment exception;
  pragma exception_init(bad_adjustment, -01839);
begin
  while new_ts is null loop
    begin
      new_ts := orig_ts - numtodsinterval(offset, 'DAY')
        + numtoyminterval(months, 'MONTH');
    exception
      when bad_adjustment then
        offset := offset + 1;
        continue;
    end;
  end loop;
  return new_ts;
end;
/

这使用为 ORA-01839 错误代码定义的异常来捕获错误日期,并在循环中执行此操作,因此它可以向后(通过offset)工作,直到找到没有错误的日期。

with t as (
select to_timestamp('2013-07-31', 'YYYY-MM-DD') as end_date from dual
union all select to_timestamp('2013-06-30', 'YYYY-MM-DD') from dual
union all select to_timestamp('2013-02-28', 'YYYY-MM-DD') from dual
union all select to_timestamp('2012-02-29', 'YYYY-MM-DD') from dual
)
select end_date, adjust_timestamp(end_date, 2)
from t;

END_DATE                       ADJUST_TIMESTAMP(END_DATE,2)
------------------------------ ------------------------------
2013-07-31 00:00:00.000000     2013-09-30 00:00:00.000000
2013-06-30 00:00:00.000000     2013-08-30 00:00:00.000000
2013-02-28 00:00:00.000000     2013-04-28 00:00:00.000000
2012-02-29 00:00:00.000000     2012-04-29 00:00:00.000000

这给add_months版本带来了不同的结果。你需要确定你得到了什么,以及你希望数据如何表现。

于 2013-07-11T10:44:53.860 回答
2

这是 ANSI 指定的预期行为 - 请参阅此 AskTom。如果将 30-JUL-2013 加上两个月,您将得到 30-SEP-2013,我认为这是完全可以理解的。如果你在 2013 年 7 月 31 日加上两个月,你会得到……什么?没有 31-SEP-2013 - 9 月只有 30 天。那么,系统应该做什么呢?它应该给你 30-SEP-2013 吗?它应该给你 01-OCT-2013 吗?这些都不对。您已经要求它两次将月份值向前更改两个月。好的,它会尝试并发现结果日期无效 - 所以它会引发错误。

哦亲爱的。

但是 - 谢天谢地,我们不仅仅是凡人。我们是优越的生物。我们是软件开发人员。我们有手册!!!!我们与神近在咫尺!!!!!!!!!!

因此,查阅手册,我们发现我们可以使用 ADD_MONTHS 功能,这几乎可以满足您在此处寻找的功能。但是,ADD_MONTHS 仅对 DATE 值起作用,因此如果您没有进行一些额外的操作来保存它们,您的小数秒将会丢失。但是,正如我所说,我们是软件开发人员......

例子:

DECLARE 
  tsIn  TIMESTAMP := TO_TIMESTAMP('31-JUL-2013 17:31:01', 'DD-MON-YYYY HH24:MI:SS');
  tsOut TIMESTAMP;
  nFrac_secs  NUMBER;
  strBuffer   VARCHAR2(1000);
  strFrac_secs VARCHAR2(1000);
BEGIN
  tsIn := tsIn + NUMTODSINTERVAL(0.1234, 'SECOND');

  strBuffer := TO_CHAR(tsIn);
  strFrac_secs := SUBSTR(strBuffer, -10, 7);

  DBMS_OUTPUT.PUT_LINE('tsIn=' || tsIn);
  DBMS_OUTPUT.PUT_LINE('strBuffer=' || strBuffer);
  DBMS_OUTPUT.PUT_LINE('strFrac_secs=' || strFrac_secs);

  nFrac_secs := TO_NUMBER(strFrac_secs);

  DBMS_OUTPUT.PUT_LINE('nFrac_secs=' || nFrac_secs);

  tsOut := ADD_MONTHS(tsIn, 2);

  DBMS_OUTPUT.PUT_LINE('tsOut before restoring fractional seconds=' || tsOut);

  tsOut := tsOut + NUMTODSINTERVAL(nFrac_secs, 'SECOND');

  DBMS_OUTPUT.PUT_LINE('tsOut after restoring fractional seconds=' || tsOut);
END;

因此,基本上,如果您尝试进行区间算术运算,Oracle 会遵循&^#@$# ANSI 规范并表现得很愚蠢。然后,他们为您提供一个功能(公平地说,记录在案),它或多或少地执行所需的操作,但仅在 DATE 值上执行。我想这就是所谓的“工作保障”...

:-)

分享和享受。

于 2013-07-11T11:27:08.920 回答
0

您可以使用add_months转到正确的日期,然后添加时间戳的小数部分:

SELECT CURRENT_TIMESTAMP,
       CAST(CAST(add_months(trunc(CURRENT_TIMESTAMP), 2) AS TIMESTAMP) +
            (CURRENT_TIMESTAMP -
             CAST(trunc(CURRENT_TIMESTAMP) as timestamp)) as timestamp)
FROM DUAL;

请注意,您将丢失时区。

于 2013-07-11T10:58:42.810 回答