2016-02-19 24 views
9

Ho un grande insieme di dati che per la fine di questa domanda ha 3 campi:Periodi Condensare di tempo con SQL

  • Group Identifier
  • Da Data
  • di sesso

On qualsiasi riga data il From Date sarà sempre inferiore allo To Date ma all'interno di ciascun gruppo i periodi di tempo (che non sono in ordine particolare) rappresentati dalle coppie di date potrebbero sovrapporsi, essere contenuti in e all'interno di un altro, o addirittura essere identici.

Quello che mi piacerebbe finire è una query che condensa i risultati per ciascun gruppo fino ai periodi continui. Per esempio un gruppo che assomiglia a questo:

| Group ID | From Date | To Date | 
-------------------------------------- 
| A  | 01/01/2012 | 12/31/2012 | 
| A  | 12/01/2013 | 11/30/2014 | 
| A  | 01/01/2015 | 12/31/2015 | 
| A  | 01/01/2015 | 12/31/2015 | 
| A  | 02/01/2015 | 03/31/2015 | 
| A  | 01/01/2013 | 12/31/2013 | 

si tradurrebbe in questo:

| Group ID | From Date | To Date | 
-------------------------------------- 
| A  | 01/01/2012 | 11/30/2014 | 
| A  | 01/01/2015 | 12/31/2015 | 

Ho letto un certo numero di articoli sulla data di confezionamento, ma non riesco a capire come applicare quello al mio set di dati.

Come si può costruire una query che mi darebbe quei risultati?

+0

Quale versione di SQL Server stai usando? –

+0

@GiorgosBetsos 2012 – matthew

+1

Perché reinventare la ruota? Itzik Ben-Gan ha scritto più articoli sugli intervalli di imballaggio, ad es. http://blogs.solidq.com/en/sqlserver/packing-intervals/ – dnoeth

risposta

3

La soluzione dal libro "Microsoft® SQL Server ® 2012 ad alte prestazioni T-SQL Utilizzo delle funzioni della finestra"

;with C1 as(
select GroupID, FromDate as ts, +1 as type, 1 as sub 
    from dbo.table_name 
union all 
select GroupID, dateadd(day, +1, ToDate) as ts, -1 as type, 0 as sub 
    from dbo.table_name), 
C2 as(
select C1.* 
    , sum(type) over(partition by GroupID order by ts, type desc 
         rows between unbounded preceding and current row) - sub as cnt 
    from C1), 
C3 as(
select GroupID, ts, floor((row_number() over(partition by GroupID order by ts) - 1)/2 + 1) as grpnum 
    from C2 
    where cnt = 0) 

select GroupID, min(ts) as FromDate, dateadd(day, -1, max(ts)) as ToDate 
    from C3 
    group by GroupID, grpnum; 

Crea una tabella:

if object_id('table_name') is not null 
    drop table table_name 
create table table_name(GroupID varchar(100), FromDate datetime,ToDate datetime) 
insert into table_name 
select 'A', '01/01/2012', '12/31/2012' union all 
select 'A', '12/01/2013', '11/30/2014' union all 
select 'A', '01/01/2015', '12/31/2015' union all 
select 'A', '01/01/2015', '12/31/2015' union all 
select 'A', '02/01/2015', '03/31/2015' union all 
select 'A', '01/01/2013', '12/31/2013' 
+0

Ho intenzione di testarlo un po 'di più ma finora questa risposta sembra dare i migliori risultati e avere le migliori prestazioni. – matthew

2
; with 
cte as 
(
    select *, rn = row_number() over (partition by [Group ID] order by [From Date]) 
    from tbl 
), 
rcte as 
(
    select rn, [Group ID], [From Date], [To Date], GrpNo = 1, GrpFrom = [From Date], GrpTo = [To Date] 
    from cte 
    where rn = 1 

    union all 

    select c.rn, c.[Group ID], c.[From Date], c.[To Date], 
     GrpNo = case when c.[From Date] between r.GrpFrom and dateadd(day, 1, r.GrpTo) 
       or c.[To Date] between r.GrpFrom and r.GrpTo 
       then r.GrpNo 
       else r.GrpNo + 1 
       end, 
     GrpFrom= case when c.[From Date] between r.GrpFrom and dateadd(day, 1, r.GrpTo) 
       or c.[To Date] between r.GrpFrom and r.GrpTo 
       then case when c.[From Date] > r.GrpFrom then c.[From Date] else r.GrpFrom end 
       else c.[From Date] 
       end, 
     GrpTo = case when c.[From Date] between r.GrpFrom and dateadd(day, 1, r.GrpTo) 
       or c.[To Date] between r.GrpFrom and dateadd(day, 1, r.GrpTo) 
       then case when c.[To Date] > r.GrpTo then c.[To Date] else r.GrpTo end 
       else c.[To Date] 
       end 

    from rcte r 
     inner join cte c on r.[Group ID] = c.[Group ID] 
        and r.rn  = c.rn - 1 
) 
select [Group ID], min(GrpFrom), max(GrpTo) 
from rcte 
group by [Group ID], GrpNo 
+0

Al momento non restituisce risultati corretti anche per i dati di esempio forniti. E non funzionerà correttamente quando ci sono più di un 'ID di gruppo'. –

+0

corretta la query – Squirrel

+0

Provare ad aggiungere un altro ID di gruppo alla tabella. Ad esempio, con queste due righe aggiuntive il risultato non è corretto: '(2, '2012-01-01', '2012-12-31')' e '(2, '2013-01-01', '2013- 12-31') '. –

2

userei un Calendar tabella. Questa tabella ha semplicemente un elenco di date per diversi decenni.

CREATE TABLE [dbo].[Calendar](
    [dt] [date] NOT NULL, 
CONSTRAINT [PK_Calendar] PRIMARY KEY CLUSTERED 
(
    [dt] ASC 
)) 

Ci sono molti modi per populate such table.

Per esempio, 100K righe (~ 270 anni) da 1900-01-01:

INSERT INTO dbo.Calendar (dt) 
SELECT TOP (100000) 
    DATEADD(day, ROW_NUMBER() OVER (ORDER BY s1.[object_id])-1, '19000101') AS dt 
FROM sys.all_objects AS s1 CROSS JOIN sys.all_objects AS s2 
OPTION (MAXDOP 1); 

Una volta che hanno un tavolo Calendar, ecco come usarlo.

Ogni riga originale viene unita alla tabella Calendar per restituire tutte le righe quante sono le date tra Da e A.

Quindi i possibili duplicati vengono rimossi.

Quindi intervalli e isole classici numerando le righe in due sequenze.

Quindi raggruppare le isole trovate insieme per ottenere il nuovo Da e A.

dati campione

ho aggiunto un secondo gruppo.

DECLARE @T TABLE (GroupID int, FromDate date, ToDate date); 
INSERT INTO @T (GroupID, FromDate, ToDate) VALUES 
(1, '2012-01-01', '2012-12-31'), 
(1, '2013-12-01', '2014-11-30'), 
(1, '2015-01-01', '2015-12-31'), 
(1, '2015-01-01', '2015-12-31'), 
(1, '2015-02-01', '2015-03-31'), 
(1, '2013-01-01', '2013-12-31'), 
(2, '2012-01-01', '2012-12-31'), 
(2, '2013-01-01', '2013-12-31'); 

Query

WITH 
CTE_AllDates 
AS 
(
    SELECT DISTINCT 
     T.GroupID 
     ,CA.dt 
    FROM 
     @T AS T 
     CROSS APPLY 
     (
      SELECT dbo.Calendar.dt 
      FROM dbo.Calendar 
      WHERE 
       dbo.Calendar.dt >= T.FromDate 
       AND dbo.Calendar.dt <= T.ToDate 
     ) AS CA 
) 
,CTE_Sequences 
AS 
(
    SELECT 
     GroupID 
     ,dt 
     ,ROW_NUMBER() OVER(PARTITION BY GroupID ORDER BY dt) AS Seq1 
     ,DATEDIFF(day, '2001-01-01', dt) AS Seq2 
     ,DATEDIFF(day, '2001-01-01', dt) - 
      ROW_NUMBER() OVER(PARTITION BY GroupID ORDER BY dt) AS IslandNumber 
    FROM CTE_AllDates 
) 
SELECT 
    GroupID 
    ,MIN(dt) AS NewFromDate 
    ,MAX(dt) AS NewToDate 
FROM CTE_Sequences 
GROUP BY GroupID, IslandNumber 
ORDER BY GroupID, NewFromDate; 

Risultato

+---------+-------------+------------+ 
| GroupID | NewFromDate | NewToDate | 
+---------+-------------+------------+ 
|  1 | 2012-01-01 | 2014-11-30 | 
|  1 | 2015-01-01 | 2015-12-31 | 
|  2 | 2012-01-01 | 2013-12-31 | 
+---------+-------------+------------+