Chapitre 8Projet SunuShop

Les jointures — INNER JOIN et LEFT JOIN

Pourquoi joindre (le problème des 4 requêtes séparées). INNER JOIN (correspondance obligatoire). LEFT JOIN (garder les lignes sans correspondance). Alias de tables. Joindre 3, 4, 5 tables. Self-join.

Concepts Théoriques

Les données de SunuShop sont réparties dans 9 tables. Pour afficher un produit avec le nom de sa catégorie, une commande avec le nom du client et le détail des produits, ou un avis avec le nom du produit — il faut joindre les tables. Les jointures sont le mécanisme qui donne tout son sens au "relationnel" dans "base de données relationnelle".

Pourquoi joindre ?

Sans jointure, pour afficher "Fatou Sall a commandé 2 Wax Ankara à 15 000 FCFA", il faudrait 4 requêtes séparées : d'abord chercher la commande, puis le client, puis les order_items, puis les produits. 4 allers-retours à la base de données, 4 résultats à assembler manuellement. Avec un JOIN, c'est UNE seule requête qui retourne tout d'un coup — plus rapide et plus lisible.

INNER JOIN — correspondance obligatoire

INNER JOIN retourne seulement les lignes qui ont une correspondance dans les DEUX tables. Si un produit a un category_id qui ne correspond à aucune catégorie (impossible avec nos FK, mais théoriquement), il n'apparaîtrait pas dans le résultat.

La syntaxe : SELECT p.name, c.name AS category FROM products p INNER JOIN categories c ON p.category_id = c.id;

Le ON spécifie la condition de jointure — comment les deux tables sont reliées. C'est toujours la clé étrangère = clé primaire.

LEFT JOIN — tout garder à gauche

LEFT JOIN retourne TOUTES les lignes de la table de gauche (la première après FROM), même si elles n'ont pas de correspondance dans la table de droite. Les colonnes de droite sont NULL quand il n'y a pas de correspondance.

C'est essentiel pour répondre aux questions "négatives" : quels produits n'ont AUCUN avis ? Quels clients n'ont JAMAIS commandé ? Quelles catégories sont vides ? Avec INNER JOIN ces lignes disparaissent — avec LEFT JOIN elles apparaissent avec des NULL, et on filtre avec WHERE ... IS NULL.

RIGHT JOIN

L'inverse de LEFT JOIN — garde toutes les lignes de la table de droite. En pratique, on l'utilise rarement : il suffit d'inverser l'ordre des tables et d'utiliser LEFT JOIN. Le résultat est identique.

Alias de tables — obligatoires

p, c, o, oi, cu, r, pay sont des alias. Ils raccourcissent les noms de tables et sont OBLIGATOIRES quand deux tables ont des colonnes du même nom (id, name, created_at). Sans alias, MySQL ne sait pas si "name" fait référence au produit ou à la catégorie.

Convention : la première lettre du nom de table, ou deux lettres si ambiguïté (cu pour customers, pas c qui est déjà categories).

Joindre 3, 4, 5 tables

Chaque JOIN supplémentaire ajoute une table avec sa condition ON. L'ordre des JOIN n'affecte pas le résultat (MySQL optimise), mais pour la lisibilité, partez de la table principale et ajoutez les tables liées logiquement.

CROSS JOIN — le produit cartésien

CROSS JOIN combine CHAQUE ligne de la table A avec CHAQUE ligne de la table B. Si A a 5 lignes et B a 10 lignes, le résultat a 5 × 10 = 50 lignes. C'est rarement utile, mais il y a un cas classique : générer toutes les combinaisons possibles.

SELECT c.name AS categorie, t.name AS tag FROM categories c CROSS JOIN tags t;

Résultat : chaque catégorie combinée avec chaque tag — utile pour créer une matrice ou un tableau croisé. Pas de condition ON (c'est le principe : tout avec tout).

Self-join (jointure réflexive)

Une table peut se joindre à elle-même. L'exemple classique est une hiérarchie : une table employees avec une colonne manager_id qui pointe vers id dans la MÊME table.

Dans SunuShop, on pourrait utiliser un self-join pour trouver des produits similaires : des produits de la même catégorie avec un prix proche.

-- Produits similaires : même catégorie, prix dans un écart de 20%
SELECT
    p1.name AS produit,
    CONCAT(FORMAT(p1.price, 0), ' FCFA') AS prix,
    p2.name AS produit_similaire,
    CONCAT(FORMAT(p2.price, 0), ' FCFA') AS prix_similaire
FROM products p1
INNER JOIN products p2
    ON p1.category_id = p2.category_id
    AND p1.id != p2.id
    AND p2.price BETWEEN p1.price * 0.8 AND p1.price * 1.2
WHERE p1.is_active = TRUE AND p2.is_active = TRUE
ORDER BY p1.name;

Le p1.id != p2.id empêche un produit de se retrouver "similaire" à lui-même.