2015-10-19 14 views
7

Che cosa ho bisogno è quello di calcolare i periodi di tempo mancanti entro l'anno solare dato un tavolo come questo in SQL:SQL segmenti data Calcolare all'interno dell'anno solare

DatesTable 
|ID|DateStart |DateEnd | 
1 NULL   NULL 
2 2015-1-1  2015-12-31 
3 2015-3-1  2015-12-31 
4 2015-1-1  2015-9-30 
5 2015-1-1  2015-3-31 
5 2015-6-1  2015-12-31 
6 2015-3-1  2015-6-30 
6 2015-7-1  2015-10-31 

Rendimento atteso sarebbero:

1 2015-1-1  2015-12-31 
3 2015-1-1  2015-2-28 
4 2015-10-1 2015-12-31 
5 2015-4-1  2015-5-31 
6 2015-1-1  2015-2-28 
6 2015-11-1 2015-12-31 

Sono essenzialmente blocchi di lavoro. Quello che devo mostrare è la parte dell'anno civile che NON ha funzionato. Quindi, per ID = 3, ha lavorato dal 3/1 al resto dell'anno. Ma non ha funzionato dall'1 al 2/28. Questo è quello che sto cercando.

+0

Puoi spiegare la logica per risultati come ID record = 3? –

+2

Questo è un problema in SQL Server 2008. È possibile eseguire l'aggiornamento a SQL Server 2012? –

+0

Consideralo fatto. Posso gestirlo nel 2012 se questo aiuta. – Brian

risposta

0

Se i dati non sono troppo grandi, questo approccio funzionerà. Si espande per tutti i giorni e gli ID e poi ri-li raggruppa:

with d as (
     select cast('2015-01-01' as date) 
     union all 
     select dateadd(day, 1, d) 
     from d 
     where d < cast('2015-12-31' as date) 
    ), 
    td as (
     select * 
     from d cross join 
      (select distinct id from t) t 
     where not exists (select 1 
         from t t2 
         where d.d between t2.startdate and t2.enddate 
         ) 
    ) 
select id, min(d) as startdate, max(d) as enddate 
from (select td.*, 
      dateadd(day, - row_number() over (partition by id order by d), d) as grp 
     from td 
    ) td 
group by id, grp 
order by id, grp; 

Un metodo alternativo si basa su somme cumulative e funzionalità simile che è molto più facile per l'espressione in SQL Server 2012+.

+0

Sto eseguendo SQL Server 2012. Ho modificato il tag associato alla domanda. – Brian

+0

Funzionerà in SQL Server 2012. –

1

È possibile farlo utilizzando LEAD, LAG funzioni finestra disponibili da SQL Server 2012+:

;WITH CTE AS (
    SELECT ID, 
      LAG(DateEnd) OVER (PARTITION BY ID ORDER BY DateEnd) AS PrevEnd, 
      DateStart, 
      DateEnd, 
      LEAD(DateStart) OVER (PARTITION BY ID ORDER BY DateEnd) AS NextStart 
    FROM DatesTable 
) 
SELECT ID, DateStart, DateEnd 
FROM (
-- Get interval right before current [DateStart, DateEnd] interval 
SELECT ID, 
     CASE 
      WHEN DateStart IS NULL THEN '20150101' 
      WHEN DateStart > start THEN start 
      ELSE NULL 
     END AS DateStart, 
     CASE 
      WHEN DateStart IS NULL THEN '20151231' 
      WHEN DateStart > start THEN DATEADD(d, -1, DateStart) 
      ELSE NULL 
     END AS DateEnd 
FROM CTE 
CROSS APPLY (SELECT COALESCE(DATEADD(d, 1, PrevEnd), '20150101')) x(start) 

-- If there is no next interval then get interval right after current 
-- [DateStart, DateEnd] interval (up-to end of year) 
UNION ALL 

SELECT ID, DATEADD(d, 1, DateEnd) AS DateStart, '20151231' AS DateEnd  
FROM CTE 
WHERE DateStart IS NOT NULl -- Do not re-examine [Null, Null] interval 
     AND NextStart IS NULL -- There is no next [DateStart, DateEnd] interval 
     AND DateEnd < '20151231' -- Current [DateStart, DateEnd] interval 
           -- does not terminate on 31/12/2015 
) AS t 
WHERE t.DateStart IS NOT NULL 
ORDER BY ID, DateStart 

L'idea alla base della query di cui sopra è semplice: per ogni intervallo di [DateStart, DateEnd] ottenere 'non hanno funzionato' intervallo di destra prima di cio. Se non vi è alcun intervallo che segue l'intervallo corrente, anche il successivo intervallo 'non funzionante' (se presente).

Si noti inoltre che presumo che se DateStart è NULL poi DateStart è anche NULL per lo stesso ID.

Demo here

0

approccio un po 'più semplice che penso.

Fondamentalmente creare un elenco di date per tutte le gamme di blocchi di lavoro (A). Quindi creare un elenco di date per l'intero anno per ciascun ID (B). Quindi rimuovere A da B. Compilare l'elenco di date rimanenti in intervalli di date per ciascun ID.

DECLARE @startdate DATETIME, @enddate DATETIME 
SET @startdate = '2015-01-01' 
SET @enddate = '2015-12-31' 

--Build date ranges from remaining date list 
;WITH dateRange(ID, dates, Grouping) 
AS 
(
    SELECT dt1.id, dt1.Dates, dt1.Dates + row_number() over (order by dt1.id asc, dt1.Dates desc) AS Grouping 
    FROM 
    (
     --Remove (A) from (B) 
     SELECT distinct dt.ID, tmp.Dates FROM DatesTable dt 
     CROSS APPLY 
     (
      --GET (B) here 
      SELECT DATEADD(DAY, number, @startdate) [Dates] 
      FROM master..spt_values 
      WHERE type = 'P' AND DATEADD(DAY, number, @startdate) <= @enddate 
     ) tmp 
     left join 
     (
      --GET (A) here 
      SELECT DISTINCT T.Id, 
       D.Dates 
      FROM DatesTable AS T 
      INNER JOIN master..spt_values as N on N.number between 0 and datediff(day, T.DateStart, T.DateEnd) 
      CROSS APPLY (select dateadd(day, N.number, T.DateStart)) as D(Dates) 
      WHERE N.type ='P' 
     ) dr 
     ON dr.Id = dt.Id and dr.Dates = tmp.Dates 
     WHERE dr.id is null 
    ) dt1 
) 
SELECT ID, CAST(MIN(Dates) AS DATE) DateStart, CAST(MAX(Dates) AS DATE) DateEnd 
FROM dateRange 
GROUP BY ID, Grouping 
ORDER BY ID 

Heres il codice: http://sqlfiddle.com/#!3/f3615/1

Spero che questo aiuta!