2016-08-20 11 views
0

私は、業界のタスクでよく知られている問題を解決して、一定の期間連続して活動しているCustIDをIDで識別し、契約間の休憩をほとんど許しています。SQL Server:継続的な登録でCustを見つけよう

私はこの日にアクティブな場合は、以下のスニペットのように、以下のスニペットのように最初に部分的に行列テーブルを作成しました。これは、契約が重複する可能性があるため、これを行う唯一の信頼できる方法です。

私はcontアクティビティのCustIDが1/0であるかどうかを確認する必要があります。これを追跡する方法を説明します。私の例では3日間の休憩がありますが、それらの日が次々と続くことを確かめてください。

私はこれをうまくやり遂げることができますか、あなたの助けとリードに感謝します。私はいくつかの例を見ましたが、彼らはSASでやったので理解しにくいです。

declare @maxBreak int = 3 -- 3 days max allowed for continuse contract 
declare @PeriodStart date = '2015-1-11', @PeriodEnd date = '2015-1-19'; 

;with matrix_dd as 
(
    select * 
    from 
     (select 111 CustID, '2015-1-11' dd, 1 Active union 
     select 111 CustID, '2015-1-12' dd, 0 Active union 
     select 111 CustID, '2015-1-13' dd, 0 Active union 
     select 111 CustID, '2015-1-14' dd, 0 Active union 
     select 111 CustID, '2015-1-15' dd, 1 Active union 
     select 111 CustID, '2015-1-16' dd, 1 Active union 
     select 111 CustID, '2015-1-17' dd, 1 Active union 
     select 111 CustID, '2015-1-18' dd, 1 Active union 
     select 111 CustID, '2015-1-19' dd, 0 Active union 
     select 111 CustID, '2015-1-20' dd, 0 Active) a 
) 
select * 
from matrix_dd 

ベスト M

+0

実際には以下のようなことが成立しました。 (アクティブ、3)OVER(パーティションCustID ORDER BY dd)、 SUM(アクティブ)OVER(PARTITION BY CustID ORDER BY dd)= LAG(アクティブ、3)OVER(CustID BY ORDER BY dd)次に '3 days off' else '' end YN alpha..matrix_dd CustID、ddによる注文 –

答えて

1

このソリューションは、アクティブな範囲を計算し、どのくらいの最後の間隔が終了したので、それがされていますブレークの:

declare @maxBreak int = 3 -- 3 days max allowed for continuse contract 
declare @PeriodStart date = '2015-1-11', @PeriodEnd date = '2015-1-19'; 

with matrix_dd as 
(
    select * from (values 
     (111, '2015-1-11', 1), 
     (111, '2015-1-12', 0), 
     (111, '2015-1-13', 0), 
     (111, '2015-1-14', 0), 
     (111, '2015-1-15', 1), 
     (111, '2015-1-16', 1), 
     (111, '2015-1-17', 1), 
     (111, '2015-1-18', 1), 
     (111, '2015-1-19', 0), 
     (111, '2015-1-20', 0) 
    ) as x(CustID, dd, Active) 
), active_with_groups as (
    select *, 
     row_number() over (partition by CustID order by dd) - 
      datediff(day, '2000-01-01', dd) as gid 
    from matrix_dd 
    where active = 1 
     and dd between @PeriodStart and @PeriodEnd 
), islands as (
    select CustId, min(dd) as islandStart, max(dd) as islandEnd 
    from active_with_groups 
    group by CustID, gid 
), islands_with_gaps as (
    select *, 
     datediff(
      day, 
      lag(islandEnd, 1, islandStart) 
       over (partition by CustID order by islandStart), 
      islandStart 
     ) - 1 as [break] 
    from islands 
) 
select * 
from islands_with_gaps 
where [break] >= @maxBreak 
order by islandStart 

はのは、それを打破してみましょう。 "active_with_groups"共通テーブル式(CTE)では、私は、日付をdatediff()を使用して同じ関係を持つ整数に変換するだけです。どうして?整数はこの問題に対してより簡単に処理できます。連続したシーケンスを取得してからそれとdatediff()の値を取得するには、row_number()も使用しています。重要な観察は、でもの日が連続して上がらなければ、その違いはまあまあではないということです。同様に、日付と連続して上がった場合、その差は同じになります。したがって、この値は連続した範囲にある値のグループ識別子として使用できます。

次に、私たちはグループ識別子を使ってグループ化します(あなたはそれが来るのを見たことはありませんでした!)。これにより、各区間の開始と終了がわかります。何も非常に巧妙なここで起こっている。

次のステップは、最後の間隔が終了し、現在の間隔が開始されたときの間隔を計算することです。このために、lag()関数の簡単な呼び出しを使用します。ここで唯一留意すべき点は、lag()関数が最初の間隔の場合にデフォルト値islandStartを出力するように選択したことです。これは、デフォルトと同じように簡単にできます(NULL値を出力する原因となります)。

最後に、指定したしきい値を超える間隔のある間隔を探します。

+0

大きな感謝とベン!!!! –

+0

Amazingg !!!コメントありがとうございます。 –

+0

私は今元のデータに戻ってきました。私は毎日マトリックス表なしでこれをまっすぐにする方法があると思います。これは私の生データです: (111,1230、 '2014-12-11'、'2015-1-11 ')、 (111,1231、'2015-1-15'、 (CustID、ContractID、StartDD、EndDD) 'として、 (111,1232、' 2015-3-22 '、' 2015-4-1 ') )' –

1

ベンの回答に似ています。私はあなたのすべての日付がデータに表されていると仮定しています。だから、本当に私たちはゼロの実行allが特に一般的ではありません使用して3

with inactive_runs as (
    select 
     CustID, 
     row_number() over (partition by CustID order by dd) 
      - datediff(day, min(dd) over (partition by CustID), dd) as grp 
    from matrix_dd 
    where Active = 0 
) 
select distinct CustID from matrix_dd m 
where 3 >= all (
    select count(*) from inactive_runs ir 
    where ir.CustID = m.CustID 
    group by grp 
); 

http://rextester.com/AHI22250

よりも長いがないことを確認する必要があります。代わりの方法は次のとおりです。

... 
with inactive_runs as (
    select 
     CustID, dd, /* <-- had to add dd */ 
     row_number() over (partition by CustID order by dd) 
      - datediff(day, min(dd) over (partition by CustID), dd) as grp 
    from #matrix_dd 
    where Active = 0 
) 
select distinct CustID from matrix_dd m 
where not exists (
    select 1 from inactive_runs ir 
    where ir.CustID = m.CustID 
    group by grp 
    having datediff(day, min(dd), max(dd)) > 2 
); 

私は上記のご意見を拝見しました。私はそれがあなたが毎日のために単一の行を持っているという私の疑惑を確認すると思う。新しいバージョンのSQL Serverをお持ちの場合は、前の3行を合計するだけです。長さが可変である場合は残念ながら、あなたはウィンドウサイズの変数を使用することができません。

with cust as (
    select 
     CustID, 
     case when 
     sum(case when Active = 0 then 1 end) over (
       partition by CustID 
       order by dd 
       rows between 3 preceding and current row 
      ) = 4 then 1 
     end as isBrk 
    from matrix_dd 
) 
select CustID 
from cust 
group by CustID 
having count(isBrk) = 0; 

編集:「プレマトリックス」のデータとあなたのコメントに基づいて

フォーマット、はい、これは簡単なクエリです。その時点で、前の終了日と現在の行の開始日を調べているだけです。

with data as (
    select * from (
     values (111, 1230, '2014-12-11', '2015-01-11'), 
       (111, 1231, '2015-01-15', '2015-01-18'), 
       (111, 1232, '2015-03-22', '2015-04-01') 
     ) as t (CustID, ContractID, StartDD, EndDD) 
), gaps as (
    select 
     CustID, 
     datediff(day, 
      lag(EndDD, 1, StartDD) over (partition by CustID order by StartDD), 
      StartDD 
     ) as days 
    from data 
) 
select CustID 
from gaps 
group by CustID; 
having max(days) <= 3; 
関連する問題