Aller au contenu principal

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):

bg-[rgba(var(--code-inline-bg),0.5)] text-[rgb(var(--fg))] px-1 roundedbg-[rgba(var(--code-inline-bg),0.5)] text-[rgb(var(--fg))] px-1 rounded
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
sqlsql
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

INNER
Garder seulement les correspondances
LEFT
Tout L ; NULL quand pas de match R
RIGHT
Tout R (équivalent LEFT en inversant)
FULL
Tout L ∪ tout R (NULL si absent)
EXISTS/NOT EXISTS
Filtrer sur l’existence sans dupliquer

#Visualisation Venn (interactive)

Users (L)Orders (R)
L ∩ R — correspondances seulement

#Générateur de requêtes

Visualisation
users uorders o
Tout L, R si match sinon NULL
SQL généré
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.
Doublons et cartésiens

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.

Filtrer la table droite en LEFT JOIN

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.

Index et FKs

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

Chargement de l’éditeur...

#Mini‑exercice: LEFT JOIN et prédicats

Écrivez deux requêtes qui listent les utilisateurs et leurs dernières commandes uniquement si amount ≥ 50.

  1. Dans WHERE (et constatez que les utilisateurs sans commande disparaissent → effet INNER).
  2. 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

  1. Utilisez les mêmes tables que dans l'exemple :
    • users(id, name)
    • orders(id, user_id, amount)
  2. Utilisez une jointure appropriée pour trouver les utilisateurs sans commandes.
  3. Pensez à la différence entre INNER JOIN et LEFT JOIN.

#Exemple de requête

sqlsql
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;
Chargement de l’éditeur...

#Pièges courants

Multiplication de lignes

Les jointures 1→N multiplient les lignes: attention aux agrégations; utiliser GROUP BY.

RIGHT/FULL selon le moteur

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)

Préparer
users(id) ; orders(user_id)
Stratégie
Nested‑loops ; hash join ; index nested‑loops
Parcourir gauche
Pour chaque u ∈ users…
Probe droite
Scan ; lookup index ; table de hachage
Émettre
LEFT → NULL si pas de match
Agrégations
GROUP BY après la jointure ; cardinalités

#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.
sqlsql
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.

sqlsql
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:

sqlsql
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

  1. Lister les utilisateurs et leur montant total (0 si aucune commande). Trier par total décroissant.

  2. Lister les utilisateurs sans commande (2 façons):

    • LEFT JOIN ... WHERE o.user_id IS NULL
    • NOT EXISTS.
  3. Anti‑duplication: compter le nombre d’utilisateurs ayant au moins une commande (sans se faire piéger par le 1→N):

sqlsql
1select count(*)2from users u3where exists (select 1 from orders o where o.user_id = u.id);

#Quiz (avancé)

Quelle construction réalise un anti‑join idiomatique ?
Quelle construction réalise un anti‑join idiomatique ?

#Quiz

Quel JOIN conserve toutes les lignes de la table de gauche ?
Quel JOIN conserve toutes les lignes de la table de gauche ?