我正在寻找一种更简单的解决方案,以查找给定日期值的最近日期(相对于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)
谢谢&amp;问候, 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 ;
/
编辑2:检查“valid_date_?&lt; add_months”是为了确保此日期在同一(迭代)年份本身(否则,值为366,非闰年将在明年开始日返回)。此外,与参考日期的相关比较是为了防范诸如(reference =“30/01/2012”,input = 365)之类的情况。这里的valid_date_1应该是“31/12/2011”而不是“30/12/2012”,因为第一个是与参考最接近的日期,一年中的某一天值为365.
答案 0 :(得分: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中生成(连续)行的原则
答案 1 :(得分: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 ;
/