2009-12-28 8 views
1

У меня есть две таблицы, каждая из которых имеет столбцы StartDate и EndDate.Поиск диапазонов дат добавления?

Я пытаюсь вернуть единый набор результатов, содержащий все диапазоны дат из одной таблицы (таблица A), и все диапазоны дат дополнений от другого (TableB).

CREATE TABLE [dbo].[TableA](
    [ID] [int] NOT NULL, 
    [StartDate] [datetime] NOT NULL, 
    [EndDate] [datetime] NOT NULL 
) 

CREATE TABLE [dbo].[TableB](
    [ID] [int] NOT NULL, 
    [StartDate] [datetime] NOT NULL, 
    [EndDate] [datetime] NOT NULL 
) 

INSERT INTO TableA (ID, StartDate, EndDate) VALUES(1, '4/1/2009', '8/1/2009') 
INSERT INTO TableA (ID, StartDate, EndDate) VALUES(1, '10/1/2009', '12/1/2009') 
INSERT INTO TableB (ID, StartDate, EndDate) VALUES(1, '1/1/2009', '2/1/2010') 

INSERT INTO TableA (ID, StartDate, EndDate) VALUES(2, '4/1/2009', '8/1/2009') 
INSERT INTO TableB (ID, StartDate, EndDate) VALUES(2, '1/1/2009', '5/1/2009') 
INSERT INTO TableB (ID, StartDate, EndDate) VALUES(2, '7/1/2009', '12/1/2009') 

Ожидаемый ResultSet из трех наборов данных должен быть:

(ID = 1) 
1/1/2009 - 4/1/2009 (from TableB) 
4/1/2009 - 8/1/2009 (from TableA) 
8/1/2009 - 10/1/2009 (from TableB) 
10/1/2009 - 12/1/2009 (from TableA) 
12/1/2009 - 2/1/2010 (from TableB) 

(ID = 2) 
1/1/2009 - 4/1/2009 (from TableB) 
4/1/2009 - 8/1/2009 (from TableA) 
8/1/2009 - 12/1/2009 (from TableB) 

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

У меня возникли проблемы с тем, чтобы разделить единые диапазоны дат в TableB на несколько частей, чтобы найти все дополнительные «области» внутри него в SQL.

У кого-нибудь есть предложения?

+1

Определите 'дополнение' в этом контексте. –

+0

Сколько у вас строк? Является ли производительность проблемой? –

+0

Я думаю, что он означает, что он хочет, чтобы все строки из A, плюс все части периодов в B, которые не пересекаются с каким-либо периодом в A. Другими словами: «Объединение (B минус (B пересекает A))», так что A и B 'не пересекаются и 'A union B' == A union B'. –

ответ

1

Если вы создадите это как представление, я думаю, что он делает то, что вы хотите. Он использует CTE, который должен поддерживаться SQL Server 2005, но не ранее.

WITH Timestamps AS (
    SELECT Id, StartDate AS Date FROM TableA 
    UNION 
    SELECT Id, EndDate AS Date FROM TableA 
    UNION 
    SELECT Id, StartDate AS Date FROM TableB 
    UNION 
    SELECT Id, EndDate AS Date FROM TableB 
), Timestamps2 AS (
    SELECT ROW_NUMBER() OVER (ORDER BY Id, Date) AS RowNumber, * FROM Timestamps 
), Timestamps3 AS (
    SELECT T1.ID, T1.Date AS StartDate, T2.Date AS EndDate 
    FROM Timestamps2 AS T1 JOIN Timestamps2 AS T2 
    ON T1.RowNumber + 1 = T2.RowNumber AND T1.ID = T2.ID 
), IntervalsFromB AS (
    SELECT T.ID, T.StartDate, T.EndDate FROM Timestamps3 AS T 
    LEFT JOIN TableA AS A 
    ON T.StartDate >= A.StartDate AND T.EndDate <= A.EndDate 
    WHERE A.StartDate IS NULL) 
SELECT * FROM TableA 
UNION ALL 
SELECT * FROM IntervalsFromB 

Полный выход (по заказу Id, StartDate для удобства чтения):

Id StartDate    EndDate 
1 2009-01-01 00:00:00.000 2009-04-01 00:00:00.000 
1 2009-04-01 00:00:00.000 2009-08-01 00:00:00.000 
1 2009-08-01 00:00:00.000 2009-10-01 00:00:00.000 
1 2009-10-01 00:00:00.000 2009-12-01 00:00:00.000 
1 2009-12-01 00:00:00.000 2010-02-01 00:00:00.000 
2 2009-01-01 00:00:00.000 2009-04-01 00:00:00.000 
2 2009-04-01 00:00:00.000 2009-08-01 00:00:00.000 
2 2009-08-01 00:00:00.000 2009-12-01 00:00:00.000 

Это было довольно сложно для меня, чтобы осуществить это, так что мне интересно, если кто-нибудь может увидеть более простой способ. Мне может быть недостаток в трюке, который делает это намного проще. Если это так, пожалуйста, дайте мне знать! Кроме того, вам почти наверняка понадобятся некоторые индексы в ваших таблицах, чтобы добиться успеха, если у вас много строк. Возможно, возможны и другие оптимизации - я не пробовал для максимально быстрой работы, а просто для получения правильного результата.

+1

Вы можете заменить окончательный 'UNION' на' FULL JOIN', в противном случае запрос будет правильным. См. Здесь: http://explainextended.com/2009/11/09/inverting-date-ranges/ – Quassnoi

+0

Хорошая ссылка - это в значительной степени объясняет запрос, который я только что написал. Я бы никогда не нашел этого в Google. –

+0

PS, я думаю, что мой последний UNION ALL правильный - это только та часть, в которой я совмещаю результаты с TableA и (TableB-TableA). Я думаю, что часть, которую вы имеете в виду с FULL JOIN в моем запросе, находится внутри Timestamps3 (да, плохие имена, извините), где вместо этого я делаю «INNER JOIN». Это убивает две строки с помощью NULL, но я думаю, что так оно и есть, поэтому я не думаю, что никаких изменений не требуется. –