根据不匹配的日期字段连接两个表

时间:2019-02-19 11:42:01

标签: sql join sas

我想按日期联接两个表(A和B)。由于日期不一定在表之间整齐地排列,这使情况变得复杂。也就是说,表B中的日期可能不在表A中,也可能不在表A中。

我怀疑必须有一种简单的方法可以在SQL / SAS中完成此操作,但是我对这两者都是陌生的,并且不知道如何做。如果有人可以指出具体的解决方案,示例或可以用来解决此问题的功能,我将感到非常高兴。我在下面创建了一个虚拟案例进行说明。

这是一个表(对于一个参与者)的外观示例:

Table A
-------------------------------------------+
participant start       end
-------------------------------------------+
101         1-1-2010    26-4-2010
101         27-4-2010   2-10-2014
101         3-10-2014   4-1-2015
101         5-1-2015    31-8-2015
101         1-9-2015    12-10-2016
101         13-10-2016  31-12-2018

下面是一个需要连接到表A的表B的示例。如您所见,对于简单的左联接,日期相差太大:

Table B
---------------------------------------------------------+
participant start_date  end_date    Content
---------------------------------------------------------+
101         1-1-2012    31-8-2012   A
101         1-9-2012    31-8-2013   B
101         1-9-2013    31-8-2014   C
101         1-9-2014    2-10-2014   D
101         3-10-2014   31-8-2015   E
101         1-9-2015    31-1-2016   F
101         1-9-2015    31-1-2016   F

联接表C的想法是表A的每一行都由表B的数据通知。我想从表B的范围内选择一个条目,如果表B的多个条目适合,则应该使用最新的。如果表B没有该时期的信息(与第一行相同),则应使用最接近的信息。另一种表达方式是,我希望将B的最新信息添加到A的每一行中。

Table C
----------------------------------------------------------------------+
participant startA      endA        startB      endB        Content
----------------------------------------------------------------------+
101         1-1-2010    26-4-2010   1-1-2012    31-8-2012   A
101         27-4-2010   2-10-2014   1-9-2013    31-8-2014   C
101         3-10-2014   4-1-2015    1-9-2014    2-10-2014   D
101         5-1-2015    31-8-2015   3-10-2014   31-8-2015   E
101         1-9-2015    12-10-2016  1-9-2015    31-1-2016   F
101         13-10-2016  31-12-2018  1-9-2015    31-1-2016   F

这是我第一次使用SAS和SQL,因此我自己的工作效果很差。下面,我将通过几个步骤在一个过程中联接这两个表:首先创建一个完全联接,以获取表A和B的所有可能(相关)排列。然后,我计算表A中的数据之间的日期差。最后,对于A的每个时间段,我选择的行中原始表中的数据之间的日期差异最小。

/* Create outer join of both tables*/
PROC SQL;
    CREATE TABLE work.fulljoin AS
    SELECT a.*, b.* 
    FROM work.table_A AS a
    FULL JOIN work.table_B AS b ON a.participant = b.participant;
quit;

/* Group by ID and entry date of each period */
PROC SORT data=work.fulljoin;
    BY participant startA; 
RUN;

/* Calculate the date differences between tables A and B */
DATA work.fulljoin_wdelta;
    SET work.fulljoin;
    delta=abs(endA-endB);
RUN;

/* Remove unnecessary rows */
PROC SQL;
    CREATE TABLE output.joined AS
    SELECT * FROM work.fulljoin_wdelta
    GROUP BY participant, startA
    HAVING delta=min(delta);
QUIT;

但是,对于大型数据集(A和B中的数百万行),这变得令人望而却步。另外,从严格意义上讲,此方法并没有强制您在每个A期间都将获取最新的B数据,而只是在结束日期中获得最接近的数据。

2 个答案:

答案 0 :(得分:0)

我认为您可以将现有逻辑简化为一个查询:

proc sql noprint _method;
  create table table_c as
    select 
      a.participant, 
      a.start as start_a, 
      a.end as end_a, 
      b.start_date as start_b, 
      b.end_date as end_b,
      abs(a.end - b.end_date) as delta
    from table_a a inner join table_b b 
    on a.participant = b.participant
    group by a.participant, start_a
    having delta = min(delta)
    ;     
quit;

只要您有足够的内存,日志输出就会确认这将执行哈希联接:

NOTE: SQL execution methods chosen are:

      sqxcrta
          sqxsumg
              sqxsort
                  sqxjhsh
                      sqxsrc( WORK.TABLE_B(alias = B) )
                      sqxsrc( WORK.TABLE_A(alias = A) )

如果结果表与您要生成的表不同,请说明。

答案 1 :(得分:0)

当您必须处理领带,最大覆盖率与水趾重叠时,日期范围联接可能会非常复杂……您当然不希望在最终解决方案中存储工会和中介人,尽管它们在调试逻辑时会有所帮助。

这是一种相关的子查询技术,用于查找与A匹配的“最佳”内容范围。如果内容数据与end_date中的participant不相同,则会出现问题。

每个one行(目标)都完成了查找。范围重叠逻辑很重要

      where one.participant = two.participant
        and two.start_date < one.end
        and two.end_date > one.start

,并允许内容日期范围部分超出目标范围。

data one;
input participant start: ddmmyy. end: ddmmyy.;

format start end yymmdd10.;

datalines;
101         1-1-2010    26-4-2010
101         27-4-2010   2-10-2014
101         3-10-2014   4-1-2015
101         5-1-2015    31-8-2015
101         1-9-2015    12-10-2016
101         13-10-2016  31-12-2018
;

data two;
input participant start_date: ddmmyy.  end_date: ddmmyy.   Content: $;

format start_date end_date yymmdd10.;

datalines;
101         1-1-2012    31-8-2012   A
101         1-9-2012    31-8-2013   B
101         1-9-2013    31-8-2014   C
101         1-9-2014    2-10-2014   D
101         3-10-2014   31-8-2015   E
101         1-9-2015    31-1-2016   F
101         1-9-2015    31-1-2017   F
run;

proc sql;
  create table want as 
  select 
    one.*
  , ( select min(content)
      from two 
      where one.participant = two.participant
        and two.start_date < one.end
        and two.end_date > one.start
      group by participant
      having end_date = max(end_date)
    ) as content
  from
    one
  order by
    participant, start
  ;
quit;