JOIN
Progression
#JOIN
Combiner des lignes de plusieurs tables par des clés correspondantes. Le choix du type de jointure contrôle quelles lignes sont conservées et comment les absences sont représentées (NULL). Savoir anticiper la cardinalité évite les doublons et les « explosions » de lignes.
Objectifs d’apprentissage
- Choisir le bon type de jointure (INNER/LEFT/RIGHT/FULL) selon le besoin.
- Anticiper la multiplication de lignes et positionner les agrégations au bon endroit.
- Reconnaître quand utiliser
EXISTS/NOT EXISTS
(semi/anti‑join) pour des filtres.
Types principaux: INNER
, LEFT
, RIGHT
(pas dans SQLite), FULL
(via UNION).
Mental model (table de gauche L, table de droite R):
1INNER: L ∩ R → seulement les correspondances2LEFT: L ⟕ R → tout L, R quand match sinon NULLs3RIGHT: L ⟖ R → tout R (équivalent à LEFT en inversant)4FULL: L ⟗ R → tout L ∪ tout R, NULLs quand pas de match
1create table orders(id int, user_id int, amount real);2insert into orders values (1,1,19.9),(2,1,5.0),(3,2,12.0);3 4-- INNER JOIN: ne garde que les correspondances5select u.name, o.amount6from users u7join orders o on o.user_id = u.id;8 9-- LEFT JOIN: garde tout à gauche, NULL si pas de match10select u.name, o.amount11from users u12left join orders o on o.user_id = u.id;
#Animation: choisir la jointure
#Visualisation Venn (interactive)
#Générateur de requêtes
select *
from users u
left join orders o on o.user_id = u.id;
#Choisir le bon pattern
- INNER JOIN: quand on veut uniquement les correspondances (ex.: commandes avec utilisateur existant). Idéal si la clé étrangère est obligatoire.
- LEFT JOIN: quand on veut conserver la table de gauche même sans correspondance (ex.: montrer tous les utilisateurs, avec total de commandes éventuellement NULL).
- EXISTS/NOT EXISTS: pour filtrer l’existence/absence sans dupliquer les lignes. Préférer à un
JOIN
+GROUP BY
quand seul le test d’existence compte (semi/anti‑join). - IN vs EXISTS: sur des sous‑ensembles petits et scalaires,
IN
est lisible; pour des corrélations complexes,EXISTS
est plus expressif.
Oublier la clause ON
(ou utiliser un prédicat trop large) produit un produit cartésien. Ajoutez des contraintes de jointure précises et validez la cardinalité attendue.
Avec un LEFT JOIN, mettre une condition sur la table de droite dans WHERE
supprime les lignes sans correspondance (et transforme l’effet en INNER JOIN). Placez plutôt le prédicat dans ON
pour conserver les lignes de gauche non appariées.
Un index sur la clé étrangère (orders.user_id
) accélère les jointures et les suppressions en cascade. Côté « one », indexer la clé primaire suffit; côté « many », indexer la FK évite des scans.
#Playground
#Mini‑exercice: LEFT JOIN et prédicats
Écrivez deux requêtes qui listent les utilisateurs et leurs dernières commandes uniquement si amount ≥ 50.
- Dans
WHERE
(et constatez que les utilisateurs sans commande disparaissent → effet INNER). - Dans
ON
(les utilisateurs sans commande restent, colonnes d’orders
à NULL).
Expliquez la différence de cardinalité et de sémantique.
#Exercice : Trouver les utilisateurs sans commandes
Créez une requête qui affiche les utilisateurs qui n'ont passé aucune commande, en utilisant une jointure.
#Instructions
- Utilisez les mêmes tables que dans l'exemple :
users(id, name)
orders(id, user_id, amount)
- Utilisez une jointure appropriée pour trouver les utilisateurs sans commandes.
- Pensez à la différence entre INNER JOIN et LEFT JOIN.
#Exemple de requête
1-- Création des tables de démonstration2create table users(id int, name text);3create table orders(id int, user_id int, amount real);4 5-- Insertion de données6insert into users values (1, 'Alice'), (2, 'Bob'), (3, 'Charlie');7insert into orders values (1, 1, 100.0), (2, 1, 50.0);8 9-- Requête pour trouver les utilisateurs sans commandes10select u.name11from users u12left join orders o on u.id = o.user_id13where o.user_id is null;
#Pièges courants
Les jointures 1→N multiplient les lignes: attention aux agrégations; utiliser GROUP BY
.
SQLite n’a pas RIGHT JOIN
ni FULL OUTER JOIN
. Utilisez LEFT JOIN
(en inversant les tables) ou UNION
pour émuler un FULL
.
#Animation: exécution d'un JOIN (vue conceptuelle)
#Semi‑joins et Anti‑joins
- SEMI‑JOIN: filtrer les lignes de gauche qui ont au moins une correspondance à droite sans dupliquer les lignes de gauche →
EXISTS
. - ANTI‑JOIN: filtrer les lignes de gauche qui n’ont aucune correspondance à droite →
NOT EXISTS
.
1-- Utilisateurs ayant au moins une commande (SEMI)2select * from users u3where exists (select 1 from orders o where o.user_id = u.id);4 5-- Utilisateurs sans commande (ANTI)6select * from users u7where not exists (select 1 from orders o where o.user_id = u.id);
#CTE (WITH) et lisibilité
Utiliser des expressions de table communes pour structurer les requêtes.
1with totals as (2 select user_id, sum(amount) total3 from orders4 group by user_id5)6select u.name, coalesce(t.total, 0) as total7from users u8left join totals t on t.user_id = u.id9order by total desc;
#EXISTS vs LEFT JOIN + GROUP BY
Quand il s’agit seulement de tester l’existence d’au moins une ligne à droite, EXISTS
est souvent plus clair et évite les doublons:
1-- Existe-t-il au moins une commande ?2select u.*3from users u4where exists (5 select 1 from orders o where o.user_id = u.id6);7 8-- Équivalent plus verbeux, potentiellement plus coûteux si l’agrégat est mal placé9select u.*10from users u11left join orders o on o.user_id = u.id12group by u.id13having count(o.id) > 0;
Moralité: exprimez l’intention. L’optimiseur choisira une stratégie adaptée; commencez lisible, mesurez ensuite.
#Mini‑atelier
-
Lister les utilisateurs et leur montant total (0 si aucune commande). Trier par total décroissant.
-
Lister les utilisateurs sans commande (2 façons):
LEFT JOIN ... WHERE o.user_id IS NULL
NOT EXISTS
.
-
Anti‑duplication: compter le nombre d’utilisateurs ayant au moins une commande (sans se faire piéger par le 1→N):
1select count(*)2from users u3where exists (select 1 from orders o where o.user_id = u.id);