【问题标题】:Accurately calculate number of years elapsed, accounting for leap years, in Oracle在 Oracle 中准确计算经过的年数,考虑闰年
【发布时间】:2020-10-22 11:52:01
【问题描述】:

在 Oracle 中,我需要计算自给定日期以来经过的年数的从 1 开始的值,考虑闰年

最初的计算没有考虑闰年。原来是

CEIL ((SYSDATE- prog_start_dt) / 365))

然后我决定使用MONTHS_BETWEEN,但是我的新计算中有一个错误:

CEIL(MONTHS_BETWEEN(SYSDATE, prog_start_dt) / 12)

错误是,如果我在 prog_start_date 之后正好 1 年,无论时间如何,值都会少 1。例如:

sysdate = 2020-07-01 15:30:00
prog_start_dt = 2019-07-01 00:00:00
--> 1. CEIL ((SYSDATE- prog_start_dt) / 365)) --> Result: 2
--> 2. CEIL(MONTHS_BETWEEN(SYSDATE, prog_start_dt) / 12) --> Result 1

正确的结果是2。所以我需要顶级公式的行为,但要考虑闰年,显然不会是365。有好的解决办法吗?

【问题讨论】:

    标签: sql oracle date select


    【解决方案1】:

    这是a documented behavior

    如果 date1date2 是一个月中的同一天或两个月的最后一天,则结果始终为整数。

    如果您真的想解决这个问题,那么一种选择是添加一些逻辑来处理这种特定情况:当比较的日期属于相同的月份和日期时,然后检查时间部分以查看年份是否还已过:

    ceil(months_between(sysdate, prog_start_dt) / 12)
    + case when to_char(sysdate, 'mm-dd') = to_char(prog_start_dt, 'mm-dd') 
           and to_char(sysdate, 'hh24:mi') > to_char(prog_start_dt, 'hh24:mi')
        then 1
        else 0
    end
    

    Demo on DB Fiddle

    with t as (
        select 
            to_date('2020-07-01 15:30:00', 'yyyy-mm-dd hh24:mi:ss') date1, 
            to_date('2019-07-01 00:00:00' , 'yyyy-mm-dd hh24:mi:ss') date2
        from dual
    )
    select 
        ceil(months_between(date1, date2) / 12)
        + case when to_char(date1, 'mm-dd') = to_char(date2, 'mm-dd') 
               and to_char(date1, 'hh24:mi') > to_char(date2, 'hh24:mi')
            then 1
            else 0
        end year_diff
    from t
    
    | YEAR_DIFF | | --------: | | 2 |

    【讨论】:

    • 这在闰年失败db<>fiddle
    • 您能解释一下为什么它不适用于 02-29 和 02-28 吗?它适用于 02-28 02-27 和同一个月的其他日期。
    【解决方案2】:

    来自documentation on MONTHS_BETWEEN

    MONTHS_BETWEEN 返回日期 date1date2 之间的月数。如果date1 晚于date2,则结果为正。如果date1 早于date2,则结果是否定的。如果date1date2 是一个月中的同一天或两个月的最后一天,则结果始终为整数。否则,Oracle 数据库会根据 31 天的月份计算结果的小数部分,并考虑时间分量 date1date2 的差异。

    您可以使用以下方法“修复”MONTHS_BETWEEN,这样当月的日期相同(或者它们都是月的最后一天)时,它并不总是使用整数值:

    MONTHS_BETWEEN( end_date, start_date )
    +
    CASE
    WHEN EXTRACT( DAY FROM start_date ) = EXTRACT( DAY FROM end_date )
    OR   ( start_date = LAST_DAY( start_date ) AND end_date = LAST_DAY( end_date ) )
    THEN ( end_date
         - ADD_MONTHS( start_date, MONTHS_BETWEEN( end_date, start_date ) )
         ) / 31
    ELSE 0
    END
    

    因此,您的查询将是:

    SELECT start_date,
           end_date,
           CEIL(
             (
             MONTHS_BETWEEN( end_date, start_date )
             +
             CASE
             WHEN EXTRACT( DAY FROM start_date ) = EXTRACT( DAY FROM end_date )
             OR   ( start_date = LAST_DAY( start_date ) AND end_date = LAST_DAY( end_date ) )
             THEN ( end_date
                    - ADD_MONTHS( start_date, MONTHS_BETWEEN( end_date, start_date ) )
                  ) / 31
             ELSE 0
             END
             )
             / 12
           ) AS years
    FROM   test_data;
    

    对于一些测试数据:

    CREATE TABLE test_data ( start_date, end_date ) AS
      SELECT DATE '2019-07-01',
             DATE '2020-07-01' + INTERVAL '15:30:00' HOUR TO SECOND
      FROM   DUAL
    UNION ALL
      SELECT DATE '2019-07-31',
             DATE '2020-02-29' + INTERVAL '15:30:00' HOUR TO SECOND
      FROM   DUAL
    UNION ALL
      SELECT DATE '2019-02-28',
             DATE '2020-02-29' + INTERVAL '15:30:00' HOUR TO SECOND
      FROM   DUAL;
    

    这个输出:

    START_DATE | END_DATE |年 :----------------- | :----------------- | ----: 2019-07-01 00:00:00 | 2020-07-01 15:30:00 | 2 2019-07-31 00:00:00 | 2020-02-29 15:30:00 | 1 2019-02-28 00:00:00 | 2020-02-29 15:30:00 | 2

    db小提琴here

    【讨论】:

    • 谢谢...看起来真的很复杂。为什么总是 31?
    • @geneb。它是 31,因为这是 Oracle 在 MONTHS_BETWEEN 中使用的,我试图与它们的功能保持一致。来自文档:“否则,Oracle 数据库会根据 31 天的月份计算结果的小数部分”。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-17
    • 2017-08-15
    • 2017-01-13
    • 1970-01-01
    • 1970-01-01
    • 2012-05-23
    相关资源
    最近更新 更多