1

I am looking for a simpler solution to find the nearest date (with respect to sysdate) for a given day of year value. Examples (in format "dd/mm/yyyy")

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"

Basically the resulting date can be in current year, previous year or next year. Initially I wrote a small procedure as given below. Here I am using a reference date instead of sysdate to test the results. This works for cases where the input day of year is not 366. But when it is 366, this fails and one may need to travel further backwards and forward to find the nearest valid date. After adding the checks for leap year (all conditions 4,100,400 etc) the code became a real mess.

I would appreciate if you can suggest a simpler, better and foolproof solution (function or single query). Please do not use complex constructs too specific to Oracle as I will have to port the same to DB2 as well. Also, efficiency is of least concern as it is not going to be executed heavily.

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 ;  
/  

Logically the algorithm I am considering is

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)

EDIT 1 : Given below is the implementation of the algorithm I have given above (there is some redundancy in the code). I am still looking forward for alternatives or refinements to the solutions.

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 : The check "valid_date_? < add_months" is to ensure that this date is on the same (iterated) year itself (otherwise, a value of 366, for a non-leap year will return next year start day). Also, the associated comparison with reference date is to guard against cases like (reference = "30/01/2012", input = 365). Here the valid_date_1 should be "31/12/2011" and not "30/12/2012", as the first one is the closest date to the reference, with day of year value as 365.

4

2 に答える 2

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

したがって、1980年から約100年間のすべての日付があり、各日付について、その年のどの日であったかを記録します。その後、たとえば年の1番目であるすべての日付までの距離を計算し、昇順で並べ替えると、最終結果が最初の行になります。

内部クエリはORACLEに少し固有ですが、DB2でも(連続した)行を生成するという原則があるはずです。

于 2012-09-14T10:13:30.243 に答える
0

以下は、当面使用する予定のソリューションです。DB2 で動作させるには、WITH 句を避ける必要がありました。Peterson のソリューションは、この方向性を考えるのに役立ちました。

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 ;  
/  
于 2012-09-16T18:02:36.690 に答える