13

テーブル C を作成するには、テーブル A とテーブル B を結合する必要があります。

テーブル A とテーブル B には、ID のステータス フラグが格納されます。ステータス フラグ (A_Flag および B_Flag) は随時変更される可能性があるため、1 つの ID に複数の行を含めることができます。これは、ID のステータスの履歴を表します。特定の ID のフラグは、互いに独立して変更できます。その結果、テーブル A の 1 つの行がテーブル B の複数の行に属したり、その逆になる場合があります。

結果のテーブル (テーブル C) は、ID の有効期間 (2008 年 1 月 1 日から 2008 年 18 月 18 日) 内のすべての日付をカバーする一意の日付範囲のリストと、各日付範囲の A_Flag および B_Flag 値である必要があります。

実際のテーブルには数百の ID が含まれており、各 ID はテーブルごとにさまざまな数の行を持っています。

最終結果を達成するために、SQL および SAS ツールにアクセスできます。

Source - Table A
ID  Start           End     A_Flag
1   01/01/2008  23/03/2008  1
1   23/03/2008  15/06/2008  0
1   15/06/2008  18/08/2008  1

Source - Table B
ID  Start           End     B_Flag
1   19/01/2008  17/02/2008  1
1   17/02/2008  15/06/2008  0
1   15/06/2008  18/08/2008  1

Result - Table C
ID  Start           End  A_Flag B_Flag
1   01/01/2008  19/01/2008  1   0
1   19/01/2008  17/02/2008  1   1
1   17/02/2008  23/03/2008  1   0
1   23/03/2008  15/06/2008  0   0
1   15/06/2008  18/08/2008  1   1
4

4 に答える 4

4

lag(SQL Server 2012、Oracle、Postgres、DB2)という関数があると仮定して、SQL でこれを解決します。相関サブクエリでも同じ効果が得られます。

アイデアは、すべての異なる期間を取得することです。次に、元のテーブルに結合してフラグを取得します。

コードのアップロードに問題がありますが、ほとんどのコードを取得できます。unionただし、1 つの列で 4 つの日付の(ではない)を実行することによって作成する開始終了で始まりますunion all。日付として a.start を選択します。これは、a.end、b.start、および b.end と結合されます。

with driver as (
    select thedate as start, lag(thedate) over (order by thedate) as end
    from startends
   ) 

select startdate, enddate, a.flag, b.flag
from  driver left outer join
     a
     on a.start >= driver.start and a.end <= driver.end left outer join
     b
     on b.start >= driver.start and b.end <= driver.end
于 2013-02-25T20:36:46.790 に答える
4

あなたが提起した問題は、非標準の拡張機能を使用せずに、1 つの SQL ステートメントで解決できます。

認識すべき最も重要なことは、開始と終了のペアの日付はそれぞれ、フラグのペアが true になる期間の潜在的な開始点または終了点を表しているということです。実際には、ある日付が「開始」であり、別の日付が「終了」であることは問題ではありません。任意の日付は、前の期間を終了し、別の期間を開始する両方を行う時間区切り文字です。一連の最小時間間隔を作成し、それらをテーブルに結合して、各間隔中に取得されたフラグを見つけます。

あなたの例 (および解決策) をCanonical SQLページに追加しました。詳細な議論については、そこを参照してください。SOに公平を期すために、クエリ自体は次のとおりです

with D (ID, bound) as (
    select   ID 
       , case T when 's' then StartDate else EndDate end as bound
    from  (
    select ID, StartDate, EndDate from so.A 
    UNION
    select ID, StartDate, EndDate from so.B
    ) as U
    cross join (select 's' as T union select 'e') as T
)
select P.*, a.Flag as A_Flag, b.Flag as B_Flag
from (
    select s.ID, s.bound as StartDate, min(e.bound) as EndDate
    from D as s join D as e 
    on s.ID = e.ID 
    and s.bound < e.bound
    group by s.ID, s.bound
) as P
left join so.A as a
on  P.ID = a.ID 
and a.StartDate <= P.StartDate and P.EndDate <= a.EndDate
left join so.B as b
on  P.ID = b.ID 
and b.StartDate <= P.StartDate and P.EndDate <= b.EndDate
order by P.ID, P.StartDate, P.EndDate
于 2013-03-30T21:15:42.187 に答える
0

これに対するSASソリューションの1つは、部分的な結合を実行してから、データステップで必要な追加の行を作成することです。これは、tableAにすべての可能なレコードがあると仮定して機能するはずです。そうでない場合(tableBがtableAの前に開始できる場合)、その可能性を検討するために追加のロジックが必要になる場合があります(first.idおよびstart gt b_startの場合)。サンプルデータに存在しない問題には追加のロジックが必要になる場合もあります。今朝はあまり時間がなく、サンプルデータの場合以外はデバッグしませんでしたが、概念は明らかです。

data tableA;
informat start end DDMMYY10.;
format start end DATE9.;
input ID  Start           End     A_Flag;
datalines;
1   01/01/2008  23/03/2008  1
1   23/03/2008  15/06/2008  0
1   15/06/2008  18/08/2008  1
;;;;
run;

data tableB;
informat start end DDMMYY10.;
format start end DATE9.;
input ID  Start           End     B_Flag;
datalines;
1   19/01/2008  17/02/2008  1
1   17/02/2008  15/06/2008  0
1   15/06/2008  18/08/2008  1
;;;;
run;


proc sql;
create table c_temp as 
    select * from tableA A 
        left join (select id, start as b_start, end as b_end, b_flag from tableB) B
    on A.Id = B.id
    where (A.start le B.b_start and A.end gt B.b_start) or (A.start lt B.b_end and A.end ge B.b_end)
    order by A.ID, A.start, B.b_start;
quit;

data tableC;
set c_temp;
by id start;
retain b_flag_ret;
format start_fin end_fin DATE9.;
if first.id then b_flag_ret=0;
do until (start=end);
    if (start lt b_start) and first.start then do;
        start_fin=start;
        end_fin=b_start;
        a_flag_fin=a_flag;
        b_flag_fin=b_flag_ret;
        output;
        start=b_start;
    end;    
    else do; *start=b_start;
            start_fin=ifn(start ge b_start, start, b_start);
            end_fin = ifn(b_end le end, b_end, end);
            a_flag_fin=a_flag;
            b_flag_fin=b_flag;
            output;
            start=end; *leave the loop as there will be a later row that matches;
    end;
end;
run;
于 2013-02-26T16:12:36.263 に答える
0

シフトとオフセットを使用したこのタイプの順次処理は、SAS DATA ステップが役立つ状況の 1 つです。この答えは簡単ではありませんが、実行できる SQL を使用するよりも簡単ですが、この順次処理を念頭に置いて設計されていません。

さらに、DATA ステップに基づくソリューションは非常に効率的である傾向があります。これは、理論上は O(n log n) の時間で実行されますが、実際には O(n) に近く、一定のスペースで実行されます。

最初の 2 つの DATA ステップは、Joe の回答からわずかに変更されたデータをロードして、複数の ID を持ち (それ以外の場合、構文ははるかに簡単です)、いくつかのコーナー ケース、つまり初期状態を特定できない ID を追加するだけです。

data tableA;
informat start end DDMMYY10.;
format start end DATE9.;
input ID  Start           End     A_Flag;
datalines;
1   01/01/2008  23/03/2008  1
2   23/03/2008  15/06/2008  0
2   15/06/2008  18/08/2008  1
;;;;
run;

data tableB;
informat start end DDMMYY10.;
format start end DATE9.;
input ID  Start           End     B_Flag;
datalines;
1   19/01/2008  17/02/2008  1
2   17/02/2008  15/06/2008  0
4   15/06/2008  18/08/2008  1
;;;;
run;

次のデータ ステップでは、各 ID とフラグの最初の変更が検出され、初期値が検出された値の逆に設定されます。

/* Get initial state by inverting first change */
data firstA;
    set tableA;
    by id;
    if first.id;
    A_Flag = ~A_Flag;
run;

data firstB;
    set tableB;
    by id;
    if first.id;
    B_Flag = ~B_Flag;
run;
data first;
    merge firstA firstB;
    by id;
run;

次のデータ ステップでは、人工的な「最初の」テーブルを他の 2 つのテーブルとマージし、既知の最後の状態を保持し、人工的な最初の行を破棄します。

data tableAB (drop=lastA lastB);
   set first tableA tableB;
   by id start;
   retain lastA lastB lastStart;
   if A_flag = . and ~first.id then A_flag = lastA;
   else lastA = A_flag;
   if B_flag = . and ~first.id then B_flag = lastB;
   else lastB = B_flag;
   if ~first.id;  /* drop artificial first row per id */
run;

上記の手順でほとんどすべてのことができます。唯一のバグは、終了日が元の行からコピーされるため、間違っていることです。これを修正するには、最終行でない限り、次の開始を各行の最後にコピーします。最も簡単な方法は、各 ID を逆順に並べ替え、1 つのレコードをさかのぼって、最後にもう一度昇順に並べ替えることです。

/* sort descending to ... */
proc sort data=tableAB;
   by id descending start;
run;
/* ... copy next start to this row's "end" field if not final */
data tableAB(drop=nextStart);
   set tableAB;
   by id descending start;
   nextStart=lag(start);
   if ~first.id then end=nextStart;
run;

proc sort data=tableAB;
   by id start;
run;
于 2013-03-05T03:17:06.533 に答える