当所有时间都重叠时,获取所有重叠的日期范围

时间:2018-11-27 08:33:31

标签: sql sql-server tsql datetime

我为此付出了几天的努力...尝试编写一个SQL查询以在所有单位同时重叠时获取所有日期范围。最好以图形方式查看它。

这是带有图像的简化表,以供参考:

UnitId  Start       End
======  ==========  ==========
1       05/01/2018  09/01/2018
1       10/01/2018  13/01/2018
2       04/01/2018  15/01/2018
2       19/01/2018  23/01/2018
3       06/01/2018  12/01/2018
3       14/01/2018  22/01/2018

预期结果:

Start       End
======      ========== 
06/01/2018  09/01/2018
10/01/2018  12/01/2018

我目前拥有的东西:

DECLARE @sourceTable TABLE (UnitId int, StartDate datetime, EndDate datetime);
INSERT INTO @sourceTable VALUES
 (1, '2018-01-05', '2018-01-09')
,(1, '2018-01-10', '2018-01-13')
,(2, '2018-01-04', '2018-01-15')
,(2, '2018-01-19', '2018-01-23')
,(3, '2018-01-06', '2018-01-12')
,(3, '2018-01-14', '2018-01-22');

SELECT DISTINCT
 (SELECT max(v) FROM (values(A.StartDate), (B.StartDate)) as value(v)) StartDate
,(SELECT min(v) FROM (values(A.EndDate), (B.EndDate)) as value(v)) EndDate
FROM @sourceTable A 
JOIN @sourceTable B 
ON A.startDate <= B.endDate AND A.endDate >= B.startDate AND A.UnitId != B.UnitId

2 个答案:

答案 0 :(得分:3)

我认为这是“重叠间隔数”问题(this picture should help)。这是一种解决方案:

DECLARE @t TABLE (UnitId INT, [Start] DATE, [End] DATE);
INSERT INTO @t VALUES
(1, '2018-01-05', '2018-01-09'),
(1, '2018-01-10', '2018-01-13'),
(2, '2018-01-04', '2018-01-15'),
(2, '2018-01-19', '2018-01-23'),
(3, '2018-01-06', '2018-01-12'),
(3, '2018-01-14', '2018-01-22');

WITH cte1(date, val) AS (
    SELECT [Start], 1 FROM @t AS t
    UNION ALL
    SELECT [End], 0 FROM @t AS t
    UNION ALL
    SELECT DATEADD(DAY, 1, [End]), -1 FROM @t AS t
), cte2 AS (
    SELECT date, SUM(val) OVER (ORDER BY date, val) AS usage
    FROM cte1
)
SELECT date, MAX(usage) AS usage
FROM cte2
GROUP BY date

它将为您提供使用次数(可能)发生更改的所有日期的列表:

date          usage
2018-01-04    1
2018-01-05    2
2018-01-06    3
2018-01-09    3
2018-01-10    3
2018-01-12    3
2018-01-13    2
2018-01-14    2
2018-01-15    2
2018-01-16    1
2018-01-19    2
2018-01-22    2
2018-01-23    1
2018-01-24    0

使用这种方法,您不需要日历表或rCTE即可构建缺少的日期。将上述内容转换为范围(2018-01-05 ... 2018-01-152018-01-19 ... 2018-01-22等)并不是很困难。

DECLARE @t TABLE (UnitId INT, [Start] DATE, [End] DATE);
INSERT INTO @t VALUES
(1, '2018-01-05', '2018-01-09'),
(1, '2018-01-10', '2018-01-13'),
(2, '2018-01-04', '2018-01-15'),
(2, '2018-01-19', '2018-01-23'),
(3, '2018-01-06', '2018-01-12'),
(3, '2018-01-14', '2018-01-22');

WITH cte1(date, val) AS (
    SELECT [Start], 1 FROM @t AS t                 -- starting date increments counter
    UNION ALL                                      
    SELECT [End], 0 FROM @t AS t                   -- we need all edges in the result
    UNION ALL                                      
    SELECT DATEADD(DAY, 1, [End]), -1 FROM @t AS t -- end date + 1 decrements counter
), cte2 AS (
    SELECT date, SUM(val) OVER (ORDER BY date, val) AS usage -- running sum for counter
    FROM cte1
), cte3 AS (
    SELECT date, MAX(usage) AS usage -- group multiple events on same date together
    FROM cte2
    GROUP BY date
), cte4 AS (
    SELECT date, usage, CASE
        WHEN usage > 1 AND LAG(usage) OVER (ORDER BY date) > 1 THEN 0
        WHEN usage < 2 AND LAG(usage) OVER (ORDER BY date) < 2 THEN 0
        ELSE 1
    END AS chg -- start new group if prev and curr usage are on opposite side of 1
    FROM cte3
), cte5 AS (
    SELECT date, usage, SUM(chg) OVER (ORDER BY date) AS grp -- number groups for each change
    FROM cte4
)
SELECT MIN(date) date1, MAX(date) date2
FROM cte5
GROUP BY grp
HAVING MIN(usage) > 1

结果:

date1         date2
2018-01-05    2018-01-15
2018-01-19    2018-01-22

答案 1 :(得分:0)

您正在寻找所有单位重叠的日期范围。因此,寻找所有单元均存在的开始日期和所有单元均存在的结束日期,然后将两者合并。

我正在使用ROW_NUMBER将第一个开始日期与第一个结束日期,第二个开始日期与第二个结束日期等相连接。

select s.startdate, e.enddate
from
(
  select startdate, row_number() over (order by startdate) as rn
  from @sourceTable s1
  where 
  (
    select count(*)
    from @sourceTable s2
    where s1.startdate between s2.startdate and s2.enddate
  ) = (select count(distinct unitid) from @sourceTable)
) s
join
(
  select enddate, row_number() over (order by startdate) as rn
  from @sourceTable s1
  where 
  (
    select count(*)
    from @sourceTable s2
    where s1.enddate between s2.startdate and s2.enddate
  ) = (select count(distinct unitid) from @sourceTable)
) e on e.rn = s.rn
order by s.startdate;

也许有更优雅的方法可以解决此问题,但是我想这个查询至少很容易理解:-)

上个月的演示:https://rextester.com/GRRSW89045