在不分解每个组合的情况下找到未覆盖的时期
Find uncovered periods without exploding each combination
我有以下两个tables
人物
+--------+---------------+-------------+
| Name | ContractStart | ContractEnd |
+--------+---------------+-------------+
| Kate | 20180101 | 20181231 |
| Sawyer | 20180101 | 20181231 |
| Ben | 20170601 | 20181231 |
+--------+---------------+-------------+
班次
+---------+--------+------------+----------+
| Station | Name | ShiftStart | ShiftEnd |
+---------+--------+------------+----------+
| Swan | Kate | 20180101 | 20180131 |
| Arrow | Kate | 20180301 | 20180331 |
| Arrow | Kate | 20180401 | 20181231 |
| Flame | Sawyer | 20180101 | 20181231 |
| Swan | Ben | 20180101 | 20181231 |
+---------+--------+------------+----------+
这意味着,例如,Kate将在20180101至20181231期间有空。在此期间,她将在20180101至20180131期间在Swan车站工作,在20180301至20180331期间以及20180401至20181231期间在Arrow车站工作。
我的目标是达到以下table
+------+---------------+-------------+
| | VacationStart | VacationEnd |
+------+---------------+-------------+
| Kate | 20180201 | 20180228 |
| Ben | 20170601 | 20171231 |
+------+---------------+-------------+
这意味着 Kate 从 20180201 到 20180228 期间有空。
我的第一个想法是用 2017 年和 2018 年的每一天创建一个 table,比方说 CalTable
,然后 JOIN table 和 People
来查找每个人都应该有空的每一天。此时再次 JOIN 结果 table 和 Shifts
以得到 NOT BETWEEN ShiftStart AND ShiftEnd
天的证据。
考虑到我有将近 1.000.000 的人,通常在 ContractStart
和 ContractEnd
之间,即 10-20 年,这个步骤给了我正确的结果但非常慢。
以更聪明、更快速的方式获得结果的正确方法是什么?
谢谢。
This is the data of the example on db<>Fiddle
对于@A_Name_Does_Not_Matter这是我的尝试
CREATE TABLE #CalTable([ID] VARCHAR(8) NOT NULL)
DECLARE @num int
SET @num = 20170101
WHILE (@num <= 20181231)
BEGIN
INSERT INTO #CalTable([ID])
SELECT @num AS [ID]
SET @num = @num + 1
END
SELECT X.[Name], X.[TIMEID]
FROM (
-- All day availables
SELECT DISTINCT A.[Name],B.[ID] AS [TIMEID]
FROM #People A INNER JOIN #CalTable B
ON B.[ID] BETWEEN A.[ContractStart] AND A.[ContractEnd]
) X
LEFT JOIN (
-- Working day
SELECT DISTINCT A.[Name],B.[ID] AS [TIMEID]
FROM #People A INNER JOIN #CalTable B
ON B.[ID] BETWEEN A.[ContractStart] AND A.[ContractEnd]
INNER JOIN #Shifts C ON A.[Name]=C.[Name] AND B.[ID] BETWEEN C.[ShiftStart] AND C.[ShiftEnd]
) Z
ON X.[Name]=Z.[Name] AND X.[TIMEID]=Z.[TIMEID]
WHERE Z.[Name] IS NULL
ORDER BY X.[Name],X.[TIMEID]
然后汇总 .
的日期
所以一个人的开始日期可能是假期的开始,您可以通过使用 CROSS APPLY 获得 TOP 1 班次找到他们的第一个轮班日期(减去 1 天)来找到假期的结束, 按日期排序
在没有轮班的特殊情况下,他们的假期在合同结束日期结束。
未来假期则在轮班后的一天开始,在下班次的前一天结束(可以通过 OUTER APPLY 找到),如果没有进一步的轮班,则默认为约定的结束日期
SELECT p.name, p.contractStart vacationstart, p.ContractEnd vacationend from people p WHERE not exists(select 1 from shifts s where p.name = s.name)
UNION
SELECT p2.name,
p2.contractStart vacationstart,
dateadd(day,-1,DQ.ShiftStart) as vacationend
from PEOPLE P2
CROSS APPLY
(SELECT TOP 1 s2.ShiftStart FROM shifts s2 WHERE p2.name = s2.name order by sfiftstart) DQ
WHERE DQ.ShiftStart > p2.contractstart
UNION
select P3.NAME,
dateadd(day,1,s3.ShiftEnd) vacationstart,
COALESCE(dateadd(day,-1, DQ2.shiftStart),P3.ContractEnd) --you might have to add handling yourself for removing a case where they work on their contract end date
FROM people p3 JOIN shifts s3 on p3.name = s3.name
OUTER APPLY (SELECT TOP 1 s4.shiftStart
from shifts s4
where s4.name = p3.name
and
s4.shiftstart > s3.shiftstart
order by s4.shiftstart) DQ2
没有测试数据我很难验证。
对于员工,我追求的是
合同开始,Shift1Start - 1
Shift1End + 1, Shift2Start - 1
Shift2End + 1, Shift3Start - 1
Shift3End + 1, ContractEnd
然后添加 'no shifts' 的大小写
finally shifts 可能是连续的,导致假期的持续时间为零或更短 - 您可以通过将查询设为子查询来过滤这些,然后简单地过滤
我有以下两个tables
人物
+--------+---------------+-------------+
| Name | ContractStart | ContractEnd |
+--------+---------------+-------------+
| Kate | 20180101 | 20181231 |
| Sawyer | 20180101 | 20181231 |
| Ben | 20170601 | 20181231 |
+--------+---------------+-------------+
班次
+---------+--------+------------+----------+
| Station | Name | ShiftStart | ShiftEnd |
+---------+--------+------------+----------+
| Swan | Kate | 20180101 | 20180131 |
| Arrow | Kate | 20180301 | 20180331 |
| Arrow | Kate | 20180401 | 20181231 |
| Flame | Sawyer | 20180101 | 20181231 |
| Swan | Ben | 20180101 | 20181231 |
+---------+--------+------------+----------+
这意味着,例如,Kate将在20180101至20181231期间有空。在此期间,她将在20180101至20180131期间在Swan车站工作,在20180301至20180331期间以及20180401至20181231期间在Arrow车站工作。
我的目标是达到以下table
+------+---------------+-------------+
| | VacationStart | VacationEnd |
+------+---------------+-------------+
| Kate | 20180201 | 20180228 |
| Ben | 20170601 | 20171231 |
+------+---------------+-------------+
这意味着 Kate 从 20180201 到 20180228 期间有空。
我的第一个想法是用 2017 年和 2018 年的每一天创建一个 table,比方说 CalTable
,然后 JOIN table 和 People
来查找每个人都应该有空的每一天。此时再次 JOIN 结果 table 和 Shifts
以得到 NOT BETWEEN ShiftStart AND ShiftEnd
天的证据。
考虑到我有将近 1.000.000 的人,通常在 ContractStart
和 ContractEnd
之间,即 10-20 年,这个步骤给了我正确的结果但非常慢。
以更聪明、更快速的方式获得结果的正确方法是什么?
谢谢。 This is the data of the example on db<>Fiddle
对于@A_Name_Does_Not_Matter这是我的尝试
CREATE TABLE #CalTable([ID] VARCHAR(8) NOT NULL)
DECLARE @num int
SET @num = 20170101
WHILE (@num <= 20181231)
BEGIN
INSERT INTO #CalTable([ID])
SELECT @num AS [ID]
SET @num = @num + 1
END
SELECT X.[Name], X.[TIMEID]
FROM (
-- All day availables
SELECT DISTINCT A.[Name],B.[ID] AS [TIMEID]
FROM #People A INNER JOIN #CalTable B
ON B.[ID] BETWEEN A.[ContractStart] AND A.[ContractEnd]
) X
LEFT JOIN (
-- Working day
SELECT DISTINCT A.[Name],B.[ID] AS [TIMEID]
FROM #People A INNER JOIN #CalTable B
ON B.[ID] BETWEEN A.[ContractStart] AND A.[ContractEnd]
INNER JOIN #Shifts C ON A.[Name]=C.[Name] AND B.[ID] BETWEEN C.[ShiftStart] AND C.[ShiftEnd]
) Z
ON X.[Name]=Z.[Name] AND X.[TIMEID]=Z.[TIMEID]
WHERE Z.[Name] IS NULL
ORDER BY X.[Name],X.[TIMEID]
然后汇总
所以一个人的开始日期可能是假期的开始,您可以通过使用 CROSS APPLY 获得 TOP 1 班次找到他们的第一个轮班日期(减去 1 天)来找到假期的结束, 按日期排序
在没有轮班的特殊情况下,他们的假期在合同结束日期结束。
未来假期则在轮班后的一天开始,在下班次的前一天结束(可以通过 OUTER APPLY 找到),如果没有进一步的轮班,则默认为约定的结束日期
SELECT p.name, p.contractStart vacationstart, p.ContractEnd vacationend from people p WHERE not exists(select 1 from shifts s where p.name = s.name)
UNION
SELECT p2.name,
p2.contractStart vacationstart,
dateadd(day,-1,DQ.ShiftStart) as vacationend
from PEOPLE P2
CROSS APPLY
(SELECT TOP 1 s2.ShiftStart FROM shifts s2 WHERE p2.name = s2.name order by sfiftstart) DQ
WHERE DQ.ShiftStart > p2.contractstart
UNION
select P3.NAME,
dateadd(day,1,s3.ShiftEnd) vacationstart,
COALESCE(dateadd(day,-1, DQ2.shiftStart),P3.ContractEnd) --you might have to add handling yourself for removing a case where they work on their contract end date
FROM people p3 JOIN shifts s3 on p3.name = s3.name
OUTER APPLY (SELECT TOP 1 s4.shiftStart
from shifts s4
where s4.name = p3.name
and
s4.shiftstart > s3.shiftstart
order by s4.shiftstart) DQ2
没有测试数据我很难验证。 对于员工,我追求的是
合同开始,Shift1Start - 1
Shift1End + 1, Shift2Start - 1
Shift2End + 1, Shift3Start - 1
Shift3End + 1, ContractEnd
然后添加 'no shifts' 的大小写 finally shifts 可能是连续的,导致假期的持续时间为零或更短 - 您可以通过将查询设为子查询来过滤这些,然后简单地过滤