甲骨文日期作为月份的分数

Oracle date as fraction of month

我想得到两个日期之间的 table 个月,以及这两个日期涵盖的每个月的一小部分。

例如,开始日期为 15/01/2017,结束日期为 01/03/2017,它将输出:

01/2017 : 0.5483..
02/2017 : 1
03/2017: 0.0322..

1 月和 3 月的计算结果分别为 17/31 和 1/31。我目前有查询:

WITH dates_between as (SELECT ADD_MONTHS(TRUNC(TO_DATE(:givenStartDate,'dd/mm/yyyy'), 'MON'), ROWNUM - 1) date_out
                    FROM   DUAL
                    CONNECT BY ADD_MONTHS(TRUNC(TO_DATE(:givenStartDate,'dd/mm/yyyy'), 'MON'), ROWNUM - 1)
                        <= TRUNC(TO_DATE(:givenEndDate,'dd/mm/yyyy'), 'MON')
)

select * from dates_between

这会输出两个日期之间的每个月并将其格式化为月初。我只需要另一列来给我开始日期和结束日期所涵盖的分数。我不确定有什么方法可以做到这一点而不会让它变得混乱。

我会这样做(n.b。我已经扩展了你的 dates_between 来处理多行,纯粹是为了演示目的。如果你只使用一组参数, 你不需要这样做):

WITH        params AS (SELECT 1 ID, '15/01/2017' givenstartdate, '01/03/2017' givenenddate FROM dual UNION ALL
                       SELECT 2 ID, '15/01/2017' givenstartdate, '23/01/2017' givenenddate FROM dual UNION ALL
                       SELECT 3 ID, '01/01/2017' givenstartdate, '07/04/2017' givenenddate FROM dual),
     dates_between AS (SELECT ID,
                              to_date(givenstartdate, 'dd/mm/yyyy') givenstartdate,
                              to_date(givenenddate, 'dd/mm/yyyy') givenenddate,
                              add_months(trunc(to_date(givenstartdate, 'dd/mm/yyyy'), 'MON'), LEVEL - 1) start_of_month,
                              last_day(add_months(trunc(to_date(givenstartdate, 'dd/mm/yyyy'), 'MON'), LEVEL - 1)) end_of_month
                       FROM   params
                       CONNECT BY add_months(trunc(to_date(givenstartdate, 'dd/mm/yyyy'), 'MON'),  LEVEL - 1) <=
                                  trunc(to_date(givenenddate, 'dd/mm/yyyy'), 'MON')
                                  AND PRIOR ID = ID
                                  AND PRIOR sys_guid() IS NOT NULL)
SELECT ID,
       givenstartdate,
       givenenddate,
       start_of_month date_out,
       end_of_month,
       months_between(LEAST(givenenddate, end_of_month) + 1, GREATEST(start_of_month, givenstartdate))
FROM   dates_between;

    ID GIVENSTARTDATE GIVENENDDATE DATE_OUT   END_OF_MONTH       DIFF

     1 15/01/2017     01/03/2017   01/01/2017 31/01/2017   0.54838709
     1 15/01/2017     01/03/2017   01/02/2017 28/02/2017            1
     1 15/01/2017     01/03/2017   01/03/2017 31/03/2017   0.03225806
     2 15/01/2017     23/01/2017   01/01/2017 31/01/2017   0.29032258
     3 01/01/2017     07/04/2017   01/01/2017 31/01/2017            1
     3 01/01/2017     07/04/2017   01/02/2017 28/02/2017            1
     3 01/01/2017     07/04/2017   01/03/2017 31/03/2017            1
     3 01/01/2017     07/04/2017   01/04/2017 30/04/2017   0.22580645

N.B。您可能需要根据您的要求添加一个 case 语句来决定是否要将 1 添加到 diff 计算中。

试试这个

第一个月,我计算了 remaining days / total days,上个月,我用 1 减去它得到 days passed / total days

DBFiddle Demo

WITH tbl AS
  (SELECT date '2017-01-15' AS givenStartDate 
        ,date '2017-03-01' AS givenEndDate
   FROM dual
   )
SELECT ADD_MONTHS(TRUNC(givenStartDate, 'MON'), ROWNUM - 1) AS date_out ,
       CASE
        WHEN 
        rownum - 1 = 0 
            THEN months_between(last_day(givenStartDate), givenStartDate)

        WHEN ADD_MONTHS(TRUNC(givenStartDate, 'MON'), ROWNUM - 1) = TRUNC(givenEndDate, 'MON') 
            THEN 1 - (months_between(last_day(givenEndDate), givenEndDate))

        ELSE 1
       END AS perc
FROM tbl 
CONNECT BY ADD_MONTHS(TRUNC(givenStartDate, 'MON'), ROWNUM - 1) 
    <= TRUNC(givenEndDate, 'MON');

输出

+-----------+-------------------------------------------+
| DATE_OUT  |                   PERC                    |
+-----------+-------------------------------------------+
| 01-JAN-17 | .5161290322580645161290322580645161290323 |
| 01-FEB-17 |                                         1 |
| 01-MAR-17 | .0322580645161290322580645161290322580645 |
+-----------+-------------------------------------------+

months_between() function "calculates the fractional portion of the result based on a 31-day month"。这意味着如果您的范围在没有 31 天的月份开始或结束,您得到的分数可能与您期望的不完全相同:

select months_between(date '2017-04-02', date '2017-04-01') as calc from dual

      CALC
----------
.0322580645

...这是 1/31,而不是 1/30。要获得 0.0333...,您需要计算每个月的天数,至少是第一个月和最后一个月。这使用递归 CTE (11gR2+) 来获取月份,使用另一个 CTE 提供的几个日期范围作为演示来显示差异(当然你也可以使用分层查询):

with ranges (id, start_date, end_date) as (
  select 1, date '2017-01-15', date '2017-03-01' from dual
  union all select 2, date '2017-01-31', date '2017-03-01' from dual
  union all select 3, date '2017-02-28', date '2017-04-01' from dual
),
months (id, month_start, month_days, range_start, range_end) as (
  select id,
    trunc(start_date, 'MM'),
    extract(day from last_day(start_date)),
    start_date,
    end_date
  from ranges
  union all
  select id,
    month_start + interval '1' month,
    extract(day from last_day(month_start + interval '1' month)),
    range_start,
    range_end
  from months
  where month_start < range_end
)
select id,
  to_char(month_start, 'YYYY-MM-DD') as month_start,
  month_days,
  case when month_start = trunc(range_start, 'MM')
      then month_days - extract(day from range_start) + 1
    when month_start = trunc(range_end, 'MM')
      then extract(day from range_end)
    else month_days end as range_days,
  (case when month_start = trunc(range_start, 'MM')
      then month_days - extract(day from range_start) + 1
    when month_start = trunc(range_end, 'MM')
      then extract(day from range_end)
    else month_days end) / month_days as fraction
from months
order by id, month_start;

得到:

    ID MONTH_STAR MONTH_DAYS RANGE_DAYS FRACTION
------ ---------- ---------- ---------- --------
     1 2017-01-01         31         17   0.5483
     1 2017-02-01         28         28        1
     1 2017-03-01         31          1   0.0322
     2 2017-01-01         31          1   0.0322
     2 2017-02-01         28         28        1
     2 2017-03-01         31          1   0.0322
     3 2017-02-01         28          1   0.0357
     3 2017-03-01         31         31        1
     3 2017-04-01         30          1   0.0333

第一个 CTE ranges 只是演示数据。第二个是递归的,CTE months 生成每个月的开始和天数,同时也跟踪原始范围日期。最终查询仅根据范围内的月份天数与该月总天数计算分数。

month_daysrange_days 仅显示在输出中,因此您可以看到计算基于什么,您显然可以从实际结果中省略它们,并格式化月份开始日期随便你。

对于您原来的一对绑定变量,等效项是:

with months (month_start, month_days, range_start, range_end) as (
  select trunc(to_date(:givenstartdate, 'DD/MM/YYYY'), 'MM'),
    extract(day from last_day(to_date(:givenstartdate, 'DD/MM/YYYY'))),
    to_date(:givenstartdate, 'DD/MM/YYYY'),
    to_date(:givenenddate, 'DD/MM/YYYY')
  from dual
  union all
  select month_start + interval '1' month,
    extract(day from last_day(month_start + interval '1' month)),
    range_start,
    range_end
  from months
  where month_start < range_end
)
select to_char(month_start, 'MM/YYYY') as month,
  (case when month_start = trunc(range_start, 'MM')
      then month_days - extract(day from range_start) + 1
    when month_start = trunc(range_end, 'MM')
      then extract(day from range_end)
    else month_days end) / month_days as fraction
from months
order by month_start;

MONTH   FRACTION
------- --------
01/2017   0.5483
02/2017        1
03/2017   0.0322