计算每月每条记录的差异日期时间

时间:2017-12-20 09:36:02

标签: sql oracle

我们假设我有下表。 我怎样才能分别计算每个月的延误。

Req start_dt    end_dt
1   1/2/2017    3/5/2017
2   5/2/2017    7/6/2017

我希望结果如下表所示。

Req start_dt    end_dt     delay    MM
1   1/2/2017    3/5/2017    30      1
1   1/2/2017    3/5/2017    28      2
1   1/2/2017    3/5/2017    5       3
2   5/2/2017    7/6/2017    30      5
2   5/2/2017    7/6/2017    30      6
2   5/2/2017    7/6/2017    6       7

2 个答案:

答案 0 :(得分:2)

您需要将日期范围拆分为多个月,中间月份的完整范围和开始和结束月份的部分范围。一种方法是使用recursive subquery factoring

with rcte (req, start_dt, end_dt, period_start_dt, period_end_dt) as (
  select req, start_dt, end_dt, start_dt,
    case when trunc(end_dt, 'MM') = trunc(start_dt, 'MM') then end_dt
      else last_day(start_dt) end
  from your_table
  union all
  select req, start_dt, end_dt, add_months(trunc(period_start_dt, 'MM'), 1),
    case when trunc(end_dt, 'MM') = add_months(trunc(period_start_dt, 'MM'), 1) then end_dt
      else last_day(add_months(trunc(period_start_dt, 'MM'), 1) ) end
  from rcte
  where trunc(end_dt, 'MM') > trunc(period_start_dt, 'MM')
)
select req, start_dt, end_dt, period_start_dt, period_end_dt,
  period_end_dt - period_start_dt + 1 as delay,
  extract(month from period_start_dt) as mm
from rcte
order by req, period_start_dt, period_end_dt;

       REQ START_DT   END_DT     PERIOD_STA PERIOD_END      DELAY         MM
---------- ---------- ---------- ---------- ---------- ---------- ----------
         1 2017-01-02 2017-03-05 2017-01-02 2017-01-31         30          1
         1 2017-01-02 2017-03-05 2017-02-01 2017-02-28         28          2
         1 2017-01-02 2017-03-05 2017-03-01 2017-03-05          5          3
         2 2017-05-02 2017-07-06 2017-05-02 2017-05-31         30          5
         2 2017-05-02 2017-07-06 2017-06-01 2017-06-30         30          6
         2 2017-05-02 2017-07-06 2017-07-01 2017-07-06          6          7

递归CTE有一个锚点成员,它从表中获取初始数据,并计算第一个周期的开始和结束。期间开始是原始范围开始日期;期末是范围结束日期(如果它在同一个月内)或该月末。

递归成员然后使用锚点中的​​值并生成一个新的句点,该句点将从下个月的开始开始,并再次在原始范围结束日期或该月末结束。

一旦您有了这些期间的开始/结束日期,使用正常日期减法计算差异就很简单了。 (我已经在输出中留下了句号的开始/结束日期以尝试使其更清晰;如果您不想要它们,只需从最终选择列表中删除。)

期间开始/结束日期的计算稍微简单:

with rcte (req, start_dt, end_dt, period_start_dt, period_end_dt) as (
  select req, start_dt, end_dt, start_dt, least(end_dt, last_day(start_dt))
  from your_table
  union all
  select req, start_dt, end_dt, add_months(trunc(period_start_dt, 'MM'), 1),
    least(end_dt, last_day(add_months(trunc(period_start_dt, 'MM'), 1)))
  from rcte
  where trunc(end_dt, 'MM') > trunc(period_start_dt, 'MM')
)
select req, start_dt, end_dt,
  period_end_dt - period_start_dt + 1 as delay,
  extract(month from period_start_dt) as mm
from rcte
order by req, period_start_dt, period_end_dt;

       REQ START_DT   END_DT          DELAY         MM
---------- ---------- ---------- ---------- ----------
         1 2017-01-02 2017-03-05         30          1
         1 2017-01-02 2017-03-05         28          2
         1 2017-01-02 2017-03-05          5          3
         2 2017-05-02 2017-07-06         30          5
         2 2017-05-02 2017-07-06         30          6
         2 2017-05-02 2017-07-06          6          7

答案 1 :(得分:2)

您可以使用相关的分层查询来执行此操作:

SQL Fiddle

Oracle 11g R2架构设置

CREATE TABLE table_name ( Req, start_dt, end_dt ) AS
SELECT 1, DATE '2017-01-02', DATE '2017-03-05' FROM DUAL UNION ALL
SELECT 2, DATE '2017-05-02', DATE '2017-07-06' FROM DUAL;

查询1

SELECT t.*,
       LEAST( LAST_DAY( d.COLUMN_VALUE ), t.end_dt )
         - GREATEST( d.COLUMN_VALUE, t.start_dt ) + 1 AS delay,
       EXTRACT( MONTH FROM d.COLUMN_VALUE ) AS MM
FROM   table_name t
       CROSS JOIN
       TABLE(
         CAST(
           MULTISET(
             SELECT ADD_MONTHS( TRUNC( t.start_dt, 'MM' ), LEVEL - 1 )
             FROM   DUAL
             CONNECT BY LEVEL <= MONTHS_BETWEEN( t.end_dt, TRUNC( t.start_dt, 'MM' ) ) + 1
           ) AS SYS.ODCIDATELIST
         )
       ) d

<强> Results

| REQ |             START_DT |               END_DT | DELAY | MM |
|-----|----------------------|----------------------|-------|----|
|   1 | 2017-01-02T00:00:00Z | 2017-03-05T00:00:00Z |    30 |  1 |
|   1 | 2017-01-02T00:00:00Z | 2017-03-05T00:00:00Z |    28 |  2 |
|   1 | 2017-01-02T00:00:00Z | 2017-03-05T00:00:00Z |     5 |  3 |
|   2 | 2017-05-02T00:00:00Z | 2017-07-06T00:00:00Z |    30 |  5 |
|   2 | 2017-05-02T00:00:00Z | 2017-07-06T00:00:00Z |    30 |  6 |
|   2 | 2017-05-02T00:00:00Z | 2017-07-06T00:00:00Z |     6 |  7 |