2013-07-11 43 views
1

我有,我有加YEAR TO MONTH間隔爲TIMESTAMP值&的情況下,實現這一目標,我用這種方式添加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

:你結束日期是否包含時間戳? –

+1

'end_date'是否有小數秒?如果是月末,你是否總想調整日期;那麼2013-02-28之後的2個月會是2013-04-28還是2013-04-30?事實上,這是否總是一個月的最後一天? –

+0

@GauravSoni是的,END_DATE是時間戳值。 –

回答

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版本提供了不同的結果。你需要確定你得到了什麼,以及你希望數據的行爲。

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; 

請注意,你將失去的時區。

2

這是ANSI所規定的預期行爲 - 請參見this AskTom。如果你增加兩個月到2013年7月30日,你會得到30-SEP-2013,我認爲這是完全可以理解的。如果你增加兩個月到2013年7月31日,你會得到......什麼?沒有31-SEP-2013 - 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; 

所以,基本上,如果你嘗試做區間算術甲骨文遵循& ^#@ $# ANSI規範,扮演愚蠢。然後他們給你一個函數(這是公平的,記錄在案的),它或多或少地要求什麼,但只是在日期值。我認爲這是所謂的「工作安全」 ...

:-)

分享和享受。

+0

+1,雖然OP提到了「add_months」,並且建立的註釋小數秒在這裏不是問題(但並不是爲什麼它在這種情況下是時間戳)。這對於某些人來說是有用的。 'add_month'行爲並不總是如預期/想要的那樣。第一步是爲邊緣案例建立所需的輸出。 –

+0

@AlexPoole - 謝謝。我經歷了使用ADD_MONTHS所需的所有垃圾,因爲OP提到它減少了小數秒。 (我最初的解決方案很像你的,但在中途改變了馬匹)。這當然是一種騙局,但是如果我們要接近上帝,我們必須願意抓住(當然使用手套)宇宙的性腺並擠壓 - 或者天堂是爲了什麼? :-) –

+0

好吧,有一張圖片......謝謝!我認爲OP的評論 - 現在被刪除 - 是沒有小數秒,它只是一個用戶輸入的日期,而不是它們存在並被刪除。也許我誤解了。只是爲了改變。 –