📘 Formation Complète

Apprendre MySQL

De débutant à expert — 50 leçons progressives et détaillées

50Leçons
5Chapitres
100+Exemples SQL
0Complétées
📌 Chapitre 1 — Les Fondamentaux
01

Introduction à MySQL

Concept

MySQL est l'un des systèmes de gestion de bases de données relationnelles (SGBDR) les plus populaires au monde. Créé en 1995 par Michael Widenius et David Axmark en Suède, puis racheté par Sun Microsystems en 2008 et aujourd'hui maintenu par Oracle, il est utilisé par des millions d'applications allant de simples blogs à de gigantesques plateformes technologiques.

📅 Créé en 1995
🆓 Open Source (GPL)
Très performant en lecture
🌍 Supporte l'UTF-8/UTF-8mb4
🏢 Facebook, Twitter, Airbnb l'utilisent
💡

Pourquoi une base de données ? Une base de données permet de stocker des informations de manière organisée, persistante et efficace. Contrairement à un simple fichier CSV ou texte, elle permet de chercher, trier, relier et sécuriser des données en quelques millisecondes même avec des millions d'enregistrements.

MySQL s'intègre avec presque tous les langages modernes :

LangageFramework populaireConnecteur
PHPLaravel, SymfonyPDO, mysqli
PythonDjango, Flaskmysql-connector, SQLAlchemy
JavaScriptNode.js, Expressmysql2, Sequelize
JavaSpring BootJDBC, Hibernate
GoGin, Echogo-sql-driver/mysql
RubyRuby on Railsmysql2 gem

L'architecture de MySQL repose sur un modèle client-serveur : le serveur MySQL gère les données, et les clients (applications, phpMyAdmin, terminal) envoient des requêtes SQL pour interagir avec ces données.

Les données dans MySQL sont persistantes : elles survivent aux redémarrages serveur. Elles sont aussi relationnelles : les tables peuvent être liées entre elles via des clés, évitant la duplication d'informations.

02

Installation de MySQL

Concept

Pour démarrer, la méthode la plus simple est d'installer une suite logicielle tout-en-un. Ces packs installent en une seule opération : Apache (serveur web), PHP, MySQL et phpMyAdmin.

SuiteSystèmesIdéal pour
XAMPPWindows, macOS, LinuxTous niveaux
WAMPWindowsDéveloppeurs Windows
MAMPmacOS, WindowsUtilisateurs Mac
LaragonWindowsLaravel/PHP

Pour une installation directe en ligne de commande (Linux/Mac) :

Terminal
# Ubuntu / Debian
sudo apt update
sudo apt install mysql-server

# macOS via Homebrew
brew install mysql

# Démarrer le service MySQL
sudo systemctl start mysql

# Sécuriser l'installation (recommandé)
sudo mysql_secure_installation
⚠️

En production : installez MySQL directement sur le serveur Linux et configurez un pare-feu. XAMPP/WAMP sont destinés uniquement au développement local. Ne jamais exposer le port 3306 sur Internet sans sécurisation.

03

Comprendre phpMyAdmin

Concept

phpMyAdmin est une interface graphique web pour gérer MySQL sans écrire toutes les commandes à la main. C'est l'outil idéal pour débuter et visualiser vos données.

URL navigateur
http://localhost/phpmyadmin

Depuis phpMyAdmin :

  • ✏️ Créer et supprimer des bases de données
  • 📋 Créer et modifier des tables
  • ➕ Ajouter, modifier et supprimer des données
  • 🔍 Exécuter des requêtes SQL directement
  • 💾 Exporter / importer des bases (sauvegardes .sql)
  • 👤 Gérer les utilisateurs et leurs droits
  • 📊 Voir les statistiques des tables et index

L'onglet "SQL" de phpMyAdmin vous permet d'écrire et tester vos requêtes. C'est votre bac à sable pour apprendre ! Vous pouvez aussi utiliser MySQL Workbench (outil officiel Oracle) ou DBeaver (open-source très complet) comme alternatives.

✅ phpMyAdmin

  • Accessible via navigateur
  • Inclus dans XAMPP/WAMP
  • Interface simple

✅ MySQL Workbench

  • Outil officiel Oracle
  • Modélisation visuelle des tables
  • Analyse des requêtes
04

Créer une base de données

DDL

Une base de données est un conteneur logique regroupant plusieurs tables. Pensez-y comme un classeur : le classeur = la base de données, les onglets = les tables.

📐 Syntaxe
CREATE DATABASE nom_de_la_base [CHARACTER SET utf8mb4] [COLLATE utf8mb4_unicode_ci];
SQL
CREATE DATABASE boutique;

-- Avec encodage recommandé (accents, emojis, toutes les langues)
CREATE DATABASE boutique
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_unicode_ci;

-- Lister toutes les bases existantes
SHOW DATABASES;

-- Supprimer une base (⚠️ irréversible)
DROP DATABASE boutique;

-- Créer seulement si elle n'existe pas déjà
CREATE DATABASE IF NOT EXISTS boutique;

Bonne pratique : toujours créer vos bases en utf8mb4 avec la collation utf8mb4_unicode_ci. Cela supporte tous les caractères : accents français, caractères arabes, emojis 😊, alphabets asiatiques…

05

Sélectionner une base de données

DDL

Après avoir créé une base, vous devez indiquer à MySQL que vous souhaitez travailler dedans avec la commande USE.

SQL
USE boutique;

-- Vérifier quelle base est active
SELECT DATABASE();  -- retourne "boutique"

-- Lister les tables de la base active
SHOW TABLES;

-- Voir la structure complète de la base
SHOW TABLE STATUS;
⚠️

Si vous oubliez le USE, MySQL retournera l'erreur "No database selected". Dans phpMyAdmin, cliquer sur une base dans le panneau gauche fait automatiquement le USE.

06

Comprendre les Tables

Concept

Une table est la structure fondamentale de stockage. Elle s'organise comme un tableau avec des colonnes (les attributs) et des lignes (les enregistrements, aussi appelés tuples).

IDNomEmailPaysActif
1Aliou Dialloaliou@gmail.comSénégal
2Fatoumata Bahfato@outlook.comGuinée
3Moussa Konémoussa@yahoo.frMali

Concepts essentiels :

  • Colonne (champ) : définit un attribut de l'entité (nom, email, âge…)
  • Ligne (enregistrement) : représente une instance unique de l'entité
  • Cellule : intersection d'une colonne et d'une ligne = une valeur précise
  • Schéma : la structure d'une table (colonnes + types + contraintes)
💡

Une base bien conçue décompose les informations en plusieurs tables liées. Par exemple : utilisateurs, commandes, produits. Cela évite la duplication et garantit la cohérence — c'est le principe de la normalisation.

07

Créer une Table — CREATE TABLE

DDL

La commande CREATE TABLE définit la structure d'une table : ses colonnes, leurs types et leurs contraintes.

📐 Syntaxe
CREATE TABLE nom_table (col1 TYPE [contraintes], col2 TYPE [contraintes], ...);
SQL — Exemple complet
CREATE TABLE utilisateurs (
  id    INT AUTO_INCREMENT PRIMARY KEY,
  nom   VARCHAR(100) NOT NULL,
  email VARCHAR(150) UNIQUE NOT NULL,
  age   INT
);

-- Voir les tables de la base
SHOW TABLES;

-- Voir la structure détaillée d'une table
DESCRIBE utilisateurs;
-- ou (équivalent)
SHOW COLUMNS FROM utilisateurs;

-- Voir le SQL complet de création
SHOW CREATE TABLE utilisateurs;

Toujours inclure une colonne id INT AUTO_INCREMENT PRIMARY KEY comme première colonne. C'est la clé primaire qui identifie chaque ligne de manière unique et sert de référence dans les jointures.

08

Les Types de Données

DDL

Chaque colonne doit avoir un type précis. Choisir le bon type est essentiel pour les performances, l'espace disque et la cohérence des données.

Types numériques :

TypePlageEspaceUsage
TINYINT-128 à 1271 octetBooléen, petits entiers
INT-2M à +2M4 octetsIDs, âges, compteurs
BIGINT±9 milliards²8 octetsGrands IDs, montants
DECIMAL(10,2)précisvariablePrix, salaires
FLOATapproximatif4 octetsNotes, températures

Types texte :

TypeLongueur maxUsage
CHAR(n)255 car.Longueur fixe (codes postaux)
VARCHAR(n)65 535 car.Noms, emails, titres
TEXT65 535 car.Articles, descriptions
MEDIUMTEXT16 MoLongs contenus HTML
LONGTEXT4 GoTrès longs textes
ENUM65535 valeursstatut ('actif','inactif')

Types dates :

TypeFormatUsage
DATEAAAA-MM-JJDate de naissance
TIMEHH:MM:SSHeure uniquement
DATETIMEAAAA-MM-JJ HH:MM:SSTimestamp de création
TIMESTAMPAAAA-MM-JJ HH:MM:SSMise à jour auto (ON UPDATE)
YEARAAAAAnnée de publication
⚠️

Pour les montants financiers, utilisez toujours DECIMAL(10,2) jamais FLOAT. Le type FLOAT est approximatif : 0.1 + 0.2 = 0.30000000000000004 — catastrophique pour les calculs d'argent !

09

Insérer des Données — INSERT INTO

DML

INSERT INTO ajoute de nouvelles lignes dans une table. C'est le moyen de peupler votre base.

📐 Syntaxe
INSERT INTO table (col1, col2) VALUES (val1, val2);
SQL
-- Insertion simple
INSERT INTO utilisateurs (nom, email, age)
VALUES ('Aliou Diallo', 'aliou@gmail.com', 25);

-- Insertion multiple en une requête (bien plus performant)
INSERT INTO utilisateurs (nom, email, age)
VALUES
  ('Fatoumata Bah',  'fato@gmail.com',   30),
  ('Moussa Koné',    'moussa@gmail.com', 28),
  ('Aminata Sow',    'ami@gmail.com',    22);

-- Insérer et récupérer l'ID généré
INSERT INTO utilisateurs (nom, email) VALUES ('Awa', 'awa@test.com');
SELECT LAST_INSERT_ID(); -- retourne l'ID de la ligne insérée

-- Insérer OU mettre à jour si l'email existe déjà (UPSERT)
INSERT INTO utilisateurs (nom, email) VALUES ('Ali', 'ali@test.com')
ON DUPLICATE KEY UPDATE nom = 'Ali (mis à jour)';

Insérer plusieurs lignes en une seule requête est 3 à 10 fois plus rapide que de faire des INSERT séparés, car MySQL n'ouvre qu'une seule connexion et transaction.

10

Lire les Données — SELECT

Requête

SELECT est la commande la plus utilisée en SQL. Elle récupère des données d'une ou plusieurs tables sans les modifier.

SQL
-- Toutes les colonnes
SELECT * FROM utilisateurs;

-- Colonnes spécifiques (recommandé en production)
SELECT nom, email FROM utilisateurs;

-- Alias de colonnes
SELECT nom AS 'Prénom', email AS 'Adresse Email'
FROM utilisateurs;

-- Valeur calculée dans le SELECT
SELECT nom,
       prix * 1.2 AS prix_ttc
FROM produits;

-- Éliminer les doublons avec DISTINCT
SELECT DISTINCT pays FROM utilisateurs;

❌ À éviter

SELECT * en production : retourne toutes les colonnes, surcharge le réseau, révèle la structure de vos tables, incompatible avec les index couvrants.

✅ Recommandé

Nommez explicitement les colonnes : SELECT id, nom, email. Plus rapide, plus lisible, plus sécurisé, permet les optimisations d'index.

🧠 Mini-Quiz

Quelle requête retourne uniquement les noms de tous les pays présents dans la table utilisateurs, sans doublons ?

SELECT pays FROM utilisateurs;
SELECT DISTINCT pays FROM utilisateurs;
SELECT UNIQUE pays FROM utilisateurs;
SELECT * FROM utilisateurs GROUP pays;
🔍 Chapitre 2 — Filtrage & Contraintes
11

Filtrer avec WHERE

Requête

La clause WHERE filtre les lignes retournées. Sans elle, MySQL traite toutes les lignes — très coûteux sur de grandes tables.

SQL
-- Condition simple
SELECT * FROM utilisateurs WHERE id = 1;

-- Combinaison AND / OR / NOT
SELECT * FROM utilisateurs
WHERE pays = 'Sénégal' AND age > 20;

SELECT * FROM utilisateurs
WHERE pays = 'Mali' OR pays = 'Guinée';

SELECT * FROM utilisateurs
WHERE NOT pays = 'Sénégal';

-- Parenthèses pour grouper les conditions
SELECT * FROM commandes
WHERE (statut = 'en_attente' OR statut = 'expédié')
  AND montant > 10000;
OpérateurSignificationExemple
=ÉgalWHERE age = 25
!= / <>DifférentWHERE pays != 'Mali'
> / <Supérieur / InférieurWHERE prix > 500
>= / <=Sup. ou égal / Inf. ou égalWHERE note >= 10
IS NULLValeur videWHERE email IS NULL
IS NOT NULLValeur non videWHERE email IS NOT NULL
⚠️

Pour comparer avec NULL, n'utilisez jamais = NULL mais toujours IS NULL. En SQL, NULL = NULL retourne FALSE — car NULL signifie "valeur inconnue".

12

Trier les résultats — ORDER BY

Requête

Sans ORDER BY, MySQL ne garantit aucun ordre — il retourne les lignes dans l'ordre le plus pratique pour lui. Utilisez toujours ORDER BY quand l'ordre compte.

SQL
-- Tri alphabétique A→Z (ASC = défaut)
SELECT * FROM utilisateurs ORDER BY nom ASC;

-- Tri du plus récent au plus ancien
SELECT * FROM commandes ORDER BY date_commande DESC;

-- Tri multi-colonnes : par catégorie ASC, puis prix DESC
SELECT * FROM produits
ORDER BY categorie ASC, prix DESC;

-- Tri par numéro de colonne (position dans SELECT)
SELECT nom, age FROM utilisateurs ORDER BY 2 DESC; -- tri par 'age'

-- NULL en dernier (comportement par défaut avec ASC)
SELECT * FROM produits ORDER BY note IS NULL ASC, note DESC;
💡

ASC = Ascendant (A→Z, 0→9, date ancienne→récente).
DESC = Descendant (Z→A, 9→0, date récente→ancienne).
Par défaut, les valeurs NULL apparaissent en premier avec ASC et en dernier avec DESC.

13

Limiter les résultats — LIMIT

RequêtePerformance

LIMIT est indispensable pour la pagination et les performances. Il évite de ramener des millions de lignes inutilement.

SQL — Pagination complète
-- Les 5 premiers
SELECT * FROM utilisateurs LIMIT 5;

-- LIMIT offset, count (page 2 = sauter 10, prendre 10)
SELECT * FROM utilisateurs LIMIT 10, 10;

-- Syntaxe alternative (plus lisible)
SELECT * FROM utilisateurs LIMIT 10 OFFSET 20; -- page 3

-- Toujours avec ORDER BY pour une pagination cohérente
SELECT * FROM articles
ORDER BY date_publication DESC
LIMIT 0, 10; -- Page 1

Formule pagination : LIMIT (page-1)*par_page, par_page
Page 1 avec 10/page → LIMIT 0, 10
Page 3 avec 10/page → LIMIT 20, 10
Page 5 avec 25/page → LIMIT 100, 25

14

Modifier des données — UPDATE

DML

UPDATE modifie les valeurs de lignes existantes dans une table.

SQL
-- Modifier un seul enregistrement
UPDATE utilisateurs
SET nom = 'Mamadou Diallo'
WHERE id = 1;

-- Modifier plusieurs colonnes à la fois
UPDATE utilisateurs
SET nom = 'Mamadou', email = 'mamadou@gmail.com'
WHERE id = 1;

-- Incrémenter une valeur (ex: ajouter des points)
UPDATE utilisateurs
SET points = points + 100
WHERE id = 1;

-- Vérifier combien de lignes seraient affectées avant de modifier
SELECT COUNT(*) FROM utilisateurs WHERE actif = 0;
🚨

DANGER : Ne JAMAIS oublier WHERE !
UPDATE utilisateurs SET nom = 'Test';
Sans WHERE → tous les utilisateurs sont renommés "Test". Il n'y a pas d'annulation automatique (sauf dans une transaction).

15

Supprimer des données — DELETE

DML

DELETE supprime des lignes d'une table. Action irréversible sans transaction ou sauvegarde.

SQL
-- Supprimer un enregistrement précis
DELETE FROM utilisateurs WHERE id = 5;

-- Supprimer selon une condition
DELETE FROM utilisateurs WHERE actif = 0;

-- DELETE avec ORDER BY et LIMIT (ex: supprimer les 100 plus anciens)
DELETE FROM logs ORDER BY date_log ASC LIMIT 100;

❌ DELETE FROM table;

Supprime toutes les lignes, ligne par ligne, avec log. Très lent sur grandes tables. Laisse l'AUTO_INCREMENT intact.

✅ TRUNCATE TABLE table;

Vide toute la table instantanément. Réinitialise l'AUTO_INCREMENT. Bien plus rapide. Utilisez-le pour vider une table en dev.

16

Clés Primaires — PRIMARY KEY

DDLConcept

Une clé primaire identifie de manière unique chaque ligne d'une table. Elle garantit l'absence de doublons et sert de référence dans les jointures.

SQL
-- Clé primaire simple
CREATE TABLE utilisateurs (
  id  INT PRIMARY KEY,
  nom VARCHAR(100)
);

-- Clé primaire composite (sur plusieurs colonnes)
CREATE TABLE inscriptions (
  etudiant_id INT,
  cours_id    INT,
  PRIMARY KEY (etudiant_id, cours_id)
);

-- Tentative de doublon → erreur
INSERT INTO utilisateurs VALUES (1, 'Ali');
INSERT INTO utilisateurs VALUES (1, 'Moussa'); -- ❌ Duplicate entry '1'
💡

La clé primaire crée automatiquement un index sur la colonne. C'est pourquoi les recherches par ID (WHERE id = 5) sont instantanées même sur des tables de millions de lignes.

17

AUTO_INCREMENT

DDL

AUTO_INCREMENT génère automatiquement des identifiants uniques et croissants. Vous n'avez plus à gérer les IDs manuellement.

SQL
CREATE TABLE utilisateurs (
  id  INT AUTO_INCREMENT PRIMARY KEY,
  nom VARCHAR(100)
);

INSERT INTO utilisateurs (nom) VALUES ('Ali');    -- id = 1
INSERT INTO utilisateurs (nom) VALUES ('Fato');   -- id = 2
INSERT INTO utilisateurs (nom) VALUES ('Moussa'); -- id = 3

-- Si on supprime id=3 et en insère un nouveau → id = 4 (jamais réutilisé)

-- Définir la valeur de départ
ALTER TABLE utilisateurs AUTO_INCREMENT = 1000;

-- Voir la prochaine valeur AUTO_INCREMENT
SHOW TABLE STATUS LIKE 'utilisateurs';
💡

Un ID supprimé n'est jamais réutilisé. Si vous supprimez les IDs 98, 99, 100 et insérez un nouveau, il recevra l'ID 101. C'est un comportement intentionnel pour éviter les conflits.

18

NOT NULL, UNIQUE et DEFAULT

DDL

Ces contraintes garantissent la qualité et la cohérence des données insérées dans votre base.

ContrainteRôleErreur si violée
NOT NULLValeur obligatoire"Column cannot be null"
UNIQUEValeur unique dans la colonne"Duplicate entry"
DEFAULT valValeur si rien n'est fourni
CHECK (condition)Valide une règle métier"Check constraint violated"
SQL
CREATE TABLE utilisateurs (
  id     INT AUTO_INCREMENT PRIMARY KEY,
  nom    VARCHAR(100) NOT NULL,
  email  VARCHAR(150) NOT NULL UNIQUE,
  pays   VARCHAR(50)  DEFAULT 'Sénégal',
  actif  BOOLEAN DEFAULT 1,
  age    INT CHECK (age >= 0 AND age <= 150),
  créé_le TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
19

Clés Étrangères — FOREIGN KEY

DDLConcept

Une clé étrangère est une colonne qui référence la clé primaire d'une autre table. Elle maintient l'intégrité référentielle : on ne peut pas créer une commande pour un utilisateur qui n'existe pas.

SQL
CREATE TABLE commandes (
  id             INT AUTO_INCREMENT PRIMARY KEY,
  utilisateur_id INT NOT NULL,
  produit        VARCHAR(200),
  montant        DECIMAL(10,2),
  FOREIGN KEY (utilisateur_id)
    REFERENCES utilisateurs(id)
    ON DELETE CASCADE       -- supprime les commandes si l'utilisateur est supprimé
    ON UPDATE CASCADE       -- met à jour si l'ID change
);
OptionComportement
ON DELETE CASCADESupprime les lignes liées automatiquement
ON DELETE SET NULLMet la FK à NULL (colonne doit accepter NULL)
ON DELETE RESTRICTInterdit la suppression si des lignes liées existent
ON DELETE NO ACTIONComme RESTRICT (comportement par défaut)
20

Modifier une Table — ALTER TABLE

DDL

ALTER TABLE modifie la structure d'une table existante sans perdre les données.

SQL — Toutes les opérations ALTER
-- Ajouter une colonne
ALTER TABLE utilisateurs
ADD COLUMN telephone VARCHAR(20);

-- Ajouter une colonne à une position précise
ALTER TABLE utilisateurs
ADD COLUMN telephone VARCHAR(20) AFTER email;

-- Modifier le type d'une colonne
ALTER TABLE utilisateurs
MODIFY COLUMN nom VARCHAR(200) NOT NULL;

-- Renommer une colonne
ALTER TABLE utilisateurs
RENAME COLUMN nom TO nom_complet;

-- Supprimer une colonne
ALTER TABLE utilisateurs
DROP COLUMN telephone;

-- Renommer la table
RENAME TABLE utilisateurs TO membres;
⚠️

Sur de très grandes tables (millions de lignes), un ALTER TABLE peut prendre plusieurs minutes et bloquer les écritures. Utilisez des outils comme gh-ost ou pt-online-schema-change en production.

🔗 Chapitre 3 — Jointures & Agrégation
21

Introduction aux Jointures (JOIN)

Concept

Dans une base bien conçue, les données sont réparties en plusieurs tables. Les jointures les combinent dans une seule requête en s'appuyant sur les clés.

❌ Sans jointures

  • Données dupliquées
  • Mises à jour difficiles
  • Incohérences possibles

✅ Avec jointures

  • Données normalisées
  • Un seul endroit à mettre à jour
  • Cohérence garantie
Type de JOINLignes retournées
INNER JOINSeulement les lignes avec correspondance dans les deux tables
LEFT JOINToutes les lignes de gauche + correspondances droite (NULL si absent)
RIGHT JOINToutes les lignes de droite + correspondances gauche (NULL si absent)
CROSS JOINProduit cartésien — toutes les combinaisons possibles
SELF JOINUne table jointe avec elle-même
22

INNER JOIN

Requête

INNER JOIN retourne uniquement les lignes ayant une correspondance dans les deux tables. C'est le JOIN le plus courant.

SQL
-- Commandes avec les infos de l'utilisateur
SELECT u.nom, c.produit, c.montant
FROM utilisateurs AS u
INNER JOIN commandes AS c
  ON u.id = c.utilisateur_id;

-- JOIN sur 3 tables
SELECT u.nom, c.produit, p.categorie
FROM utilisateurs u
INNER JOIN commandes c ON u.id = c.utilisateur_id
INNER JOIN produits  p ON c.produit_id = p.id;

Utilisez des alias courts (u, c, p) pour les tables dans les JOIN. Cela rend les requêtes bien plus lisibles. Qualifiez toujours les colonnes ambiguës : u.id et non juste id.

23

LEFT JOIN et RIGHT JOIN

Requête

LEFT JOIN retourne toutes les lignes de la table gauche, même si aucune correspondance n'existe à droite (les colonnes absentes valent NULL).

SQL
-- Tous les utilisateurs, même ceux sans commande
SELECT u.nom, c.produit
FROM utilisateurs u
LEFT JOIN commandes c
  ON u.id = c.utilisateur_id;
-- c.produit vaut NULL pour les utilisateurs sans commande

-- Trouver les utilisateurs qui n'ont JAMAIS commandé
SELECT u.nom
FROM utilisateurs u
LEFT JOIN commandes c
  ON u.id = c.utilisateur_id
WHERE c.id IS NULL; -- Technique anti-JOIN
💡

Astuce anti-JOIN : en ajoutant WHERE c.id IS NULL après un LEFT JOIN, on trouve précisément les lignes sans correspondance — très utile pour détecter des données orphelines.

24

SELF JOIN & CROSS JOIN

RequêteAvancé

Le SELF JOIN joint une table avec elle-même — utile pour les hiérarchies (employé/manager). Le CROSS JOIN produit toutes les combinaisons possibles.

SQL — SELF JOIN
-- Trouver le manager de chaque employé
SELECT
  e.nom     AS employe,
  m.nom     AS manager
FROM   employes AS e
LEFT JOIN employes AS m
  ON e.manager_id = m.id;
SQL — CROSS JOIN
-- Générer toutes les combinaisons taille × couleur
SELECT t.taille, c.couleur
FROM tailles t
CROSS JOIN couleurs c;
-- Si 3 tailles × 5 couleurs = 15 lignes résultantes
25

GROUP BY et HAVING

Requête

GROUP BY regroupe les lignes partageant la même valeur pour appliquer des agrégats.

SQL
-- Nombre d'utilisateurs par pays
SELECT pays, COUNT(*) AS total
FROM utilisateurs
GROUP BY pays
ORDER BY total DESC;

-- HAVING : filtre appliqué APRÈS le regroupement
SELECT pays, COUNT(*) AS total
FROM utilisateurs
GROUP BY pays
HAVING total > 5
ORDER BY total DESC;

-- Chiffre d'affaires par mois
SELECT
  YEAR(date_commande)  AS annee,
  MONTH(date_commande) AS mois,
  SUM(montant)          AS ca
FROM commandes
GROUP BY annee, mois
ORDER BY annee, mois;
💡

WHERE filtre avant le regroupement (sur les lignes brutes). HAVING filtre après (sur les résultats d'agrégation). On peut utiliser les deux dans la même requête.

26

Fonctions sur les chaînes de caractères

Requête

MySQL offre de nombreuses fonctions pour manipuler les textes directement en SQL.

SQL — Fonctions texte essentielles
-- Concaténer des chaînes
SELECT CONCAT(prenom, ' ', nom) AS nom_complet FROM utilisateurs;
SELECT CONCAT_WS(', ', ville, pays) AS adresse FROM utilisateurs;

-- Casse
SELECT UPPER(nom), LOWER(email) FROM utilisateurs;

-- Longueur et extraction
SELECT LENGTH(nom) AS nb_chars FROM utilisateurs;
SELECT SUBSTRING(email, 1, 5) FROM utilisateurs;  -- 5 premiers caractères
SELECT LEFT(nom, 3), RIGHT(nom, 3) FROM utilisateurs;

-- Nettoyage
SELECT TRIM('  Aliou  ');  -- "Aliou" (supprime espaces)
SELECT REPLACE(email, '@gmail.com', '@outlook.com') FROM utilisateurs;

-- Recherche de position
SELECT INSTR('bonjour@gmail.com', '@'); -- retourne 8
27

Fonctions d'agrégation : COUNT, SUM, AVG, MIN, MAX

Requête

Ces 5 fonctions calculent des statistiques sur un ensemble de lignes. Elles s'utilisent souvent avec GROUP BY.

SQL
SELECT COUNT(*)            AS total_utilisateurs FROM utilisateurs;
SELECT COUNT(email)        AS avec_email       FROM utilisateurs; -- ignore NULL
SELECT COUNT(DISTINCT pays) AS nb_pays          FROM utilisateurs;

SELECT SUM(montant)         AS ca_total    FROM commandes;
SELECT AVG(montant)         AS panier_moyen FROM commandes;
SELECT MIN(prix), MAX(prix) FROM produits;

-- Tout en une requête
SELECT
  COUNT(*)            AS nb_commandes,
  SUM(montant)         AS total,
  AVG(montant)         AS moyenne,
  MIN(montant)         AS plus_petite,
  MAX(montant)         AS plus_grande
FROM commandes
WHERE statut = 'terminé';
💡

COUNT(*) compte toutes les lignes y compris celles avec NULL. COUNT(colonne) ignore les valeurs NULL — la différence peut être significative !

28

Fonctions de dates

Requête

MySQL dispose d'un arsenal complet de fonctions pour manipuler les dates et les heures.

SQL — Dates essentielles
-- Date et heure actuelle
SELECT NOW();         -- 2026-06-08 14:30:00
SELECT CURDATE();     -- 2026-06-08
SELECT CURTIME();     -- 14:30:00

-- Extraire des parties
SELECT YEAR(NOW()),  MONTH(NOW()),  DAY(NOW());
SELECT HOUR(NOW()), MINUTE(NOW()), WEEKDAY(NOW());

-- Calculs de dates
SELECT DATE_ADD(NOW(), INTERVAL 30 DAY);   -- dans 30 jours
SELECT DATE_SUB(NOW(), INTERVAL 1 YEAR);   -- il y a 1 an
SELECT DATEDIFF('2026-12-31', CURDATE()); -- jours restants en 2026

-- Formater une date
SELECT DATE_FORMAT(NOW(), '%d/%m/%Y %H:%i'); -- "08/06/2026 14:30"

-- Commandes des 7 derniers jours
SELECT * FROM commandes
WHERE date_commande >= DATE_SUB(NOW(), INTERVAL 7 DAY);
29

Sous-requêtes (Subqueries)

AvancéRequête

Une sous-requête est une requête imbriquée à l'intérieur d'une autre. Elle permet des filtres dynamiques sans créer de tables temporaires.

SQL
-- Utilisateurs ayant commandé plus que la moyenne
SELECT * FROM commandes
WHERE montant > (
  SELECT AVG(montant) FROM commandes
);

-- Sous-requête dans FROM (table dérivée)
SELECT stats.pays, stats.total
FROM (
  SELECT pays, COUNT(*) AS total
  FROM utilisateurs
  GROUP BY pays
) AS stats
WHERE stats.total > 3;

-- EXISTS : vérifie l'existence d'une correspondance
SELECT * FROM utilisateurs u
WHERE EXISTS (
  SELECT 1 FROM commandes c
  WHERE c.utilisateur_id = u.id
);

Quand possible, préférez les JOIN aux sous-requêtes — ils sont généralement plus performants car MySQL peut les optimiser plus facilement. Mais les sous-requêtes sont parfois plus lisibles.

30

UNION et UNION ALL

Requête

UNION combine les résultats de plusieurs SELECT en une seule liste. Les colonnes doivent être compatibles en nombre et type.

SQL
-- UNION : élimine les doublons (plus lent)
SELECT email FROM utilisateurs
UNION
SELECT email FROM employes;

-- UNION ALL : garde les doublons (plus rapide)
SELECT nom, 'Utilisateur' AS type FROM utilisateurs
UNION ALL
SELECT nom, 'Employé'    AS type FROM employes
ORDER BY nom;
💡

Utilisez UNION ALL si vous savez qu'il n'y a pas de doublons ou si vous voulez les conserver. C'est bien plus performant que UNION qui fait un tri pour éliminer les doublons.

⚡ Chapitre 4 — Requêtes Avancées
31

LIKE, IN et BETWEEN

RequêteAvancé

Ces opérateurs permettent des conditions de filtrage plus expressives que les simples comparaisons.

SQL
-- LIKE : recherche par motif (% = n'importe quoi, _ = 1 caractère)
SELECT * FROM utilisateurs WHERE nom LIKE 'Ali%';    -- commence par Ali
SELECT * FROM utilisateurs WHERE nom LIKE '%ba';    -- finit par ba
SELECT * FROM utilisateurs WHERE nom LIKE '%ou%';   -- contient ou
SELECT * FROM utilisateurs WHERE nom LIKE 'A___';   -- A suivi de 3 chars
SELECT * FROM utilisateurs WHERE nom NOT LIKE '%test%'; -- exclure les tests

-- IN : liste de valeurs exactes
SELECT * FROM utilisateurs
WHERE pays IN ('Sénégal', 'Mali', 'Guinée');

-- NOT IN : exclure une liste
SELECT * FROM produits
WHERE categorie NOT IN ('archive', 'supprimé');

-- BETWEEN : plage inclusive
SELECT * FROM utilisateurs WHERE age BETWEEN 18 AND 30;
SELECT * FROM commandes
WHERE date_commande BETWEEN '2026-01-01' AND '2026-12-31';
⚠️

LIKE '%mot%' (avec % au début) ne peut pas utiliser un index et force un scan complet de la table. Évitez-le sur de grandes tables. Pour la recherche full-text, utilisez FULLTEXT INDEX avec MATCH ... AGAINST.

32

CASE WHEN — Conditions dans les requêtes

AvancéRequête

CASE WHEN est l'équivalent SQL du if/else. Il permet de créer des colonnes calculées selon des conditions.

SQL — CASE WHEN
-- Catégoriser les utilisateurs par âge
SELECT nom, age,
  CASE
    WHEN age < 18 THEN 'Mineur'
    WHEN age BETWEEN 18 AND 25 THEN 'Jeune adulte'
    WHEN age BETWEEN 26 AND 60 THEN 'Adulte'
    ELSE 'Senior'
  END AS tranche_age
FROM utilisateurs;

-- Dans un GROUP BY : stats par catégorie de prix
SELECT
  CASE
    WHEN prix < 1000  THEN 'Entrée de gamme'
    WHEN prix < 10000 THEN 'Milieu de gamme'
    ELSE 'Haut de gamme'
  END AS gamme,
  COUNT(*) AS nb_produits,
  AVG(prix) AS prix_moyen
FROM produits
GROUP BY gamme;
33

INDEX — Indexation

PerformanceDDL

Un index est une structure de données qui accélère la recherche dans une table — comme l'index d'un livre. Sans index, MySQL lit chaque ligne (scan complet). Avec index : accès direct.

SQL — Gestion des index
-- Créer un index simple
CREATE INDEX idx_email ON utilisateurs (email);

-- Index unique (comme UNIQUE + INDEX)
CREATE UNIQUE INDEX idx_email_unique ON utilisateurs (email);

-- Index composite (plusieurs colonnes)
CREATE INDEX idx_pays_age ON utilisateurs (pays, age);

-- Voir les index d'une table
SHOW INDEX FROM utilisateurs;

-- Supprimer un index
DROP INDEX idx_email ON utilisateurs;

Impact sur les performances :

Sans index (1M lignes)
~2 sec
Avec index (1M lignes)
<5 ms
⚠️

N'indexez pas tout ! Chaque index ralentit les INSERT/UPDATE/DELETE (car l'index doit être mis à jour). Indexez uniquement les colonnes utilisées fréquemment dans WHERE, JOIN et ORDER BY.

34

EXPLAIN — Analyser une requête

Performance

EXPLAIN révèle comment MySQL exécute votre requête : quels index sont utilisés, combien de lignes sont examinées, etc.

SQL
EXPLAIN SELECT * FROM utilisateurs
WHERE email = 'aliou@gmail.com';
Colonne EXPLAINSignification
typeALL = scan complet (lent) / ref ou eq_ref = index utilisé (rapide)
keyNom de l'index utilisé (NULL = aucun index)
rowsEstimation du nombre de lignes examinées
Extra"Using index" = très rapide / "Using filesort" = tri en mémoire (à optimiser)

Si type = ALL et rows est grand → votre requête fait un scan complet. Ajoutez un index sur la colonne dans le WHERE pour l'accélérer drastiquement.

35

Les Vues (VIEW)

AvancéDDL

Une vue est une table virtuelle créée à partir d'une requête. Elle ne stocke pas les données mais les recalcule à chaque appel.

SQL
-- Créer une vue
CREATE VIEW vue_clients_actifs AS
SELECT id, nom, email
FROM utilisateurs
WHERE actif = 1;

-- Utiliser la vue comme une table
SELECT * FROM vue_clients_actifs;

-- Vue complexe avec JOIN
CREATE VIEW vue_commandes_detaillees AS
SELECT
  u.nom       AS client,
  c.montant,
  c.date_commande
FROM utilisateurs u
INNER JOIN commandes c ON u.id = c.utilisateur_id;

-- Modifier une vue
CREATE OR REPLACE VIEW vue_clients_actifs AS
SELECT id, nom, email, pays
FROM utilisateurs WHERE actif = 1;

-- Supprimer une vue
DROP VIEW vue_clients_actifs;

Les vues servent à : simplifier des requêtes complexes, masquer des colonnes sensibles (mots de passe, données personnelles) pour certains utilisateurs, centraliser une logique de filtrage réutilisée partout.

36

Les Triggers

Avancé

Un trigger s'exécute automatiquement lors d'un événement sur une table (INSERT, UPDATE, DELETE). On peut le déclencher BEFORE ou AFTER l'événement.

SQL — Trigger journalisation
DELIMITER $$

CREATE TRIGGER log_inscription
AFTER INSERT ON utilisateurs
FOR EACH ROW
BEGIN
  INSERT INTO journal (action, details, date_action)
  VALUES ('Inscription', NEW.email, NOW());
END$$

-- Trigger BEFORE UPDATE : validation avant modification
CREATE TRIGGER verif_age
BEFORE INSERT ON utilisateurs
FOR EACH ROW
BEGIN
  IF NEW.age < 0 THEN
    SIGNAL SQLSTATE '45000'
      SET MESSAGE_TEXT = 'Âge invalide';
  END IF;
END$$

DELIMITER ;
ÉvénementNEWOLD
INSERT✅ Disponible❌ Non disponible
UPDATE✅ Nouvelle valeur✅ Ancienne valeur
DELETE❌ Non disponible✅ Disponible
37

Procédures Stockées

Avancé

Une procédure stockée est un programme SQL enregistré dans la base, réutilisable avec des paramètres entrants (IN), sortants (OUT) ou les deux (INOUT).

SQL
DELIMITER $$

CREATE PROCEDURE rechercherUtilisateur(IN recherche VARCHAR(100))
BEGIN
  SELECT * FROM utilisateurs
  WHERE nom LIKE CONCAT('%', recherche, '%');
END$$

-- Procédure avec paramètre OUT (retour de valeur)
CREATE PROCEDURE compterUtilisateurs(
  IN  p_pays  VARCHAR(50),
  OUT p_total INT
)
BEGIN
  SELECT COUNT(*) INTO p_total
  FROM utilisateurs
  WHERE pays = p_pays;
END$$

DELIMITER ;

-- Appeler les procédures
CALL rechercherUtilisateur('Ali');

CALL compterUtilisateurs('Sénégal', @total);
SELECT @total;
38

Fonctions Personnalisées (UDF)

Avancé

Une fonction personnalisée (User Defined Function) retourne une seule valeur et peut être utilisée directement dans un SELECT, WHERE ou ORDER BY.

SQL
DELIMITER $$

-- Fonction qui calcule la TVA
CREATE FUNCTION calculerTTC(prix_ht DECIMAL(10,2), taux_tva DECIMAL(5,2))
RETURNS DECIMAL(10,2)
DETERMINISTIC
BEGIN
  RETURN prix_ht * (1 + taux_tva / 100);
END$$

DELIMITER ;

-- Utilisation dans une requête
SELECT nom, prix, calculerTTC(prix, 18) AS prix_ttc
FROM produits;

✅ Fonction (FUNCTION)

  • Retourne une valeur
  • Utilisable dans SELECT/WHERE
  • Ne peut pas modifier des données

✅ Procédure (PROCEDURE)

  • Peut retourner plusieurs résultats
  • Appelée avec CALL
  • Peut modifier des données
39

Transactions — START, COMMIT, ROLLBACK

AvancéSécurité

Une transaction est un groupe d'opérations qui doivent toutes réussir ou toutes échouer. Elle garantit l'intégrité des données dans les opérations critiques (principe ACID).

🔒 Atomicité — tout ou rien
Cohérence — état valide
🧱 Isolation — indépendante
💾 Durabilité — persistante
SQL — Virement bancaire
START TRANSACTION;

UPDATE comptes
SET solde = solde - 5000
WHERE id = 1; -- Débit compte source

UPDATE comptes
SET solde = solde + 5000
WHERE id = 2; -- Crédit compte destination

COMMIT; -- ✅ Valider si tout réussit
-- ROLLBACK; -- ❌ Annuler tout en cas d'erreur

-- SAVEPOINT : points de restauration intermédiaires
START TRANSACTION;
INSERT INTO commandes ... ;
SAVEPOINT apres_commande;
UPDATE stocks ... ;
ROLLBACK TO apres_commande; -- Annule seulement depuis le SAVEPOINT
COMMIT;
CommandeEffet
START TRANSACTIONDémarre une transaction
COMMITValide — changements permanents
ROLLBACKAnnule tout depuis START
SAVEPOINT nomCrée un point de restauration
ROLLBACK TO nomRevient à ce SAVEPOINT
40

Gestion des erreurs en MySQL

Avancé

Dans les procédures et triggers, MySQL permet de gérer les erreurs avec des gestionnaires (HANDLER) pour éviter les arrêts brutaux.

SQL — Gestion d'erreurs
DELIMITER $$

CREATE PROCEDURE insererUtilisateur(
  IN p_nom   VARCHAR(100),
  IN p_email VARCHAR(150),
  OUT p_succes BOOLEAN
)
BEGIN
  DECLARE EXIT HANDLER FOR SQLEXCEPTION
  BEGIN
    SET p_succes = FALSE;
    ROLLBACK;
  END;

  START TRANSACTION;
  INSERT INTO utilisateurs (nom, email) VALUES (p_nom, p_email);
  SET p_succes = TRUE;
  COMMIT;
END$$

DELIMITER ;
🔒 Chapitre 5 — Transactions, Sécurité & Production
41

Moteurs de stockage : InnoDB vs MyISAM

ConceptPerformance

MySQL supporte plusieurs moteurs de stockage. Le choix du moteur impacte les performances, la sécurité et les fonctionnalités disponibles.

FonctionnalitéInnoDBMyISAM
Transactions (ACID)✅ Oui❌ Non
Clés étrangères (FK)✅ Oui❌ Non
Verrous de lignes✅ Oui❌ Table entière
Récupération crash✅ Automatique⚠️ Manuelle
Performances lecture⚡ Bonne⚡⚡ Très bonne
Full-Text Search✅ MySQL 5.6+✅ Oui

Utilisez toujours InnoDB (moteur par défaut depuis MySQL 5.5). Il est sûr, supporte les transactions et les clés étrangères. MyISAM ne devrait être utilisé que dans de très rares cas spécifiques de lecture intensive sans écriture concurrente.

42

Sécurité MySQL

Sécurité

La sécurité est fondamentale. Une mauvaise configuration peut exposer des données sensibles ou permettre une destruction totale de la base.

SQL — Gestion des utilisateurs
-- Créer un utilisateur dédié à l'application (ne jamais utiliser root)
CREATE USER 'app_user'@'localhost'
  IDENTIFIED BY 'MotDePasse$ecur1s!';

-- Accorder uniquement les droits nécessaires (principe du moindre privilège)
GRANT SELECT, INSERT, UPDATE
  ON boutique.*
  TO 'app_user'@'localhost';

-- Appliquer les changements
FLUSH PRIVILEGES;

-- Voir les droits d'un utilisateur
SHOW GRANTS FOR 'app_user'@'localhost';

-- Révoquer des droits
REVOKE DELETE ON boutique.* FROM 'app_user'@'localhost';

-- Supprimer un utilisateur
DROP USER 'app_user'@'localhost';
🚨

Ne jamais :
• Utiliser root dans votre application web
• Stocker les mots de passe utilisateurs en clair (utilisez bcrypt ou argon2)
• Exposer le port 3306 directement sur Internet
• Donner des droits ALL PRIVILEGES à un utilisateur applicatif

43

Injection SQL — Comprendre et se protéger

Sécurité

L'injection SQL est l'une des attaques web les plus répandues. Elle consiste à injecter du code SQL malveillant dans les champs de formulaire pour manipuler la base.

Exemple d'attaque
-- Si email reçu = "admin'--" l'attaquant contourne le mot de passe
-- ❌ DANGEREUX : concaténation directe
"SELECT * FROM users WHERE email = '" + email + "'"
-- Devient : SELECT * FROM users WHERE email = 'admin'--'
-- Le -- commente le reste → accès sans mot de passe !

-- ✅ SOLUTION : Requêtes préparées (paramétrisées)
-- En PHP avec PDO :
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = ?");
$stmt->execute([$email]); // Le paramètre est échappé automatiquement

❌ Vulnérable

  • Concaténation de chaînes
  • Données non validées
  • Erreurs affichées aux utilisateurs

✅ Sécurisé

  • Requêtes préparées (PDO, mysqli)
  • Validation et sanitisation des entrées
  • Gestion d'erreurs discrète
44

Normalisation des bases de données

Concept

La normalisation est le processus d'organisation d'une base pour réduire la redondance et améliorer l'intégrité des données. Elle suit des formes normales (NF).

Forme NormaleRègle principale
1NFValeurs atomiques (pas de listes dans une cellule), chaque colonne a un seul type
2NFDépendance totale envers la clé primaire (pas de dépendance partielle)
3NFPas de dépendance transitive (colonnes qui dépendent d'une autre colonne non-clé)
BCNFForme de Boyce-Codd — variante plus stricte de la 3NF
💡

En pratique, viser la 3NF est suffisant pour la majorité des applications. Une base bien normalisée : moins de données dupliquées, moins d'anomalies de mise à jour, structure plus cohérente.

45

Sauvegardes et Restauration

SécuritéPratique

Une sauvegarde régulière est non-négociable en production. Une panne, une erreur humaine ou une attaque peuvent entraîner une perte totale et irréversible.

Terminal (Bash)
# Sauvegarder une base
mysqldump -u root -p boutique > sauvegarde_2026-06-08.sql

# Avec compression gzip
mysqldump -u root -p boutique | gzip > sauvegarde_2026-06-08.sql.gz

# Sauvegarder toutes les bases
mysqldump -u root -p --all-databases > toutes_les_bases.sql

# Sauvegarder seulement la structure (sans données)
mysqldump -u root -p --no-data boutique > structure.sql

# Restaurer une base
mysql -u root -p boutique < sauvegarde_2026-06-08.sql

# Restaurer depuis gzip
gunzip < sauvegarde_2026-06-08.sql.gz | mysql -u root -p boutique

# Automatiser avec cron (tous les jours à 2h du matin)
# 0 2 * * * mysqldump -u root -pmotdepasse boutique > /backup/$(date +%Y-%m-%d).sql

Règle 3-2-1 : 3 copies, sur 2 types de supports différents, dont 1 hors site (cloud, disque externe). Testez régulièrement vos restaurations — une sauvegarde non testée n'est pas fiable !

46

Réplication MySQL

PerformanceSécurité

La réplication copie automatiquement les données d'un serveur principal (master) vers un ou plusieurs serveurs secondaires (replicas). Elle sert à la tolérance aux pannes et à répartir la charge.

🏎️ Lectures distribuées sur les replicas
🛡️ Haute disponibilité
💾 Sauvegardes sans impact production
🌍 Distribution géographique
ArchitectureDescription
Master → ReplicaUn serveur écrit, les autres lisent uniquement
Master → MasterLes deux peuvent écrire (complexe, risqué)
Master → Relay → ReplicasChaîne de réplication (grand nombre de replicas)
47

Optimisation des Performances

Performance

Sur de grandes tables (millions de lignes), les performances deviennent critiques. Un index bien placé peut transformer une requête de 10 secondes en 10 millisecondes.

SELECT * sans index
~8 sec
SELECT cols sans index
~5 sec
SELECT * avec index
~60 ms
SELECT cols + index couvrant
<5 ms

❌ Lent

  • SELECT * sur grandes tables
  • Pas d'index sur WHERE/JOIN
  • LIKE '%mot%'
  • Sous-requêtes dans des boucles
  • N+1 requêtes (ORM non optimisé)

✅ Rapide

  • Colonnes utiles uniquement
  • INDEX sur colonnes filtrées
  • LIKE 'mot%' (préfixe)
  • JOIN à la place des sous-requêtes
  • LIMIT sur toutes les listes
48

MySQL en Production — Checklist

PratiqueSécurité

Avant de mettre une base MySQL en production, vérifiez ces points essentiels.

CatégorieActionPriorité
🔒 SécuritéDésactiver l'accès root distant🔴 Critique
🔒 SécuritéPare-feu sur le port 3306🔴 Critique
🔒 SécuritéUtilisateur dédié à l'app (pas root)🔴 Critique
💾 BackupSauvegardes automatisées quotidiennes🔴 Critique
⚡ PerfIndex sur toutes les FK et colonnes WHERE🟠 Important
⚡ PerfActiver le slow_query_log🟠 Important
📊 MonitoringAlertes sur CPU, mémoire, espace disque🟠 Important
🔧 ConfigAjuster innodb_buffer_pool_size🟡 Recommandé

Activez le slow query log en production : SET GLOBAL slow_query_log = 'ON'; et SET GLOBAL long_query_time = 1; — MySQL enregistrera toutes les requêtes prenant plus d'1 seconde pour que vous puissiez les optimiser.

49

Projet Complet — Base de Données École

Pratique

Appliquons tout ce que nous avons appris en créant une base de données de gestion scolaire complète avec toutes les bonnes pratiques.

SQL — Projet École complet
CREATE DATABASE ecole
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_unicode_ci;
USE ecole;

CREATE TABLE etudiants (
  id             INT AUTO_INCREMENT PRIMARY KEY,
  nom            VARCHAR(100) NOT NULL,
  prenom         VARCHAR(100) NOT NULL,
  email          VARCHAR(150) UNIQUE NOT NULL,
  date_naissance DATE,
  cree_le        TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE cours (
  id         INT AUTO_INCREMENT PRIMARY KEY,
  titre      VARCHAR(200) NOT NULL,
  credits    INT DEFAULT 3,
  professeur VARCHAR(100),
  duree_h    INT
);

CREATE TABLE inscriptions (
  id               INT AUTO_INCREMENT PRIMARY KEY,
  etudiant_id      INT NOT NULL,
  cours_id         INT NOT NULL,
  date_inscription DATETIME DEFAULT CURRENT_TIMESTAMP,
  note             DECIMAL(4,2) CHECK (note BETWEEN 0 AND 20),
  FOREIGN KEY (etudiant_id) REFERENCES etudiants(id) ON DELETE CASCADE,
  FOREIGN KEY (cours_id)    REFERENCES cours(id)    ON DELETE CASCADE
);

-- Étudiants avec leurs notes par cours
SELECT
  CONCAT(e.prenom, ' ', e.nom) AS etudiant,
  c.titre                         AS cours,
  i.note,
  CASE
    WHEN i.note >= 16 THEN 'Très Bien'
    WHEN i.note >= 14 THEN 'Bien'
    WHEN i.note >= 10 THEN 'Passable'
    ELSE 'Insuffisant'
  END AS mention
FROM inscriptions i
INNER JOIN etudiants e ON i.etudiant_id = e.id
INNER JOIN cours     c ON i.cours_id    = c.id
ORDER BY etudiant, cours;

-- Moyenne par étudiant et classement
SELECT
  CONCAT(e.prenom, ' ', e.nom) AS etudiant,
  ROUND(AVG(i.note), 2)         AS moyenne,
  COUNT(i.cours_id)               AS nb_cours
FROM etudiants e
LEFT JOIN inscriptions i ON e.id = i.etudiant_id
GROUP BY e.id
HAVING moyenne >= 10
ORDER BY moyenne DESC;
50

Les Bonnes Pratiques MySQL

Pratique

Pour devenir un excellent développeur ou administrateur MySQL, voici les règles d'or à intégrer dans tous vos projets :

RèglePourquoi c'est importantNiveau
✏️ Noms explicites (snake_case)Code lisible et maintenable🟢 Débutant
🔑 Toujours une clé primaireIdentification unique, performance🟢 Débutant
🌍 utf8mb4 sur toutes les basesSupport universel des caractères🟢 Débutant
📇 Index sur FK et colonnes filtréesRequêtes 100x plus rapides🟡 Intermédiaire
💾 Sauvegardes automatiséesProtection contre les pertes🟡 Intermédiaire
🔒 Principe du moindre privilègeSécurité renforcée🟡 Intermédiaire
🚫 Normalisation (éviter la duplication)Cohérence et économie d'espace🟡 Intermédiaire
⚡ Requêtes préparées (anti-injection)Sécurité fondamentale🟡 Intermédiaire
⏱️ Utiliser les transactionsIntégrité des opérations critiques🔴 Avancé
🔍 EXPLAIN sur les requêtes lentesDétecter et corriger les goulots🔴 Avancé
📊 Surveiller le slow_query_logPerformance proactive🔴 Avancé
🧪 Environnement dev séparéÉviter les catastrophes prod🔴 Avancé
🎉

Félicitations ! Vous avez terminé les 50 leçons MySQL — de la création d'une base aux transactions, triggers, procédures stockées et sécurité. Vous maîtrisez maintenant les fondations solides pour travailler sur des projets réels. La prochaine étape : pratiquer sur de vrais projets et explorer des sujets comme MySQL Cluster, le partitionnement ou les bases de données distribuées !