5

次のようなテーブルがあるとします。

declare @periods table (
    s date, 
    e date,
    t tinyint
);

開始日順に並べられたギャップのない日付間隔

insert into @periods values
('2013-01-01' , '2013-01-02', 3),
('2013-01-02' , '2013-01-04', 1),
('2013-01-04' , '2013-01-05', 1),
('2013-01-05' , '2013-01-06', 2),
('2013-01-06' , '2013-01-07', 2),
('2013-01-07' , '2013-01-08', 2),
('2013-01-08' , '2013-01-09', 1);

すべての日付間隔には異なるタイプ (t) があります。

他のタイプの間隔によって分割されていない同じタイプの日付間隔を組み合わせる必要があります (すべての間隔を開始日順に並べます)。

したがって、結果テーブルは次のようになります。

      s     |      e     |  t
------------|------------|-----
 2013-01-01 | 2013-01-02 |  3
 2013-01-02 | 2013-01-05 |  1
 2013-01-05 | 2013-01-08 |  2
 2013-01-08 | 2013-01-09 |  1

カーソルなしでこれを行う方法はありますか?


私は1つの実用的な解決策を持っています:

declare @periods table (
    s datetime primary key clustered, 
    e datetime,
    t tinyint,
    period_number int   
);

insert into @periods (s, e, t) values
('2013-01-01' , '2013-01-02', 3),
('2013-01-02' , '2013-01-04', 1),
('2013-01-04' , '2013-01-05', 1),
('2013-01-05' , '2013-01-06', 2),
('2013-01-06' , '2013-01-07', 2),
('2013-01-07' , '2013-01-08', 2),
('2013-01-08' , '2013-01-09', 1);

declare @t tinyint = null;  
declare @PeriodNumber int = 0;
declare @anchor date;

update @periods
    set  period_number = @PeriodNumber, 
    @PeriodNumber = case
                        when @t <> t
                            then  @PeriodNumber + 1
                        else
                            @PeriodNumber
                    end,
    @t = t,
    @anchor = s
option (maxdop 1);

select 
    s = min(s),
    e = max(e),
    t = min(t)
from 
    @periods    
group by 
    period_number
order by 
    s;

しかし、UPDATEステートメントのそのような動作に頼ることができるかどうか疑問に思いますか?

SQL Server 2008 R2 を使用しています。


編集:

ダニエルとこの記事に感謝します: http://www.sqlservercentral.com/articles/T-SQL/68467/

上記のソリューションで見落とされていた 3 つの重要な点が見つかりました。

  1. テーブルにクラスター化インデックスが必要です
  2. クラスター化された列のアンカー変数と呼び出しが必要です
  3. 更新ステートメントは 1 つのプロセッサで実行する必要があります。つまり、並列処理は使用しません。

これらのルールに従って、上記のソリューションを変更しました。

4

5 に答える 5

5

あなたの範囲は連続的であるため、問題は本質的にの問題になります。同じ値を持つ異なるシーケンスを区別するのに役立つ基準がある場合は、tその基準を使用してすべての行をグループ化し、すべてのグループを取得することができますMIN(s), MAX(e)

このような基準を取得する1つの方法は、2つのROW_NUMBER呼び出しを使用することです。次のクエリについて考えてみます。

SELECT
  *,
  rnk1 = ROW_NUMBER() OVER (               ORDER BY s),
  rnk2 = ROW_NUMBER() OVER (PARTITION BY t ORDER BY s)
FROM @periods
;

あなたの例では、次のセットを返します。

s           e           t   rnk1  rnk2
----------  ----------  --  ----  ----
2013-01-01  2013-01-02  3   1     1
2013-01-02  2013-01-04  1   2     1
2013-01-04  2013-01-05  1   3     2
2013-01-05  2013-01-06  2   4     1
2013-01-06  2013-01-07  2   5     2
2013-01-07  2013-01-08  2   6     3
2013-01-08  2013-01-09  1   7     3

rnk1とランキングの興味深い点rnk2は、一方を他方から減算すると、と一緒に、同じ行のすべての異なるシーケンスをt一意に識別する値が得られることです。t

s           e           t   rnk1  rnk2  rnk1 - rnk2
----------  ----------  --  ----  ----  -----------
2013-01-01  2013-01-02  3   1     1     0
2013-01-02  2013-01-04  1   2     1     1
2013-01-04  2013-01-05  1   3     2     1
2013-01-05  2013-01-06  2   4     1     3
2013-01-06  2013-01-07  2   5     2     3
2013-01-07  2013-01-08  2   6     3     3
2013-01-08  2013-01-09  1   7     3     4

それを知っていると、グループ化と集約を簡単に適用できます。最終的なクエリは次のようになります。

WITH partitioned AS (
  SELECT
    *,
    g = ROW_NUMBER() OVER (               ORDER BY s)
      - ROW_NUMBER() OVER (PARTITION BY t ORDER BY s)
  FROM @periods
)
SELECT
  s = MIN(s),
  e = MAX(e),
  t
FROM partitioned
GROUP BY
  t,
  g
;

必要に応じて、SQLFiddleでこのソリューションを試すことができます。

于 2013-02-24T21:44:05.663 に答える
2

更新とカーソルを回避するためのおそらく解決策は、共通のテーブル式を使用する必要があります...

このような...

    declare @periods table (
        s date, 
        e date,
        t tinyint
    );


    insert into @periods values
    ('2013-01-01' , '2013-01-02', 3),
    ('2013-01-02' , '2013-01-04', 1),
    ('2013-01-04' , '2013-01-05', 1),
    ('2013-01-05' , '2013-01-06', 2),
    ('2013-01-06' , '2013-01-07', 2),
    ('2013-01-07' , '2013-01-08', 2),
    ('2013-01-08' , '2013-01-09', 1);

    with cte as ( select 0 as n
                        ,p.s as s
                        ,p.e as e
                        ,p.t
                        ,case when p2.s is null then 1 else 0 end fl_s
                        ,case when p3.e is null then 1 else 0 end fl_e
                  from @periods p
                  left outer join @periods p2
                  on p2.e = p.s
                  and p2.t = p.t
                  left outer join @periods p3
                  on p3.s = p.e
                  and p3.t = p.t

                  union all 

                  select  n+1 as n
                        , p2.s as s
                        , p.e as e
                        ,p.t
                        ,case when not exists(select * from @periods p3 where p3.e =p2.s and p3.t=p2.t) then 1 else 0 end as fl_s
                        ,p.fl_e as fl_e
                  from cte p
                  inner join @periods p2
                  on p2.e = p.s
                  and p2.t = p.t
                  where p.fl_s = 0

                  union all 

                  select  n+1 as n
                        , p.s as s
                        , p2.e as e
                        ,p.t
                        ,p.fl_s as fl_s
                        ,case when not exists(select * from @periods p3 where p3.s =p2.e and p3.t=p2.t) then 1 else 0 end as fl_e
                  from cte p
                  inner join @periods p2
                  on p2.s = p.e
                  and p2.t = p.t
                  where p.fl_s = 1
                  and p.fl_e = 0
    )
    ,result as (select s,e,t,COUNT(*) as count_lines
                 from cte
                 where fl_e = 1
                 and fl_s = 1
                 group by s,e,t
                 )
    select * from result
    option(maxrecursion 0)

達成された結果セット...

    s           e           t   count_lines
    2013-01-01  2013-01-02  3   1
    2013-01-02  2013-01-05  1   2
    2013-01-05  2013-01-08  2   3
    2013-01-08  2013-01-09  1   1
于 2013-02-19T19:27:13.657 に答える
2

これはどう?

declare @periods table (
    s datetime primary key, 
    e datetime,
    t tinyint,
    s2 datetime
);

insert into @periods (s, e, t) values
('2013-01-01' , '2013-01-02', 3),
('2013-01-02' , '2013-01-04', 1),
('2013-01-04' , '2013-01-05', 1),
('2013-01-05' , '2013-01-06', 2),
('2013-01-06' , '2013-01-07', 2),
('2013-01-07' , '2013-01-08', 2),
('2013-01-08' , '2013-01-09', 1);

update @periods set s2 = s;

while @@ROWCOUNT > 0
begin
    update p2 SET s2=p1.s
    from @periods p1
    join @PERIODS P2 ON p2.t = p1.t AND p2.s2 = p1.e;
end

select s2 as s, max(e) as e, min(t) as t
from @periods
group by s2
order by s2;
于 2013-02-19T18:54:47.443 に答える
1

万歳!私は自分に合った解決策を見つけました。それは反復なしで行われます

with cte1 as (   
    select s, t  from @periods
    union all
    select max(e), null from @periods
),
cte2 as (
    select rn = row_number() over(order by s), s, t from cte1   
),
cte3 as (
    select 
        rn = row_number() over(order by a.rn),
        a.s,
        a.t 
    from 
        cte2 a 
        left join cte2 b on a.rn = b.rn + 1 and a.t = b.t
    where
        b.rn is null 
)
select 
    s = a.s, 
    e = b.s, 
    a.t  
from 
    cte3 a 
    inner join cte3 b on b.rn = a.rn + 1;

あなたの考えと解決策を共有してくれてありがとう!


詳細:

cte1 は、日付のチェーンとその後の型を返します

s          t
---------- ----
2013-01-01 3
2013-01-02 1
2013-01-04 1
2013-01-05 2
2013-01-06 2
2013-01-07 2
2013-01-08 1
2013-01-09 NULL  -- there is no type *after* the last date

ct2 は、上記の結果に行番号を追加するだけです:

 rn       s       t
---- ----------  ----
 1    2013-01-01  3
 2    2013-01-02  1
 3    2013-01-04  1
 4    2013-01-05  2
 5    2013-01-06  2
 6    2013-01-07  2
 7    2013-01-08  1
 8    2013-01-09  NULL

where条件なしで cte3 のクエリからすべてのフィールドを出力すると、次の結果が得られます。

select * from cte2 a left join cte2 b on a.rn = b.rn + 1 and a.t = b.t;

rn       s        t       rn      s          t
---- ----------  ----    ------ ----------  ----
1    2013-01-01  3       NULL   NULL        NULL
2    2013-01-02  1       NULL   NULL        NULL
3    2013-01-04  1       2      2013-01-02  1
4    2013-01-05  2       NULL   NULL        NULL
5    2013-01-06  2       4      2013-01-05  2
6    2013-01-07  2       5      2013-01-06  2
7    2013-01-08  1       NULL   NULL        NULL
8    2013-01-09  NULL    NULL   NULL        NULL

タイプが繰り返される日付については、結果の右側に値があります。したがって、右側に値が存在するすべての行を削除できます。

したがって、cte3 は次のように返します。

rn    s           t
----- ----------  ----
1     2013-01-01  3
2     2013-01-02  1
3     2013-01-05  2
4     2013-01-08  1
5     2013-01-09  NULL

一部の行が削除されたため、rn シーケンスにいくつかのギャップがあることに注意してください。したがって、番号を付け直す必要があります。

ここからは、日付をピリオドに変換するだけです。

select 
    s = a.s, 
    e = b.s, 
    a.t  
from 
    cte3 a 
    inner join cte3 b on b.rn = a.rn + 1;

必要な結果が得られました。

s          e          t
---------- ---------- ----
2013-01-01 2013-01-02 3
2013-01-02 2013-01-05 1
2013-01-05 2013-01-08 2
2013-01-08 2013-01-09 1
于 2013-02-20T07:17:04.710 に答える
0

これは、テーブルに異なるデータを使用したソリューションです..

    declare @periods table (
        s datetime primary key, 
        e datetime,
        t tinyint,
        period_number int   
    );

    insert into @periods (s, e, t) values
    ('2013-01-01' , '2013-01-02', 3),
    ('2013-01-02' , '2013-01-04', 1),
    ('2013-01-04' , '2013-01-05', 1),
    ('2013-01-05' , '2013-01-06', 2),
    ('2013-01-09' , '2013-01-10', 2),
    ('2013-01-10' , '2013-01-11', 1);

    declare @t tinyint = null;  
    declare @PeriodNumber int = 0;

    update @periods
        set  period_number = @PeriodNumber, 
        @PeriodNumber = case
                            when @t <> t
                                then  @PeriodNumber + 1
                            else
                                @PeriodNumber
                        end,
        @t = t;

    select 
        s = min(s),
        e = max(e),
        t = min(t)
    from 
        @periods    
    group by 
        period_number
    order by 
        s;

の間にギャップがある場所

 ('2013-01-05' , '2013-01-06', 2),
 --and
 ('2013-01-09' , '2013-01-10', 2),

あなたのソリューションの結果セットは..

    s           e           t
    2013-01-01  2013-01-02  3
    2013-01-02  2013-01-05  1
    2013-01-05  2013-01-10  2
    2013-01-10  2013-01-11  1

このような結果セットは見られませんでした..??

    s           e           t
    2013-01-01  2013-01-02  3
    2013-01-02  2013-01-05  1
    2013-01-05  2013-01-06  2
    2013-01-09  2013-01-10  2
    2013-01-10  2013-01-11  1

多分私はあなたの問題のルールを誤解しました...

于 2013-02-19T19:18:53.773 に答える