Un trigger SQLite exécute du SQL automatiquement
Un trigger SQLite, c'est un bloc de SQL stocké dans la base qui se déclenche dès qu'un événement précis survient sur une table donnée. Tu l'écris une seule fois, et c'est SQLite qui s'occupe du « quand ».
Voici la structure générale :
Nous n'avons jamais écrit explicitement d'INSERT dans price_history. C'est le trigger qui s'en est chargé. Toute mise à jour future du prix sera enregistrée de la même manière, qu'elle vienne du CLI, d'un script ou d'une appli.
Anatomie d'un CREATE TRIGGER en SQLite
Décortiquons la syntaxe morceau par morceau :
CREATE TRIGGER trigger_name
{ BEFORE | AFTER | INSTEAD OF } { INSERT | UPDATE [ OF column_list ] | DELETE }
ON table_name
[ FOR EACH ROW ]
[ WHEN condition ]
BEGIN
-- une ou plusieurs instructions
END;
- Timing —
BEFOREs'exécute avant la modification,AFTERs'exécute après, etINSTEAD OFremplace l'opération (uniquement sur les vues). - Événement — l'opération qui déclenche le trigger.
UPDATE OF col1, col2restreint le déclenchement aux mises à jour de colonnes précises. - Table — la table surveillée.
FOR EACH ROW— SQLite ne gère que des triggers au niveau de la ligne, donc c'est implicite. Vous pouvez l'écrire pour la lisibilité, ça ne change rien.WHEN— une condition facultative. Le corps du trigger ne s'exécute que si elle est vraie.- Corps — une ou plusieurs instructions entre
BEGINetEND. Chacune doit se terminer par un point-virgule.
Voilà toute la syntaxe. En pratique, la plupart des triggers font cinq à dix lignes.
OLD et NEW : la ligne en cours de modification
Dans le corps du trigger, deux pseudo-lignes permettent d'accéder aux données :
NEW— la nouvelle ligne. Disponible dans les triggersINSERTetUPDATE.OLD— la ligne existante. Disponible dans les triggersUPDATEetDELETE.
Un trigger DELETE ne dispose que de OLD. Un trigger INSERT ne dispose que de NEW. Un trigger UPDATE, lui, a accès aux deux.
La ligne supprimée a bien disparu de accounts, mais ses données ont été capturées dans deletions juste avant.
BEFORE : valider ou ajuster la ligne
Les triggers BEFORE se déclenchent avant que la modification ne soit écrite sur disque. Pratique pour lever une erreur ou normaliser les données :
Le second INSERT est interrompu avant qu'aucune ligne ne soit écrite. RAISE(ABORT, '...') annule l'instruction en cours et restaure l'état d'avant son exécution. Pour un contrôle plus fin, vous avez aussi RAISE(FAIL, ...), RAISE(ROLLBACK, ...) et RAISE(IGNORE).
Pour de la simple validation de données, privilégiez les contraintes CHECK : elles sont déclaratives et l'optimiseur en tient compte. Réservez le trigger BEFORE aux règles qui doivent consulter d'autres tables ou exprimer une logique qu'un CHECK ne peut pas couvrir.
La clause WHEN : des triggers conditionnels
La clause WHEN permet de filtrer quelles modifications déclenchent réellement le corps du trigger. Elle est évaluée ligne par ligne, une fois OLD et NEW liés :
La première commande ne passe pas le filtre. Les deux autres si. Sans la clause WHEN, chaque insertion irait dans big_orders et vous vous retrouverais à filtrer côté lecture.
INSTEAD OF : rendre une vue modifiable
Par défaut, les vues sont en lecture seule. Un trigger INSTEAD OF intercepte une écriture sur une vue et exécute votre SQL à la place — en général pour traduire l'opération en écritures sur la ou les tables sous-jacentes :
L'application interagit avec la vue comme s'il s'agissait d'une vraie table. C'est le trigger qui se charge, en coulisses, de découper la valeur en first_name et last_name.
Lister et supprimer les triggers
Les triggers sont stockés dans sqlite_master, au même titre que les tables et les index :
DROP TRIGGER IF EXISTS name; est la forme à privilégier. Et bonne nouvelle : si vous supprimez la table sur laquelle vit un trigger, celui-ci disparaît automatiquement — pas besoin de faire le ménage avant.
Pièges à connaître
Quelques détails qui surprennent toujours au premier essai :
- Un trigger se déclenche par ligne, pas par instruction. Un
UPDATEqui touche 1 000 lignes va déclencher le trigger 1 000 fois. Si le corps du trigger est coûteux, l'addition grimpe vite. - Les triggers s'exécutent dans la transaction englobante. Si l'instruction extérieure fait un rollback, les écritures du trigger sont annulées avec elle. C'est souvent ce qu'on veut, mais ça veut aussi dire qu'un trigger n'est pas une porte de sortie pour « logguer quoi qu'il arrive ».
- Les triggers récursifs sont désactivés par défaut. Un trigger qui modifie sa propre table ne se re-déclenchera pas tout seul, sauf si vous activez
PRAGMA recursive_triggers = ON;. À laisser désactivé tant que vous n'avez pas une raison précise. - Le code applicatif ne peut les contourner qu'en court-circuitant la base. Tant que toutes les écritures passent par SQLite, le trigger s'exécute. Même les ORM qui envoient du SQL brut en batch les déclenchent.
- N'éparpillez pas votre logique métier dans une multitude de triggers. Ils sont invisibles depuis le code appelant — la personne qui debugge « d'où sort cette ligne ? » devra fouiller
sqlite_masterau grep. Réservez-les aux préoccupations transverses (journaux d'audit, colonnes calculées, vues modifiables) et gardez le reste dans le code applicatif.
Un exemple concret de journal d'audit
Mettons toutes ces idées bout à bout — on va tracer chaque modification sur une table posts :
Un seul trigger garde updated_at à jour et écrit la ligne d'audit au même endroit. Le code applicatif qui fait l'UPDATE n'a besoin de connaître ni l'un ni l'autre.
La suite : le support JSON
Les triggers gèrent l'automatisation autour des événements de ligne. La prochaine brique avancée de SQLite, c'est ce que vous pouvez stocker dans une ligne : du JSON. SQLite propose tout un arsenal de fonctions JSON pour interroger et modifier des données structurées sans quitter le SQL, et c'est justement le sujet de la page suivante.
Questions fréquentes
C'est quoi un trigger en SQLite ?
Un trigger, c'est un bloc de SQL qui se déclenche tout seul quand un événement précis arrive sur une table : un INSERT, un UPDATE ou un DELETE. Tu le définis une seule fois avec CREATE TRIGGER et SQLite s'occupe de l'exécuter à chaque fois. C'est la bonne approche pour tenir un journal d'audit, maintenir une colonne calculée ou imposer une règle métier sans devoir compter sur l'application pour le faire.
Quelle est la différence entre BEFORE, AFTER et INSTEAD OF ?
BEFORE s'exécute avant que la modification de la ligne soit appliquée — pratique pour valider ou retoucher les données. AFTER se déclenche une fois le changement effectué — typiquement pour journaliser ou synchroniser d'autres tables. INSTEAD OF ne fonctionne que sur les vues : il remplace carrément l'opération attendue, ce qui permet de rendre une vue modifiable.
Comment référencer la ligne modifiée à l'intérieur d'un trigger ?
On utilise NEW.colonne pour la nouvelle ligne lors d'un INSERT ou d'un UPDATE, et OLD.colonne pour la ligne existante lors d'un UPDATE ou d'un DELETE. Les triggers INSERT ne voient que NEW, ceux de DELETE que OLD, et ceux d'UPDATE voient les deux. Ces références concernent uniquement la ligne en cours de traitement.
Comment lister ou supprimer les triggers dans SQLite ?
Les triggers sont stockés dans sqlite_master : SELECT name, tbl_name FROM sqlite_master WHERE type = 'trigger'; te les affiche tous. Pour en supprimer un, fais DROP TRIGGER nom_du_trigger; ou DROP TRIGGER IF EXISTS nom_du_trigger; si vous n'êtes pas sûr qu'il existe. À noter : supprimer une table supprime aussi ses triggers.