SQL计算日历年

时间:2015-10-19 19:21:08

标签: sql date sql-server-2012

我需要的是在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日没有工作。这就是我在寻找的东西。

3 个答案:

答案 0 :(得分:1)

您可以使用SQL Server 2012 +提供的LEADLAG窗口函数来执行此操作:

;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]间隔,在它之前获取'not working'间隔。如果当前间隔之后没有间隔,那么也会连续'not working'间隔(如果有)。

另请注意,我假设如果DateStartNULL,那么DateStart对于同一NULL也是ID

Demo here

答案 1 :(得分: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 +中更容易表达的累积总和和类似功能。

答案 2 :(得分: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

我希望这有帮助!