如何在 SQL 中选择重叠的日期范围

Posted

技术标签:

【中文标题】如何在 SQL 中选择重叠的日期范围【英文标题】:How to select overlapping date ranges in SQL 【发布时间】:2018-04-14 21:50:24 【问题描述】:

我有一个包含以下列的表格: sID、开始日期和结束日期

部分值如下:

1   1995-07-28  2003-07-20 
1   2003-07-21  2010-05-04 
1   2010-05-03  2010-05-03 
2   1960-01-01  2011-03-01 
2   2011-03-02  2012-03-13 
2   2012-03-12  2012-10-21 
2   2012-10-22  2012-11-08 
3   2003-07-23  2010-05-02

我只想要结果中的第 2 行和第 3 行,因为它们是重叠的日期范围。

我试过这个,但它不会摆脱第一行。不知道哪里出错了?

select a.sID from table a
inner join table b 
on a.sID = b.sID
and ((b.start_date between a.start_date and a.end_date)
and (b.end_date between a.start_date and b.end_date ))
order by end_date desc

我正在尝试在 SQL Server 中做

【问题讨论】:

【参考方案1】:

一种合理有效的方法是

WITH T1
     AS (SELECT *,
                MAX(end_date) OVER (PARTITION BY sID ORDER BY start_date) AS max_end_date_so_far
         FROM   YourTable),
     T2
     AS (SELECT *,
                range_start = IIF(start_date <= LAG(max_end_date_so_far) OVER (PARTITION BY sID ORDER BY start_date), 0, 1),
                next_range_start = IIF(LEAD(start_date) OVER (PARTITION BY sID ORDER BY start_date) <= max_end_date_so_far, 0, 1)
         FROM   T1)
SELECT SId,
       start_date,
       end_date
FROM   T2
WHERE  0 IN ( range_start, next_range_start ); 

如果您在(sID, start_date) INCLUDE (end_date) 上有一个索引,则可以使用单次有序扫描来完成这项工作。

【讨论】:

哇,效果很好!谢谢!但是,有什么方法可以在不使用按功能分区/更简单的查询的情况下做到这一点? @koala - 这是针对性能而非语法简单性而优化的【参考方案2】:

您的逻辑并不完全正确,尽管它几乎适用于您的示例数据。它失败的具体原因是因为between 包括端点,所以任何给定的行都匹配自身。也就是说,逻辑仍然不正确,因为它没有捕捉到这种情况:

 a-------------a
      b----b

这是正确的逻辑:

select a.*
from table a
where exists (select 1
              from table b
              where a.sid = b.sid and
                    a.start_date < b.end_date and
                    a.end_date > b.start_date and
                    (a.start_date <> b.start_date or  -- filter out the record itself
                     a.end_date <> b.end_date
                    )
             )
order by a.end_date;

重叠时间段(或任何类型的范围)的规则是,当时间段 1 在时间段 2 结束之前开始,而时间段 1 在时间段 2 开始之后结束时,时间段 1 与时间段 2 重叠。幸运的是,between 不需要或用于此目的。 (我强烈反对将 between 与日期/时间操作数一起使用。)

我应该注意到,当一个时间段在同一天结束另一个开始时,这个版本不考虑两个时间段重叠。这可以通过将&lt;&gt; 更改为&lt;=&gt;= 来轻松调整。

Here 是一个 SQL Fiddle。

【讨论】:

我的表还有很多其他的 sID。我尝试运行您的查询并在 sID=1 的位置对其进行过滤,但它仍然给我相同的结果。 @koala。 . .对。您没有标识每一行的唯一 ID,因此通常会有 and a.id &lt;&gt; b.id。我根据开始和结束日期添加了类似的逻辑。 现在,当我不对 sID=1 进行过滤时,它只会给我这 2 行,而不是任何其他 sID 的任何重叠日期范围 @koala。 . .答案中没有任何内容过滤到sID = 1。您的查询一定有其他问题。我包含了一个 SQL Fiddle 来证明它可以工作。 你是对的,现在可以工作了!对此感到抱歉,谢谢!你能解释一下“a.start_date b.start_date or a.end_date b.end_date”背后的逻辑吗

以上是关于如何在 SQL 中选择重叠的日期范围的主要内容,如果未能解决你的问题,请参考以下文章

在 SQL 中检测和合并日期范围的连续重叠

在 oracle SQL 中基于重叠日期范围连接客户端记录

SQL 重叠日期范围

如果我们有很多任务并且每个任务的日期范围可能重叠,如何计算任务的工作天数

SQL Server 2014 合并重叠日期范围

如何确定Javascript中项目网格中的选择范围之间的重叠