DELETE supprime des lignes, rien d'autre
DELETE retire des lignes d'une table. Il ne supprime pas la table, ne modifie pas son schéma et ne touche à aucune autre table (sauf si vous avez configuré des cascades). La syntaxe tient en une ligne :
DELETE FROM users WHERE id = 2; repère les lignes qui correspondent à la condition et les supprime. Les deux autres lignes restent intactes. La table, elle, existe toujours — vous pouvez continuer à y insérer des données.
L'image à garder en tête : un DELETE, c'est un SELECT qui jette les lignes correspondantes au lieu de les renvoyer.
Tout se joue dans la clause WHERE
Un DELETE sérieux repose entièrement sur sa clause WHERE. Si elle est juste, vous supprimez exactement ce que vous vouliez. Si elle est fausse, vous en supprimez bien plus — parfois toute la table.
Les deux brouillons non publiés à zéro vue ont disparu. Les lignes publiées, elles, restent intactes puisque la condition ne s'appliquait pas. Vous pouvez utiliser n'importe quelle expression acceptée par WHERE : IN, LIKE, BETWEEN, des sous-requêtes, des combinaisons AND/OR, etc.
Un réflexe à prendre : avant de lancer un DELETE, exécute d'abord la même clause WHERE dans un SELECT.
-- Aperçu de ce qui va être supprimé :
SELECT * FROM posts WHERE published = 0 AND views = 0;
-- Satisfait des lignes ? Maintenant, supprimez-les :
DELETE FROM posts WHERE published = 0 AND views = 0;
Cette petite danse en deux temps a sauvé plus de bases de données que tous les outils de sauvegarde réunis.
DELETE sans WHERE vide la table
Si vous oubliez le WHERE, DELETE supprime toutes les lignes :
La table est vide mais existe toujours. SQLite n'a pas d'instruction TRUNCATE — l'équivalent, c'est DELETE FROM table;, et SQLite applique en interne une « truncate optimization » qui libère toutes les pages d'un coup au lieu de supprimer les lignes une par une. C'est rapide, mais ça reste une opération transactionnelle que vous pouvez annuler avec un rollback.
Si vous avez mis AUTOINCREMENT sur la clé primaire, le compteur ne se réinitialise pas tout seul. Pour faire repartir les ids à 1, il faut aussi vider la ligne correspondante dans la séquence :
DELETE FROM log;
DELETE FROM sqlite_sequence WHERE name = 'log';
Pour une simple INTEGER PRIMARY KEY (sans AUTOINCREMENT), SQLite réutilise déjà les identifiants librement : pas besoin d'y toucher.
Supprimer plusieurs lignes précises
IN reste la façon la plus propre de supprimer un ensemble connu de lignes :
Vous pouvez aussi piloter un DELETE depuis une sous-requête — pratique quand les lignes à supprimer sont définies par une jointure ou via une autre table :
SQLite ne propose pas la syntaxe DELETE ... JOIN comme le fait MySQL, mais une sous-requête dans le WHERE rend exactement le même service.
RETURNING : récupérer les lignes supprimées
Ajoutez RETURNING pour récupérer les lignes supprimées sous forme de résultat, exactement comme un SELECT :
Vous récupérez l'id et l'email de chaque ligne supprimée. C'est super pratique pour :
- Journaliser précisément ce qui a été effacé.
- Mettre en place une fonctionnalité d'annulation (il suffit de stocker les lignes renvoyées quelque part).
- Vérifier en un seul aller-retour que le
DELETEa bien touché les lignes attendues.
RETURNING fonctionne aussi bien avec INSERT, UPDATE qu'avec DELETE. Une page dédiée détaille tout ça.
ON DELETE CASCADE pour les lignes liées
Quand une table parent et une table enfant sont reliées par une clé étrangère, supprimer le parent laisse des enfants orphelins — sauf si vous demandez à SQLite de répercuter la suppression en cascade :
Supprimer l'auteur entraîne la suppression de ses livres. Sans ON DELETE CASCADE, le même DELETE réussirait en laissant des livres orphelins (si les clés étrangères sont désactivées), ou échouerait avec une erreur de contrainte (si elles sont activées).
Le piège classique : les clés étrangères sont désactivées par défaut dans SQLite. Il faut exécuter PRAGMA foreign_keys = ON; sur chaque connexion. Si ce pragma n'est pas activé, ON DELETE CASCADE est ignoré silencieusement — les livres restent en place. La plupart des pilotes applicatifs s'en chargent pour vous ou exposent une option dédiée ; vérifiez le vôtre.
Quelques autres options de cascade bonnes à connaître : ON DELETE SET NULL (met la clé étrangère à NULL), ON DELETE RESTRICT (refuse la suppression si des enfants existent), ON DELETE NO ACTION (le comportement par défaut — équivalent à RESTRICT dans la plupart des cas).
DELETE avec LIMIT (option à la compilation)
Certaines versions de SQLite prennent en charge DELETE ... LIMIT, pratique pour grignoter de grosses tables par lots :
DELETE FROM logs
WHERE created_at < '2024-01-01'
ORDER BY created_at
LIMIT 1000;
Cela suppose que SQLite ait été compilé avec l'option SQLITE_ENABLE_UPDATE_DELETE_LIMIT. Les binaires officiels et la plupart des bindings (le module sqlite3 de Python, better-sqlite3 côté Node) l'activent par défaut. Si ce n'est pas le cas chez vous, vous tomberez sur une erreur de syntaxe — il faut alors se rabattre sur une sous-requête :
DELETE FROM logs
WHERE id IN (
SELECT id FROM logs
WHERE created_at < '2024-01-01'
ORDER BY created_at
LIMIT 1000
);
Les suppressions par lots permettent de garder des transactions courtes, ce qui est important quand d'autres connexions lisent la base en même temps.
Encapsuler les gros DELETE dans une transaction
Un DELETE est implicitement transactionnel : soit toutes les lignes correspondantes partent, soit aucune. Mais dès que vous vous apprêtez à en supprimer beaucoup, mieux vaut l'envelopper dans une transaction explicite — comme ça, vous pouvez faire un ROLLBACK si quelque chose vous semble louche :
ROLLBACK annule complètement la suppression. Dans une vraie session, vous feriez un COMMIT une fois que le décompte te paraît bon. Les transactions accélèrent aussi énormément la suppression de plusieurs lignes une à une — encapsuler la boucle dans un BEGIN/COMMIT évite un fsync à chaque DELETE.
Ce qui ne supprime pas vraiment
Voici quelques confusions classiques qui méritent d'être clarifiées :
DELETE FROM table;vide la table mais ne la supprime pas. Pour supprimer la table elle-même, il fautDROP TABLE table;.- Un
DELETEne réduit pas la taille du fichier de base de données. Les pages sont simplement marquées comme réutilisables. Pour récupérer l'espace disque, lanceVACUUM;(abordé dans le chapitre sur les performances). - Supprimer une ligne ne supprime pas les lignes filles dans les autres tables, sauf si
ON DELETE CASCADEest défini et que les clés étrangères sont activées. - Un
DELETEqui ne correspond à aucune ligne n'est pas une erreur. C'est une instruction réussie avecchanges() = 0. Si vous avez besoin de le savoir, vérifie le nombre de lignes affectées.
La suite : UPSERT
Souvent, vous ne voulez pas vraiment supprimer — vous voulez insérer si la ligne est nouvelle, ou mettre à jour si elle existe déjà. SQLite appelle ça un UPSERT, et la clause ON CONFLICT permet de tout faire en une seule instruction au lieu de trois. C'est le sujet du prochain chapitre.
Questions fréquentes
Comment supprimer une ligne en SQLite ?
On utilise DELETE FROM nom_table WHERE condition;. C'est la clause WHERE qui décide quelles lignes partent. Par exemple, DELETE FROM users WHERE id = 7; efface uniquement l'utilisateur dont l'id vaut 7. Attention : sans WHERE, toutes les lignes de la table sont supprimées.
Comment vider toutes les lignes d'une table SQLite ?
Il suffit d'exécuter DELETE FROM nom_table; sans clause WHERE. SQLite ne propose pas de TRUNCATE : c'est DELETE sans filtre qui en tient lieu, et le moteur l'optimise en interne (la fameuse « truncate optimization »). Si vous voulez aussi remettre les compteurs AUTOINCREMENT à zéro, il faut ensuite supprimer les entrées correspondantes dans sqlite_sequence.
SQLite peut-il propager la suppression aux tables liées ?
Oui, à condition de déclarer ON DELETE CASCADE sur la clé étrangère et d'avoir activé les clés étrangères avec PRAGMA foreign_keys = ON;. Par défaut, SQLite désactive les clés étrangères, donc ce pragma est crucial : sans lui, les cascades sont ignorées en silence.
Comment savoir quelles lignes ont été supprimées ?
Ajoute une clause RETURNING à la fin de la requête : DELETE FROM users WHERE active = 0 RETURNING id, email; renvoie les lignes effacées comme le ferait un SELECT. Très pratique pour journaliser, implémenter une fonction d'annulation ou simplement vérifier que vous avez bien supprimé ce que vous visiez.