У меня есть две таблицы, каждая из которых содержит даты начала и окончания нескольких периодов. Мне нужен эффективный способ найти периоды (диапазоны дат), где даты находятся в пределах диапазонов первой таблицы, но не находятся в пределах диапазонов второй таблицы.
Например, если это моя первая таблица (с датами, которые Я хочу)
start_date end_date
2001-01-01 2010-01-01
2012-01-01 2015-01-01
И это моя вторая таблица (с датами, которые я не хочу)
start_date end_date
2002-01-01 2006-01-01
2003-01-01 2004-01-01
2005-01-01 2009-01-01
2014-01-01 2018-01-01
Тогда вывод выглядит как
start_date end_date
2001-01-01 2001-12-31
2009-01-02 2010-01-01
2012-01-01 2013-12-31
Мы могу смело предположить, что периоды в первой таблице не перекрываются, но не могу предположить, что периоды во второй таблице перекрываются.
У меня уже есть метод для этого, но он на порядок медленнее, чем я могу принять. Поэтому надеемся, что кто-то может предложить более быстрый подход.
Мой нынешний метод выглядит следующим образом:
- объединить таблицу 2 в непересекающиеся периоды
- найти обратную таблицу 2
- объединить перекрывающиеся периоды из таблицы 1 и перевернутой таблицы-2
Я уверен, что есть быстрый путь, если некоторые из этих шагов можно объединить вместе.
Более подробно
/* (1) merge overlapping preiods */
WITH
spell_starts AS (
SELECT [start_date], [end_date]
FROM table_2 s1
WHERE NOT EXISTS (
SELECT 1
FROM table_2 s2
WHERE s2.[start_date] < s1.[start_date]
AND s1.[start_date] <= s2.[end_date]
)
),
spell_ends AS (
SELECT [start_date], [end_date]
FROM table_2 t1
WHERE NOT EXISTS (
SELECT 1
FROM table_2 t2
WHERE t2.[start_date] <= t1.[end_date]
AND t1.[end_date] < t2.[end_date]
)
)
SELECT s.[start_date], MIN(e.[end_date]) as [end_date]
FROM spell_starts s
INNER JOIN spell_ends e
ON s.[start_date] <= e.[end_date]
GROUP BY s.[start_date]
/* (2) inverse table 2 */
SELECT [start_date], [end_date]
FROM (
/* all forward looking spells */
SELECT DATEADD(DAY, 1, [end_date]) AS [start_date]
,LEAD(DATEADD(DAY, -1, [start_date]), 1, '9999-01-01') OVER ( ORDER BY [start_date] ) AS [end_date]
FROM merge_table_2
UNION ALL
/* back looking spell (to 'origin of time') created separately */
SELECT '1900-01-01' AS [start_date]
,DATEADD(DAY, -1, MIN([start_date])) AS [end_date]
FROM merge_table_2
) k
WHERE [start_date] <= [end_date]
AND '1900-01-01' <= [start_date]
AND [end_date] <= '9999-01-01'
/* (3) overlap spells */
SELECT IIF(t1.start_date < t2.start_date, t2.start_date, t1.start_date) AS start_date
,IIF(t1.end_date < t2.end_date, t1.end_date, t2.end_date) AS end_date
FROM table_1 t1
INNER JOIN inverse_merge_table_2 t2
ON t1.start_date < t2.end_date
AND t2.start_date < t1.end_date