2012-09-14 78 views
1

我正在尋找一個更簡單的解決方案來查找給定日期的年份值的最近日期(相對於sysdate)。實施例(在格式「日/月/年」)Oracle sql查詢一年中最近的日期

sysdate = "01/01/2012" input = 365  result = "31/12/2011" 
sysdate = "01/01/2012" input = 366  result = "31/12/2012" 
sysdate = "01/01/2012" input = 1  result = "01/01/2012" 
sysdate = "31/12/2012" input = 1  result = "01/01/2013" 

基本上所得到的日期可以在當前年,前一年或明年。最初我寫了一個如下所示的小程序。這裏我使用的是參考日期而不是sysdate來測試結果。這適用於一年中的輸入日不是366的情況。但是,當它是366時,這會失敗,並且可能需要進一步向前和向後旅行以找到最近的有效日期。在添加閏年檢查(所有條件4,100,400等)後,代碼變得非常混亂。

如果你能提出一個更簡單,更好,更簡單的解決方案(函數或單個查詢),我將不勝感激。請不要使用針對Oracle特有的複雜結構,因爲我將不得不將它們移植到DB2。而且,效率最不受關注,因爲它不會被嚴重執行。

CREATE OR REPLACE PROCEDURE test(ref_date_str varchar2, doy number) IS 
    ref_date   date ; 
    nearest_date  date ; 
BEGIN 
    ref_date := to_date(ref_date_str, 'dd/mm/yyyy') ; 

    WITH choices AS 
    ( 
     SELECT trunc(ref_date, 'yyyy') + doy - 1 AS choice_date FROM dual 
     UNION 
     SELECT trunc(trunc(ref_date, 'yyyy') - 1, 'yyyy') + doy - 1 AS choice_date FROM dual 
     UNION 
     SELECT add_months(trunc(ref_date, 'yyyy'), 12) + doy - 1 AS choice_date FROM dual 
    ) 
    SELECT choice_date INTO nearest_date FROM choices WHERE abs(ref_date - choice_date) = 
     (SELECT min(abs(ref_date - choice_date)) FROM choices) AND rownum < 2 ; 

    dbms_output.put_line(to_char(nearest_date, 'dd/mm/yyyy')) ; 
END ; 
/ 

按道理我正在考慮的算法是

for each year backwards from current year 
    if a valid date found for the doy, and it is <= sysdate 
    first_date = this valid date 
    exit loop 

for each year forward from current year 
    if a valid date found for the doy, and it is > sysdate 
    second_date = this valid date 
    exit loop 

chosen_date = closest_to_sysdate_among(first_date, second_date) 

感謝&問候, Reji

編輯1:下面給出的是我在上面已經給出了算法的實現(有一些代碼冗餘)。我仍然期待着解決方案的替代或改進。

CREATE OR REPLACE FUNCTION GetNearestDate(reference_date DATE, day_of_year NUMBER) RETURN DATE IS 
    valid_date_1 DATE ; 
    valid_date_2 DATE ; 
    iter_date  DATE ; 
BEGIN 
    iter_date := trunc(reference_date, 'yyyy') ; 

    WHILE TRUE 
    LOOP 
     valid_date_1 := iter_date + day_of_year - 1 ; 

     IF valid_date_1 < add_months(iter_date, 12) AND valid_date_1 <= reference_date THEN 
      EXIT ; 
     END IF ; 

     iter_date := trunc(iter_date - 1, 'yyyy') ; 
    END LOOP ; 

    iter_date := trunc(reference_date, 'yyyy') ; 

    WHILE TRUE 
    LOOP 
     valid_date_2 := iter_date + day_of_year - 1 ; 

     IF valid_date_2 < add_months(iter_date, 12) AND valid_date_2 > reference_date THEN 
      EXIT ; 
     END IF ; 

     iter_date := add_months(iter_date, 12) ; 
    END LOOP ; 

    IF abs(valid_date_1 - reference_date) <= abs(valid_date_2 - reference_date) THEN 
     RETURN valid_date_1 ; 
    END IF ; 

    RETURN valid_date_2 ; 
END ; 
/ 

EDIT 2:檢查「?valid_date_ < ADD_MONTHS」是爲了保證這個日期是相同的(重複)當年的自己(否則,366的值,對於非閏年接下來將返回一年的開始日期)。此外,與參考日期相關聯的比較是爲了防範例如(參考=「2012年1月30日」,輸入= 365)。這裏valid_date_1應該是「2011年12月31日」而不是「2012年12月30日」,因爲第一個日期與參考日期最接近,日年值爲365.

+0

也許我不明白,但是你正在尋找的日期存儲在一個表中,或者你只是在尋找最近的日期? – Ben

+0

不錯的問題。我認爲這是閏年。如果你在2000年左右尋找366,最近的一個可能會在4年後。 – Rene

+0

@Ben,不幸的是日期被存儲在表中。它包含modified_date,start_day,end_day(例如:31/12/2012,365,1,在這種情況下,預期轉換爲365 = 2012年12月30日和1 = 2013年1月1日。modified_date接近日期值,但可以在之前,之間或之後(例如:2012年12月31日,1日)。現在我面臨將這幾天轉換爲實際日期值的問題。雖然時間之間的差距保證不到365,因此不需要複雜的解決方案,但我認爲我將使這個功能足夠萬無一失來處理任何一般情況。 – mpathi

回答

0

以下的原則是,我打算用暫時的解決方案。我必須避免使用WITH子句才能使其與DB2一起工作。彼得森的解決方案有助於朝這個方向思考。

CREATE OR REPLACE FUNCTION GetNearestDate(reference_date DATE, day_of_year NUMBER) RETURN DATE IS 
    nearest_date DATE ; 
BEGIN 
    SELECT valid_date INTO nearest_date 
    FROM 
    ( 
     SELECT first_date + day_of_year - 1 AS valid_date 
     FROM 
     ( 
      SELECT add_months(trunc(reference_date, 'YYYY'), (rownum-10) * 12) AS first_date 
      FROM all_objects 
      WHERE rownum <= 20 
     ) 
     WHERE to_char(first_date, 'YYYY') = to_char(first_date + day_of_year - 1, 'YYYY') 
     ORDER BY abs(first_date + day_of_year - 1 - reference_date), first_date 
    ) 
    WHERE rownum < 2 ; 

    RETURN nearest_date ; 

EXCEPTION WHEN OTHERS THEN 
    RETURN nvl(reference_date, sysdate) ; 
END ; 
/ 
1

所以如果效率是不是一個問題,我會用帶有日期的預計算表,類似於以下內容:

with dates as 
(SELECT to_date('01-01-1980', 'DD-MM-YYYY') + rownum day, 
    to_number(to_char(to_date('01-01-1980', 'DD-MM-YYYY') + rownum, 
         'DDD')) day_of_year 
    FROM ALL_OBJECTS 
WHERE ROWNUM <= 100 * 365) 
select t.* 
from (select dates.*, 
      abs(to_date('01-01-2012', 'DD-MM-YYYY') - dates.day) diff 
     from dates 
    where dates.day_of_year = 1 
    order by 3) t 
where rownum <= 1 

因此,我們有所有〜100年日期1980年以後和我們注意到每個日期是哪天在這一年。之後,我們計算出所有日期的距離,例如一年中第一次將它們升序排列,最終結果將成爲第一排。

內部查詢是有點具體到ORACLE,但我認爲應該有生成DB2(連續)行以及

+0

感謝彼得森。這是一個蠻力(但不是你的錯)。它絕對開闢了一種與我的邏輯相結合的可能性,以稍微緩解執行。 – mpathi