2012-02-14 11 views
8

私は、SQL Serverの次のようなデータ構造のテーブルを持っている:作成グループは

ID Date  Allocation 
1, 2012-01-01, 0 
2, 2012-01-02, 2 
3, 2012-01-03, 0 
4, 2012-01-04, 0 
5, 2012-01-05, 0 
6, 2012-01-06, 5 

など

私はすべての日連続期間を取得されて行う必要がある場所を割り当て= 0、そして次の形式で:

Start Date End Date  DayCount 
2012-01-01 2012-01-01 1 
2012-01-03 2012-01-05 3 

など

それはPOSSですSQLでこれを行うことができるかどうか、どうすればよいのでしょうか? CTEなし

+0

@ istariは終了日です。テーブル構造の列 – Devjosh

+0

カーソルを使用しようとしましたか?カーソルが不要です – Vikram

+0

「1日離れている」のように「連続」を意味しますか、「行が日付順にソートされているときに隣接していますか?つまり、すべての固有の日付は '日付'の列に一度だけ表示されますか? – gcbenison

答えて

3

それは例のデータではないように、日付を増やすことでソートしたとき、私はその「ID」のフィールド番号連続して行と仮定します。 (そのような列が存在しない場合は作成できます)。

これは、herehereと記載されているテクニックの例です。

1)隣接する「id」値でテーブルを結合します。これにより、隣接する行がペアになります。 [割り当て]フィールドが変更された行を選択します。結果を一時テーブルに格納し、実行中のインデックスも保持します。

SET @idx = 0; 
CREATE TEMPORARY TABLE boundaries 
SELECT 
    (@idx := @idx + 1) AS idx, 
    a1.date AS prev_end, 
    a2.date AS next_start, 
    a1.allocation as allocation 
FROM allocations a1 
JOIN allocations a2 
ON (a2.id = a1.id + 1) 
WHERE a1.allocation != a2.allocation; 

これはあなたの各行の「前の期間に 『割り当て』の値は」「前期間の終わり」、「次の期間の開始」とを有するテーブルを与える:

+------+------------+------------+------------+ 
| idx | prev_end | next_start | allocation | 
+------+------------+------------+------------+ 
| 1 | 2012-01-01 | 2012-01-02 |   0 | 
| 2 | 2012-01-02 | 2012-01-03 |   2 | 
| 3 | 2012-01-05 | 2012-01-06 |   0 | 
+------+------------+------------+------------+ 

2)同じ行の各期間の開始と終了が必要なので、隣接する行を再度結合する必要があります。今idxフィールドに参加して、我々は答えを得る

+------+------------+------------+ 
| idx | prev_end | next_start | 
+------+------------+------------+ 
| 2 | 2012-01-01 | 2012-01-02 | 
| 3 | 2012-01-02 | 2012-01-03 | 
| 4 | 2012-01-05 | 2012-01-06 | 
+------+------------+------------+ 

boundaries状の第2の一時テーブルを作成するが、1より大きいidxフィールドを持っていることによって、これを行う

SELECT 
    boundaries2.next_start AS start, 
    boundaries.prev_end AS end, 
    allocation 
FROM boundaries 
JOIN boundaries2 
USING(idx); 

+------------+------------+------------+ 
| start  | end  | allocation | 
+------------+------------+------------+ 
| 2012-01-02 | 2012-01-02 |   2 | 
| 2012-01-03 | 2012-01-05 |   0 | 
+------------+------------+------------+ 

**注この答えています「内部」期間が正しく取得されますが、開始時にallocation = 0、最後にallocation = 5の2つの「エッジ」期間が欠落しています。それらはUNION句を使用して引き出すことができますが、私はその複雑さなしにコアアイデアを提示したいと思いました。

0

ソリューション:

SELECT a.aDate AS StartDate 
    , MIN(c.aDate) AS EndDate 
    , (datediff(day, a.aDate, MIN(c.aDate)) + 1) AS DayCount 
FROM (
    SELECT x.aDate, x.allocation, COUNT(*) idn FROM table1 x 
    JOIN table1 y ON y.aDate <= x.aDate 
    GROUP BY x.id, x.aDate, x.allocation 
) AS a 
LEFT JOIN (
    SELECT x.aDate, x.allocation, COUNT(*) idn FROM table1 x 
    JOIN table1 y ON y.aDate <= x.aDate 
    GROUP BY x.id, x.aDate, x.allocation 
) AS b ON a.idn = b.idn + 1 AND b.allocation = a.allocation 
LEFT JOIN (
    SELECT x.aDate, x.allocation, COUNT(*) idn FROM table1 x 
    JOIN table1 y ON y.aDate <= x.aDate 
    GROUP BY x.id, x.aDate, x.allocation 
) AS c ON a.idn <= c.idn AND c.allocation = a.allocation 
LEFT JOIN (
    SELECT x.aDate, x.allocation, COUNT(*) idn FROM table1 x 
    JOIN table1 y ON y.aDate <= x.aDate 
    GROUP BY x.id, x.aDate, x.allocation 
) AS d ON c.idn = d.idn - 1 AND d.allocation = c.allocation 
WHERE b.idn IS NULL AND c.idn IS NOT NULL AND d.idn IS NULL AND a.allocation = 0 
GROUP BY a.aDate 

Example

+0

これを実行すると、次のエラーメッセージが表示されます。 メッセージ530、レベル16、状態1、行1 ステートメントが終了しました。ステートメントc – Istari

3

それを行うための一つの方法だろう後。この解決策の要旨は、スタート - とenddatesの両方に依存rownumbersを割り当てることROW_NUMBERウィンドウ関数を使用しAllocation = 0

  • ですべての連続した開始とenddatesのリストを取得するにはCTEを使用し

    • です。
    • 選択両方ROW_NUMBERS等しい1.
    • 使用DATEDIFFレコードだけがDayCount

    SQLステートメント

    ;WITH r AS (
        SELECT StartDate = Date, EndDate = Date 
        FROM YourTable 
        WHERE Allocation = 0 
        UNION ALL 
        SELECT r.StartDate, q.Date 
        FROM r 
          INNER JOIN YourTable q ON DATEDIFF(dd, r.EndDate, q.Date) = 1 
        WHERE q.Allocation = 0   
    ) 
    SELECT [Start Date] = s.StartDate 
         , [End Date ] = s.EndDate 
         , [DayCount] = DATEDIFF(dd, s.StartDate, s.EndDate) + 1 
    FROM (
          SELECT * 
            , rn1 = ROW_NUMBER() OVER (PARTITION BY StartDate ORDER BY EndDate DESC) 
            , rn2 = ROW_NUMBER() OVER (PARTITION BY EndDate ORDER BY StartDate ASC) 
          FROM r   
         ) s 
    WHERE s.rn1 = 1 
         AND s.rn2 = 1 
    OPTION (MAXRECURSION 0) 
    

    テストスクリプト

    ;WITH q (ID, Date, Allocation) AS (
        SELECT * FROM (VALUES 
        (1, '2012-01-01', 0) 
        , (2, '2012-01-02', 2) 
        , (3, '2012-01-03', 0) 
        , (4, '2012-01-04', 0) 
        , (5, '2012-01-05', 0) 
        , (6, '2012-01-06', 5) 
    ) a (a, b, c) 
    ) 
    , r AS (
        SELECT StartDate = Date, EndDate = Date 
        FROM q 
        WHERE Allocation = 0 
        UNION ALL 
        SELECT r.StartDate, q.Date 
        FROM r 
          INNER JOIN q ON DATEDIFF(dd, r.EndDate, q.Date) = 1 
        WHERE q.Allocation = 0   
    ) 
    SELECT s.StartDate, s.EndDate, DATEDIFF(dd, s.StartDate, s.EndDate) + 1 
    FROM (
          SELECT * 
            , rn1 = ROW_NUMBER() OVER (PARTITION BY StartDate ORDER BY EndDate DESC) 
            , rn2 = ROW_NUMBER() OVER (PARTITION BY EndDate ORDER BY StartDate ASC) 
          FROM r   
         ) s 
    WHERE s.rn1 = 1 
         AND s.rn2 = 1 
    OPTION (MAXRECURSION 0) 
    
    を計算しますCTEを有するがROW_NUMBER()なし
  • +0

    @Istariの前に最大再帰100が使い果たされました - エラーメッセージを修正するmaxrecursionオプションが必要です。 –

    1

    別の方法、

    サンプルデータ:

    if object_id('tempdb..#tab') is not null 
        drop table #tab 
    
    create table #tab (id int, date datetime, allocation int) 
    
    insert into #tab 
    select 1, '2012-01-01', 0 union 
    select 2, '2012-01-02', 2 union 
    select 3, '2012-01-03', 0 union 
    select 4, '2012-01-04', 0 union 
    select 5, '2012-01-05', 0 union 
    select 6, '2012-01-06', 5 union 
    select 7, '2012-01-07', 0 union 
    select 8, '2012-01-08', 5 union 
    select 9, '2012-01-09', 0 union 
    select 10, '2012-01-10', 0 
    

    問合せ:

    ;with cte(s_id, e_id, b_id) as (
        select s.id, e.id, b.id 
        from #tab s 
        left join #tab e on dateadd(dd, 1, s.date) = e.date and e.allocation = 0 
        left join #tab b on dateadd(dd, -1, s.date) = b.date and b.allocation = 0 
        where s.allocation = 0 
    ) 
    select ts.date as [start date], te.date as [end date], count(*) as [day count] from (
        select c1.s_id as s, (
         select min(s_id) from cte c2 
         where c2.e_id is null and c2.s_id >= c1.s_id 
        ) as e 
        from cte c1 
        where b_id is null 
    ) t 
    join #tab t1 on t1.id between t.s and t.e and t1.allocation = 0 
    join #tab ts on ts.id = t.s 
    join #tab te on te.id = t.e 
    group by t.s, t.e, ts.date, te.date 
    

    Live example at data.SE。このサンプル・データを使用し

    1

    CREATE TABLE MyTable (ID INT, Date DATETIME, Allocation INT); 
    INSERT INTO MyTable VALUES (1, {d '2012-01-01'}, 0); 
    INSERT INTO MyTable VALUES (2, {d '2012-01-02'}, 2); 
    INSERT INTO MyTable VALUES (3, {d '2012-01-03'}, 0); 
    INSERT INTO MyTable VALUES (4, {d '2012-01-04'}, 0); 
    INSERT INTO MyTable VALUES (5, {d '2012-01-05'}, 0); 
    INSERT INTO MyTable VALUES (6, {d '2012-01-06'}, 5); 
    GO 
    

    この試し:クエリの最初のセクションが割り当て= 0であるすべての行によって固定されている再帰SELECT、ある

    WITH DateGroups (ID, Date, Allocation, SeedID) AS (
        SELECT MyTable.ID, MyTable.Date, MyTable.Allocation, MyTable.ID 
         FROM MyTable 
         LEFT JOIN MyTable Prev ON Prev.Date = DATEADD(d, -1, MyTable.Date) 
              AND Prev.Allocation = 0 
        WHERE Prev.ID IS NULL 
         AND MyTable.Allocation = 0 
        UNION ALL 
        SELECT MyTable.ID, MyTable.Date, MyTable.Allocation, DateGroups.SeedID 
         FROM MyTable 
         JOIN DateGroups ON MyTable.Date = DATEADD(d, 1, DateGroups.Date) 
        WHERE MyTable.Allocation = 0 
    
    ), StartDates (ID, StartDate, DayCount) AS (
        SELECT SeedID, MIN(Date), COUNT(ID) 
         FROM DateGroups 
        GROUP BY SeedID 
    
    ), EndDates (ID, EndDate) AS (
        SELECT SeedID, MAX(Date) 
         FROM DateGroups 
        GROUP BY SeedID 
    
    ) 
    SELECT StartDates.StartDate, EndDates.EndDate, StartDates.DayCount 
        FROM StartDates 
        JOIN EndDates ON StartDates.ID = EndDates.ID; 
    

    、およびその前日は存在しないか、または割り当て!= 0になります。これは、戻りたい期間の開始日であるID:1と3を効果的に返します。

    この同じクエリの再帰部分は、アンカー行から開始し、allocation = 0も持つすべての後続の日付を検出します。SeedIDは、すべての反復を通じて固定IDを追跡します。次のサブクエリは、各SeedIDのためのすべての開始日をフィルタリングするために、単純なGROUP BYを使用し、また、日カウント

    ID   Date     Allocation SeedID 
    ----------- ----------------------- ----------- ----------- 
    1   2012-01-01 00:00:00.000 0   1 
    3   2012-01-03 00:00:00.000 0   3 
    4   2012-01-04 00:00:00.000 0   3 
    5   2012-01-05 00:00:00.000 0   3 
    

    結果は、これまでのところ、このです。

    最後のサブクエリは終了日で同じことを行いますが、今回はすでにこのように日数が必要ではありません。

    最後のSELECTクエリは、これらの2つを結合して開始日と終了日を結合し、それらを日数とともに返します。

    1

    あなたのために試してみてください ここであなたのDATEのSDATEはあなたのテーブルと同じです。この回答で

    SELECT SDATE, 
    CASE WHEN (SELECT COUNT(*)-1 FROM TABLE1 WHERE ID BETWEEN TBL1.ID AND (SELECT MIN(ID) FROM TABLE1 WHERE ID > TBL1.ID AND ALLOCATION!=0)) >0 THEN(
    CASE WHEN (SELECT SDATE FROM TABLE1 WHERE ID =(SELECT MAX(ID) FROM TABLE1 WHERE ID >TBL1.ID AND ID<(SELECT MIN(ID) FROM TABLE1 WHERE ID > TBL1.ID AND ALLOCATION!=0))) IS NULL THEN SDATE 
    ELSE (SELECT SDATE FROM TABLE1 WHERE ID =(SELECT MAX(ID) FROM TABLE1 WHERE ID >TBL1.ID AND ID<(SELECT MIN(ID) FROM TABLE1 WHERE ID > TBL1.ID AND ALLOCATION!=0))) END 
    )ELSE (SELECT SDATE FROM TABLE1 WHERE ID = (SELECT MAX(ID) FROM TABLE1 WHERE ID > TBL1.ID))END AS EDATE 
    ,CASE WHEN (SELECT COUNT(*)-1 FROM TABLE1 WHERE ID BETWEEN TBL1.ID AND (SELECT MIN(ID) FROM TABLE1 WHERE ID > TBL1.ID AND ALLOCATION!=0)) <0 THEN 
    (SELECT COUNT(*) FROM TABLE1 WHERE ID BETWEEN TBL1.ID AND (SELECT MAX(ID) FROM TABLE1 WHERE ID > TBL1.ID)) ELSE 
    (SELECT COUNT(*)-1 FROM TABLE1 WHERE ID BETWEEN TBL1.ID AND (SELECT MIN(ID) FROM TABLE1 WHERE ID > TBL1.ID AND ALLOCATION!=0)) END AS DAYCOUNT 
    FROM TABLE1 TBL1 WHERE ALLOCATION = 0 
    AND (((SELECT ALLOCATION FROM TABLE1 WHERE ID=(SELECT MAX(ID) FROM TABLE1 WHERE ID < TBL1.ID))<> 0) OR (SELECT MAX(ID) FROM TABLE1 WHERE ID < TBL1.ID)IS NULL); 
    
    関連する問題