我有一个案例,我已将YEAR TO MONTH
间隔添加到TIMESTAMP
值&实现这一目标我正在以这种方式使用它
SELECT (END_DATE + NUMTOYMINTERVAL(2, 'MONTH')) FROM DUAL
上述代码几乎可以在除某些值之外的所有END_DATE值中成功运行。
例如,当END_DATE = 31-JULY-2013
时,上述代码的预期结果为30-SEPT-2013
,但会引发错误
ORA-01839: date not valid for month specified
这是因为上面的代码返回31-SEPT-2013
,这是一个无效的日期。
有没有其他方法可以实现这一目标?
(我可以使用ADD_MONTHS
但是对此功能有问题,它只返回DATE
个值并且我需要TIMESTAMP
作为返回值)
我错过了什么吗?
答案 0 :(得分: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
版本提供了不同的结果。您需要确定所获得的内容以及数据的行为方式。
答案 1 :(得分:2)
这是ANSI规定的预期行为 - 请参阅this AskTom。如果您在2013年7月30日之前添加两个月,您将获得2013年9月30日,我认为这是完全可以理解的。如果你在2013年7月31日增加两个月,你会得到......什么?没有2013年9月31日 - 9月只有30天。那么,系统应该做什么?它应该给你30-SEP-2013吗?它应该给你01-OCT-2013吗?这些都不正确。你已经问过两个月将月值改为两个月。好吧,它会尝试并发现结果日期无效 - 因此它会抛出错误。
哦,亲爱的。
但是 - 谢天谢地,我们不仅仅是凡人。我们是优越的生命。我们是软件开发人员。我们有手册!!!!我们不是上帝!!!!!!!!!!
所以,咨询the manual我们发现我们可以使用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值上做。我认为这就是所谓的“工作保障”......
: - )
分享并享受。
答案 2 :(得分: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;
请注意,您将失去时区。