Menu

Triggers SQLite : CREATE TRIGGER, BEFORE/AFTER, OLD/NEW

Comprendre les triggers SQLite : BEFORE et AFTER, INSTEAD OF sur les vues, les références OLD et NEW, et les cas où c'est vraiment l'outil adapté.

Cette page contient des éditeurs exécutables — modifiez, exécutez et voyez la sortie instantanément.

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;
  • TimingBEFORE s'exécute avant la modification, AFTER s'exécute après, et INSTEAD OF remplace l'opération (uniquement sur les vues).
  • Événement — l'opération qui déclenche le trigger. UPDATE OF col1, col2 restreint 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 BEGIN et END. 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 triggers INSERT et UPDATE.
  • OLD — la ligne existante. Disponible dans les triggers UPDATE et DELETE.

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 UPDATE qui 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_master au 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.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER