Найти непокрытые периоды, не взрывая каждую комбинацию - PullRequest
0 голосов
/ 13 декабря 2018

У меня есть следующие две таблицы

Люди

+--------+---------------+-------------+
|  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 |
+---------+--------+------------+----------+

Это означает, чтоНапример, Кейт будет доступна с 20180101 по 20181231. В этот период она будет работать на станции Лебедь с 20180101 по 20180131, на станции Стрелка с 20180301 до 20180331 и с 20180401 по 20181231.

Моя цельэто перейти к следующей таблице

+------+---------------+-------------+
|      | VacationStart | VacationEnd |
+------+---------------+-------------+
| Kate |      20180201 |    20180228 |
| Ben  |      20170601 |    20171231 |
+------+---------------+-------------+

, что означает, что Кейт будет свободна с 20180201 по 20180228.

Моя первая идея состояла в том, чтобы создать таблицу с каждым днем ​​2017 и 2018 годов.скажем CalTable, затем JOIN таблица с People, чтобы каждый день находить, что каждый человек должен быть доступен.В этот момент снова СОЕДИНИТЕ полученную таблицу с Shifts, чтобы получить данные о днях NOT BETWEEN ShiftStart AND ShiftEnd.Эти шаги дают мне правильные результаты, но очень медленные, учитывая, что у меня почти 1.000.000 человек, и обычно между ContractStart и ContractEnd 10-20 лет.

Что может быть правильным подходомчтобы получить результаты более умным и быстрым способом?

Спасибо. Это данные примера на 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]

, а затем агрегирование дат с этот запрос .

1 Ответ

0 голосов
/ 13 декабря 2018

таким образом, дата начала людей может быть началом отпуска, и вы можете найти конец этого отпуска, найдя дату их первой смены (минус 1 день), используя CROSS APPLY, чтобы получить ТОП 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

, затем добавьте регистр без 'смещений', наконец, сдвиги могут быть смежными, что приведет к отпуску с нулевой или меньшей длительностью - вы можете отфильтровать их, сделав запрос подзапросоми просто фильтрация

...