查找时间间隔的簇

Find clusters of time intervals

我有一个包含多个条目的 table。一项由开始日期时间和结束日期时间组成。

我想通过以下方式查找条目集群:

如果一个条目在前一个条目结束之前开始,那么两者都是集群的一部分。 这是某种重叠问题。

示例:

id      start                    end
1       2007-04-11 15:34:02      2007-05-11 13:09:01
2       2007-06-13 15:42:39      2009-07-21 11:30:00
3       2007-11-26 14:30:02      2007-12-11 14:09:07
4       2008-02-14 08:52:11      2010-02-23 16:00:00

我想要

的输出
id      start                    end
1       2007-04-11 15:34:02      2007-05-11 13:09:01
2-4     2007-06-13 15:42:39      2010-02-23 16:00:00

我有一个解决方案,可以开始排序,然后使用行号和 lag/lead 等进行一些计算。 问题是第 4 行确实紧接在第 2 行之后的特殊情况,所以我不认识它...

这里sql有好的解决办法吗?也许我遗漏了什么?

试试这个...

select a.id ,a.start,a.end,b.id,b.start,b.end
from   tab   a
cross join tab b
where  a.start between b.start and b.end
order by a.start, a.end

我们必须将每一行与所有其他行进行检查,就像使用循环和内循环一样。为此,我们进行交叉连接。

然后我们将使用 BETWEEN AND 运算符检查重叠

要回答这个问题,您需要确定开始新组的时间。然后,在每次开始之前,统计这样的开始次数来定义一个组——并按这个值聚合。

假设你没有重复的时间,这应该可以设置标志:

select e.*,
       (case when not exists (select 1
                              from entries e2
                              where e2.start < e.start and e2.end > e.start
                             )
             then 1 else 0
        end) as BeginsIsland
from entries e;

下面接着做累加和聚合,假设SQL Server 2012+(这很容易适配到更早的版本,但这样更容易编码):

with e as (
      select e.*,
             (case when not exists (select 1
                                    from entries e2
                                    where e2.start < e.start and e2.end > e.start
                                   )
                       then 1 else 0
              end) as BeginIslandFlag
      from entries e
     )
select (case when min(id) = max(id) then cast(max(id) as varchar(255))
             else cast(min(id) as varchar(255)) + '-' + cast(max(id) as varchar(255))
        end) as ids,
       min(start) as start, max(end) as end
from (select e.* sum(BeginIslandFlag) over (order by start) as grp
      from e
     ) e
group by grp;

好的,这是一些递归 cte 的解决方案:

CREATE TABLE t
(
    id INT,
    s  DATE,
    e  DATE
);

INSERT INTO t
VALUES (1, '20070411', '20070511'),
       (2, '20070613', '20090721'),
       (3, '20071126', '20071211'),
       (4, '20080214', '20100223');

WITH cte AS (
    SELECT id, s, e, id AS rid, s AS rs, e AS re
    FROM t
    WHERE NOT EXISTS(
            SELECT *
            FROM t ti
            WHERE t.s > ti.s
              AND t.s < ti.e
        )
    UNION ALL
    SELECT t.*, c.rid, c.rs,
       CASE
           WHEN t.e > c.re THEN t.e
           ELSE c.re
           END
    FROM t
    JOIN cte c ON t.s > c.s AND t.s < c.e
)
SELECT min(id) minid,
       max(id) maxid,
       min(rs) startdate,
       max(re) enddate
FROM cte
GROUP BY rid

输出:

minid   maxid   startdate   enddate
1       1       2007-04-11  2007-05-11
2       4       2007-06-13  2010-02-23

Fiddle http://sqlfiddle.com/#!6/2d6d3/10