我需要连接表A和表B来创建表C.
表A和表B存储ID的状态标志。状态标志(A_Flag和B_Flag)可以不时更改,因此一个ID可以包含多行,表示ID状态的历史记录。特定ID的标志可以彼此独立地改变,这可以导致表A中的一行属于表B中的多行,反之亦然。
结果表(表C)需要是唯一日期范围列表,其中包含ID生命周期中的每个日期(01/01 / 2008-18 / 08/2008),以及每个日期范围的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
答案 0 :(得分:3)
我将在SQL中解决这个问题,假设您有一个名为lag
的函数(SQL Server 2012,Oracle,Postgres,DB2)。您可以使用相关子查询获得相同的效果。
想法是获得所有不同的时间段。然后加入到原始表中以获取标志。
我无法上传代码,但可以获得大部分代码。但是,它从开始结束开始,您通过在一列中执行四个日期的union
(而非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
答案 1 :(得分:3)
您提出的问题可以通过一个没有非标准扩展的SQL语句来解决。
最重要的是要认识到,开始 - 结束对中的日期分别代表标志对为真的时间跨度的潜在起点或终点。实际上,一个日期是“开始”而另一个日期是“结束”并不重要;任何日期都是两者的时间分隔符:它结束前一个时间段并开始另一个时间段。构造一组最小时间间隔,并将它们连接到表以查找在每个间隔期间获得的标志。
我将您的示例(和解决方案)添加到我的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
答案 2 :(得分:0)
一种可能的SAS解决方案是执行部分连接,然后在数据步骤中创建必要的其他行。假设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;
答案 3 :(得分:0)
这种带有移位和偏移的顺序处理是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;