Intro SQL : ta passerelle vers les données
Lire, filtrer, croiser et modifier des données avec SQL. Les bases solides pour comprendre une base relationnelle et écrire tes premières requêtes utiles.
À la fin du cours, tu sais
- Comprendre comment les données s'organisent en tables et relations
- Écrire des SELECT avec filtres, tri, pagination
- Insérer, modifier et supprimer des données proprement
- Croiser plusieurs tables avec les jointures
- Agréger pour répondre à des questions métier (COUNT, SUM, GROUP BY)
Prérequis
- Être à l'aise avec un terminal
- Avoir installé PostgreSQL ou MySQL en local (Docker fait l'affaire)
Chapitre 1
Pourquoi SQL et où ça vit
SQL est le langage standard pour interroger les bases relationnelles. C'est l'outil que tu vas croiser dans 90 % des projets back-end.
Le rôle du SQL dans ton stack
- Lire des données pour les afficher dans une app
- Écrire des migrations et manipuler le schéma
- Faire des scripts d'analyse, de cleanup, de debug en prod
- Comprendre ce que fait un ORM (Sequelize, Prisma, TypeORM) sous le capot
Moteurs et clients
- Moteur (serveur) : PostgreSQL 16 ou MySQL 8 sont les références. SQLite pour le local léger.
- Client :
psql,mysql, ou GUI comme DBeaver, TablePlus, Postico - Tout passe par TCP/IP entre client et serveur (sauf SQLite, fichier local)
SQL vs NoSQL : quand choisir quoi
SQL : tu as un schéma stable, des relations claires, besoin de transactions ACID. NoSQL : données très hétérogènes, scaling horizontal massif, schéma qui change tout le temps. 80 % des projets sont mieux servis par du SQL.
psql -U postgres -d mabase
# Une fois dedans :
\dt # liste les tables
\d utilisateurs # décrit une table
SELECT version();
\q # quitterChapitre 2
Modèle relationnel et types de données
Avant d'écrire une requête, comprendre comment les données sont structurées. La force du relationnel : tout est table.
Tables, colonnes, lignes
- Table : une entité (utilisateurs, commandes, produits)
- Colonne : un attribut typé (id, email, prix)
- Ligne : une instance (un utilisateur précis)
- Clé primaire (PK) : identifie une ligne de façon unique
- Clé étrangère (FK) : pointe vers la PK d'une autre table
Les types essentiels
INTEGER,BIGINT: entiersNUMERIC(10,2): décimaux précis (utilise-le pour de l'argent, pasFLOAT)VARCHAR(255),TEXT: chaînes de caractèresBOOLEAN: true / falseTIMESTAMP,DATE: datesJSONB(Postgres) : JSON avec indexation possible
Contraintes utiles dès la création
CREATE TABLE utilisateurs (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
prenom VARCHAR(100),
age INTEGER CHECK (age >= 0),
cree_le TIMESTAMP NOT NULL DEFAULT NOW()
);NUMERIC pour l'argent, toujours
FLOAT et DOUBLE font des erreurs d'arrondi. NUMERIC(10,2) garantit 10 chiffres avec 2 décimales exactes. Indispensable dès qu'il s'agit d'euros.Chapitre 3
SELECT : le couteau suisse
La lecture, c'est 80 % de ton usage SQL au quotidien. Les variations sont infinies, mais 5 mots-clés couvrent l'essentiel.
La structure de base
SELECT colonnes
FROM table
WHERE conditions
ORDER BY colonne
LIMIT n;Filtrer avec WHERE
SELECT email, prenom
FROM utilisateurs
WHERE age >= 18
AND email LIKE '%@gmail.com'
AND cree_le BETWEEN '2026-01-01' AND '2026-12-31';=,<>(différent),<,>,<=,>=IN (...): appartient à une listeBETWEEN ... AND ...: plage inclusiveLIKEavec%(n'importe quoi) ou_(un caractère)IS NULL/IS NOT NULL(jamais= NULL, ça ne marche pas)
Trier et paginer
-- Les 20 derniers inscrits
SELECT email, cree_le
FROM utilisateurs
ORDER BY cree_le DESC
LIMIT 20;
-- Pagination : page 3 (20 par page)
SELECT email FROM utilisateurs
ORDER BY id
LIMIT 20 OFFSET 40;Alias et DISTINCT
-- Renommer dans le résultat
SELECT email AS adresse, prenom AS nom
FROM utilisateurs;
-- Valeurs uniques
SELECT DISTINCT pays FROM clients;SELECT * en prod, à éviter
SELECT * est pratique en exploration mais dangereux en code applicatif : un ALTER TABLE ADD COLUMN et ton app crashe ou ramène des colonnes inutiles. Liste explicitement les colonnes dont tu as besoin.Chapitre 4
Écrire et modifier les données
INSERT, UPDATE, DELETE : les commandes qui modifient ta base. Le réflexe à avoir : toujours un WHERE.
INSERT
-- Une ligne
INSERT INTO utilisateurs (email, prenom)
VALUES ('alice@exemple.fr', 'Alice');
-- Plusieurs lignes d'un coup
INSERT INTO utilisateurs (email, prenom) VALUES
('bob@exemple.fr', 'Bob'),
('clara@exemple.fr', 'Clara');
-- Retourner la ligne insérée (Postgres)
INSERT INTO utilisateurs (email) VALUES ('dan@exemple.fr')
RETURNING id, cree_le;UPDATE
UPDATE utilisateurs
SET prenom = 'Alice', age = 28
WHERE email = 'alice@exemple.fr';UPDATE sans WHERE = catastrophe
WHERE, tu modifies toutes les lignes de la table. Réflexe absolu : commence toujours par écrire le WHERE, puis ajoute le SET.DELETE
DELETE FROM utilisateurs WHERE id = 42;
-- Vider toute la table (utilise TRUNCATE plutôt, plus rapide)
TRUNCATE utilisateurs RESTART IDENTITY CASCADE;Transactions
Une transaction groupe plusieurs opérations en un tout indivisible : soit tout passe (COMMIT), soit rien (ROLLBACK).
BEGIN;
UPDATE comptes SET solde = solde - 100 WHERE id = 1;
UPDATE comptes SET solde = solde + 100 WHERE id = 2;
COMMIT;
-- Si ça plante au milieu : ROLLBACK et la base reste cohérenteChapitre 5
Jointures : croiser plusieurs tables
Le vrai pouvoir du relationnel. Tu connectes des tables liées par des clés étrangères pour répondre à des questions métier.
Les jointures fondamentales
- INNER JOIN : seulement les lignes qui ont une correspondance des deux côtés
- LEFT JOIN : toutes les lignes de gauche, avec NULL à droite si pas de correspondance
- RIGHT JOIN : l'inverse (rare en pratique, on inverse les tables)
- FULL OUTER JOIN : toutes les lignes des deux côtés
SELECT u.email, c.id AS commande_id, c.total
FROM utilisateurs u
LEFT JOIN commandes c ON c.utilisateur_id = u.id
ORDER BY u.email, c.id;Trouver les utilisateurs sans commande
SELECT u.email
FROM utilisateurs u
LEFT JOIN commandes c ON c.utilisateur_id = u.id
WHERE c.id IS NULL;L'anti-jointure
LEFT JOIN ... WHERE x.id IS NULL est ultra-courant : il trouve ce qui n'a pas de correspondance. Utile pour des audits ou du cleanup.Plusieurs jointures enchaînées
SELECT u.email, c.id AS commande_id, p.nom AS produit
FROM utilisateurs u
INNER JOIN commandes c ON c.utilisateur_id = u.id
INNER JOIN lignes_commande lc ON lc.commande_id = c.id
INNER JOIN produits p ON p.id = lc.produit_id
WHERE u.email = 'alice@exemple.fr';Chapitre 6
Agréger : transformer des lignes en chiffres
COUNT, SUM, AVG, GROUP BY : tu passes de liste de transactions à chiffre d'affaires par client.
Les fonctions d'agrégation
COUNT(*): nombre de lignesCOUNT(colonne): nombre de valeurs non NULLSUM(colonne): sommeAVG(colonne): moyenneMIN(colonne),MAX(colonne): min et max
GROUP BY : regrouper par catégorie
-- CA par utilisateur·ice
SELECT utilisateur_id, SUM(total) AS ca, COUNT(*) AS nb_commandes
FROM commandes
GROUP BY utilisateur_id
ORDER BY ca DESC;WHERE vs HAVING
WHERE filtre avant le regroupement (sur les lignes). HAVING filtre après le regroupement (sur les agrégats).
SELECT utilisateur_id, SUM(total) AS ca
FROM commandes
WHERE cree_le >= '2026-01-01' -- filtre des lignes
GROUP BY utilisateur_id
HAVING SUM(total) > 1000; -- filtre des groupesChapitre 7
Performance et sécurité
Trois réflexes pour éviter les ennuis : indexer ce qui se filtre, utiliser EXPLAIN, et ne jamais concaténer des entrées utilisateur dans une requête.
Index : accélérer les recherches
-- Sans index, ce filtre fait un Sequential Scan
SELECT * FROM commandes WHERE utilisateur_id = 42;
-- On crée un index
CREATE INDEX idx_commandes_utilisateur ON commandes(utilisateur_id);
-- Désormais : Index Scan, beaucoup plus rapideIndex = structure de données séparée qui accélère les recherches. Crée des index sur les colonnes filtrées (WHERE) ou jointes (JOIN). Mais attention : chaque index ralentit les INSERT et UPDATE. Ne mets pas d'index partout sans raison.
EXPLAIN : lire un plan
EXPLAIN ANALYZE
SELECT * FROM commandes WHERE utilisateur_id = 42;Si tu vois Seq Scan sur une grosse table, c'est qu'un index manque. Index Scan = bon signe. Bitmap Heap Scan = aussi correct pour les filtres larges.
Injections SQL : la base à ne jamais oublier
Jamais de concaténation
// Requête paramétrée : sûr
await client.query(
'SELECT * FROM utilisateurs WHERE email = $1',
[emailFourniParUser]
);// Concaténation : vulnérable à l'injection
await client.query(
`SELECT * FROM utilisateurs WHERE email = '${emailFourniParUser}'`
);🛠️ Exercice optionnel
Construire et interroger une mini-boutique
Tu vas modéliser une mini-boutique avec deux tables, y mettre des données, et répondre à des questions métier classiques.
Ta mission
- Crée les tables
clientsetcommandesavec les bonnes contraintes (PK, FK, NOT NULL). - Insère 3 clients (dont au moins 1 à Lyon) et 5 commandes (dont au moins 1 client sans commande).
- Liste les clients de Lyon.
- Calcule le chiffre d'affaires total par client, trié décroissant.
- Trouve les clients qui n'ont jamais commandé (anti-jointure).
- Crée un index pertinent sur
commandes.client_idet lanceEXPLAIN ANALYZEsur la requête du point 4 pour voir l'impact.
Tu bloques ? Des indices, à dévoiler quand tu en as besoin.
Indice 1
Indice masqué.
Indice 2
Indice masqué.
Indice 3
Indice masqué.
✅ QCM de fin de cours
Teste tes acquis
10 questions, plusieurs réponses parfois possibles. Coche tout ce qui te semble juste, puis valide pour voir ton score et les explications.
- 1
Quelle clause filtre des lignes avant agrégation ?
- 2
Que fait un
LEFT JOIN? - 3
Quel type est adapté à un montant en euros ?
- 4
Comment éviter une injection SQL ?
- 5
Que renvoie
COUNT(*)? - 6
Quelle commande supprime toutes les lignes mais garde la structure de la table ?
- 7
À quoi sert
EXPLAIN ANALYZE? - 8
Une clé étrangère garantit quoi ?
- 9
Que se passe-t-il si tu lances
UPDATE utilisateurs SET actif = falsesans WHERE ? - 10
Comment t'assurer que ton
SELECTretourne des lignes uniques ?
Tu peux laisser des questions sans réponse, elles compteront comme fausses.
Tu veux ce cours pour ton équipe ?
Je peux adapter et animer ce cours pour tes formateur·ices ou tes apprenant·es, en présentiel ou en distanciel. Parlons-en pendant l'audit gratuit.
Réserver un audit gratuit →