Вот запрос, который пока выполняет лучшие из всех представлений, имея только два обращения к таблицам в плане выполнения (вместо трех или более).Все запросы, конечно, помогают с помощью индексов.Обратите внимание, что план выполнения оценивает этот запрос как более дорогой, но фактические операции чтения и загрузки процессора значительно лучше.Ориентировочные затраты в планах выполнения не совпадают с фактической производительностью.
WITH Grps AS (
SELECT
(Row_Number() OVER (ORDER BY P1.StartDate) - 1) / 2 Grp,
P1.StartDate,
P1.EndDate
FROM
Periods P1
CROSS JOIN (SELECT -1 UNION ALL SELECT 1) D (Dir)
LEFT JOIN Periods P2 ON
DateAdd(Day, D.Dir, P1.StartDate) = P2.EndDate
OR DateAdd(Day, D.Dir, P1.EndDate) = P2.StartDate
WHERE
(Dir = -1 AND P2.EndDate IS NULL)
OR (Dir = 1 AND P2.StartDate IS NULL)
)
SELECT
Min(StartDate) StartDate,
Max(EndDate) EndDate
FROM Grps
GROUP BY Grp;
Еще одна вещь, на которую, я думаю, стоит обратить внимание, это то, что запрос к вашей таблице периодов в большинстве случаев был бы проще и эффективнее, если бы вы использовалиЭксклюзивные даты окончания (также называемые «открытыми» датами окончания) вместо закрытых:
StartDate | EndDate | EndDate
(Inclusive) | (Inclusive) | (Exclusive)
---------------------------------------
1982.03.02 | 1982.09.30 | 1982.10.01
1982.10.01 | 1985.01.17 | 1985.01.18
Использование исключительных дат окончания является (на мой взгляд) лучшей практикой в большинстве случаев, поскольку позволяет изменять тип данных.столбца даты или изменить разрешение даты, не затрагивая запросы, код или другую логику.Например, если ваши даты должны быть с точностью до 12 часов вместо 24 часов, у вас будет большая работа для достижения этой цели, тогда как, если вы используете эксклюзивные даты окончания, ничего не нужно будет менять!
Если бы вы использовали эксклюзивные даты окончания, мой запрос выглядел бы так:
WITH Grps AS (
SELECT
(Row_Number() OVER (ORDER BY P1.StartDate) - 1) / 2 Grp,
P1.StartDate,
P1.EndDate
FROM
Periods P1
CROSS JOIN (SELECT 1 UNION ALL SELECT 2) X (Which)
LEFT JOIN Periods P2 ON
(X.Which = 1 AND P1.StartDate = P2.EndDate)
OR (X.Which = 2 AND P1.EndDate = P2.StartDate)
WHERE
P2.EndDate IS NULL
OR P2.StartDate IS NULL
)
SELECT
Min(StartDate) StartDate,
Max(EndDate) EndDate
FROM Grps
GROUP BY Grp;
Обратите внимание, что сейчас нет DateAdd или DateDiff со значениями в жестком коде "1 день", которые должны были бы измениться, если выпример переключился на 12-часовые периоды.
Обновление
Вот обновленный запрос, который включает в себя то, что я узнал за последние почти 5 лет.Этот запрос теперь не имеет вообще никаких объединений, и хотя в нем есть 3 операции сортировки, которые могут вызвать проблемы с производительностью, я думаю, что этот запрос будет достаточно конкурентоспособным, и в отсутствие индексов, вероятно, побьет все остальные.
WITH Groups AS (
SELECT Grp = Row_Number() OVER (ORDER BY StartDate) / 2, *
FROM
#Periods
(VALUES (0), (0)) X (Dup)
), Ranges AS (
SELECT StartDate = Max(StartDate), EndDate = Min(EndDate)
FROM Groups
GROUP BY Grp
HAVING Max(StartDate) <> DateAdd(day, 1, Min(EndDate))
), ReGroups AS (
SELECT
Grp = Row_Number() OVER (ORDER BY StartDate) / 2,
StartDate,
EndDate
FROM
Ranges
CROSS JOIN (VALUES (0), (0)) X (Dup)
)
SELECT
StartDate = Min(StartDate),
EndDate = Max(EndDate)
FROM ReGroups
GROUP BY Grp
HAVING Count(*) = 2
;
А вот еще одна версия, использующая оконные функции (вроде того, что имитирует предыдущий запрос):
WITH LeadLag AS (
SELECT
PrevEndDate = Coalesce(Lag(EndDate) OVER (ORDER BY StartDate), '00010101'),
NextStartDate = Coalesce(Lead(StartDate) OVER (ORDER BY StartDate), '99991231'),
*
FROM #Periods
), Dates AS (
SELECT
X.*
FROM
LeadLag
CROSS APPLY (
SELECT
StartDate = CASE WHEN DateAdd(day, 1, PrevEndDate) <> StartDate THEN StartDate ELSE NULL END,
EndDate = CASE WHEN DateAdd(day, 1, EndDate) <> NextStartDate THEN EndDate ELSE NULL END
) X
WHERE
X.StartDate IS NOT NULL
OR X.EndDate IS NOT NULL
), Final AS (
SELECT
StartDate,
EndDate = Min(EndDate) OVER (ORDER BY EndDate ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM Dates
)
SELECT *
FROM Final
WHERE StartDate IS NOT NULL
;