2015-10-19 12 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

データが大きすぎない場合は、この方法が有効です。

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+で表現する方がはるかに簡単である同様の機能に依存している:それは、すべての日とIDと再グループにそれらを展開します。

+0

私はSQL Server 2012を実行しています。質問に関連付けられたタグを変更しました。 – Brian

+0

これはSQL Server 2012で動作します。 –

1

あなたはLEADを使用してそれを行うことができ、SQL Serverの2012+から入手LAGウィンドウ関数:

;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 worked'のインターバル(インターバルがある場合)を取得します。

はまた、私はDateStartNULLであるならば、DateStartも同じIDためNULLであることを前提としています。

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

私はこれが役に立てば幸い!