Votre base infra.db contient des serveurs, des services qui tournent dessus, des déploiements faits par des utilisateurs, et des alertes. Vous devez lister les services avec le hostname du serveur sur lequel ils tournent. L’information est répartie sur deux tables — un simple SELECT sur une seule table ne suffit plus. C’est exactement le problème que les jointures résolvent.
En SQL, les données sont structurées en tables normalisées : chaque entité a sa propre table, reliée aux autres par des clés. Les jointures (JOIN) reconstituent l’information complète en combinant ces tables. Les sous-requêtes offrent une alternative pour filtrer ou calculer à partir de données d’une autre table.
Ces deux mécanismes sont fondamentaux — impossible d’administrer une base relationnelle sans les maîtriser.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Écrire un
INNER JOINpour combiner deux tables sur une clé commune - Distinguer
LEFT JOIN,RIGHT JOINetFULL OUTER JOIN - Utiliser
CROSS JOINet les self joins - Écrire des sous-requêtes scalaires, avec
INet avecEXISTS - Choisir entre jointure et sous-requête selon le contexte
- Joindre plus de deux tables dans une même requête
Dans quel contexte ?
Section intitulée « Dans quel contexte ? »Les jointures interviennent dans presque toute requête réelle d’administration :
- Lister les services avec le hostname du serveur hôte
- Trouver les serveurs sans alertes récentes (
LEFT JOIN … IS NULL) - Croiser les tables de déploiements avec les utilisateurs pour un audit
- Alimenter un tableau de bord qui agrège des données de plusieurs tables
- Écrire des scripts de migration qui vérifient la cohérence entre tables liées
Ce guide ne couvre pas…
Section intitulée « Ce guide ne couvre pas… »- Les bases du
SELECTetWHERE→ voir le guide Syntaxe de base - Les
UPDATE … FROMouDELETE … USINGavec jointure → voir le guide Manipuler les données - Les fonctions d’agrégation (
COUNT,SUM,GROUP BY) → voir le guide Fonctions et agrégations
Prérequis
Section intitulée « Prérequis »- Savoir écrire un
SELECT … WHERE(guide Syntaxe de base) - La base fil rouge
infra.dbchargée dans~/Projets/cours-sql - Comprendre la notion de clé primaire (colonne qui identifie chaque ligne de façon unique)
Pourquoi les jointures existent
Section intitulée « Pourquoi les jointures existent »Le modèle relationnel en 30 secondes
Section intitulée « Le modèle relationnel en 30 secondes »Une base relationnelle sépare les données en tables distinctes pour éviter la redondance. Dans votre base fil rouge :
servers:id,hostname,ip_address, …services:id,name,server_id,port,status, …
La colonne server_id dans services est une clé étrangère : elle pointe vers id dans servers. Cette relation permet de retrouver sur quel serveur tourne chaque service — à condition de joindre les deux tables.
Clé primaire et clé étrangère
Section intitulée « Clé primaire et clé étrangère »| Concept | Rôle | Exemple dans infra.db |
|---|---|---|
| Clé primaire (PK) | Identifiant unique de chaque ligne | servers.id |
| Clé étrangère (FK) | Référence vers la PK d’une autre table | services.server_id → servers.id |
La jointure utilise cette relation pour associer les lignes des deux tables.
INNER JOIN : l’intersection
Section intitulée « INNER JOIN : l’intersection »INNER JOIN renvoie uniquement les lignes qui ont une correspondance dans les deux tables.
Syntaxe et exemple
Section intitulée « Syntaxe et exemple »SELECT s.name AS service, sv.hostname, s.port, s.statusFROM services sINNER JOIN servers sv ON sv.id = s.server_id;setsvsont des alias pour éviter de répéter les noms completsONdéfinit la condition de jointure- Seuls les services qui ont un serveur existant apparaissent
Jointure sur plusieurs colonnes
Section intitulée « Jointure sur plusieurs colonnes »Quand la relation repose sur plusieurs colonnes (moins courant dans notre base) :
SELECT *FROM deliveries dINNER JOIN warehouses w ON w.region = d.region AND w.city = d.city;La syntaxe implicite — à éviter
Section intitulée « La syntaxe implicite — à éviter »L’ancienne syntaxe SQL-89 place la jointure dans le WHERE :
-- Syntaxe implicite (déconseillée)SELECT s.name, sv.hostnameFROM services s, servers svWHERE sv.id = s.server_id;LEFT JOIN : toutes les lignes de gauche
Section intitulée « LEFT JOIN : toutes les lignes de gauche »LEFT JOIN (ou LEFT OUTER JOIN) renvoie toutes les lignes de la table de gauche, même celles sans correspondance à droite. Les colonnes de droite sont remplies de NULL quand il n’y a pas de match.
SELECT sv.hostname, a.severity, a.messageFROM servers svLEFT JOIN alerts a ON a.server_id = sv.id;Résultat : tous les serveurs, y compris ceux sans aucune alerte (avec severity = NULL).
Détecter les lignes sans correspondance
Section intitulée « Détecter les lignes sans correspondance »Le pattern LEFT JOIN … WHERE … IS NULL est très courant pour trouver les lignes orphelines :
-- Serveurs qui n'ont jamais eu d'alerteSELECT sv.hostnameFROM servers svLEFT JOIN alerts a ON a.server_id = sv.idWHERE a.id IS NULL;RIGHT JOIN et FULL OUTER JOIN
Section intitulée « RIGHT JOIN et FULL OUTER JOIN »RIGHT JOIN
Section intitulée « RIGHT JOIN »RIGHT JOIN est le miroir de LEFT JOIN : toutes les lignes de la table de droite, même sans correspondance à gauche.
SELECT u.username, d.version, d.deployed_atFROM users uRIGHT JOIN deployments d ON d.deployed_by = u.id;En pratique, RIGHT JOIN est rarement utilisé — il est plus lisible d’inverser les tables et d’utiliser un LEFT JOIN.
FULL OUTER JOIN
Section intitulée « FULL OUTER JOIN »FULL OUTER JOIN renvoie toutes les lignes des deux tables, avec NULL quand il n’y a pas de correspondance d’un côté.
-- PostgreSQLSELECT u.username, d.versionFROM users uFULL OUTER JOIN deployments d ON d.deployed_by = u.id;CROSS JOIN : le produit cartésien
Section intitulée « CROSS JOIN : le produit cartésien »CROSS JOIN combine chaque ligne de la première table avec chaque ligne de la seconde — sans condition ON.
-- Générer toutes les combinaisons serveur × environnementSELECT sv.hostname, env.nameFROM servers svCROSS JOIN ( SELECT 'production' AS name UNION SELECT 'staging' UNION SELECT 'development') env;Self JOIN : joindre une table avec elle-même
Section intitulée « Self JOIN : joindre une table avec elle-même »Un self join relie une table à elle-même, typiquement pour explorer une hiérarchie. La table categories de votre base fil rouge a une colonne parent_id qui pointe vers id de la même table :
SELECT c.name AS categorie, p.name AS parentFROM categories cLEFT JOIN categories p ON p.id = c.parent_id;Les alias sont obligatoires pour distinguer les deux rôles de la même table.
Résultat :
| categorie | parent |
|---|---|
| Infrastructure | NULL |
| Compute | Infrastructure |
| Réseau | Infrastructure |
| Machines virtuelles | Compute |
| Conteneurs | Compute |
| … | … |
Joindre plus de deux tables
Section intitulée « Joindre plus de deux tables »Il suffit de chaîner les JOIN. Ici, on récupère l’historique complet de déploiement avec le service, le serveur et l’utilisateur :
SELECT sv.hostname, s.name AS service, d.version, d.status, u.full_name AS deploye_par, d.deployed_atFROM deployments dINNER JOIN services s ON s.id = d.service_idINNER JOIN servers sv ON sv.id = s.server_idINNER JOIN users u ON u.id = d.deployed_byWHERE d.deployed_at > '2026-04-01'ORDER BY d.deployed_at DESC;Chaque JOIN ajoute une table. L’optimiseur de la base de données choisit l’ordre d’exécution le plus efficace — vous n’avez pas besoin de vous en soucier dans la plupart des cas.
Les sous-requêtes
Section intitulée « Les sous-requêtes »Une sous-requête est un SELECT imbriqué dans une autre requête. Elle s’exécute en premier et son résultat alimente la requête principale.
Sous-requête scalaire (renvoie une seule valeur)
Section intitulée « Sous-requête scalaire (renvoie une seule valeur) »-- Dernier serveur ajouté à l'inventaireSELECT hostname, ip_addressFROM serversWHERE created_at = (SELECT MAX(created_at) FROM servers);La sous-requête renvoie une seule valeur (la date la plus récente), utilisée dans le WHERE.
Sous-requête avec IN (renvoie une liste)
Section intitulée « Sous-requête avec IN (renvoie une liste) »-- Serveurs qui ont au moins une alerte critiqueSELECT hostnameFROM serversWHERE id IN (SELECT DISTINCT server_id FROM alerts WHERE severity = 'critical');IN vérifie si la valeur fait partie de la liste retournée.
Sous-requête avec EXISTS (test d’existence)
Section intitulée « Sous-requête avec EXISTS (test d’existence) »-- Utilisateurs qui ont effectué au moins un déploiement (souvent plus performant)SELECT u.full_nameFROM users uWHERE EXISTS ( SELECT 1 FROM deployments d WHERE d.deployed_by = u.id);EXISTS renvoie vrai dès qu’au moins une ligne correspond — il ne parcourt pas toute la sous-requête.
Sous-requête corrélée vs non corrélée
Section intitulée « Sous-requête corrélée vs non corrélée »| Type | Comportement | Performance |
|---|---|---|
| Non corrélée | Exécutée une seule fois, résultat réutilisé | Généralement rapide |
| Corrélée | Exécutée pour chaque ligne de la requête externe | Peut être lente sur de grosses tables |
-- Non corrélée : le SELECT interne ne dépend pas de la requête externeWHERE id IN (SELECT server_id FROM alerts WHERE severity = 'critical')
-- Corrélée : le SELECT interne référence sv.id de la requête externeWHERE EXISTS (SELECT 1 FROM alerts a WHERE a.server_id = sv.id)Jointure vs sous-requête : comment choisir
Section intitulée « Jointure vs sous-requête : comment choisir »| Critère | Jointure | Sous-requête |
|---|---|---|
| Afficher des colonnes des deux tables | Oui (naturel) | Nécessite un JOIN quand même |
| Tester l’existence | LEFT JOIN … IS NULL | EXISTS / NOT EXISTS (plus lisible) |
| Filtrer par une liste | INNER JOIN | IN (SELECT …) |
| Performance | Souvent équivalent grâce à l’optimiseur | Les corrélées peuvent être lentes |
| Lisibilité | Meilleure pour 2-3 tables liées | Meilleure pour des conditions ponctuelles |
Règle pratique : si vous avez besoin de colonnes des deux tables dans le résultat, utilisez un JOIN. Si vous filtrez juste sur une condition externe, une sous-requête est souvent plus lisible.
Exercice pratique
Section intitulée « Exercice pratique »Ouvrez sqlite3 infra.db et pratiquez :
-
Listez chaque serveur avec ses services (incluez les serveurs sans service).
SELECT sv.hostname, s.name AS service, s.port, s.statusFROM servers svLEFT JOIN services s ON s.server_id = sv.idORDER BY sv.hostname; -
Trouvez les utilisateurs qui n’ont jamais effectué de déploiement (anti-join).
SELECT u.full_name, u.roleFROM users uLEFT JOIN deployments d ON d.deployed_by = u.idWHERE d.id IS NULL; -
Affichez l’historique de déploiement complet (jointure sur 4 tables) : hostname, service, version, statut, déployé par, date.
SELECT sv.hostname, s.name AS service, d.version, d.status, u.full_name, d.deployed_atFROM deployments dINNER JOIN services s ON s.id = d.service_idINNER JOIN servers sv ON sv.id = s.server_idINNER JOIN users u ON u.id = d.deployed_byORDER BY d.deployed_at DESC; -
Trouvez les serveurs de production qui ont eu des alertes critiques (sous-requête).
SELECT hostnameFROM serversWHERE environment = 'production'AND id IN (SELECT server_id FROM alerts WHERE severity = 'critical');
Dépannage
Section intitulée « Dépannage »| Symptôme | Cause probable | Solution |
|---|---|---|
| Résultat avec des lignes dupliquées | La jointure produit un produit cartésien partiel (relation many-to-many) | Vérifiez les conditions ON, ajoutez DISTINCT si approprié |
JOIN renvoie trop de lignes | Condition ON manquante ou incorrecte → produit cartésien | Vérifiez que chaque JOIN a un ON correct |
| Sous-requête corrélée très lente | Exécutée pour chaque ligne de la table principale | Réécrivez en JOIN ou en CTE (guide SQL avancé) |
NULL inattendu dans le résultat | LEFT JOIN : les lignes sans correspondance ont des NULL à droite | C’est le comportement normal — filtrez avec WHERE … IS NOT NULL si nécessaire |
FULL OUTER JOIN non supporté | Ni SQLite ni MySQL ne le supportent | Utilisez LEFT JOIN UNION LEFT JOIN inversé (voir section ci-dessus) |
À retenir
Section intitulée « À retenir »INNER JOINrenvoie uniquement les lignes qui ont une correspondance dans les deux tablesLEFT JOINconserve toutes les lignes de gauche, même sans correspondance- Le pattern
LEFT JOIN … WHERE … IS NULLest un anti-join puissant pour détecter les orphelins CROSS JOINproduit un produit cartésien — à n’utiliser que sur de petites tables- Les sous-requêtes offrent une alternative lisible pour filtrer (
IN,EXISTS) EXISTSest souvent plus performant queINsur de grosses tables- Si vous avez besoin de colonnes des deux tables, préférez un
JOIN