Skip to main content

Code that compiles on the first try! CodeWithMpia wishes you very happy holidays.✨

Aller au contenu principal

SQL : De zéro à autonome, le guide ultime pour interroger vos bases de données.

Un guide progressif qui explique, illustre et met en pratique toutes les notions clés de SQL (bases de données, requêtes, jointures, agrégation, optimisation) pour vous permettre de passer de débutant complet à autonome sur des cas réels.

M
Mpia
12/14/2025 80 min
72 vues
0 comme.
#SQL#Requêtes#DB
SQL : De zéro à autonome, le guide ultime pour interroger vos bases de données.

Vous avez entendu parler de SQL partout. "Apprends SQL", "SQL c'est essentiel", "Toutes les applications utilisent le SQL". Mais quand vous ouvrez un tutoriel, vous voyez SELECT * FROM WHERE JOIN ON et vous fermez l'onglet. 😵

Le problème ? La plupart des tutoriels SQL vous balancent 50 commandes d'un coup sans vous expliquer pourquoi ni dans quel ordre les apprendre.

La réalité ? SQL est simple. Vraiment. C'est presque de l'anglais. SELECT nom FROM clients WHERE age > 18 → "Sélectionne le nom depuis les clients où l'âge est supérieur à 18". Vous comprenez déjà !

Dans ce guide ultra-complet mais progressif, on va apprendre SQL de zéro, avec des exemples concrets basés sur une vraie base de données d'entreprise. Pas de théorie inutile, que du pratique.

Ce que vous allez apprendre :

  • Les fondamentaux (SELECT, WHERE, ORDER BY)

  • Filtrer et trier les données comme un pro

  • Les fonctions d'agrégation (COUNT, SUM, AVG, etc.)

  • Les jointures (INNER, LEFT, RIGHT, FULL)

  • Les sous-requêtes et requêtes imbriquées

  • Créer et modifier des bases de données (CREATE, ALTER, DROP)

  • Gérer les données (INSERT, UPDATE, DELETE)

  • Les index et optimisation des performances

  • Les transactions et la sécurité

  • SQL avancé (window functions, CTE, etc.)

Après cet article, vous pourrez :

  • Extraire n'importe quelle donnée d'une base

  • Créer vos propres bases de données

  • Optimiser des requêtes lentes

  • Passer des entretiens techniques SQL

  • Utiliser SQL dans n'importe quel contexte (MySQL, PostgreSQL, SQLite, etc.)

Allez, on plonge dans le SQL ! 🚀

Partie 1 : Comprendre les bases de données relationnelles

C'est quoi SQL ?

SQL = Structured Query Language (Langage de requête structuré)

C'est le langage pour parler aux bases de données. Vous posez des questions (requêtes), la base répond avec des données.

Exemples de questions SQL :

  • "Montre-moi tous les clients"

  • "Combien de produits ai-je vendus ce mois ?"

  • "Qui sont mes 10 meilleurs clients ?"

  • "Quels produits sont en rupture de stock ?"

Les différents dialectes SQL

SQL a plusieurs "versions" selon la base de données :

  • MySQL : Le plus populaire (web, WordPress, etc.)

  • PostgreSQL : Puissant et open source (startup, entreprises)

  • SQLite : Léger, un seul fichier (mobile, petits projets)

  • SQL Server : Microsoft (entreprises Windows)

  • Oracle : Entreprises (très cher)

Bonne nouvelle : 95% du SQL est identique partout. Apprenez les bases, vous savez tout utiliser.

Pour ce tutoriel : On utilise SQLite (facile, aucune installation), mais tout fonctionne sur MySQL/PostgreSQL.

Structure d'une base de données

Base de donnéesTablesLignes (enregistrements)Colonnes (champs)

Base de données : ENTREPRISE

├── Table : clients
   ├── Ligne 1 : id=1, nom="Alice", email="alice@mail.com"
   ├── Ligne 2 : id=2, nom="Bob", email="bob@mail.com"
   └── ...

├── Table : produits
   ├── Ligne 1 : id=1, nom="Ordinateur", prix=899.99
   └── ...

└── Table : ventes
    └── ...

Notre base de données exemple

On va créer une base pour une entreprise de vente en ligne avec :

Table : clients

id nom email ville date_inscription
1 Alice Dupont alice@mail.com Paris 2024-01-15
2 Bob Martin bob@mail.com Lyon 2024-02-20
3 Charlie Dubois charlie@mail.com Paris 2024-03-10

Table : produits

id nom categorie prix stock
1 MacBook Pro Informatique 2499.00 10
2 iPhone 15 Téléphonie 969.00 25
3 AirPods Pro Audio 279.00 50

Table : commandes

id client_id produit_id quantite date_commande
1 1 1 1 2024-03-15
2 2 2 2 2024-03-16
3 1 3 1 2024-03-17

Partie 2 : Créer la base de données

Avant d'interroger, créons notre base !

Créer les tables

-- Créer la table clients
CREATE TABLE clients (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    nom VARCHAR(100) NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    telephone VARCHAR(20),
    ville VARCHAR(50),
    date_inscription DATE DEFAULT CURRENT_DATE
);

-- Créer la table produits
CREATE TABLE produits (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    nom VARCHAR(100) NOT NULL,
    categorie VARCHAR(50),
    prix DECIMAL(10, 2) NOT NULL,
    stock INTEGER DEFAULT 0,
    description TEXT
);

-- Créer la table commandes
CREATE TABLE commandes (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    client_id INTEGER NOT NULL,
    produit_id INTEGER NOT NULL,
    quantite INTEGER NOT NULL,
    prix_unitaire DECIMAL(10, 2) NOT NULL,
    montant_total DECIMAL(10, 2) NOT NULL,
    date_commande TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (client_id) REFERENCES clients(id),
    FOREIGN KEY (produit_id) REFERENCES produits(id)
);

Explications :

  • PRIMARY KEY : Identifiant unique

  • AUTOINCREMENT : Auto-incrémente (1, 2, 3...)

  • NOT NULL : Obligatoire

  • UNIQUE : Pas de doublons

  • DEFAULT : Valeur par défaut

  • FOREIGN KEY : Lien vers une autre table

  • VARCHAR(n) : Texte jusqu'à n caractères

  • TEXT : Texte long

  • INTEGER : Nombre entier

  • DECIMAL(10,2) : Nombre décimal (10 chiffres, 2 après virgule)

Insérer des données

Pour peupler ou insérer les données dans notre base de données, nous utilisons l'instruction INSERT.

Voici un exemple :

-- Insérer des clients
INSERT INTO clients (nom, email, telephone, ville) VALUES
('Alice Dupont', 'alice@mail.com', '0601020304', 'Paris'),
('Bob Martin', 'bob@mail.com', '0605060708', 'Lyon'),
('Charlie Dubois', 'charlie@mail.com', '0609101112', 'Paris'),
('Diana Petit', 'diana@mail.com', '0612131415', 'Marseille'),
('Eve Bernard', 'eve@mail.com', '0616171819', 'Lyon');

-- Insérer des produits
INSERT INTO produits (nom, categorie, prix, stock) VALUES
('MacBook Pro', 'Informatique', 2499.00, 10),
('iPhone 15', 'Téléphonie', 969.00, 25),
('AirPods Pro', 'Audio', 279.00, 50),
('iPad Air', 'Informatique', 699.00, 15),
('Apple Watch', 'Accessoires', 449.00, 30),
('Magic Mouse', 'Accessoires', 89.00, 40),
('MacBook Air', 'Informatique', 1299.00, 12);

-- Insérer des commandes
INSERT INTO commandes (client_id, produit_id, quantite, prix_unitaire, montant_total) VALUES
(1, 1, 1, 2499.00, 2499.00),
(2, 2, 2, 969.00, 1938.00),
(1, 3, 1, 279.00, 279.00),
(3, 4, 1, 699.00, 699.00),
(4, 5, 1, 449.00, 449.00),
(2, 6, 2, 89.00, 178.00),
(5, 7, 1, 1299.00, 1299.00),
(1, 5, 1, 449.00, 449.00);

Maintenant, on a des données ! On peut commencer à interroger.

Partie 3 : SELECT - La base de tout

SELECT est LA commande la plus importante. Vous l'utiliserez 90% du temps.

SELECT basique

-- Tout sélectionner
SELECT * FROM clients;

-- Sélectionner des colonnes spécifiques
SELECT nom, email FROM clients;

-- Sélectionner avec alias (renommer)
SELECT nom AS client_nom, email AS client_email FROM clients;

-- Sélectionner des valeurs calculées
SELECT nom, prix, prix * 0.8 AS prix_promo FROM produits;

Résultat de SELECT nom, email FROM clients :

nom             | email
----------------|------------------
Alice Dupont    | alice@mail.com
Bob Martin      | bob@mail.com
Charlie Dubois  | charlie@mail.com
...

SELECT DISTINCT (éliminer les doublons)

-- Toutes les villes (avec doublons)
SELECT ville FROM clients;
-- Paris, Lyon, Paris, Marseille, Lyon

-- Toutes les villes (sans doublons)
SELECT DISTINCT ville FROM clients;
-- Paris, Lyon, Marseille

-- Toutes les catégories de produits
SELECT DISTINCT categorie FROM produits;

LIMIT (limiter le nombre de résultats)

-- Les 3 premiers clients
SELECT * FROM clients LIMIT 3;

-- Les 5 premiers produits
SELECT nom, prix FROM produits LIMIT 5;

-- Pagination : 10 résultats, en commençant au 20ème
SELECT * FROM clients LIMIT 10 OFFSET 20;

Partie 4 : WHERE - Filtrer les données

WHERE permet de filtrer les résultats selon des conditions.

Comparaisons simples

-- Clients de Paris
SELECT * FROM clients WHERE ville = 'Paris';

-- Produits de plus de 500€
SELECT nom, prix FROM produits WHERE prix > 500;

-- Produits à exactement 279€
SELECT * FROM produits WHERE prix = 279.00;

-- Produits à 500€ ou plus
SELECT * FROM produits WHERE prix >= 500;

-- Produits de moins de 1000€
SELECT * FROM produits WHERE prix < 1000;

-- Produits différents de "Accessoires"
SELECT * FROM produits WHERE categorie != 'Accessoires';
-- Ou :
SELECT * FROM produits WHERE categorie <> 'Accessoires';

Opérateurs logiques (AND, OR, NOT)

-- Produits Informatique de plus de 1000€
SELECT * FROM produits 
WHERE categorie = 'Informatique' AND prix > 1000;

-- Produits de Paris OU Lyon
SELECT * FROM clients 
WHERE ville = 'Paris' OR ville = 'Lyon';

-- Produits Informatique OU Audio
SELECT * FROM produits 
WHERE categorie = 'Informatique' OR categorie = 'Audio';

-- Produits entre 200 et 1000€
SELECT * FROM produits 
WHERE prix >= 200 AND prix <= 1000;

-- Clients qui ne sont PAS de Paris
SELECT * FROM clients 
WHERE NOT ville = 'Paris';
-- Équivalent :
SELECT * FROM clients WHERE ville != 'Paris';

-- Produits Informatique de plus de 1000€ OU produits Audio
SELECT * FROM produits 
WHERE (categorie = 'Informatique' AND prix > 1000) 
   OR categorie = 'Audio';

⚠️ Attention à la priorité : Utilisez des parenthèses pour clarifier !

BETWEEN (entre deux valeurs)

-- Produits entre 200€ et 1000€
SELECT * FROM produits WHERE prix BETWEEN 200 AND 1000;

-- Commandes de mars 2024
SELECT * FROM commandes 
WHERE date_commande BETWEEN '2024-03-01' AND '2024-03-31';

IN (dans une liste)

-- Clients de Paris, Lyon ou Marseille
SELECT * FROM clients WHERE ville IN ('Paris', 'Lyon', 'Marseille');

-- Produits Informatique ou Téléphonie
SELECT * FROM produits WHERE categorie IN ('Informatique', 'Téléphonie');

-- Inverse (NOT IN)
SELECT * FROM clients WHERE ville NOT IN ('Paris', 'Lyon');

LIKE (recherche de motifs)

-- Clients dont le nom commence par "A"
SELECT * FROM clients WHERE nom LIKE 'A%';

-- Clients dont le nom contient "Martin"
SELECT * FROM clients WHERE nom LIKE '%Martin%';

-- Clients dont le nom se termine par "Dupont"
SELECT * FROM clients WHERE nom LIKE '%Dupont';

-- Produits dont le nom contient "Mac"
SELECT * FROM produits WHERE nom LIKE '%Mac%';

-- Email se terminant par "gmail.com"
SELECT * FROM clients WHERE email LIKE '%@gmail.com';

-- _ = un seul caractère
-- Noms de 3 lettres commençant par "B"
SELECT * FROM clients WHERE nom LIKE 'B__';

Symboles :

  • % : N'importe quelle séquence de caractères (0 ou plus)

  • _ : Exactement un caractère

IS NULL / IS NOT NULL

-- Clients sans téléphone
SELECT * FROM clients WHERE telephone IS NULL;

-- Clients avec un téléphone
SELECT * FROM clients WHERE telephone IS NOT NULL;

-- Produits sans description
SELECT * FROM produits WHERE description IS NULL;

⚠️ Attention : N'utilisez JAMAIS = NULL, utilisez IS NULL !

Partie 5 : ORDER BY - Trier les résultats

Tri simple

-- Clients triés par nom (A-Z)
SELECT * FROM clients ORDER BY nom;

-- Clients triés par nom décroissant (Z-A)
SELECT * FROM clients ORDER BY nom DESC;

-- Produits triés par prix croissant
SELECT * FROM produits ORDER BY prix;

-- Produits triés par prix décroissant (du plus cher au moins cher)
SELECT * FROM produits ORDER BY prix DESC;

-- Commandes triées par date (plus récentes d'abord)
SELECT * FROM commandes ORDER BY date_commande DESC;

Tri sur plusieurs colonnes

-- Clients triés par ville, puis par nom
SELECT * FROM clients ORDER BY ville, nom;

-- Produits triés par catégorie (A-Z), puis par prix (décroissant)
SELECT * FROM produits ORDER BY categorie ASC, prix DESC;

-- Commandes triées par client, puis par date
SELECT * FROM commandes ORDER BY client_id, date_commande DESC;

Tri avec calculs

-- Produits triés par prix avec TVA (20%)
SELECT nom, prix, prix * 1.20 AS prix_ttc 
FROM produits 
ORDER BY prix_ttc DESC;

-- Commandes triées par montant total
SELECT * FROM commandes ORDER BY montant_total DESC;

Partie 6 : Fonctions d'agrégation

Les fonctions d'agrégation calculent des valeurs sur plusieurs lignes.

COUNT (compter)

-- Nombre total de clients
SELECT COUNT(*) FROM clients;

-- Nombre de clients avec un téléphone
SELECT COUNT(telephone) FROM clients;

-- Nombre de clients distincts ayant commandé
SELECT COUNT(DISTINCT client_id) FROM commandes;

-- Nombre de produits par catégorie
SELECT categorie, COUNT(*) AS nombre_produits 
FROM produits 
GROUP BY categorie;

SUM (somme)

-- Montant total de toutes les commandes
SELECT SUM(montant_total) FROM commandes;

-- Quantité totale vendue
SELECT SUM(quantite) FROM commandes;

-- Chiffre d'affaires par client
SELECT client_id, SUM(montant_total) AS ca_total 
FROM commandes 
GROUP BY client_id;

AVG (moyenne)

-- Prix moyen des produits
SELECT AVG(prix) FROM produits;

-- Panier moyen (montant moyen des commandes)
SELECT AVG(montant_total) FROM commandes;

-- Prix moyen par catégorie
SELECT categorie, AVG(prix) AS prix_moyen 
FROM produits 
GROUP BY categorie;

MIN et MAX (minimum et maximum)

-- Produit le moins cher
SELECT MIN(prix) FROM produits;

-- Produit le plus cher
SELECT MAX(prix) FROM produits;

-- Prix min et max par catégorie
SELECT 
    categorie, 
    MIN(prix) AS prix_min, 
    MAX(prix) AS prix_max 
FROM produits 
GROUP BY categorie;

-- Première et dernière commande
SELECT 
    MIN(date_commande) AS premiere_commande,
    MAX(date_commande) AS derniere_commande
FROM commandes;

GROUP BY (regrouper)

GROUP BY regroupe les lignes par valeurs communes.

-- Nombre de clients par ville
SELECT ville, COUNT(*) AS nb_clients 
FROM clients 
GROUP BY ville;

-- Résultat :
-- Paris     | 2
-- Lyon      | 2
-- Marseille | 1

-- Chiffre d'affaires par client
SELECT 
    client_id, 
    COUNT(*) AS nb_commandes,
    SUM(montant_total) AS ca_total 
FROM commandes 
GROUP BY client_id;

-- Stock total par catégorie
SELECT 
    categorie, 
    SUM(stock) AS stock_total,
    COUNT(*) AS nb_produits
FROM produits 
GROUP BY categorie;

-- Ventes par mois
SELECT 
    strftime('%Y-%m', date_commande) AS mois,
    COUNT(*) AS nb_ventes,
    SUM(montant_total) AS ca_mois
FROM commandes 
GROUP BY strftime('%Y-%m', date_commande)
ORDER BY mois;

HAVING (filtrer les groupes)

HAVING filtre après le GROUP BY (WHERE filtre avant).

-- Villes avec plus de 1 client
SELECT ville, COUNT(*) AS nb_clients 
FROM clients 
GROUP BY ville 
HAVING COUNT(*) > 1;

-- Clients ayant dépensé plus de 1000€
SELECT 
    client_id, 
    SUM(montant_total) AS ca_total 
FROM commandes 
GROUP BY client_id 
HAVING SUM(montant_total) > 1000;

-- Catégories avec un prix moyen > 500€
SELECT 
    categorie, 
    AVG(prix) AS prix_moyen 
FROM produits 
GROUP BY categorie 
HAVING AVG(prix) > 500;

-- Produits vendus plus de 2 fois
SELECT 
    produit_id, 
    COUNT(*) AS nb_ventes 
FROM commandes 
GROUP BY produit_id 
HAVING COUNT(*) > 2;

Différence WHERE vs HAVING :

-- WHERE : filtre AVANT regroupement
SELECT categorie, COUNT(*) 
FROM produits 
WHERE prix > 100  -- Filtre les produits individuels
GROUP BY categorie;

-- HAVING : filtre APRÈS regroupement
SELECT categorie, COUNT(*) 
FROM produits 
GROUP BY categorie 
HAVING COUNT(*) > 2;  -- Filtre les groupes

Partie 7 : Jointures (JOINS) - Combiner des tables

Les jointures lient des tables entre elles. LE concept le plus important en SQL.

INNER JOIN (jointure interne)

Retourne seulement les lignes qui ont une correspondance dans les deux tables.

-- Commandes avec nom du client
SELECT 
    commandes.id,
    clients.nom AS client,
    commandes.montant_total,
    commandes.date_commande
FROM commandes
INNER JOIN clients ON commandes.client_id = clients.id;

-- Avec alias (plus court)
SELECT 
    c.id,
    cl.nom AS client,
    c.montant_total,
    c.date_commande
FROM commandes c
INNER JOIN clients cl ON c.client_id = cl.id;

-- Commandes avec client ET produit
SELECT 
    cm.id,
    cl.nom AS client,
    p.nom AS produit,
    cm.quantite,
    cm.montant_total
FROM commandes cm
INNER JOIN clients cl ON cm.client_id = cl.id
INNER JOIN produits p ON cm.produit_id = p.id;

-- Résultat :
-- id | client        | produit      | quantite | montant_total
-- 1  | Alice Dupont  | MacBook Pro  | 1        | 2499.00
-- 2  | Bob Martin    | iPhone 15    | 2        | 1938.00
-- ...

Visualisation :

Commandes          Clients
client_id=1   →   id=1 (Alice)
client_id=2   →   id=2 (Bob)
client_id=1   →   id=1 (Alice)

LEFT JOIN (jointure gauche)

Retourne toutes les lignes de la table de gauche, avec correspondances de droite (NULL si pas de correspondance).

-- Tous les clients avec leurs commandes (même ceux sans commande)
SELECT 
    cl.nom,
    cl.email,
    COUNT(cm.id) AS nb_commandes,
    COALESCE(SUM(cm.montant_total), 0) AS ca_total
FROM clients cl
LEFT JOIN commandes cm ON cl.id = cm.client_id
GROUP BY cl.id;

-- Résultat :
-- Alice Dupont  | alice@mail.com | 3 | 3227.00
-- Bob Martin    | bob@mail.com   | 2 | 2116.00
-- Charlie       | ...            | 1 | 699.00
-- Diana         | ...            | 1 | 449.00
-- Eve           | ...            | 1 | 1299.00

-- Clients sans commande
SELECT 
    cl.nom,
    cl.email
FROM clients cl
LEFT JOIN commandes cm ON cl.id = cm.client_id
WHERE cm.id IS NULL;

Visualisation :

Clients (TOUS)     Commandes
Alice      →       3 commandes
Bob        →       2 commandes
Charlie    →       1 commande
Diana      →       NULL (pas de commande)

RIGHT JOIN (jointure droite)

Inverse de LEFT JOIN. Rarement utilisé (on préfère inverser l'ordre et utiliser LEFT JOIN).

-- Équivalent avec RIGHT JOIN (peu utilisé)
SELECT cl.nom, COUNT(cm.id) 
FROM commandes cm
RIGHT JOIN clients cl ON cm.client_id = cl.id
GROUP BY cl.id;

-- Préférez LEFT JOIN :
SELECT cl.nom, COUNT(cm.id) 
FROM clients cl
LEFT JOIN commandes cm ON cl.id = cm.client_id
GROUP BY cl.id;

FULL OUTER JOIN (jointure complète)

Retourne toutes les lignes des deux tables. Pas supporté par SQLite (mais MySQL/PostgreSQL oui).

-- PostgreSQL/MySQL seulement
SELECT 
    cl.nom AS client,
    p.nom AS produit
FROM clients cl
FULL OUTER JOIN produits p ON 1=1;  -- Produit cartésien

CROSS JOIN (produit cartésien)

Combine chaque ligne de la première table avec chaque ligne de la seconde.

-- Toutes les combinaisons client-produit
SELECT 
    cl.nom AS client,
    p.nom AS produit
FROM clients
CROSS JOIN produits;

-- Si 5 clients et 7 produits = 35 résultats

Rarement utile, sauf cas spécifiques (calendriers, etc.).

Auto-jointure (SELF JOIN)

Joindre une table avec elle-même.

-- Exemple : Table employés avec manager_id
-- Trouver les employés et leur manager

-- Créons d'abord la table
CREATE TABLE employes (
    id INTEGER PRIMARY KEY,
    nom VARCHAR(100),
    manager_id INTEGER
);

INSERT INTO employes VALUES
(1, 'Alice', NULL),      -- PDG
(2, 'Bob', 1),           -- Manager sous Alice
(3, 'Charlie', 1),       -- Manager sous Alice
(4, 'Diana', 2),         -- Employé sous Bob
(5, 'Eve', 2);           -- Employé sous Bob

-- Auto-jointure pour voir employé et son manager
SELECT 
    e.nom AS employe,
    m.nom AS manager
FROM employes e
LEFT JOIN employes m ON e.manager_id = m.id;

-- Résultat :
-- Alice   | NULL
-- Bob     | Alice
-- Charlie | Alice
-- Diana   | Bob
-- Eve     | Bob

Partie 8 : Sous-requêtes (Subqueries)

Une requête à l'intérieur d'une autre requête.

Sous-requête dans WHERE

-- Produits plus chers que le prix moyen
SELECT nom, prix 
FROM produits 
WHERE prix > (SELECT AVG(prix) FROM produits);

-- Clients ayant commandé le produit le plus cher
SELECT nom 
FROM clients 
WHERE id IN (
    SELECT client_id 
    FROM commandes 
    WHERE produit_id = (
        SELECT id FROM produits ORDER BY prix DESC LIMIT 1
    )
);

-- Produits jamais commandés
SELECT nom 
FROM produits 
WHERE id NOT IN (
    SELECT DISTINCT produit_id FROM commandes
);

Sous-requête dans SELECT

-- Clients avec leur nombre de commandes
SELECT 
    nom,
    email,
    (SELECT COUNT(*) FROM commandes WHERE client_id = clients.id) AS nb_commandes
FROM clients;

-- Produits avec quantité vendue
SELECT 
    nom,
    prix,
    stock,
    (SELECT COALESCE(SUM(quantite), 0) 
     FROM commandes 
     WHERE produit_id = produits.id) AS quantite_vendue
FROM produits;

Sous-requête dans FROM (table dérivée)

-- Top 3 clients par CA
SELECT * 
FROM (
    SELECT 
        client_id,
        SUM(montant_total) AS ca_total
    FROM commandes
    GROUP BY client_id
    ORDER BY ca_total DESC
    LIMIT 3
) AS top_clients;

-- Ventes par mois avec cumul
SELECT 
    mois,
    ca_mois,
    (SELECT SUM(ca_mois) 
     FROM ventes_mensuelles vm2 
     WHERE vm2.mois <= vm1.mois) AS ca_cumule
FROM (
    SELECT 
        strftime('%Y-%m', date_commande) AS mois,
        SUM(montant_total) AS ca_mois
    FROM commandes
    GROUP BY strftime('%Y-%m', date_commande)
) vm1;

EXISTS et NOT EXISTS

-- Clients ayant au moins une commande
SELECT nom 
FROM clients c
WHERE EXISTS (
    SELECT 1 FROM commandes WHERE client_id = c.id
);

-- Clients sans commande
SELECT nom 
FROM clients c
WHERE NOT EXISTS (
    SELECT 1 FROM commandes WHERE client_id = c.id
);

-- Produits qui ont été commandés
SELECT nom 
FROM produits p
WHERE EXISTS (
    SELECT 1 FROM commandes WHERE produit_id = p.id
);

Partie 9 : Fonctions et opérateurs

Fonctions de chaînes (strings)

-- Concaténer
SELECT nom || ' - ' || email AS info FROM clients;

-- Majuscules / Minuscules
SELECT UPPER(nom), LOWER(email) FROM clients;

-- Longueur
SELECT nom, LENGTH(nom) AS longueur FROM clients;

-- Sous-chaîne
SELECT SUBSTR(email, 1, 5) AS debut_email FROM clients;

-- Remplacer
SELECT REPLACE(telephone, '06', '07') FROM clients;

-- Supprimer espaces
SELECT TRIM('  texte  ');  -- 'texte'
SELECT LTRIM('  texte');   -- 'texte  '
SELECT RTRIM('texte  ');   -- '  texte'

Fonctions numériques

-- Arrondi
SELECT ROUND(prix, 0) FROM produits;  -- Entier
SELECT ROUND(3.14159, 2);  -- 3.14

-- Plafond / Plancher
SELECT CEIL(3.2);   -- 4
SELECT FLOOR(3.9);  -- 3

-- Valeur absolue
SELECT ABS(-10);  -- 10

-- Puissance
SELECT POWER(2, 3);  -- 8

-- Racine carrée
SELECT SQRT(16);  -- 4

-- Aléatoire
SELECT RANDOM();  -- Nombre aléatoire

Fonctions de date

-- Date actuelle
SELECT DATE('now');        -- 2024-11-29
SELECT DATETIME('now');    -- 2024-11-29 15:30:45
SELECT TIME('now');        -- 15:30:45

-- Extraire des parties
SELECT 
    strftime('%Y', date_commande) AS annee,
    strftime('%m', date_commande) AS mois,
    strftime('%d', date_commande) AS jour
FROM commandes;

-- Calculs de dates
SELECT DATE('now', '+7 days');     -- Dans 7 jours
SELECT DATE('now', '-1 month');    -- Il y a 1 mois
SELECT DATE('now', '+1 year');     -- Dans 1 an

-- Différence entre dates (en jours)
SELECT julianday('now') - julianday(date_inscription) AS jours_depuis_inscription
FROM clients;

-- Age d'une commande
SELECT 
    id,
    date_commande,
    julianday('now') - julianday(date_commande) AS jours_depuis_commande
FROM commandes;

Fonctions conditionnelles

-- CASE (comme if/else)
SELECT 
    nom,
    prix,
    CASE 
        WHEN prix < 100 THEN 'Bon marché'
        WHEN prix < 500 THEN 'Moyen'
        WHEN prix < 1000 THEN 'Cher'
        ELSE 'Très cher'
    END AS gamme_prix
FROM produits;

-- CASE avec agrégations
SELECT 
    SUM(CASE WHEN prix < 500 THEN 1 ELSE 0 END) AS nb_pas_cher,
    SUM(CASE WHEN prix >= 500 THEN 1 ELSE 0 END) AS nb_cher
FROM produits;

-- COALESCE (première valeur non NULL)
SELECT 
    nom,
    COALESCE(telephone, 'Non renseigné') AS tel
FROM clients;

-- NULLIF (retourne NULL si égal)
SELECT NULLIF(stock, 0) FROM produits;  -- NULL si stock = 0

Partie 10 : Modification de données (INSERT, UPDATE, DELETE)

INSERT (insérer)

-- Insérer une ligne
INSERT INTO clients (nom, email, ville) 
VALUES ('Nouveau Client', 'nouveau@mail.com', 'Paris');

-- Insérer plusieurs lignes
INSERT INTO clients (nom, email, ville) VALUES
('Client 1', 'c1@mail.com', 'Lyon'),
('Client 2', 'c2@mail.com', 'Paris'),
('Client 3', 'c3@mail.com', 'Marseille');

-- Insérer depuis une autre table
INSERT INTO clients_archive 
SELECT * FROM clients WHERE date_inscription < '2023-01-01';

-- Insérer et récupérer l'ID (SQLite)
INSERT INTO clients (nom, email) VALUES ('Test', 'test@mail.com');
SELECT last_insert_rowid();

UPDATE (mettre à jour)

-- Modifier un client
UPDATE clients 
SET telephone = '0601020304' 
WHERE id = 1;

-- Modifier plusieurs colonnes
UPDATE clients 
SET 
    telephone = '0605060708',
    ville = 'Lyon'
WHERE id = 2;

-- Augmenter tous les prix de 10%
UPDATE produits 
SET prix = prix * 1.10;

-- Augmenter seulement une catégorie
UPDATE produits 
SET prix = prix * 1.05 
WHERE categorie = 'Informatique';

-- Mettre à jour selon une condition
UPDATE produits 
SET stock = stock - 1 
WHERE id = 3 AND stock > 0;

-- ⚠️ ATTENTION : Oublier WHERE modifie TOUTES les lignes !
UPDATE clients SET ville = 'Paris';  -- DANGER !

DELETE (supprimer)

-- Supprimer un client
DELETE FROM clients WHERE id = 10;

-- Supprimer selon condition
DELETE FROM commandes WHERE date_commande < '2023-01-01';

-- Supprimer tout (DANGER !)
DELETE FROM clients;  -- Supprime TOUS les clients

-- Mieux : TRUNCATE (plus rapide, remet compteur à 0)
-- SQLite n'a pas TRUNCATE, utiliser :
DELETE FROM clients;
DELETE FROM sqlite_sequence WHERE name='clients';  -- Reset auto-increment

⚠️ DANGER : Toujours utiliser WHERE avec UPDATE et DELETE !

Transactions (sécurité)

-- Commencer une transaction
BEGIN TRANSACTION;

-- Opérations
UPDATE produits SET stock = stock - 1 WHERE id = 1;
INSERT INTO commandes (client_id, produit_id, quantite, prix_unitaire, montant_total) 
VALUES (1, 1, 1, 2499.00, 2499.00);

-- Si tout OK : valider
COMMIT;

-- Si erreur : annuler
ROLLBACK;

Exemple complet avec gestion d'erreur :

BEGIN TRANSACTION;

-- Vérifier le stock
SELECT stock FROM produits WHERE id = 1;  -- Si < 1, erreur

-- Si OK :
UPDATE produits SET stock = stock - 1 WHERE id = 1;
INSERT INTO commandes (...) VALUES (...);
COMMIT;

-- Si KO :
ROLLBACK;

Partie 11 : Modification de structure (DDL)

CREATE TABLE (options avancées)

-- Table avec contraintes
CREATE TABLE utilisateurs (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    username VARCHAR(50) UNIQUE NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    age INTEGER CHECK (age >= 18),
    solde DECIMAL(10,2) DEFAULT 0.00 CHECK (solde >= 0),
    date_creation TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    actif BOOLEAN DEFAULT 1
);

-- Table avec clés étrangères et CASCADE
CREATE TABLE commentaires (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    utilisateur_id INTEGER NOT NULL,
    texte TEXT NOT NULL,
    date_creation TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (utilisateur_id) REFERENCES utilisateurs(id) 
        ON DELETE CASCADE  -- Si utilisateur supprimé, ses commentaires aussi
        ON UPDATE CASCADE  -- Si ID utilisateur change, ça suit
);

ALTER TABLE (modifier une table)

-- Ajouter une colonne
ALTER TABLE clients ADD COLUMN date_naissance DATE;

-- Renommer une colonne (SQLite 3.25+)
ALTER TABLE clients RENAME COLUMN telephone TO tel;

-- Renommer une table
ALTER TABLE clients RENAME TO customers;

-- Supprimer une colonne (pas toutes les versions SQLite)
-- En SQLite, il faut recréer la table sans la colonne

DROP TABLE (supprimer)

-- Supprimer une table
DROP TABLE IF EXISTS clients_archive;

-- Supprimer plusieurs tables
DROP TABLE IF EXISTS table1, table2, table3;

CREATE INDEX (index pour performances)

-- Index simple
CREATE INDEX idx_clients_email ON clients(email);

-- Index composite
CREATE INDEX idx_commandes_client_date ON commandes(client_id, date_commande);

-- Index unique
CREATE UNIQUE INDEX idx_clients_email_unique ON clients(email);

-- Supprimer un index
DROP INDEX IF EXISTS idx_clients_email;

-- Voir les index
SELECT * FROM sqlite_master WHERE type = 'index';

Quand créer des index ?

  • ✅ Colonnes souvent dans WHERE

  • ✅ Colonnes souvent dans JOIN

  • ✅ Colonnes souvent dans ORDER BY

  • ❌ Petites tables (< 1000 lignes)

  • ❌ Colonnes rarement utilisées

  • ❌ Trop d'index (ralentit INSERT/UPDATE)

Partie 12 : Requêtes avancées

UNION (combiner des résultats)

-- Combiner deux requêtes (sans doublons)
SELECT nom, email FROM clients
UNION
SELECT nom, email FROM fournisseurs;

-- Avec doublons (UNION ALL)
SELECT ville FROM clients
UNION ALL
SELECT ville FROM fournisseurs;

-- Différence : UNION élimine doublons, UNION ALL garde tout

WITH (CTE - Common Table Expressions)

CTE = sous-requête nommée, plus lisible.

-- Sans CTE (difficile à lire)
SELECT * FROM (
    SELECT client_id, SUM(montant_total) AS ca
    FROM commandes
    GROUP BY client_id
) WHERE ca > 1000;

-- Avec CTE (plus clair)
WITH ca_clients AS (
    SELECT 
        client_id, 
        SUM(montant_total) AS ca_total
    FROM commandes
    GROUP BY client_id
)
SELECT 
    c.nom,
    ca.ca_total
FROM ca_clients ca
JOIN clients c ON ca.client_id = c.id
WHERE ca.ca_total > 1000;

-- CTE multiples
WITH 
    ventes_2024 AS (
        SELECT * FROM commandes 
        WHERE strftime('%Y', date_commande) = '2024'
    ),
    top_clients AS (
        SELECT client_id, SUM(montant_total) AS ca
        FROM ventes_2024
        GROUP BY client_id
        ORDER BY ca DESC
        LIMIT 10
    )
SELECT 
    c.nom,
    tc.ca
FROM top_clients tc
JOIN clients c ON tc.client_id = c.id;

Window Functions (fonctions fenêtre)

Pas supportées par toutes les versions SQLite. Disponibles dans PostgreSQL, MySQL 8+.

-- Classement (RANK)
SELECT 
    nom,
    prix,
    RANK() OVER (ORDER BY prix DESC) AS rang
FROM produits;

-- Numéro de ligne
SELECT 
    nom,
    prix,
    ROW_NUMBER() OVER (ORDER BY prix DESC) AS numero
FROM produits;

-- Partition (classement par catégorie)
SELECT 
    nom,
    categorie,
    prix,
    RANK() OVER (PARTITION BY categorie ORDER BY prix DESC) AS rang_categorie
FROM produits;

-- Cumul (running total)
SELECT 
    date_commande,
    montant_total,
    SUM(montant_total) OVER (ORDER BY date_commande) AS ca_cumule
FROM commandes;

-- LAG / LEAD (valeur précédente/suivante)
SELECT 
    date_commande,
    montant_total,
    LAG(montant_total) OVER (ORDER BY date_commande) AS vente_precedente
FROM commandes;

Requêtes récursives

-- Exemple : Hiérarchie d'employés
WITH RECURSIVE hierarchie AS (
    -- Base : PDG
    SELECT id, nom, manager_id, 1 AS niveau
    FROM employes
    WHERE manager_id IS NULL

    UNION ALL

    -- Récursion : employés sous les managers
    SELECT e.id, e.nom, e.manager_id, h.niveau + 1
    FROM employes e
    JOIN hierarchie h ON e.manager_id = h.id
)
SELECT * FROM hierarchie ORDER BY niveau, nom;

Partie 13 : Optimisation et bonnes pratiques

Analyser les performances (EXPLAIN)

-- Voir le plan d'exécution
EXPLAIN QUERY PLAN
SELECT c.nom, cm.montant_total
FROM clients c
JOIN commandes cm ON c.id = cm.client_id
WHERE c.ville = 'Paris';

-- Si vous voyez "SCAN TABLE" → mauvais (lit toute la table)
-- Si vous voyez "SEARCH TABLE USING INDEX" → bon (utilise un index)

Optimisations courantes

1. Utiliser des index

-- Avant (lent)
SELECT * FROM clients WHERE email = 'alice@mail.com';

-- Créer un index
CREATE INDEX idx_clients_email ON clients(email);

-- Après : 100x plus rapide

2. Limiter les colonnes sélectionnées

-- ❌ Mauvais (ramène tout)
SELECT * FROM produits;

-- ✅ Bon (seulement ce qu'on utilise)
SELECT id, nom, prix FROM produits;

3. Éviter les fonctions dans WHERE

-- ❌ Lent (empêche l'utilisation d'index)
SELECT * FROM clients WHERE UPPER(nom) = 'ALICE';

-- ✅ Rapide (utilise l'index)
SELECT * FROM clients WHERE nom = 'Alice';

4. Utiliser EXISTS au lieu de IN pour grosses sous-requêtes

-- ❌ Plus lent
SELECT * FROM clients 
WHERE id IN (SELECT client_id FROM commandes);

-- ✅ Plus rapide
SELECT * FROM clients c
WHERE EXISTS (SELECT 1 FROM commandes WHERE client_id = c.id);

5. LIMIT pour de gros résultats

-- Ne jamais charger 1 million de lignes d'un coup
SELECT * FROM commandes ORDER BY date_commande DESC LIMIT 100;

Éviter les pièges courants

1. Divisions par zéro

-- ❌ ERREUR si quantite = 0
SELECT montant_total / quantite FROM commandes;

-- ✅ Sécurisé
SELECT 
    CASE 
        WHEN quantite = 0 THEN NULL 
        ELSE montant_total / quantite 
    END AS prix_unitaire
FROM commandes;

2. Comparaison avec NULL

-- ❌ Ne fonctionne pas
SELECT * FROM clients WHERE telephone = NULL;

-- ✅ Correct
SELECT * FROM clients WHERE telephone IS NULL;

3. Oublier WHERE dans UPDATE/DELETE

-- ❌ DANGER : modifie TOUTES les lignes
UPDATE produits SET prix = 999;

-- ✅ Toujours un WHERE
UPDATE produits SET prix = 999 WHERE id = 1;

Partie 14 : Sécurité

Injections SQL (LE danger n°1)

❌ DANGEREUX (injection SQL) :

# Python - NE JAMAIS FAIRE ÇA
email = input("Email : ")
query = f"SELECT * FROM clients WHERE email = '{email}'"
# Si email = "'; DROP TABLE clients; --" → BOOM

✅ SÉCURISÉ (paramètres) :

# Python - TOUJOURS faire ça
email = input("Email : ")
cursor.execute("SELECT * FROM clients WHERE email = ?", (email,))

Autres langages :

// PHP (PDO)
$stmt = $pdo->prepare("SELECT * FROM clients WHERE email = ?");
$stmt->execute([$email]);
// Node.js (parameterized query)
db.query("SELECT * FROM clients WHERE email = ?", [email], callback);

Gestion des droits (GRANT/REVOKE)

-- Créer un utilisateur (MySQL/PostgreSQL)
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'password';

-- Donner des droits
GRANT SELECT, INSERT, UPDATE ON entreprise.* TO 'app_user'@'localhost';

-- Retirer des droits
REVOKE DELETE ON entreprise.* FROM 'app_user'@'localhost';

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

Ne jamais stocker de mots de passe en clair

-- ❌ JAMAIS ça
CREATE TABLE users (
    id INTEGER PRIMARY KEY,
    password VARCHAR(50)  -- EN CLAIR = DANGER
);

-- ✅ Toujours hasher
CREATE TABLE users (
    id INTEGER PRIMARY KEY,
    password_hash VARCHAR(255)  -- Hash bcrypt/argon2
);

En Python :

import bcrypt

# Hasher
password = "mon_password"
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt())

# Vérifier
bcrypt.checkpw(password.encode(), hashed)  # True/False

Partie 15 : Exercices pratiques

Testez vos connaissances !

Exercice 1 : Requêtes simples

-- 1. Lister tous les clients de Paris
-- Votre réponse :


-- 2. Produits de plus de 500€, triés par prix décroissant
-- Votre réponse :


-- 3. Nombre total de commandes
-- Votre réponse :


-- 4. Clients dont le nom commence par "A"
-- Votre réponse :


-- 5. Prix moyen des produits de catégorie "Informatique"
-- Votre réponse :
Solutions
-- 1.
SELECT * FROM clients WHERE ville = 'Paris';

-- 2.
SELECT * FROM produits WHERE prix > 500 ORDER BY prix DESC;

-- 3.
SELECT COUNT(*) FROM commandes;

-- 4.
SELECT * FROM clients WHERE nom LIKE 'A%';

-- 5.
SELECT AVG(prix) FROM produits WHERE categorie = 'Informatique';

Exercice 2 : Jointures

-- 1. Liste des commandes avec nom du client et du produit


-- 2. Clients qui n'ont jamais commandé


-- 3. Top 3 produits les plus vendus (quantité)


-- 4. Chiffre d'affaires par ville


-- 5. Clients ayant dépensé plus de 1000€
Solutions
-- 1.
SELECT cm.id, cl.nom AS client, p.nom AS produit
FROM commandes cm
JOIN clients cl ON cm.client_id = cl.id
JOIN produits p ON cm.produit_id = p.id;

-- 2.
SELECT c.nom FROM clients c
LEFT JOIN commandes cm ON c.id = cm.client_id
WHERE cm.id IS NULL;

-- 3.
SELECT p.nom, SUM(cm.quantite) AS total_vendu
FROM commandes cm
JOIN produits p ON cm.produit_id = p.id
GROUP BY p.id
ORDER BY total_vendu DESC
LIMIT 3;

-- 4.
SELECT c.ville, SUM(cm.montant_total) AS ca
FROM commandes cm
JOIN clients c ON cm.client_id = c.id
GROUP BY c.ville;

-- 5.
SELECT c.nom, SUM(cm.montant_total) AS total
FROM clients c
JOIN commandes cm ON c.id = cm.client_id
GROUP BY c.id
HAVING total > 1000;

Exercice 3 : Requêtes avancées

-- 1. Produits jamais commandés


-- 2. Client ayant la commande la plus récente


-- 3. Produits avec stock inférieur à la moyenne


-- 4. Mois avec le meilleur CA


-- 5. Clients ayant commandé au moins 3 produits différents
Solutions
-- 1.
SELECT nom FROM produits
WHERE id NOT IN (SELECT DISTINCT produit_id FROM commandes);

-- 2.
SELECT c.nom
FROM clients c
JOIN commandes cm ON c.id = cm.client_id
ORDER BY cm.date_commande DESC
LIMIT 1;

-- 3.
SELECT nom, stock
FROM produits
WHERE stock < (SELECT AVG(stock) FROM produits);

-- 4.
SELECT strftime('%Y-%m', date_commande) AS mois, SUM(montant_total) AS ca
FROM commandes
GROUP BY mois
ORDER BY ca DESC
LIMIT 1;

-- 5.
SELECT c.nom, COUNT(DISTINCT cm.produit_id) AS nb_produits
FROM clients c
JOIN commandes cm ON c.id = cm.client_id
GROUP BY c.id
HAVING nb_produits >= 3;

Conclusion : Vous maîtrisez SQL ! 🎉

Félicitations ! Vous connaissez maintenant SQL de A à Z.

Ce qu'on a couvert :

  • SELECT, WHERE, ORDER BY (les bases)

  • Fonctions d'agrégation (COUNT, SUM, AVG, etc.)

  • GROUP BY et HAVING

  • Jointures (INNER, LEFT, RIGHT, FULL)

  • Sous-requêtes et CTE

  • INSERT, UPDATE, DELETE

  • CREATE, ALTER, DROP

  • Index et optimisation

  • Transactions et sécurité

  • Requêtes avancées (window functions, recursion)

Avec ces connaissances, vous pouvez :

  • Gérer n'importe quelle base de données

  • Extraire des insights de millions de lignes

  • Optimiser des requêtes lentes

  • Passer des entretiens techniques

  • Travailler avec MySQL, PostgreSQL, SQLite, etc.

Les commandements du SQL-fu :

  1. Toujours utiliser WHERE avec UPDATE/DELETE

  2. Jamais de concaténation pour les requêtes (injections SQL)

  3. Indexer les colonnes souvent cherchées

  4. SELECT seulement les colonnes nécessaires

  5. Utiliser EXPLAIN pour comprendre les performances

  6. Penser en ensembles, pas en boucles

  7. Tester sur peu de données d'abord

  8. Commenter les requêtes complexes

  9. Utiliser des transactions pour les opérations critiques

  10. Backup avant toute modification importante

Ressources pour continuer :

Projet final suggéré : Créez une base de données complète pour :

  1. E-commerce (produits, clients, commandes, avis)

  2. Netflix-like (films, séries, utilisateurs, visionnages)

  3. Clinique (patients, médecins, rendez-vous, prescriptions)

  4. Bibliothèque (livres, auteurs, emprunts, réservations)

Implémentez :

  • Schéma complet avec contraintes

  • 20+ requêtes variées

  • Index pour performances

  • Vues pour requêtes courantes

  • Procédures stockées (si supportées)

Vous avez des questions sur SQL ? Un concept qui vous bloque ? Partagez en commentaire ! Et si ce guide vous a aidé, partagez-le avec d'autres devs qui veulent maîtriser SQL. 😉

Bon SQL, et que les données soient avec vous ! 🗄️✨

Commentaires (0)

Laisser un commentaire

Aucun commentaire pour le moment. Soyez le premier à commenter !