2012-09-14 76 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