Microsoft SQL Server 2016 - T-SQL 难题 - 分隔行中的重叠日期范围 - 'Gaps & Islands' 问题

Microsoft SQL Server 2016 - T-SQL puzzle - overlapping date ranges in segregated rows - 'Gaps & Islands' problem

我已经问过 questions in the ,但是这个明显不同。我在 Microsoft SQL Server 2016 数据库中有一个有趣的问题,T-SQL 语言。 (参考图像文件和 T-SQL 脚本与临时 tables)

我有一个名为 #EmployeeManagersSource 的 table(添加了 T-SQL 脚本,请参阅底部的图像文件)。

CREATE TABLE #EmployeeManagersSource

(

EmployeeName varchar(50),
EmployeeId int,
ManagerId int,
ManagerName varchar(50),
StartDate date,
EndDate date

);



INSERT INTO #EmployeeManagersSource

VALUES

('Andrew',  2367,   311,    'James',   '1/1/2017',  '1/31/2021'),
('Andrew',  2367,   411,    'Alex',    '2/1/2021',  '7/3/2021'),
('Andrew',  2367,   555,    'Sam',     '7/4/2021',  '2/27/2022'),
('Andrew',  2367,   444,    'Martin',  '7/5/2021',  '2/27/2022'),
('Andrew',  2367,   677,    'Frank',   '2/28/2022', '3/5/2022'),
('Andrew',  2367,   977,    'Whitney', '2/28/2022', '3/5/2022'),
('Andrew',  2367,   845,    'Joe',     '3/6/2022',  '3/15/2022'),
('Andrew',  2367,   652,    'Don',     '3/9/2022',  '3/12/2022'),
('Andrew',  2367,   559,    'Dan',     '3/16/2022', '3/19/2022'),
('Andrew',  2367,   439,    'Autumn',  '3/20/2022', '3/24/2022'),
('Andrew',  2367,   567,    'Melissa', '3/20/2022', '3/26/2022'),
('Andrew',  2367,   233,    'Ben',     '3/27/2022', '3/30/2022'),
('Andrew',  2367,   399,    'Lisa',    '3/31/2022', '4/8/2022'),
('Andrew',  2367,   555,    'Sam',     '4/4/2022',  '4/13/2022'),
('Andrew',  2367,   677,    'Frank',   '4/14/2022', '4/14/2022'),
('Andrew',  2367,   311,    'James',   '4/15/2022', '4/30/2022'),
('Andrew',  2367,   439,    'Autumn',  '4/19/2022', '4/26/2022'),
('Andrew',  2367,   399,    'Lisa',    '4/24/2022', '5/3/2022')

它有一个员工及其经理的列表。员工由 EmployeeId 列唯一标识,同样,经理由 ManagerId 列唯一标识。

我有这个 table 的样本,只使用了一个名为 Andrew 的员工,他的唯一标识(即 EmployeeId 列值)是 2367。(#EmployeeManagersSource table 有很多现实世界中的员工。)

多年来,安德鲁有很多经理。安德鲁同时在公司的多个部门工作,因此他可能同时向多个经理汇报。如果在某个时间点,他只在一个部门工作,显然他会有一个经理。

运行下面查询T-SQL得到一个思路:

SELECT *
FROM #EmployeeManagersSource

(忽略某些日期是未来的事实,数据是虚构的)。

我需要开发如下数据集(添加了T-SQL脚本,见底部的图像文件):

CREATE TABLE #EmployeeManagersDesiredOutput

(

EmployeeName varchar(50),
EmployeeId int,
ManagerId int,
ManagerName varchar(50),
StartDate date,
EndDate date

);


INSERT INTO #EmployeeManagersDesiredOutput

VALUES

('Andrew',  2367,   311,    'James',   '1/1/2017',  '1/31/2021'),
('Andrew',  2367,   411,    'Alex',    '2/1/2021',  '7/3/2021'),
('Andrew',  2367,   555,    'Sam',     '7/4/2021',  '7/4/2021'),
('Andrew',  2367,   555,    'Sam',     '7/5/2021',  '2/27/2022'),
('Andrew',  2367,   444,    'Martin',  '7/5/2021',  '2/27/2022'),
('Andrew',  2367,   677,    'Frank',   '2/28/2022', '3/5/2022'),
('Andrew',  2367,   977,    'Whitney', '2/28/2022', '3/5/2022'),
('Andrew',  2367,   845,    'Joe',     '3/6/2022',  '3/8/2022'),
('Andrew',  2367,   845,    'Joe',     '3/9/2022',  '3/12/2022'),
('Andrew',  2367,   652,    'Don',     '3/9/2022',  '3/12/2022'),
('Andrew',  2367,   845,    'Joe',     '3/13/2022', '3/15/2022'),
('Andrew',  2367,   559,    'Dan',     '3/16/2022', '3/19/2022'),
('Andrew',  2367,   439,    'Autumn',  '3/20/2022', '3/24/2022'),
('Andrew',  2367,   567,    'Melissa', '3/20/2022', '3/24/2022'),
('Andrew',  2367,   567,    'Melissa', '3/25/2022', '3/26/2022'),
('Andrew',  2367,   233,    'Ben',     '3/27/2022', '3/30/2022'),
('Andrew',  2367,   399,    'Lisa',    '3/31/2022', '4/3/2022'),
('Andrew',  2367,   399,    'Lisa',    '4/4/2022',  '4/8/2022'),
('Andrew',  2367,   555,    'Sam',     '4/4/2022',  '4/8/2022'),
('Andrew',  2367,   555,    'Sam',     '4/9/2022',  '4/13/2022'),
('Andrew',  2367,   677,    'Frank',   '4/14/2022', '4/14/2022'),
('Andrew',  2367,   311,    'James',   '4/15/2022', '4/18/2022'),
('Andrew',  2367,   311,    'James',   '4/19/2022', '4/23/2022'),
('Andrew',  2367,   439,    'Autumn',  '4/19/2022', '4/23/2022'),
('Andrew',  2367,   311,    'James',   '4/24/2022', '4/26/2022'),
('Andrew',  2367,   439,    'Autumn',  '4/24/2022', '4/26/2022'),
('Andrew',  2367,   399,    'Lisa',    '4/24/2022', '4/26/2022'),
('Andrew',  2367,   311,    'James',   '4/27/2022', '4/30/2022'),
('Andrew',  2367,   399,    'Lisa',    '4/27/2022', '4/30/2022'),
('Andrew',  2367,   399,    'Lisa',    '5/1/2022',  '5/3/2022')

SELECT *
FROM #EmployeeManagersDesiredOutput

此场景中时间的最低粒度日历日

如果安德鲁在给定的日历日向多个经理报告,则必须在单独的行中显示重叠时间段和相关范围。请运行以下查询以获取想法。

SELECT *
FROM #EmployeeManagersDesiredOutput
WHERE StartDate >= '7/5/2021'
AND EndDate <= '2/27/2022'

SELECT *
FROM #EmployeeManagersDesiredOutput
WHERE StartDate >= '3/9/2022'
AND EndDate <= '3/12/2022'

SELECT *
FROM #EmployeeManagersDesiredOutput
WHERE StartDate >= '3/20/2022'
AND EndDate <= '3/24/2022'

SELECT *
FROM #EmployeeManagersDesiredOutput
WHERE StartDate >= '4/4/2022'
AND EndDate <= '4/8/2022'

SELECT *
FROM #EmployeeManagersDesiredOutput
WHERE StartDate >= '4/19/2022'
AND EndDate <= '4/23/2022'

SELECT *
FROM #EmployeeManagersDesiredOutput
WHERE StartDate >= '4/24/2022'
AND EndDate <= '4/26/2022'

SELECT *
FROM #EmployeeManagersDesiredOutput
WHERE StartDate >= '4/27/2022'
AND EndDate <= '4/30/2022'

不属于重叠的其他行必须通过增加或减去一天来分隔。

例如:

SELECT *
FROM #EmployeeManagersDesiredOutput
WHERE StartDate = '7/4/2021'
AND EndDate = '7/4/2021'


SELECT *
FROM #EmployeeManagersDesiredOutput
WHERE StartDate >= '3/6/2022'
AND EndDate <= '3/8/2022'

SELECT *
FROM #EmployeeManagersDesiredOutput
WHERE StartDate >= '3/13/2022'
AND EndDate <= '3/15/2022'

SELECT *
FROM #EmployeeManagersDesiredOutput
WHERE StartDate >= '3/31/2022'
AND EndDate <= '4/3/2022'


SELECT *
FROM #EmployeeManagersDesiredOutput
WHERE StartDate >= '4/15/2022'
AND EndDate <= '4/18/2022'

SELECT *
FROM #EmployeeManagersDesiredOutput
WHERE StartDate >= '5/1/2022'
AND EndDate <= '5/3/2022'

我有以下标准(对于任何给定的员工,例如安德鲁)(请参阅#EmployeeManagersSource table):

  1. 多个经理的开始日期相同(例如:Autumn 和 Melissa,2022 年 3 月 20 日),但结束日期不同
  2. 多个经理的开始日期不同,但这些经理的结束日期相同(例如:Sam 和 Martin,2022 年 2 月 27 日)。
  3. 多个经理之间的 StartDate 和 EndDate 部分重叠。 (例如:Lisa(2022 年 3 月 31 日至 2022 年 4 月 8 日)和 Sam(2022 年 4 月 4 日至 2022 年 4 月 13 日))
  4. 一位经理的开始日期和结束日期完全是另一位经理的开始日期和结束日期的子集。 (例如:乔(2022 年 3 月 6 日至 2022 年 3 月 15 日)和唐(2022 年 3 月 9 日至 2022 年 3 月 12 日))
  5. 有时甚至有 3 位经理同时处理(例如:James、Autumn、Lisa(2022 年 4 月 24 日至 2022 年 4 月 26 日))
  6. 一位经理的开始日期和结束日期与另一位经理完全匹配(例如:弗兰克和惠特尼,2022 年 2 月 28 日至 2022 年 3 月 5 日)
  7. 一个时期内只有一位经理(例如:Dan,2022 年 3 月 16 日至 2022 年 3 月 19 日)

在情况 (6) 和 (7) 中,数据可以从 #EmployeeManagersSource table 移动到 #EmployeeManagersDesiredOutput 而无需任何更改。

知道如何将 #EmployeeManagersSource 转换为 #EmployeeManagersDesiredOutput 吗?

我的做法:

使用#EmployeeManagersSource table 作为来源:

开发具有分解日期的临时 table (#EmployeeManagersIntermediate)
;
WITH E00(N) AS (SELECT 1 UNION ALL SELECT 1)
    ,E02(N) AS (SELECT 1 FROM E00 a, E00 b)
    ,E04(N) AS (SELECT 1 FROM E02 a, E02 b)
    ,E08(N) AS (SELECT 1 FROM E04 a, E04 b)
    ,E16(N) AS (SELECT 1 FROM E08 a, E08 b)
    ,E32(N) AS (SELECT 1 FROM E16 a, E16 b)
    ,cteTally(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E32)
    ,DateRange AS
(
    SELECT ExplodedDate = DATEADD(DAY,N - 1,'1960-01-01')
    FROM cteTally
    WHERE N <= 365000
)
SELECT EmployeeName, EmployeeId, ManagerId, ManagerName, StartDate, EndDate, CONVERT(date,ExplodedDate) AS ExplodedDate
INTO #EmployeeManagersIntermediate
FROM #EmployeeManagersSource eh
JOIN DateRange d ON d.ExplodedDate >= eh.[StartDate]
 AND d.ExplodedDate <= eh.[EndDate];
 

 SELECT *
 FROM #EmployeeManagersIntermediate
 WHERE ManagerName = 'Lisa'

但是,我无法使用 #EmployeeManagersDesiredOutput,基于这个 'Gaps and Islands' 问题 .

我想我需要一个合适的 PARTITION BY 子句。有人可以就如何将 #EmployeeManagersIntermediate 更改为 #EmployeeManagersDesiredOutput 提出解决方案吗?

您可以使用以下查询:

;WITH Dates AS (
    SELECT EmployeeId, EmployeeName, SomeDate,
        ROW_NUMBER() OVER (PARTITION BY x.EmployeeId ORDER BY x.SomeDate) AS RowNum
    FROM (
        SELECT EmployeeId, EmployeeName, StartDate AS SomeDate
        FROM #EmployeeManagersSource
        UNION
        SELECT EmployeeId, EmployeeName, DATEADD(DAY,1,EndDate)
        FROM #EmployeeManagersSource
    ) x
), Intervals AS (
    SELECT d1.EmployeeId, d1.EmployeeName, 
        d1.SomeDate AS StartDate, DATEADD(DAY,-1,d2.SomeDate) AS EndDate
    FROM Dates d1 
    INNER JOIN Dates d2 
    ON d2.EmployeeId = d1.EmployeeId AND d1.RowNum=d2.RowNum-1
)
SELECT i.EmployeeName, i.EmployeeId, s.ManagerId, s.ManagerName, i.StartDate, i.EndDate
FROM Intervals i
INNER JOIN #EmployeeManagersSource s 
ON s.EmployeeId = i.EmployeeId AND s.StartDate<=i.StartDate AND s.EndDate>=i.EndDate

第一个 CTE 构建出现更改的日期列表,第二个 CTE 构建时间间隔,最后的查询查找每个时间间隔的管理器。