Comment faire une jointure entre deux tables mais en se limitant à la première ligne qui remplit la condition de jointure?
Dans cet exemple simple, je voudrais obtenir pour chaque ligne de table_A la première ligne de table_B qui satisfait la condition:
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
Peut-être qu'Oracle fournit une telle fonction (les performances sont une préoccupation).
Le mot clé ici est PREMIER . Vous pouvez utiliser la fonction analytique FIRST_VALUE
ou construction agrégée FIRST
.
Pour FIRST
ou LAST
les performances ne sont jamais moins bonnes et souvent meilleures que l'équivalent FIRST_VALUE
ou LAST_VALUE
construct car nous n'avons pas de tri de fenêtre superflu et par conséquent un coût d'exécution moindre:
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 */
;
Depuis que 12c a introduit l'opérateur LATERAL
, ainsi que CROSS/OUTER APPLY
jointures, permet d'utiliser une sous-requête corrélée à droite de la clause 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 */
;
Si vous ne voulez qu'une seule valeur, une sous-requête scalaire peut être utilisée:
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 ..
Sur Oracle12c, il y a enfin le nouveau opérateur d'application croisée/externe qui permettra ce que vous avez demandé sans aucune solution.
ce qui suit est un exemple qui examine les vues de dictionnaire pour un seul des (probablement) nombreux objets appartenant aux utilisateurs dont le nom commence par "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
)
Sur Oracle 11g et les versions antérieures, vous ne devez utiliser que des solutions de contournement qui analysent généralement complètement la deuxième table en fonction des ID de la deuxième table pour obtenir les mêmes résultats, mais pour les tests, vous pouvez activer opérateur latéral (également disponible sur 12c sans avoir besoin d'activer de nouvelles choses) et utilisez celui-ci
-- 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
);
Requête:
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 ...
Sorties:
ID NAME CITY
---------- ---- --------
1 John New York
2 Marc Boston
Cette solution utilise la table entière, comme dans une jointure régulière, mais limite la première ligne. Je poste ceci parce que pour moi, les autres solutions n'étaient pas suffisantes car elles n'utilisent qu'un seul champ ou elles ont des problèmes de performances avec de grandes tables. Je ne suis pas un expert d'Oracle, donc si quelqu'un peut améliorer cela, faites-le, je serai heureux d'utiliser votre version.
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