这种带有移位和偏移的顺序处理是 SAS DATA 步大放异彩的情况之一。并不是说这个答案很简单,而是它比使用 SQL更简单,可以做到,但在设计时并未考虑到这种顺序处理。
此外,基于 DATA 步的解决方案往往非常有效。这个在理论上运行时间 O(n log n),但在实践中更接近 O(n),并且在恒定空间中。
前两个 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 和 flag 找到第一个修改,并将初始值设置为它找到的相反值。
/* 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;
下一个数据步骤将人工“第一个”表与其他两个表合并,保留最后一个已知状态并丢弃人工初始行。
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进行反向排序,回溯一条记录,最后再升序排序。
/* 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;