2015-10-19 108 views
7

我需要做的是计算给定一个表历年之内丢失的时间段,如在SQL中:SQL计算日期段

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 

预期回报率将是:

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 

它本质上是工作块。我需要展示的是日历年中没有工作的部分。所以对于ID = 3,他从3/1开始工作到今年的其余时间。但他从1/1到2/28没有工作。这就是我要找的。

+0

你能解释一下结果的逻辑吗,比如记录ID = 3吗? –

+2

这是SQL Server 2008中的一个痛点。你可以升级到SQL Server 2012吗? –

+0

考虑完成。如果有帮助,我可以在2012年运行。 – Brian

回答

0

如果你的数据不是太大,这种方法将起作用。它扩展了所有的日子和ID,然后重新它们分组:

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; 

的另一种方法依赖于累积和以及类似功能,是SQL Server 2012+来表达容易得多。

+0

我正在运行SQL Server 2012.我已更改与问题关联的标记。 – Brian

+0

这将在SQL Server 2012中工作。 –

1

可以使用LEAD做到这一点,LAG可以从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 

上面的查询背后的想法很简单:每[DateStart, DateEnd]间隔获得“没有工作”区间权在它之前。如果在当前时间间隔之后没有间隔,那么也会得到连续的'未工作'间隔(如果有的话)。

另外请注意,我认为如果DateStartNULL然后DateStartNULL为同一ID

Demo here

0

有些简单的方法,我想。

基本上为所有工作块范围创建一个日期列表(A)。然后为每个ID(B)创建一整年的日期列表。然后从B中删除A.将剩余日期列表编译为每个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 

继承人的代码: http://sqlfiddle.com/#!3/f3615/1

我希望这有助于!