2009-12-28 56 views
1

我有兩個表都有列StartDate和EndDate。尋找補充日期範圍?

我想返回一個結果集,它包含一個表(TableA)中的所有日期範圍,以及另一個表(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') 

從三個數據集的預期結果集應該是:

(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

在此上下文中定義「補充」。 – 2009-12-28 21:04:07

+0

你有幾行?性能是一個問題嗎? – 2009-12-28 21:10:53

+0

我認爲他意味着他想要A中的所有行,加上B中所有與A中的任何時段都不重疊的時段的部分。換句話說:「一個聯合(B減去(B相交A))',因此A和B'是不相交的並且'A聯合B'==聯合B'。 – 2009-12-28 21:53:44

回答

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

您可以用'FULL JOIN'替換最終的'UNION',否則查詢是正確的。看到這裏:http://explainextended.com/2009/11/09/inverting-date-ranges/ – Quassnoi 2009-12-28 21:53:28

+0

不錯的鏈接 - 它幾乎解釋了我剛纔寫的查詢。我從來沒有發現通過谷歌搜索。 – 2009-12-28 22:00:25

+0

PS,我認爲我最後的UNION ALL是正確的 - 這只是我將TableA和TableB-TableA結果結合在一起的部分。我認爲你在查詢中提到的F​​ULL JOIN的部分是在Timestamps3(是的,壞的名字,我很抱歉)而是我做了一個'INNER JOIN'。這殺死了兩行NULL,但我認爲這正是他想要的,所以我不認爲需要進行任何更改。 – 2009-12-28 22:05:37