计算时间范围

时间:2016-03-10 09:54:53

标签: sql oracle performance

我用一个例子来解释它。我们有5个事件(每个事件都有一个开始日期和结束日期),部分重叠:

create table event (
  id integer primary key,
  date_from date,
  date_to date
);
--
insert into event (id, date_from, date_to) values (1, to_date('01.01.2016', 'DD.MM.YYYY'), to_date('03.01.2016 23:59:59', 'DD.MM.YYYY HH24:MI:SS'));
insert into event (id, date_from, date_to) values (2, to_date('05.01.2016', 'DD.MM.YYYY'), to_date('08.01.2016 23:59:59', 'DD.MM.YYYY HH24:MI:SS'));
insert into event (id, date_from, date_to) values (3, to_date('03.01.2016', 'DD.MM.YYYY'), to_date('05.01.2016 23:59:59', 'DD.MM.YYYY HH24:MI:SS'));
insert into event (id, date_from, date_to) values (4, to_date('03.01.2016', 'DD.MM.YYYY'), to_date('03.01.2016 23:59:59', 'DD.MM.YYYY HH24:MI:SS'));
insert into event (id, date_from, date_to) values (5, to_date('05.01.2016', 'DD.MM.YYYY'), to_date('07.01.2016 23:59:59', 'DD.MM.YYYY HH24:MI:SS'));
--
commit;

这里可视化的事件:

1.JAN  2.JAN  3.JAN  4.JAN  5.JAN  6.JAN  7.JAN  8.JAN
---------1---------         ------------2-------------
              ---------3---------
              --4--         ---------5---------

现在我想选择在给定时间范围内重叠的最大事件数。

对于时间范围01.01.2016 00:00:00 - 08.01.2016 23:59:59,结果应该是3,因为最多3个事件重叠(在03.01.2016 00:00:00 - 03.01.2016 23:59之间) :59和05.01.2016 00:00:00 - 05.01.2016 23:59:59之间。

对于时间范围06.01.2016 00:00:00 - 08.01.2016 23:59:59,结果应为2,因为最多2个事件重叠(2016年1月16日00:00:00 - 07.01.2016 23:59之间:59)

SQL中是否有(高效)解决方案?我正在考虑性能,因为在很宽的时间范围内可能会有很多事件。

更新#1

我最喜欢MTO的答案。它甚至适用于时间范围01.01.2016 00:00:00 - 01.01.2016 23:59:59。我根据我的确切需求调整了SQL:

select max(num_events)
from (
  select sum(startend) over (order by dt) num_events
  from (
    select e1.date_from dt,
      1 startend
    from event e1
    where e1.date_to >= :date_from
      and e1.date_from <= :date_to
    union all
    select e2.date_to dt,
      -1 startend
    from event e2
    where e2.date_to >= :date_from
      and e2.date_from <= :date_to
  )
);

4 个答案:

答案 0 :(得分:2)

这将获得所有时间范围和在这些范围内发生的事件计数:

SELECT *
FROM   (
  SELECT dt AS date_from,
         LEAD( dt ) OVER ( ORDER BY dt ) AS date_to
         SUM( startend ) OVER ( ORDER BY dt ) AS num_events
  FROM   (
    SELECT date_from AS dt, 1 AS startend FROM event
    UNION ALL
    SELECT date_to, -1 FROM event
  )
)
WHERE date_from < date_to;

答案 1 :(得分:0)

如果您只需要获取数字而不需要使用更精确的时间值进行操作,那么它就是这样的:

SELECT MAX(c) max_overlap FROM
(SELECT d, COUNT(1) c 
FROM
(SELECT date_from d FROM event
UNION ALL
SELECT date_to FROM event
) A
GROUP BY A.d
) B

否则它将需要使用递归等。

答案 2 :(得分:0)

您必须将问题分解为几个子问题:

  1. 查找给定时间范围内的所有受影响事件
  2. 查找受影响事件的所有开始
  3. 对于每次开始,请查看此时有多少事件重叠
  4. 找出这些重叠的最大值
  5. 您可以尝试以下查询,其中这些子问题在with-statements(公用表表达式)中建模:

        with myinterval as (
            select to_date('2016-01-01 0:00:00', 'yyyy-mm-dd hh24:mi:ss') as date_from,
                to_date('2016-01-08 23:59:59', 'yyyy-mm-dd hh24:mi:ss') as date_to
            from dual
        ), affected_events as (
        select * 
        from event e
        where wm_overlaps(
                        wm_period(myinterval.date_from, myinterval.date_to),
                        wm_period(e.date_from, e.date_to)
                    ) = 1
        ), starts as (
            select distinct date_from from affected_events
        ), overlapped as (
            select starts.date_from, count(*) as cnt
            from affected_events ae
            join starts on (wm_overlaps(wm_period(starts.date_from, starts.date_from+0.001), wm_period(ae.date_from, ae.date_to))= 1)
            group by starts.date_from
        )
        select max(cnt) from overlapped
    

答案 3 :(得分:0)

这将返回事件重叠的所有峰值,包括峰值开始和结束。

select distinct
    max(e1.date_from), 
    case when
      min(e1.date_to) < min(e2.date_to)
    then
      min(e1.date_to)
    else
      min(e2.date_to)
    end,
    count(1) + 1
from event e1 inner join event e2 on (e2.date_from <= e1.date_from and e2.date_to >= e1.date_from and e1.id != e2.id)
group by e1.ID;