2016-02-19 36 views
9

Mam duży zestaw danych, który dla celów niniejszego pytanie ma 3 pola:Okresy kondensacyjne czas z SQL

  • Grupa Identifier
  • od daty
  • Do tej pory

On każdy dany wiersz zawsze będzie mniejszy niż To Date, ale w każdej grupie okresy czasu (które nie są w żadnej kolejności) reprezentowane przez pary w innym, a nawet identyczne.

To, co chciałbym skończyć, to zapytanie, które skrapla wyniki dla każdej grupy aż do ciągłych okresów. Na przykład grupa, która wygląda następująco:

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

spowodowałoby to:

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

Czytałem wiele artykułów o dacie pakowania, ale nie mogę dość dowiedzieć się, jak zastosować do mojego zestawu danych.

Jak skonstruować zapytanie, które dałoby mi te wyniki?

+0

Która wersja SQL Server używasz? –

+0

@GiorgosBetsos 2012 – matthew

+1

Po co na nowo wynajdować koło? Itzik Ben-Gan napisał wiele artykułów na temat okresów między pakowaniem, np. http://blogs.solidq.com/en/sqlserver/packing-intervals/ – dnoeth

Odpowiedz

3

roztworu z książki "Microsoft SQL Server ® 2012 High-Performance T-SQL przy użyciu funkcji okna"

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

Tworzenie tabeli:

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

Mam zamiar przetestować to nieco więcej, ale jak dotąd ta odpowiedź wydaje się dawać najlepsze wyniki i mieć najlepszą wydajność. – 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

W tej chwili nie zwraca poprawnego wyniku nawet dla danych przykładowych. I nie będzie działać poprawnie, gdy istnieje więcej niż jeden "identyfikator grupy". –

+0

dostosowano zapytanie – Squirrel

+0

Spróbuj dodać inny identyfikator grupy do tabeli. Na przykład dla tych dwóch dodatkowych wierszy wynik jest niepoprawny: '(2, '2012-01-01', '2012-12-31')' oraz '(2, '2013-01-01', '2013- 12-31 ') '. –

2

bym użyć Calendar stół. Ta tabela ma po prostu listę dat na kilka dekad.

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

Istnieje wiele sposobów na populate such table.

Na przykład 100K wiersze (~ 270 lat) od 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); 

Gdy masz Calendar stół, tutaj jest, jak go używać.

Każdy oryginalny wiersz jest połączony z tabelą Calendar, aby zwrócić tyle wierszy, ile jest dat między Od i Do.

Następnie możliwe duplikaty są usuwane.

Następnie klasyczne luki i wyspy, numerując wiersze w dwóch sekwencjach.

Następnie grupowanie znalezionych wysp w celu uzyskania nowych Od i Do.

Przykładowe dane

I dodaje się drugą grupę.

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

Zapytanie

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; 

Wynik

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