2010-04-01 12 views
6

C'è un modo migliore di unire intervalli di date sovrapposti?
La soluzione che ho trovato è così semplice che ora mi chiedo se qualcun altro ha un'idea migliore di come questo possa essere fatto.Intervalli di data di sovrapposizione sovrapposti

/***** DATA EXAMPLE *****/ 
DECLARE @T TABLE (d1 DATETIME, d2 DATETIME) 
INSERT INTO @T (d1, d2) 
     SELECT '2010-01-01','2010-03-31' UNION SELECT '2010-04-01','2010-05-31' 
    UNION SELECT '2010-06-15','2010-06-25' UNION SELECT '2010-06-26','2010-07-10' 
    UNION SELECT '2010-08-01','2010-08-05' UNION SELECT '2010-08-01','2010-08-09' 
    UNION SELECT '2010-08-02','2010-08-07' UNION SELECT '2010-08-08','2010-08-08' 
    UNION SELECT '2010-08-09','2010-08-12' UNION SELECT '2010-07-04','2010-08-16' 
    UNION SELECT '2010-11-01','2010-12-31' UNION SELECT '2010-03-01','2010-06-13' 

/***** INTERVAL ANALYSIS *****/ 
WHILE (1=1) BEGIN 
    UPDATE t1 SET t1.d2 = t2.d2 
    FROM @T AS t1 INNER JOIN @T AS t2 ON 
      DATEADD(day, 1, t1.d2) BETWEEN t2.d1 AND t2.d2 
    IF @@ROWCOUNT = 0 BREAK 
END 

/***** RESULT *****/ 
SELECT StartDate = MIN(d1) , EndDate = d2 
FROM @T 
GROUP BY d2 
ORDER BY StartDate, EndDate 

/***** OUTPUT *****/ 
/***** 
StartDate EndDate 
2010-01-01 2010-06-13 
2010-06-15 2010-08-16 
2010-11-01 2010-12-31 
*****/ 
+1

sono la intervalli open-aperto, chiuso-chiuso, aperto-chiuso o chiuso-aperto? Importa perché le condizioni di fine variano leggermente in base. Per molti scopi, open-closed (compresa la prima data, esclusa la seconda data) è la migliore rappresentazione; open-open (entrambe le estremità incluse) è spesso ciò che le persone hanno in mente. –

+0

Jonathan, stavo pensando ai casi in cui entrambi i giorni (data di inizio e data di fine) sono parte del periodo. – leoinfo

+0

È possibile eseguirlo a passaggio singolo, ma è un'implementazione del cursore in modo che dipenda dalla dimensione del set di dati. –

risposta

0

In questa soluzione, ho creato una tabella calendario temporanea che memorizza un valore per ogni giorno su un intervallo. Questo tipo di tavolo può essere reso statico. Inoltre, sto memorizzando solo 400 date strane a partire dal 2009-12-31. Ovviamente, se le tue date si estendono su un intervallo più ampio, avrai bisogno di più valori.

Inoltre, questa soluzione funzionerà solo con SQL Server 2005+ in quanto sto utilizzando un CTE.

With Calendar As 
    (
    Select DateAdd(d, ROW_NUMBER() OVER (ORDER BY s1.object_id), '1900-01-01') As [Date] 
    From sys.columns as s1 
     Cross Join sys.columns as s2 
    ) 
    , StopDates As 
    (
    Select C.[Date] 
    From Calendar As C 
     Left Join @T As T 
      On C.[Date] Between T.d1 And T.d2 
    Where C.[Date] >= (Select Min(T2.d1) From @T As T2) 
     And C.[Date] <= (Select Max(T2.d2) From @T As T2) 
     And T.d1 Is Null 
    ) 
    , StopDatesInUse As 
    (
    Select D1.[Date] 
    From StopDates As D1 
     Left Join StopDates As D2 
      On D1.[Date] = DateAdd(d,1,D2.Date) 
    Where D2.[Date] Is Null 
    ) 
    , DataWithEariestStopDate As 
    (
    Select * 
    , (Select Min(SD2.[Date]) 
     From StopDatesInUse As SD2 
     Where T.d2 < SD2.[Date]) As StopDate 
    From @T As T 
    ) 
Select Min(d1), Max(d2) 
From DataWithEariestStopDate 
Group By StopDate 
Order By Min(d1) 

EDIT Il problema con l'utilizzo di date nel 2009 non ha nulla a che fare con la query finale. Il problema è che la tabella del calendario non è abbastanza grande. Ho iniziato la tavola del calendario dal 2009-12-31. L'ho revisionato a partire dal 1900-01-01.

+0

Il tuo codice è intervalli di fusione che non dovrebbero essere uniti.Utilizzando questi intervalli iniziali/**/SELECT '2009-01-01', '2009-01-01' UNION SELECT '2009-01-03', '2009-01-03'/**/il codice restituisce un singolo periodo: dal 2009-01-01 al 2009-01-03. In questo caso, il 2009-01-02 non dovrebbe essere incluso nell'intervallo risultante. – leoinfo

+0

Innanzitutto, è necessario aggiungere lo schema e in particolare se D1 = D2. Nessuno dei tuoi dati di esempio lo suggerisce. In secondo luogo, se ** aggiungi ** {2010-01-01,2010-01-01}, ai tuoi dati di esempio esistenti, il primo intervallo dovrebbe essere ancora dal 01/01/2010 al 2010-06-13 perché la prima voce nel tuo esempio copre dal 2010-01-01 al 2010-03-31. Terzo, se invece sostituisci ** la prima voce dell'esempio con {2010-01-01, 2010-01-01}, {2010-03-01, 2010-03-01}, i risultati della mia query sono ancora corretti Apportando tale modifica, le prime due voci risultano {2010-01-01, 2010-01-01}, {2010-03-01, 2010-06-13}. – Thomas

+0

Un altro scenario, se si sostituiscono tutte le voci con solo {2010-01-01,2010-01-01}, {2010-03-01,2010-03-01}, si otterranno le stesse due voci. – Thomas

0

Prova questa

;WITH T1 AS 
(
    SELECT d1, d2, ROW_NUMBER() OVER(ORDER BY (SELECT 0)) AS R 
    FROM @T 
), NUMS AS 
(
    SELECT ROW_NUMBER() OVER(ORDER BY (SELECT 0)) AS R 
    FROM T1 A 
    CROSS JOIN T1 B 
    CROSS JOIN T1 C 
), ONERANGE AS 
(
    SELECT DISTINCT DATEADD(DAY, ROW_NUMBER() OVER(PARTITION BY T1.R ORDER BY (SELECT 0)) - 1, T1.D1) AS ELEMENT 
    FROM T1 
    CROSS JOIN NUMS 
    WHERE NUMS.R <= DATEDIFF(DAY, d1, d2) + 1 
), SEQUENCE AS 
(
    SELECT ELEMENT, DATEDIFF(DAY, '19000101', ELEMENT) - ROW_NUMBER() OVER(ORDER BY ELEMENT) AS rownum 
    FROM ONERANGE 
) 
SELECT MIN(ELEMENT) AS StartDate, MAX(ELEMENT) as EndDate 
FROM SEQUENCE 
GROUP BY rownum 

L'idea di base è quella di srotolare prima i dati esistenti, in modo da ottenere una riga separata per ogni giorno. Questo viene fatto in ONERANGE

Quindi, identificare la relazione tra l'incremento delle date e il modo in cui i numeri di riga vengono eseguiti. La differenza rimane costante all'interno di un intervallo/isola esistente. Non appena si arriva a una nuova isola dati, la differenza tra loro aumenta perché la data aumenta di oltre 1, mentre il numero della riga aumenta di 1.

13

Stavo cercando la stessa soluzione e ho trovato questo post su Combine overlapping datetime to return single overlapping range record.

C'è un altro thread su Packing Date Intervals.

Ho provato questo con vari intervalli di date, compresi quelli elencati qui, e funziona correttamente ogni volta.


SELECT 
     s1.StartDate, 
     --t1.EndDate 
     MIN(t1.EndDate) AS EndDate 
FROM @T s1 
INNER JOIN @T t1 ON s1.StartDate <= t1.EndDate 
    AND NOT EXISTS(SELECT * FROM @T t2 
       WHERE t1.EndDate >= t2.StartDate AND t1.EndDate < t2.EndDate) 
WHERE NOT EXISTS(SELECT * FROM @T s2 
       WHERE s1.StartDate > s2.StartDate AND s1.StartDate <= s2.EndDate) 
GROUP BY s1.StartDate 
ORDER BY s1.StartDate 

Il risultato è:

StartDate | EndDate 
2010-01-01 | 2010-06-13 
2010-06-15 | 2010-06-25 
2010-06-26 | 2010-08-16 
2010-11-01 | 2010-12-31 
+0

Inoltre, trovato un altro esempio con la spiegazione su come ottenere questo qui: http://www.sqlmag.com/blog/puzzled-by-t-sql-blog-15/tsql/packing-date-intervals-136831 – user1045402

+0

You puoi modificare la tua risposta per aggiungere ulteriori informazioni, fai clic sul link "modifica" nella parte inferiore della risposta. – ForceMagic

+0

Funziona perfettamente ed è conciso! – ensisNoctis

Problemi correlati