2009-12-28 76 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