【问题标题】:Oracle SQL, join tables taking the most recent date that does not exceed the reference dateOracle SQL,以不超过参考日期的最近日期连接表
【发布时间】:2021-02-23 09:50:49
【问题描述】:

在 Oracle SQL(版本 11g)中,我想将表 A 与表 B 连接起来,从 B 中获取具有最近日期但不晚于 A 中日期的行。 示例:

  table A                   table B
                             
   | year | id |       | year | id | val |
   | 2000 | 'a'|       | 2000 | 'a'|  1  |
   | 2000 | 'b'|       | 2001 | 'a'|  2  |
   | 2000 | 'c'|       | 1999 | 'b'|  1  |
   | 2003 | 'c'|       | 2003 | 'c'|  1  |

想要的结果是

| year |  id |  val  |
| 2000 | 'a' |   1   |
| 2000 | 'b' |   1   |
| 2000 | 'c' | (null)|
| 2003 | 'c' |   1   |

执行此操作的简单方法是仅在“id”列上连接两个表,然后从 A 和 B 中获取“年份”之间的差异,并仅保留差异最小的行对于每个“id”(使用窗口函数)。但是这需要先创建一个很大的表,一个很长的操作。

我想知道是否有更有效的方法来做到这一点,也许使用半连接(=exist 子句)。

编辑:建议使用 LEFT JOIN LATERAL,这似乎是一个很好的解决方案,但可从 Oracle 版本 12c 获得。我需要一个适用于以前版本的解决方案。

【问题讨论】:

    标签: sql oracle join exists


    【解决方案1】:

    在 11g 中使用两步方法。在第一步中,简单地连接两个带有年份约束的表。

    select a.id, a.year, b.year b_year, b.val
    from a
    left outer join b 
    on a.id = b.id and a.year >= b.year
    ;
    
    I       YEAR     B_YEAR        VAL
    - ---------- ---------- ----------
    a       2000       2000          1
    b       2000       1999          1
    c       2003       2003          1
    c       2000                    
    

    通常每个a.ida.year 都会获得更多行,因此在第二步中,您只过滤具有最低b.year 的行 - 这是使用ROW_NUMBER 完成的,并在第一行(@ 987654326@)

    with ab as (
    select a.id, a.year, b.val,
     row_number() over (partition by a.id, a.year order by b.year) as rn
    from a
    left outer join b 
    on a.id = b.id and a.year >= b.year
    )
    select  
      id, year, val
    from ab
    where rn = 1
    
    I       YEAR        VAL
    - ---------- ----------
    a       2000          1
    b       2000          1
    c       2000           
    c       2003          1
    

    正如您所提到的,如果要加入许多版本,这种方法可能会令人望而却步。我将通过交换你的条件来模拟它以获得最小的更高年份(因为没有多少更低的年份可用)。

    这里是示例数据

    create table a as
    select 'a' id, 2000 year from dual union all 
    select 'b' id, 2000 year from dual union all
    select 'c' id, 2000 year from dual union all
    select 'c' id, 2003 year from dual;
    
    drop table b;
    create table b as
    select 'a' id, 1000 + rownum year, rownum val  from dual connect by level <= 1000000
    union all
    select 'b', 1000 + rownum year, rownum val  from dual connect by level <= 1000000
    union all
    select 'c', 2000 + rownum year, rownum val  from dual connect by level <= 2
    ;
    

    确实,前面的方法会导致大量的中间结果

    with ab as (
    select a.id, a.year, b.val
    from a
    left outer join b 
    on a.id = b.id and a.year <= b.year
    ) 
    select  count(*) from ab;
    
    1998005 
    

    那么替代解决方案是什么?

    简单的在第一步中预计算最小的最高年份

    select a.id, a.year, min(case when b.year >= a.year then b.year end) b_year
    from a
    left outer join b 
    on a.id = b.id 
    group by a.id, a.year
    
    I       YEAR     B_YEAR
    - ---------- ----------
    c       2000       2001
    a       2000       2000
    c       2003           
    b       2000       2000
    

    并使用预先计算的表与b 进行等值连接

    with ab as ( 
    select a.id, a.year, min(case when b.year >= a.year then b.year end) b_year
    from a
    left outer join b 
    on a.id = b.id 
    group by a.id, a.year)
    select  
      ab.id, ab.year, b.val
    from ab
    left outer join b
    on ab.id = b.id and ab.b_year = b.year;
    
    I       YEAR        VAL
    - ---------- ----------
    c       2000          1
    a       2000       1000
    c       2003           
    b       2000       1000
    

    【讨论】:

    • 虽然这是正确的,但这基本上是我暗示的明显但不切实际的解决方案:它意味着在第一步中创建一个非常大的表......在我的实际应用程序中也是大到易于管理(表的大小将是表 A 的 20 倍)。这就是为什么我要求更有效的解决方案。
    • @Giuseppe 对不起,我重新阅读了关于这个主题的评论,我会更新/删除答案
    • 没关系,我认为保留它仍然可以,如果有人看到实现了我只是暗示的解决方案,可能会有用。
    • @Giuseppe - 我添加了如何解决大量可能匹配项的选项。
    【解决方案2】:

    横向连接很方便:

    select a.*, b.*
    from a left join lateral
         (select b.*
          from b
          where b.id = a.id and b.year <= a.year
          fetch first 1 row only
         ) b;
    

    编辑:

    您只需要来自b 的一列,因此请使用相关子查询:

    select a.*, 
           (select b.val
            from b
            where b.id = a.id and b.year <= a.year
            fetch first 1 row only
           ) b
    from a;
    

    【讨论】:

    • 不幸的是,我在 Oracle 11g 中,而 LATERAL 似乎仅在 Oracle 12c 中可用(我将相应地编辑问题)。
    猜你喜欢
    • 1970-01-01
    • 2017-02-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-30
    相关资源
    最近更新 更多