sql - 在不爆炸每个组合的情况下查找未覆盖的时期
问题描述
我有以下两张表
人们
+--------+---------------+-------------+
| 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 可用。在此期间,她将在 Swan 站从 20180101 到 20180131 工作,在 Arrow 站从 20180301 到 20180331 和从 20180401 到 20181231 工作。
我的目标是来到下表
+------+---------------+-------------+
| | VacationStart | VacationEnd |
+------+---------------+-------------+
| Kate | 20180201 | 20180228 |
| Ben | 20170601 | 20171231 |
+------+---------------+-------------+
这意味着凯特将从 20180201 到 20180228 自由。
我的第一个想法是创建一个包含 2017 年和 2018 年的每一天的表格,比如说 a CalTable
,然后加入表格People
以查找每个人都应该有空的每一天。此时 JOIN 再次将结果表与Shifts
有证据的天NOT BETWEEN ShiftStart AND ShiftEnd
。考虑到我有将近 1.000.000 人,并且通常在10 到 20 年之间ContractStart
,这些步骤给了我正确的结果,但速度很慢。ContractEnd
以更聪明和快速的方式获得结果的正确方法是什么?
对于@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 找到他们第一次轮班的日期(减去 1 天)来找到该假期的结束日期,以获取按日期排序的 TOP 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
然后添加“无班次”的情况,最后班次可能是连续的,导致持续时间为零或更短的假期 - 您可以通过使查询成为子查询来过滤这些,并简单地过滤
推荐阅读
- iis - 授权不适用于 IIS 中的 Windows 身份验证
- python - 提高二元分类模型的准确性
- android - 为什么recyclerview设计在不同的行android中显示不同的视图对齐方式?
- gson - 使用gson在spring rest中设置日期格式输出
- python - 使用 pandas read_excel 从标准输入读取
- php - preg_match 在 7.1.11 上不工作,但在其他版本上工作
- android - 我无法插入数据
- magento - Magento REST API - “无法处理指定的请求”错误
- android - 将 ExpandableTextView 与 ScrollView 一起使用时应用程序崩溃
- angular - 如何使用 lambda 函数访问我的 Get 请求的内容