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がそのような機能を提供している可能性があります(パフォーマンスが問題です)。
ここでのキーワードは[〜#〜] 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には、最終的に新しい クロス/アウター適用演算子 があり、回避策なしで要求された内容を許可します。
以下は、名前が '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
);
クエリ:
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つのフィールドのみを使用するため十分ではなかったため、または大きなテーブルでパフォーマンスの問題があるため、これを投稿しています。私は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