SQL:如何限制第一个找到的行的连接?

问题描述 投票:12回答:6

如何在两个表之间建立连接但限制为满足连接条件的第一行?

在这个简单的例子中,我想为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提供了这样一个功能(性能是一个值得关注的问题)。

sql oracle join greatest-n-per-group
6个回答
10
投票

这里的关键词是FIRST。您可以使用分析函数FIRST_VALUE或聚合构造FIRST。 对于FIRSTLAST来说,性能永远不会比同等的FIRST_VALUELAST_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 */
;

7
投票

如果只需要单个值,则可以使用标量子查询:

SELECT
    id, name, (SELECT city FROM table_B WHERE id2 = table_A.id AND ROWNUM = 1) city
FROM
    table_A

3
投票
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 ..

2
投票

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

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   

0
投票

此解决方案使用整个表,如常规联接,但限制第一行。我发布这个是因为对我来说其他解决方案还不够,因为它们只使用一个字段,或者它们对大表有性能问题。我不是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
© www.soinside.com 2019 - 2024. All rights reserved.