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

Posted

技术标签:

【中文标题】根据不匹配的日期字段连接两个表【英文标题】:Joining two tables based on non-matching date fields 【发布时间】:2019-02-19 11:42:00 【问题描述】:

我想按日期加入两个表(A 和 B)。由于日期不一定在表格中整齐排列,这一事实使情况变得复杂。也就是说,表 B 中的日期可能落在表 A 的条目之外或之间。

我怀疑在 SQL/SAS 中一定有一种简单的方法可以实现这一点,但我对这两种方法都很陌生,不知道怎么做。如果有人能指出我可以用来解决这个问题的具体解决方案、示例或功能,我将非常高兴。我在下面创建了一个虚构的案例来说明。

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

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 中选择一个位于表 A 范围内的条目。如果 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 中数据之间的日期差异和 B. 最后,对于 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 数据,而只是获取结束日期最接近的 B 数据。

【问题讨论】:

您的表格 C 是您希望连接表格的外观示例,还是您迄今为止设法制作的最佳示例?如果是后者,请根据您的输入表 A 和 B 提供一个示例,说明您希望它的外观。 数百万行是 SAS 数据集还是远程数据库表?如果有,数据集上有哪些索引? 感谢您的 cmets! @user667489,表 C 是我想要摆脱的。理查德,这是关于 SAS 数据集的。 【参考方案1】:

当您必须处理关系时,日期范围连接可能会非常复杂,最大覆盖范围与水中脚趾重叠等...您当然不想将联合和中介存储在最终解决方案中,尽管它们在调试逻辑时会有所帮助。

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

每个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;

【讨论】:

这看起来很酷!范围查找逻辑确实很棘手。你能解释一下 min(content) 是否很重要吗?似乎在 ONE 中每行选择 TWO 中最旧的条目;如果我离开 min() ,它会得到我更喜欢的最新数据。这就引出了关于这段代码的第二个问题。真正的“TWO”表有很多列。我无法使用此代码选择多个。理想情况下,我想声明“SELECT * FROM two”,但随后在“ORDER BY 参与者,开始”行中出现关于复合表达式的错误。这有什么诀窍吗? 当关联子查询匹配多于一行时会报错,因此子查询被分组,当有两个或多个@时返回最低值content 987654327@ 具有相同最高 end_date 的行。在 它会有问题 中进行了修饰。您对使用这种查询方法的探索可能会发现高资源使用率(cpu/磁盘/时间)和寻求更多替代方案的愿望。 SAS SQL 相关子查询 (CSQ) 只能使用 1 元组(一列)提供(某些数据库可能允许 n 元组(CSQ 中的多个选择))并且您有看到它不适合替换从two 中选择多个列的更传统的连接。对所有日期范围匹配条件进行稳健处理的查询并非易事。 有道理,感谢您的解释!不知道元组限制。我想我要做的是将流程分成几部分 - 使用您的代码从表 2 中附加正确的日期并将它们存储在表 1 中。在第二步中,我应该能够根据新的日期列左加入表 2 的其余部分。我要上班了!【参考方案2】:

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

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) )

如果生成的表格与您尝试生成的表格不同,请澄清。

【讨论】:

谢谢,我还在做很多小步骤,因为我已经习惯了 SQL/SAS。这确实更优雅。

以上是关于根据不匹配的日期字段连接两个表的主要内容,如果未能解决你的问题,请参考以下文章

连接查询详解

26.MySQL中的内连接INNER JOIN

使用不匹配的外键连接两个表时的空结果集

选择连接表上的最近日期

如何通过聚合连接mongoDB中的两个表并需要根据匹配获取结果[重复]

MS Access 内连接不精确匹配(通配符或类似)