2009-12-28 16 views
1

J'ai deux tables dont les deux ont des colonnes StartDate et EndDate. J'essaie de renvoyer un seul jeu de résultats contenant toutes les plages de dates d'une table (TableA) et toutes les plages de dates complémentaires de l'autre (TableB).Trouver des plages de dates de complément?

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

Le jeu de résultats attendus des trois ensembles de données devrait être:

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

Les plages de dates ne sont pas garantis d'être continue, et je ne peux pas faire des hypothèses sur la façon dont ils se chevauchent entre les tables ... dans chaque tableau, on peut supposer qu'ils ne se chevauchent pas.

Je n'arrive pas à comprendre comment diviser les plages de dates uniques de la TableB en plusieurs parties pour trouver toutes les «régions» complémentaires en SQL.

Quelqu'un a des suggestions?

+1

définir 'complément' dans ce contexte. –

+0

Combien de rangs avez-vous? La performance est-elle un problème? –

+0

Je pense qu'il veut dire qu'il veut toutes les lignes de A, plus toutes les parties de périodes dans B qui ne chevauchent aucune période dans A. En d'autres termes: 'Une union (B moins (B intersection A))', de sorte que A et B 'sont disjoints et' A union B '== A union B'. –

Répondre

1

Si vous créez ceci en vue, je pense qu'il fait ce que vous voulez. Il utilise des CTE, qui devraient être supportés par SQL Server 2005, mais pas plus tôt.

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 

sortie complète (ordonnée par Id, StartDate pour une meilleure lisibilité):

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 

Il était assez compliqué pour moi de mettre en œuvre, alors je me demande si quelqu'un peut voir une façon plus simple. Je pourrais manquer un truc qui rend cela beaucoup plus simple. Si oui, s'il vous plaît faites le moi savoir! En outre, vous aurez certainement besoin de quelques index sur vos tables pour que cela fonctionne bien si vous avez beaucoup de lignes. D'autres optimisations peuvent être possibles - je n'ai pas essayé pour la performance la plus rapide possible, mais juste pour obtenir le bon résultat.

+1

Vous pouvez remplacer le 'UNION' final par un' FULL JOIN', sinon la requête est correcte. Voir ici: http://explainextended.com/2009/11/09/inverting-date-ranges/ – Quassnoi

+0

Nice link - il explique à peu près la requête que je viens d'écrire. Je n'aurais jamais trouvé ça par Googling. –

+0

PS, je pense que mon dernier ALL UNION est correct - c'est juste la partie où je combine les résultats de TableA et (TableB-TableA). Je pense que la partie dont vous parlez avec le FULL JOIN dans ma requête se trouve dans Timestamps3 (ouais, mauvais noms, je suis désolé) où à la place je fais un 'INNER JOIN'. Cela tue les deux lignes avec des valeurs NULL, mais je pense que c'est ce qu'il veut de toute façon, donc je ne pense pas qu'il faille changer. –