如何在两个表之间建立连接但限制为满足连接条件的第一行?
在这个简单的例子中,我想为table_A中的每一行获取满足条件的table_B中的第一行:
select table_A.id, table_A.name, table_B.city
from table_A join table_B
on table_A.id = table_B.id2
where ..
table_A (id, name)
1, John
2, Marc
table_B (id2, city)
1, New York
1, Toronto
2, Boston
The output would be:
1, John, New York
2, Marc, Boston
可能是Oracle提供了这样一个功能(性能是一个值得关注的问题)。
这里的关键词是FIRST。您可以使用分析函数FIRST_VALUE
或聚合构造FIRST
。
对于FIRST
或LAST
来说,性能永远不会比同等的FIRST_VALUE
或LAST_VALUE
构造更差,因为我们没有多余的窗口排序,因此执行成本更低:
select table_A.id, table_A.name, firstFromB.city
from table_A
join (
select table_B.id2, max(table_B.city) keep (dense_rank first order by table_B.city) city
from table_b
group by table_B.id2
) firstFromB on firstFromB.id2 = table_A.id
where 1=1 /* some conditions here */
;
由于12c引入了运算符LATERAL
,以及CROSS/OUTER APPLY
连接,因此可以在JOIN
子句的右侧使用相关子查询:
select table_A.id, table_A.name, firstFromB.city
from table_A
cross apply (
select max(table_B.city) keep (dense_rank first order by table_B.city) city
from table_b
where table_B.id2 = table_A.id
) firstFromB
where 1=1 /* some conditions here */
;
如果只需要单个值,则可以使用标量子查询:
SELECT
id, name, (SELECT city FROM table_B WHERE id2 = table_A.id AND ROWNUM = 1) city
FROM
table_A
select table_A.id, table_A.name,
FIRST_VALUE(table_B.city) IGNORE NULLS
OVER (PARTITION BY table_B.id2 ORDER BY table_B.city) AS "city"
from table_A join table_B
on table_A.id = table_B.id2
where ..
在Oracle12c上最终有新的cross/outer apply operator,它将允许你所要求的,没有任何解决方法。
以下是一个示例,它查看字典视图中的一个(可能)多个对象,这些对象的名称以'SYS'开头:
select *
from (
select USERNAME
from ALL_USERS
where USERNAME like 'SYS%'
) U
cross apply (
select OBJECT_NAME
from ALL_OBJECTS O
where O.OWNER = U.USERNAME
and ROWNUM = 1
)
在Oracle 11g及以前的版本中,您应该只使用通常根据第二个表的ID完全扫描第二个表的变通方法来获得相同的结果,但是对于测试目的,您可以启用lateral operator(也可以在12c上使用而无需启用新的东西)并使用另一个
-- Enables some new features
alter session set events '22829 trace name context forever';
select *
from (
select USERNAME
from ALL_USERS
where USERNAME like 'SYS%'
) U,
lateral (
select OBJECT_NAME
from ALL_OBJECTS O
where O.OWNER = U.USERNAME
and ROWNUM = 1
);
查询:
SELECT a.id,
a.name,
b.city
FROM table_A a
INNER JOIN
( SELECT id2,
city
FROM (
SELECT id2,
city,
ROW_NUMBER() OVER ( PARTITION BY id2 ORDER BY NULL ) rn
FROM Table_B
)
WHERE rn = 1
) b
ON ( a.id = b.id2 )
--WHERE ...
输出:
ID NAME CITY
---------- ---- --------
1 John New York
2 Marc Boston
此解决方案使用整个表,如常规联接,但限制第一行。我发布这个是因为对我来说其他解决方案还不够,因为它们只使用一个字段,或者它们对大表有性能问题。我不是Oracle的专家,所以如果有人可以改进,请这样做,我很乐意使用你的版本。
select *
from tableA A
cross apply (
select *
from (
select B.*,
ROW_NUMBER() OVER (
-- replace this by your own partition/order statement
partition by B.ITEM_ID order by B.DELIVERYDATE desc
) as ROW_NUM
from tableB B
where
A.ITEM_ID=B.ITEM_ID
)
where ROW_NUM=1
) B