【问题标题】:How to select overlapping date ranges in SQL如何在 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 中做

【问题讨论】:

    标签: sql sql-server join


    【解决方案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”背后的逻辑吗
    猜你喜欢
    • 2022-01-25
    • 2018-03-03
    • 2020-04-24
    • 1970-01-01
    • 2013-10-21
    • 1970-01-01
    • 2021-10-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多