Objectifs d'apprentissage
À la fin de ce cours, l'apprenant peut :
- écrire des
SELECTavec filtres, tris, limites et alias ; - expliquer l'ordre logique d'évaluation d'une requête et en déduire où placer chaque clause ;
- calculer des agrégats avec
GROUP BYetHAVING; - combiner des tables avec
INNER JOINetLEFT JOIN, et prévoir la taille du résultat ; - employer sous-requêtes, CTE (
WITH) et fonctions de fenêtre ; - modifier des données (
INSERT,UPDATE,DELETE) dans une transaction et annuler ; - définir des tables, contraintes et index ; lire un plan d'exécution ;
- repérer les pièges de
NULL, des doublons et des jointures ; - distinguer ce qui relève de la norme et ce qui est propre à un dialecte.
Prérequis
- Avoir lu, ou connaître, les notions de table, clé primaire et clé étrangère (cours « Bases de données » du même dossier).
- Savoir ouvrir Terminal et lire une commande (cours Terminal macOS).
- Un client SQLite en ligne de commande,
sqlite3: sa présence et sa version sur le poste de Stéphane sont non vérifiées. L'installation ne fait pas partie des exercices ; le cours n'en décrit aucune. - Un dossier de test jetable : tous les fichiers du cours (
demo.sql,demo.db) y sont créés, jamais ailleurs.
Parcours et chapitres
1. Préparer le terrain d'essai
SQLite est une bibliothèque de base embarquée : une base est un fichier. Le shell sqlite3 ouvre ou crée ce fichier ; sans nom de fichier, la base est en mémoire et disparaît à la sortie (documentation du shell).
Étape 1 : créer le script demo.sql dans le dossier de test (copier le bloc ci-dessous).
PRAGMA foreign_keys = ON;
DROP TABLE IF EXISTS ligne_commande;
DROP TABLE IF EXISTS commande;
DROP TABLE IF EXISTS produit;
DROP TABLE IF EXISTS client;
CREATE TABLE client (
id INTEGER PRIMARY KEY,
nom TEXT NOT NULL,
ville TEXT NOT NULL,
courriel TEXT UNIQUE
);
CREATE TABLE produit (
id INTEGER PRIMARY KEY,
nom TEXT NOT NULL,
categorie TEXT NOT NULL,
prix REAL NOT NULL CHECK (prix >= 0)
);
CREATE TABLE commande (
id INTEGER PRIMARY KEY,
client_id INTEGER NOT NULL REFERENCES client(id),
date_commande TEXT NOT NULL,
statut TEXT NOT NULL CHECK (statut IN ('en_cours', 'livree', 'annulee'))
);
CREATE TABLE ligne_commande (
commande_id INTEGER NOT NULL REFERENCES commande(id),
produit_id INTEGER NOT NULL REFERENCES produit(id),
quantite INTEGER NOT NULL CHECK (quantite > 0),
PRIMARY KEY (commande_id, produit_id)
);
INSERT INTO client (id, nom, ville, courriel) VALUES
(1, 'Alpha', 'Ville-A', 'alpha@example.test'),
(2, 'Bravo', 'Ville-B', 'bravo@example.test'),
(3, 'Charlie', 'Ville-A', NULL),
(4, 'Delta', 'Ville-C', 'delta@example.test'),
(5, 'Echo', 'Ville-B', NULL);
INSERT INTO produit (id, nom, categorie, prix) VALUES
(1, 'Cahier', 'Papeterie', 3.50),
(2, 'Stylo', 'Papeterie', 1.20),
(3, 'Classeur', 'Papeterie', 4.80),
(4, 'Lampe', 'Maison', 18.00),
(5, 'Mug', 'Maison', 6.50),
(6, 'Sac', 'Maison', 25.00);
INSERT INTO commande (id, client_id, date_commande, statut) VALUES
(1, 1, '2026-01-10', 'livree'),
(2, 1, '2026-02-05', 'livree'),
(3, 2, '2026-02-07', 'annulee'),
(4, 2, '2026-03-01', 'livree'),
(5, 3, '2026-03-15', 'en_cours'),
(6, 4, '2026-03-20', 'livree'),
(7, 1, '2026-04-02', 'en_cours');
INSERT INTO ligne_commande (commande_id, produit_id, quantite) VALUES
(1, 1, 10), (1, 2, 20), (2, 4, 1), (3, 5, 4), (4, 1, 5), (4, 3, 2),
(5, 2, 50), (6, 4, 2), (6, 5, 3), (7, 1, 2), (7, 5, 1);Attention : ce script commence par quatre DROP TABLE IF EXISTS, qui détruisent les tables de ces noms et leurs données dans la base ouverte. Ne le lancer que sur le fichier demo.db créé pour ce cours, jamais sur une base qui contient autre chose. Sauvegarde : aucune nécessaire pour une base jetable ; retour arrière : supprimer demo.db et relancer le script.
Les clients, produits et commandes sont inventés. Particularités voulues : deux clients sans courriel (NULL), un client sans commande (Echo), un produit jamais commandé (Sac), une commande annulée, des villes en double.
Étape 2 : créer la base à partir du script.
sqlite3 demo.db < demo.sqlBloc actif : la touche C copie la commande seule, sans invite ni sortie.
- Contexte : Terminal, dossier de test jetable contenant
demo.sql;sqlite3présent (non vérifié). La forme avec redirection de l'entrée est décrite dans la documentation du shell. - Droits : utilisateur normal, écriture dans le dossier courant.
- Effet : crée (ou ouvre)
demo.db, y exécute le script ; les tables de même nom existantes sont supprimées puis recréées. - Vérification :
sqlite3 demo.db "SELECT COUNT(*) FROM client;"affiche5(forme « SQL en argument » décrite dans la documentation du shell). - Retour arrière : supprimer le fichier
demo.dbdu dossier de test.
Étape 3 : ouvrir une session interactive et régler l'affichage.
sqlite3 demo.dbBloc actif : la touche C copie la commande seule, sans invite ni sortie.
- Contexte : même dossier. L'invite change : on est dans le shell SQLite.
- Droits : utilisateur normal ; la session peut modifier
demo.db. - Effet : ouvre la base ; rien n'est modifié tant qu'on n'écrit pas.
- Vérification : l'invite du shell SQLite apparaît ;
.tableslisteclient,commande,ligne_commande,produit. - Retour arrière :
.quitouCtrl-D.
Dans la session, saisir ces commandes « point » (elles commencent en début de ligne et tiennent sur une ligne, selon la documentation ; les instructions SQL se terminent par ;) :
.headers on
.mode box
.tables
.schema client
Astuce : la version du moteur s'obtient par sqlite3 --version ou par la fonction SQL sqlite_version() (formes non vérifiées dans les pages consultées). Notez-la : les fonctions de fenêtre exigent SQLite 3.25.0 ou plus, RETURNING 3.35.0, RIGHT/FULL JOIN 3.39.0 (documentation SQLite).
2. Anatomie d'un SELECT et ordre logique
Forme générale (les clauses entre crochets sont facultatives, l'ordre d'écriture est imposé) :
SELECT [DISTINCT] colonnes_ou_expressions
FROM table
[JOIN autre_table ON condition]
[WHERE condition_sur_les_lignes]
[GROUP BY colonnes]
[HAVING condition_sur_les_groupes]
[ORDER BY colonnes]
[LIMIT n [OFFSET m]];À retenir : l'ordre d'écriture (SELECT en premier) n'est pas l'ordre d'évaluation (FROM en premier). Conséquences : WHERE agit avant les groupes et ne peut pas utiliser un agrégat ; HAVING agit après ; un alias défini dans le SELECT n'existe pas encore pour WHERE selon ce modèle (certains dialectes l'admettent quand même : non vérifié, ne pas en dépendre).
Norme et dialecte : SELECT, FROM, WHERE, GROUP BY, HAVING, ORDER BY, INNER/LEFT JOIN, UNION, INTERSECT, EXCEPT sont du SQL courant ; SQLite documente UNION, UNION ALL, INTERSECT et EXCEPT. La limitation du nombre de lignes varie selon les moteurs : voir chapitre 14.
3. Choisir des colonnes, des alias, supprimer les doublons
SELECT nom, ville FROM client;
SELECT nom AS client, ville AS localite FROM client;
SELECT DISTINCT ville FROM client ORDER BY ville;- Effet : lecture seule.
ASdonne un nom au résultat ;DISTINCTretire les lignes identiques. - Vérification : la troisième requête renvoie trois lignes :
Ville-A,Ville-B,Ville-C.
Attention : éviter SELECT * dans du code durable : l'ordre et le nombre de colonnes changent avec le schéma. Pour explorer, c'est pratique.
4. Filtrer avec WHERE
Opérateurs courants : =, <>, <, <=, >, >=, AND, OR, NOT, IN (...), BETWEEN a AND b, IS NULL, IS NOT NULL. LIKE existe aussi ; sa sensibilité à la casse et ses caractères spéciaux varient : non vérifié, relire la documentation du moteur.
SELECT nom FROM client WHERE ville = 'Ville-A';
SELECT nom, prix FROM produit WHERE categorie = 'Maison' AND prix < 20;
SELECT id, date_commande FROM commande WHERE date_commande BETWEEN '2026-02-01' AND '2026-03-31';
SELECT nom FROM client WHERE ville IN ('Ville-A', 'Ville-C');- Vérification : première requête : Alpha et Charlie. Deuxième : Lampe (18) et Mug (6,5). Troisième : commandes 2, 3, 4, 5, 6. Quatrième : Alpha, Charlie, Delta.
À retenir : en SQLite les dates n'ont pas de type dédié : stockées en texte ISO 8601 (AAAA-MM-JJ), elles se comparent correctement comme du texte (documentation des types). Les comparaisons de texte, par défaut, distinguent les majuscules (collation BINARY par défaut selon la page CREATE TABLE).
5. Trier et limiter
SELECT nom, prix FROM produit ORDER BY prix DESC;
SELECT nom, courriel FROM client ORDER BY courriel, nom;
SELECT nom, prix FROM produit ORDER BY prix DESC LIMIT 3;
SELECT nom, prix FROM produit ORDER BY prix DESC LIMIT 3 OFFSET 3;- Vérification : la première commence par Sac (25) puis Lampe (18). La deuxième place en premier les clients au courriel
NULL(Charlie, Echo) : SQLite considèreNULLcomme inférieur à toute valeur. La troisième : Sac, Lampe, Mug. La quatrième : Classeur, Cahier, Stylo.
Attention : sans ORDER BY, l'ordre des lignes n'est pas garanti, même s'il paraît stable. LIMIT sans ORDER BY donne un résultat arbitraire. Le placement des NULL varie selon les dialectes ; la syntaxe NULLS FIRST / NULLS LAST figure dans le diagramme de SQLite (version d'apparition non vérifiée) et n'est pas supportée partout : non vérifié ailleurs.
6. NULL : la logique à trois valeurs
NULL n'est pas une valeur mais « inconnu ». Toute comparaison avec NULL donne NULL (ni vrai ni faux), et WHERE ne garde que les lignes vraies.
SELECT nom FROM client WHERE courriel = NULL;
SELECT nom FROM client WHERE courriel IS NULL;
SELECT nom FROM client WHERE courriel <> 'alpha@example.test';
SELECT nom FROM client WHERE ville NOT IN ('Ville-A', NULL);
SELECT COUNT(*) AS lignes, COUNT(courriel) AS courriels FROM client;- Vérification (calculée, non exécutée) : la première ne renvoie rien ; la deuxième Charlie et Echo ; la troisième Bravo et Delta (Charlie et Echo manquent :
NULL <> xest inconnu) ; la quatrième ne renvoie rien (unNULLdansNOT INrend tout inconnu) ; la dernière5et3.
Faits documentés par SQLite : count(*) compte les lignes, count(X) compte les valeurs non NULL ; sum() renvoie NULL s'il n'y a aucune valeur non nulle, alors que total() (propre à SQLite) renvoie 0.0 ; avg(), min(), max() ignorent les NULL. Pour UNIQUE, les NULL sont distincts (plusieurs lignes sans courriel sont permises) ; pour DISTINCT et UNION, ils sont confondus. La page SQLite signale que d'autres moteurs traitent l'unicité des NULL différemment (SQL Server notamment).
Astuce : utiliser IS NULL, IS NOT NULL et COALESCE(valeur, defaut) (fonction courante ; forme non vérifiée dans les pages consultées).
7. Agrégations : GROUP BY et HAVING
Fonctions d'agrégat : COUNT, SUM, AVG, MIN, MAX (et group_concat en SQLite, alias string_agg pour la compatibilité avec PostgreSQL et SQL Server). FILTER (WHERE ...) restreint un agrégat ; COUNT(DISTINCT x) compte les valeurs distinctes.
SELECT statut, COUNT(*) AS nb FROM commande GROUP BY statut ORDER BY statut;
SELECT commande_id, ROUND(SUM(l.quantite * p.prix), 2) AS total
FROM ligne_commande l
JOIN produit p ON p.id = l.produit_id
GROUP BY commande_id
HAVING total > 50
ORDER BY commande_id;- Vérification (calculée) : la première :
annulee1,en_cours2,livree4. La seconde : commandes 1 (59,0), 5 (60,0) et 6 (55,5). Totaux de toutes les commandes : 1 → 59,0 ; 2 → 18,0 ; 3 → 26,0 ; 4 → 27,1 ; 5 → 60,0 ; 6 → 55,5 ; 7 → 13,5. WHEREfiltre les lignes avant le regroupement,HAVINGfiltre les groupes après.
Attention : les colonnes du SELECT doivent être soit dans GROUP BY, soit agrégées. Certains moteurs le font respecter (réglage ONLY_FULL_GROUP_BY de MySQL, page non consultée, non vérifié), SQLite est plus permissif (comportement exact non vérifié dans les pages consultées) : écrire toujours des requêtes conformes. L'alias total dans HAVING est accepté par SQLite mais pas par tous les dialectes : sinon répéter l'expression.
L'argent doit se stocker en entier (centimes) ou en décimal exact ; REAL (virgule flottante) produit des arrondis : d'où ROUND ici. Le cours choisit REAL pour la simplicité.
8. Jointures
Une jointure associe des lignes de deux tables selon une condition.
| Jointure | Résultat |
|---|---|
INNER JOIN (ou JOIN) | seulement les lignes qui correspondent des deux côtés |
LEFT JOIN | toutes les lignes de gauche, NULL à droite sans correspondance |
RIGHT JOIN, FULL JOIN | symétriques / les deux côtés ; SQLite : depuis 3.39.0 (2022-06-25) |
CROSS JOIN | produit cartésien (toutes les combinaisons) |
NATURAL JOIN | jointure implicite sur les colonnes de même nom : à éviter |
SELECT c.nom, o.id AS commande, o.date_commande
FROM commande o
JOIN client c ON c.id = o.client_id
ORDER BY o.id;
SELECT c.nom
FROM client c
LEFT JOIN commande o ON o.client_id = c.id
WHERE o.id IS NULL;
SELECT COUNT(*) AS lignes, COUNT(DISTINCT o.id) AS commandes
FROM commande o
JOIN ligne_commande l ON l.commande_id = o.id;- Vérification (calculée) : la première renvoie 7 lignes ; la deuxième
Echo(le seul client sans commande, repéré grâce auNULLcôté droit) ; la troisième11et7.
Attention : une jointure « un à plusieurs » multiplie les lignes du côté « un » : la troisième requête compte 11 lignes parce que chaque commande apparaît autant de fois qu'elle a de lignes. Compter des commandes, c'est COUNT(DISTINCT o.id). Placer une condition sur la table de droite d'un LEFT JOIN dans le WHERE (au lieu du ON) transforme de fait la jointure en INNER JOIN (les lignes NULL sont filtrées).
9. Sous-requêtes
Une sous-requête est un SELECT imbriqué : scalaire (une valeur), liste (pour IN), ou corrélée (qui dépend de la ligne extérieure).
SELECT nom, prix FROM produit WHERE prix > (SELECT AVG(prix) FROM produit);
SELECT nom FROM produit
WHERE id NOT IN (SELECT produit_id FROM ligne_commande);
SELECT p.nom FROM produit p
WHERE NOT EXISTS (SELECT 1 FROM ligne_commande l WHERE l.produit_id = p.id);- Vérification (calculée) : la moyenne des prix vaut 59/6 environ 9,83 ; première requête : Lampe (18) et Sac (25). Les deux autres :
Sac(jamais commandé).
Attention : NOT IN donne un résultat vide dès que la sous-requête contient un NULL (chapitre 6). Préférer NOT EXISTS quand la colonne peut être nulle. Une sous-requête corrélée peut être exécutée pour chaque ligne : surveiller les performances (chapitre 15).
10. Expressions de table commune (CTE)
WITH nom AS (SELECT ...) nomme une requête pour l'utiliser comme une table temporaire le temps d'une instruction (documentation SQLite : « comme des vues temporaires »). Utile pour la lisibilité et pour décomposer un calcul.
WITH total_commande AS (
SELECT commande_id, SUM(l.quantite * p.prix) AS total
FROM ligne_commande l
JOIN produit p ON p.id = l.produit_id
GROUP BY commande_id
)
SELECT c.nom, ROUND(SUM(t.total), 2) AS total_client
FROM total_commande t
JOIN commande o ON o.id = t.commande_id
JOIN client c ON c.id = o.client_id
WHERE o.statut <> 'annulee'
GROUP BY c.nom
ORDER BY total_client DESC;- Vérification (calculée) : Alpha 90,5 ; Charlie 60,0 ; Delta 55,5 ; Bravo 27,1 (la commande annulée est exclue ; Echo, sans commande, n'apparaît pas).
CTE récursive : une requête composée dont une partie se réfère à la CTE elle-même, utile pour parcourir arbres et graphes ; les parties non récursives viennent d'abord ; UNION évite les boucles infinies là où UNION ALL ne s'arrête que par une condition ou un LIMIT (SQLite). Exemple minimal :
WITH RECURSIVE compteur(n) AS (
SELECT 1
UNION ALL
SELECT n + 1 FROM compteur WHERE n < 5
)
SELECT n FROM compteur;- Vérification : cinq lignes, de 1 à 5. Attention : sans la condition
WHERE n < 5, la récursion ne s'arrête pas ; toujours prévoir une borne.
MATERIALIZED / NOT MATERIALIZED (SQLite depuis 3.35.0) influencent l'optimiseur ; la documentation déconseille de les utiliser sans raison impérieuse.
11. Fonctions de fenêtre
Une fonction de fenêtre calcule une valeur sans réduire le nombre de lignes, en regardant un groupe de lignes voisines (OVER (PARTITION BY ... ORDER BY ...)). SQLite les propose depuis 3.25.0 (2018-09-15) ; la 3.28.0 a ajouté les cadres GROUPS et EXCLUDE. Fonctions natives : row_number, rank, dense_rank, percent_rank, cume_dist, ntile, lag, lead, first_value, last_value, nth_value, ainsi que les agrégats utilisés en fenêtre. Restrictions SQLite : pas de DISTINCT dans une fenêtre, usage limité au SELECT et à l'ORDER BY.
SELECT categorie, nom, prix,
RANK() OVER (PARTITION BY categorie ORDER BY prix DESC) AS rang
FROM produit
ORDER BY categorie, rang;
WITH t AS (
SELECT commande_id, SUM(l.quantite * p.prix) AS total
FROM ligne_commande l JOIN produit p ON p.id = l.produit_id
GROUP BY commande_id
)
SELECT o.id, o.date_commande, ROUND(t.total, 2) AS total,
ROUND(SUM(t.total) OVER (ORDER BY o.date_commande, o.id), 2) AS cumul
FROM t JOIN commande o ON o.id = t.commande_id
ORDER BY o.date_commande, o.id;- Vérification (calculée) : première requête,
Maison: Sac 1, Lampe 2, Mug 3 ;Papeterie: Classeur 1, Cahier 2, Stylo 3. Seconde, colonnecumul: 59,0 ; 77,0 ; 103,0 ; 130,1 ; 190,1 ; 245,6 ; 259,1.
À retenir : GROUP BY réduit les lignes, une fenêtre les conserve. RANK laisse des trous après des ex æquo, DENSE_RANK non, ROW_NUMBER départage arbitrairement.
12. Modifier les données : INSERT, UPDATE, DELETE
Toute modification est faite dans une transaction (BEGIN ... COMMIT ou ROLLBACK). Sans BEGIN, SQLite valide chaque instruction aussitôt (mode automatique, documentation).
Attention : UPDATE ou DELETE sans WHERE s'applique à toutes les lignes. Avant de modifier : écrire d'abord un SELECT avec le même WHERE, vérifier le nombre de lignes, sauvegarder (chapitre 16), travailler dans une transaction, et ne COMMIT qu'après vérification.
Exercice complet, annulé à la fin (aucun changement durable) :
BEGIN;
INSERT INTO client (nom, ville) VALUES ('Foxtrot', 'Ville-C') RETURNING id;
UPDATE produit SET prix = prix * 1.10 WHERE categorie = 'Papeterie';
SELECT nom, ROUND(prix, 2) AS prix FROM produit WHERE categorie = 'Papeterie' ORDER BY nom;
DELETE FROM client WHERE nom = 'Foxtrot';
SELECT COUNT(*) FROM client;
ROLLBACK;- Contexte : session
sqlite3 demo.db(chapitre 1) ; SQLite 3.35.0 ou plus pourRETURNING. - Droits : écriture dans
demo.db. - Effet : pendant la transaction, ajoute un client, augmente de 10 % les prix de papeterie et supprime le client ajouté ;
ROLLBACKannule tout. - Vérification (calculée) :
RETURNINGaffiche6; les prix deviennent Cahier 3,85, Classeur 5,28, Stylo 1,32 ; leCOUNT(*)vaut5. AprèsROLLBACK,SELECT nom, prix FROM produit;redonne les prix d'origine. - Retour arrière :
ROLLBACK;(déjà dans le script). Si la session est restée dans une transaction ouverte, saisirROLLBACK;avant de quitter.
Autres formes :
INSERT INTO t (a, b) VALUES (1, 2), (3, 4);insère plusieurs lignes ;INSERT ... SELECT ...en insère depuis une requête.- Upsert :
INSERT ... ON CONFLICT (colonne) DO UPDATE SET c = excluded.c;DO NOTHINGignore le conflit. SQLite : depuis 3.24.0, généralisé en 3.35.0 ; syntaxe empruntée à PostgreSQL, non standard. RETURNING: SQLite 3.35.0 ; la page précise que ce n'est pas du SQL standard, que l'ordre des lignes renvoyées est arbitraire et qu'il ne reflète pas les changements faits par des déclencheurs.DELETEsupprime des lignes,DROP TABLEsupprime la table entière (chapitre 13).
Astuce : les clés étrangères et leurs actions (ON DELETE CASCADE, etc.) peuvent supprimer des lignes enfants en cascade : lire la définition avant un DELETE.
13. Définir la structure : DDL et contraintes
Le DDL (« Data Definition Language ») crée et modifie les objets : CREATE TABLE, ALTER TABLE, DROP TABLE, CREATE INDEX, DROP INDEX.
Contraintes (page CREATE TABLE de SQLite) :
| Contrainte | Rôle | Remarque SQLite |
|---|---|---|
PRIMARY KEY | identifiant unique | une par table ; INTEGER PRIMARY KEY est un alias du rowid ; sinon, la clé peut contenir NULL sauf si NOT NULL, WITHOUT ROWID ou STRICT |
NOT NULL | valeur obligatoire | |
UNIQUE | pas de doublon | les NULL sont distincts |
CHECK (expr) | règle de valeur | refuse la ligne si l'expression est fausse (zéro) ; pas de sous-requête |
DEFAULT | valeur par défaut | constante, expression entre parenthèses ou CURRENT_TIMESTAMP |
FOREIGN KEY / REFERENCES | intégrité référentielle | désactivée par défaut : PRAGMA foreign_keys = ON; à chaque connexion, hors transaction |
Actions sur clé étrangère : NO ACTION (défaut), RESTRICT, SET NULL, SET DEFAULT, CASCADE. Types : SQLite a un typage dynamique (affinités) ; les tables STRICT (3.37.0) imposent les types.
Essai de contraintes (dans une transaction annulée) :
PRAGMA foreign_keys = ON;
BEGIN;
INSERT INTO commande (id, client_id, date_commande, statut) VALUES (99, 999, '2026-05-01', 'livree');
INSERT INTO commande (id, client_id, date_commande, statut) VALUES (98, 1, '2026-05-01', 'perdue');
INSERT INTO produit (nom, categorie, prix) VALUES ('Test', 'Maison', -1);
ROLLBACK;- Contexte : session
sqlite3 demo.db,PRAGMAexécuté avant leBEGIN. - Effet : chaque
INSERTdoit être refusé : client inexistant (clé étrangère), statut hors liste, prix négatif (CHECK). Aucune ligne n'est ajoutée. - Vérification : trois messages d'erreur ;
SELECT COUNT(*) FROM commande;donne encore7. La forme exacte des messages dépend de la version (non vérifiée). - Retour arrière :
ROLLBACK;.
Création, modification et suppression d'une table d'essai :
CREATE TABLE avis (
id INTEGER PRIMARY KEY,
produit_id INTEGER NOT NULL REFERENCES produit(id),
note INTEGER NOT NULL CHECK (note BETWEEN 1 AND 5),
cree_le TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
ALTER TABLE avis ADD COLUMN commentaire TEXT;
DROP TABLE avis;- Contexte : session
sqlite3 demo.db. - Droits : écriture dans
demo.db. - Effet : crée
avis, lui ajoute une colonne, puis la supprime avec ses données (ici vide).ALTER TABLE ... ADD COLUMNest la forme employée ; les autres modifications deALTER TABLEde SQLite : non vérifiées. - Vérification :
.tablesaprèsCREATEmontreavis, puis ne la montre plus aprèsDROP. - Retour arrière : après
DROP, recréer la table vide ; les données supprimées ne sont pas récupérables sans sauvegarde.
Attention : DROP TABLE est irréversible hors transaction. Pour une vraie base : sauvegarder avant (chapitre 16) et tester le script sur une copie.
14. Index et plan d'exécution
Un index accélère la recherche et la jointure sur ses colonnes, au prix de l'espace et des écritures. SQLite : index uniques, partiels (WHERE), sur expression, multi-colonnes ; DROP INDEX les supprime.
EXPLAIN QUERY PLAN SELECT * FROM commande WHERE client_id = 1;
CREATE INDEX idx_commande_client ON commande (client_id);
EXPLAIN QUERY PLAN SELECT * FROM commande WHERE client_id = 1;- Contexte : session
sqlite3 demo.db. - Effet :
EXPLAIN QUERY PLANn'exécute pas la requête et ne modifie rien ;CREATE INDEXajoute un index. - Vérification (selon la documentation) : avant l'index, une ligne
SCAN commande(parcours complet) ; après,SEARCH commande USING INDEX idx_commande_client (client_id=?)(recherche ciblée).USING COVERING INDEXsignale qu'on n'a pas besoin de lire la table,USE TEMP B-TREE FOR ORDER BYun tri temporaire. La sortie n'est pas stable d'une version à l'autre (changements notés en 3.24.0 et 3.36.0) : ne jamais l'analyser par programme. - Retour arrière :
DROP INDEX idx_commande_client;.
Autres moteurs : PostgreSQL propose EXPLAIN (coûts estimés) et EXPLAIN ANALYZE, qui exécute réellement la requête : pour un INSERT/UPDATE/DELETE, l'entourer de BEGIN et ROLLBACK (conseil de la documentation). Les plans de MySQL/MariaDB et SQL Server ont leurs propres formes : non consultées.
Astuce : un index sur une colonne de clé étrangère (commande.client_id) accélère les jointures et les suppressions côté parent. Une fonction appliquée à la colonne dans le WHERE empêche souvent l'usage d'un index simple (règle générale, non vérifiée dans les pages consultées).
15. Les pièges classiques
| Piège | Symptôme | Parade |
|---|---|---|
= NULL | aucune ligne | IS NULL |
NOT IN avec NULL | résultat vide | NOT EXISTS |
| jointure un-à-plusieurs | totaux gonflés, lignes en double | agréger avant de joindre, COUNT(DISTINCT ...) |
LEFT JOIN + filtre dans WHERE | les lignes sans correspondance disparaissent | filtre dans le ON |
UPDATE/DELETE sans WHERE | toutes les lignes touchées | SELECT d'abord, transaction |
LIMIT sans ORDER BY | résultat arbitraire | toujours trier |
SELECT * | colonnes instables | liste explicite |
| division entière | 7/2 donne 3 en SQLite entre entiers | convertir un opérande (7/2.0) ; comportement non vérifié dans les pages consultées, à tester |
| doublons de données | clés manquantes | PRIMARY KEY, UNIQUE |
| clés étrangères inactives (SQLite) | références orphelines | PRAGMA foreign_keys = ON; |
REAL pour l'argent | arrondis | entiers en centimes, décimaux exacts |
| concaténation de texte saisi | injection SQL | requêtes paramétrées |
NATURAL JOIN | jointure sur une colonne ajoutée plus tard | JOIN ... ON explicite |
Vérifications utiles : SELECT 7 / 2, 7 / 2.0; (attendu en SQLite : 3 et 3.5, à confirmer à l'exécution).
16. Sauvegarder la base de démonstration
Pour une base de démonstration, une copie suffit quand aucune session n'écrit. La documentation de SQLite déconseille la copie brute d'une base en cours d'utilisation (risque de copie incohérente) et propose l'API de sauvegarde en ligne, VACUUM INTO et, dans le shell, .backup et .dump.
Attention, effet notable. Cette commande peut écrire ou écraser un fichier. Lire le contexte, les droits et l’effet attendu indiqués après le bloc ; ne jamais l’exécuter sur un système réel sans validation, d’abord dans un environnement de test.
sqlite3 demo.db ".backup demo-copie.db"sqlite3 demo.db .dump > demo-export.sqlBloc actif : la touche C copie la commande seule, sans invite ni sortie.
- Contexte : dossier de test,
demo.dbexistant, aucune autre session ouverte dessus ; formes.backup FICHIERet.dumpdécrites dans la documentation du shell. - Droits : lecture de
demo.db, écriture dans le dossier courant. - Effet : crée
demo-copie.db(copie cohérente) etdemo-export.sql(instructions SQL qui reconstruisent la base).>écrasedemo-export.sqls'il existe. - Vérification :
sqlite3 demo-copie.db "SELECT COUNT(*) FROM client;"donne5;demo-export.sqlcontient desCREATE TABLEet desINSERT. - Retour arrière : supprimer les deux fichiers créés.
Restauration à partir de l'export, vers un nouveau fichier : sqlite3 demo-restaure.db < demo-export.sql (forme décrite dans la documentation du shell pour une sauvegarde .dump). Attention : ne jamais restaurer sur un fichier existant sans copie préalable.
17. Norme SQL et dialectes
La norme est ISO/IEC 9075 « Database Language SQL ». Versions citées par la documentation de PostgreSQL : SQL-92, SQL:1999, SQL:2003, SQL:2006, SQL:2008, SQL:2011, SQL:2016, SQL:2023. D'après cette même documentation, PostgreSQL « supporte la plupart des fonctions majeures de SQL:2023 », se conforme à au moins 170 des 177 fonctions obligatoires du niveau Core, et aucun SGBD actuel ne revendique la conformité complète au Core SQL:2023. Le texte de la norme est payant ; il n'a pas été consulté.
Différences de dialecte relevées dans les pages consultées :
| Sujet | Constat | Source |
|---|---|---|
RETURNING | extension, non standard (SQLite) | SQLite |
Upsert ON CONFLICT | non standard, syntaxe de PostgreSQL reprise par SQLite | SQLite |
RIGHT/FULL JOIN | absents de SQLite avant 3.39.0 | SQLite |
total() | fonction propre à SQLite | SQLite |
string_agg / group_concat | alias de compatibilité avec PostgreSQL et SQL Server | SQLite |
| Typage | dynamique (SQLite) ; strict dans la plupart des autres moteurs, STRICT en option | SQLite |
| Clés étrangères | inactives par défaut (SQLite) | SQLite |
NULL et UNIQUE | distincts (SQLite, PostgreSQL) ; autre comportement pour SQL Server | SQLite |
| Isolation par défaut | Read Committed (PostgreSQL, SQL Server) ; Repeatable Read (MySQL/InnoDB) ; sérialisable à un écrivain (SQLite) | docs des moteurs |
| T-SQL | dialecte de SQL Server et des produits Azure SQL | Microsoft |
| Limiter les lignes | LIMIT n [OFFSET m] (SQLite) ; autres formes (TOP, FETCH FIRST, OFFSET ... FETCH) non vérifiées | — |
| Concaténation, dates, fonctions de chaînes | varient fortement ; non vérifié | — |
À retenir : toujours préciser le moteur et la version quand on cherche une réponse ; un exemple trouvé en ligne peut ne fonctionner que dans un dialecte.
Exercices et vérifications de fin de cours
Tous sur demo.db, dans le dossier de test. Écrire la requête avant de regarder la correction du cours.
- Mise en place. Créer
demo.sqletdemo.db; vérifier les 5 clients. - Filtres. Lister les produits
Papeteriede moins de 4 €, triés par prix décroissant. (Attendu : Classeur 4,8 exclu ; Cahier 3,5, Stylo 1,2.) - NULL. Lister les clients dont le courriel est inconnu ; expliquer pourquoi
= NULLéchoue. - Agrégation. Nombre de commandes par client, y compris les clients sans commande (indice :
LEFT JOIN,COUNT(o.id)). Attendu : Alpha 3, Bravo 2, Charlie 1, Delta 1, Echo 0. - Jointure. Chiffre d'affaires par catégorie de produit, commandes annulées exclues. (Attendu, calculé à la main : Papeterie 153,1 ; Maison 80,0 ; total 233,1.)
- Sous-requête. Produits dont le prix dépasse la moyenne de leur catégorie (sous-requête corrélée).
- CTE. Réécrire l'exercice 5 avec un
WITH. - Fenêtre. Pour chaque client, la date de sa première commande (
MINen fenêtre ourow_number). - Transaction. Mettre à zéro le prix d'un produit dans un
BEGIN, constater, annuler. Que se passe-t-il avecCHECK (prix >= 0)si on met-1? - Contraintes. Provoquer volontairement une violation de clé étrangère, une d'unicité (courriel déjà présent) et une de
CHECK. - Index. Comparer
EXPLAIN QUERY PLANavant et aprèsCREATE INDEX, puis supprimer l'index. - Sauvegarde. Exporter avec
.dump, restaurer vers un nouveau fichier, comparer les nombres de lignes de chaque table. - Dialecte. Pour
RETURNINGetFULL JOIN, dire dans quelle version de SQLite ils sont apparus et pourquoi ils ne sont pas portables sans vérification.
Critères de réussite
- Prévoir le nombre de lignes d'un
JOINet d'unLEFT JOINavant d'exécuter. - Expliquer pourquoi
WHERE total > 50échoue etHAVINGfonctionne, en s'appuyant sur l'ordre logique. - Ne jamais écrire
UPDATEouDELETEsansWHEREni transaction. - Lire
SCANcontreSEARCHdans un plan. - Nommer au moins trois différences de dialecte et la source qui les établit.
Points non vérifiés ou à confirmer
- Moteur, versions et système de Stéphane : inconnus. Présence et version de
sqlite3sur le Mac : non vérifiées ;sqlite3 --versionetsqlite_version(): formes non consultées. - Tous les résultats « attendus » sont calculés à la main, non exécutés, à confronter à la sortie réelle (arrondis de virgule flottante possibles).
LIKE,COALESCE, division entière,ALTER TABLE(au-delà deADD COLUMN), comportement de SQLite sur les colonnes non agrégées sansGROUP BY, alias dansWHERE/HAVINGailleurs que SQLite : non confirmés dans les pages consultées.- Version d'apparition de
NULLS FIRST/NULLS LASTdans SQLite : non trouvée dans l'extrait de l'historique lu. ONLY_FULL_GROUP_BYet modes SQL de MySQL/MariaDB, formesTOP/FETCH FIRST/OFFSET ... FETCH, plans d'exécution MySQL/MariaDB et SQL Server : pages non consultées (limite de session).- Norme ISO/IEC 9075 : texte payant, non consulté ; les versions de la norme proviennent de la documentation PostgreSQL. Page ISO 403 lors de la tentative.
- Documentation MariaDB : non consultée pour le SQL.
- Les résumés de pages de documentation ont été obtenus par un outil de lecture qui reformule : relire les pages avant de valider.
Sources officielles
Consultées le 17 octobre 2026.