Oracle SQL,联接表的最新日期不超过参考日期

问题描述

在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的“ year”之差,只保留该差最小的行。对于每个“ id”(使用窗口函数)。但是,这需要首先创建一个很大的表,一个非常长的操作。

我想知道是否有更有效的方法,也许使用半联接(= exist子句)。

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

解决方法

横向连接对此很方便:

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;
,

在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完成的并在第一行(rn = 1)上进行过滤

with ab as (
select a.id,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,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',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,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,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)
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