【问题标题】:Oracle sql query closest date for day of yearOracle sql查询一年中最近的日期
【发布时间】:2012-09-07 09:31:19
【问题描述】:

我正在寻找一种更简单的解决方案来查找给定年份值的最近日期(相对于 sysdate)。示例(格式为“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"

基本上,结果日期可以是当年、上一年或下一年。最初我写了一个小程序,如下所示。在这里,我使用参考日期而不是 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)

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

编辑 2:检查“valid_date_?

【问题讨论】:

  • 也许我不明白,但您要查找的日期是存储在表格中还是只是在查找最近的日期?
  • 好问题。我认为关键是闰年。如果您在 2000 年左右寻找 366 个,那么最近的可能是 4 年之后。
  • @Ben,不幸的是,日期记录在表格中。它包含 modified_date、start_day、end_day(例如:31/12/2012、365、1 在这种情况下,预期的转化率为 365 = 30/12/2012 和 1 = 01/01/2013。modified_date 接近日期值,但可以在它们之前,之间甚至之后(例如:31/12/2012, 1, 1)。现在我面临将这些天转换为实际日期值的问题。虽然保证天之间的差距是小于 365,因此不需要复杂的解决方案,我想我会让这个函数足够简单,可以处理任何一般情况。
  • 为什么是sysdate = "01/01/2012" input = 1 result = "01/01/2012" 而不是result = "01/02/2012"
  • @René,我认为有些混乱。要求是,对于参考日期(比如 sysdate),找到最接近年份的日期(sql 中的“DDD”)。因此,可以给出任何日期作为参考,并将 1 到 366 之间的值作为一年中的日期。对于给定的示例,它是最接近参考日期的同一天(01/01/2012),一年中的一天 = 1。抱歉,如果我的问题不够清楚。

标签: oracle date


【解决方案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 年的所有日期,并且对于每个日期,我们都会记录那一年的哪一天。之后,我们计算到所有日期的距离,例如一年中的第一个按升序排列,最后的结果将是第一行。

内部查询是ORACLE特有的,但我相信在DB2中也应该有生成(连续)行的原则

【讨论】:

  • 谢谢彼得森。这有点蛮力(虽然不是你的错)。这无疑开辟了一种与我的逻辑相结合以简化执行的可能性。
【解决方案2】:

以下是我打算暂时使用的解决方案。我必须避免 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 ;  
/  

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-10-21
    • 2023-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多