web-dev-qa-db-ja.com

SQL:最初に見つかった行の結合を制限する方法は?

2つのテーブル間の結合を作成する方法を、結合条件を満たす最初の行に制限するにはどうすればよいですか?

この簡単な例では、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がそのような機能を提供している可能性があります(パフォーマンスが問題です)。

16
kkung

ここでのキーワードは[〜#〜] 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では演算子LATERALCROSS/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 */
;
12
0xdb

単一の値だけが必要な場合は、スカラーサブクエリを使用できます。

SELECT
    id, name, (SELECT city FROM table_B WHERE id2 = table_A.id AND ROWNUM = 1) city
FROM
    table_A
9
Husqvik
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 ..
4
Mihai

Oracle12cには、最終的に新しい クロス/アウター適用演算子 があり、回避策なしで要求された内容を許可します。

以下は、名前が 'SYS'で始まるユーザーが所有する(おそらく)多くのオブジェクトの1つだけを辞書ビューで検索する例です。

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以前のバージョンでは、通常、2番目のテーブルのIDに基づいて2番目のテーブルをフルスキャンして同じ結果を得る回避策のみを使用する必要がありますが、テストの目的で ラテラルオペレーター (また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
    );
2

クエリ

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   
1
MT0

このソリューションでは、通常の結合と同様にテーブル全体を使用しますが、最初の行に制限します。他のソリューションは1つのフィールドのみを使用するため十分ではなかったため、または大きなテーブルでパフォーマンスの問題があるため、これを投稿しています。私は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
0
Martien de Jong