如何按月分割两个日期之间的间隔?

时间:2018-03-10 13:07:27

标签: sql oracle plsql

对不起标题......最好用一个例子描述问题...

我有一个事件列表和每个事件的两个日期,我需要在各自的几个月内“中断”或“分发”这些日期。

示例1:

活动:活动A

开始日期:12/15/2017 - MM / DD / YYYY

结束日期:01/17/2018 - MM / DD / YYYY

如果我在我的桌子上搜索此事件,我会得到一个包含该数据的结果行。

但我需要两个结果,如下所示:

结果1:事件A> 15至31

结果2:事件A> 01至17

示例2:

活动:活动B

开始日期:02/07/2018 - MM / DD / YYYY

结束日期:04/22/2018 - MM / DD / YYYY

结果1:事件B> 07至28

结果2:事件B> 01至31

结果3:事件B> 01至22

最有效的方法是什么?

3 个答案:

答案 0 :(得分:1)

我没有完整的解决方案(如果你为它创建一个可行的SQLFiddle测试平台,我可能会解决它),但我认为这是需要一个CONNECT BY子句的东西,它会是非常接近this solution from Ask Tom

它基本上是这样的(来自Ask Tom的例子):

variable sdate varchar2(30);
variable edate varchar2(30);
exec :sdate := '01-mar-2011'; :edate := '31-dec-2011';

select level r,
        greatest( add_months(trunc(sdate,'mm'),level-1), sdate ),
        least( last_day( add_months(sdate,level-1) ), edate )
from (select to_date( :sdate, 'dd-mon-yyyy' ) sdate,
                to_date( :edate, 'dd-mon-yyyy' ) edate
           from dual)
connect by level <= months_between( trunc( edate,'mm'), trunc(sdate,'mm') ) + 1;


     R GREATEST( LEAST(LAS
------ --------- ---------
     1 01-MAR-11 31-MAR-11
     2 01-APR-11 30-APR-11
     3 01-MAY-11 31-MAY-11
     4 01-JUN-11 30-JUN-11
     5 01-JUL-11 31-JUL-11
     6 01-AUG-11 31-AUG-11
     7 01-SEP-11 30-SEP-11
     8 01-OCT-11 31-OCT-11
     9 01-NOV-11 30-NOV-11
    10 01-DEC-11 31-DEC-11

10 rows selected.

答案 1 :(得分:1)

可以使用Oracle 12c cross apply子句:

create table e_vents(
 name varchar2(10),
 startdate date,
 enddate date
);

insert all 
into e_vents values( 'A', date '2017-12-15', date '2018-01-17' )
into e_vents values( 'B', date '2017-12-15', date '2017-12-22' )
into e_vents values( 'C', date '2017-12-15', date '2018-05-22' )
select null from dual;

commit;
select e.name,
       case when e.startdate > x.s_date then e.startdate else x.s_date end as start_date,
       case when e.enddate < x.e_date then e.enddate else x.e_date end as end_date
from e_vents e
cross apply (
  select 
         trunc( e.startdate, 'mm') + (level-1) * interval '1' month as s_date,
         trunc( e.startdate + (level) * interval '1' month, 'mm') -1 as e_date 
  from dual
  connect by level <= months_between( trunc( e.enddate, 'mm'),trunc( e.startdate, 'mm')) + 1
) x
NAME       START_DATE END_DATE        
---------- ---------- ----------
A          2017-12-15 2017-12-31
A          2018-01-01 2018-01-17
B          2017-12-15 2017-12-22
C          2017-12-15 2017-12-31
C          2018-01-01 2018-01-31
C          2018-02-01 2018-02-28
C          2018-03-01 2018-03-31
C          2018-04-01 2018-04-30
C          2018-05-01 2018-05-22

9 rows selected. 

答案 2 :(得分:1)

这个问题可以使用两种解决方案

在Oracle 12C中,您可以使用以下查询

SELECT DISTINCT e.name,
  CASE
    WHEN e.startdate > x.sdate
    THEN e.startdate
    ELSE x.sdate
  END AS startdate,
  CASE
    WHEN e.enddate < x.edate
    THEN e.enddate
    ELSE x.edate
  END AS enddate
FROM e_vents e CROSS apply
  (SELECT TRUNC( e.startdate, 'mm') + (level-1) * interval '1' MONTH         AS sdate,
    TRUNC( e.startdate              + (level) * interval '1' MONTH, 'mm') -1 AS edate
  FROM e_vents
    CONNECT BY level <= months_between( TRUNC( e.enddate, 'mm'),TRUNC( e.startdate, 'mm')) + 1
  ) x
ORDER BY 1 ASC;
旧版本的oracle中的

使用以下查询

SELECT e.name,
  greatest(e.startdate,x.sdate) AS startdate,
  least(e.enddate,x.edate)      AS enddate
FROM e_vents e,
  (SELECT TRUNC( e.min_startdate, 'mm') + (level-1) * interval '1' MONTH         AS sdate,
    TRUNC( e.min_startdate              + (level) * interval '1' MONTH, 'mm') -1 AS edate
  FROM
    (SELECT MIN(startdate) min_startdate,MAX(enddate) max_enddate FROM e_vents
    ) e
    CONNECT BY level<= months_between( TRUNC( e.max_enddate, 'mm'),TRUNC( e.min_startdate, 'mm')) + 1
  ) x
WHERE e.startdate BETWEEN x.sdate AND x.edate
OR e.enddate BETWEEN x.sdate AND x.edate
OR x.sdate BETWEEN e.startdate AND e.enddate
OR x.edate BETWEEN e.startdate AND e.enddate
ORDER BY 1 ASC ;
相关问题