Oracle日期为月份的一小部分

时间:2017-06-13 07:26:22

标签: sql oracle date-arithmetic

我想得到两个日期之间的月份表,两个日期涵盖的每个月的一小部分。

例如,开始日期为15/01/2017,结束日期为2017年3月1日,它将输出:

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

这将在两个日期之间输出每个月,并将其格式化为月初。我只需要另一列来给出开始和结束日期所涵盖的分数。我不知道如何做到这一点,如果没有它变得混乱。

3 个答案:

答案 0 :(得分:1)

以下是我将如何做到这一点(nb我已经扩展了你的dates_between以对抗多行,纯粹是为了演示目的。如果你只使用一组参数,你就不会这样做了#39 ; t需要这样做):

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语句来决定是否要在diff计算中添加1。

答案 1 :(得分:1)

months_between() function&#34;根据31天的月份计算结果的小数部分&#34;。这意味着,如果您的范围在一个月内没有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

答案 2 :(得分:0)

试试这个

第一个月,我计算了GET https://{instance}/DefaultCollection/{project}/_apis/build/builds?api-version={version} ,而上个月,我将其减去remaining days / total days以获得1

DBFiddle Demo

days passed / total days

输出

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');