Anaïs Sparesotto
SQL · DébutantDébutant≈ 2h30 · 7 chapitres

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.

Premier contact avec psql
psql -U postgres -d mabase
# Une fois dedans :
\dt              # liste les tables
\d utilisateurs  # décrit une table
SELECT version();
\q               # quitter

Chapitre 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 : entiers
  • NUMERIC(10,2) : décimaux précis (utilise-le pour de l'argent, pas FLOAT)
  • VARCHAR(255), TEXT : chaînes de caractères
  • BOOLEAN : true / false
  • TIMESTAMP, DATE : dates
  • JSONB (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 liste
  • BETWEEN ... AND ... : plage inclusive
  • LIKE avec % (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

Sans 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érente

Chapitre 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
Lister les utilisateur·ices avec leurs commandes
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

Le pattern 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 lignes
  • COUNT(colonne) : nombre de valeurs non NULL
  • SUM(colonne) : somme
  • AVG(colonne) : moyenne
  • MIN(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 groupes

Chapitre 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 rapide

Index = 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

Si tu construis une requête en concaténant des entrées utilisateur, un attaquant peut injecter du SQL. Utilise toujours des requêtes paramétrées (préparées). Tous les drivers le supportent.
Bon (avec pg en Node.js)
// Requête paramétrée : sûr
await client.query(
  'SELECT * FROM utilisateurs WHERE email = $1',
  [emailFourniParUser]
);
Mauvais (jamais)
// 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

  1. Crée les tables clients et commandes avec les bonnes contraintes (PK, FK, NOT NULL).
  2. Insère 3 clients (dont au moins 1 à Lyon) et 5 commandes (dont au moins 1 client sans commande).
  3. Liste les clients de Lyon.
  4. Calcule le chiffre d'affaires total par client, trié décroissant.
  5. Trouve les clients qui n'ont jamais commandé (anti-jointure).
  6. Crée un index pertinent sur commandes.client_id et lance EXPLAIN ANALYZE sur 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. 1

    Quelle clause filtre des lignes avant agrégation ?

  2. 2

    Que fait un LEFT JOIN ?

  3. 3

    Quel type est adapté à un montant en euros ?

  4. 4

    Comment éviter une injection SQL ?

  5. 5

    Que renvoie COUNT(*) ?

  6. 6

    Quelle commande supprime toutes les lignes mais garde la structure de la table ?

  7. 7

    À quoi sert EXPLAIN ANALYZE ?

  8. 8

    Une clé étrangère garantit quoi ?

  9. 9

    Que se passe-t-il si tu lances UPDATE utilisateurs SET actif = false sans WHERE ?

  10. 10

    Comment t'assurer que ton SELECT retourne 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 →