Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

Chapitre 514 minAvancé

Triggers (déclencheurs)

Par Josselyn C., prof de BTS SIO. Mis à jour le .

5.1 Qu'est-ce qu'un trigger ?

Un trigger est un bloc de code SQL qui s'execute automatiquement quand un evenement precis se produit sur une table : insertion, modification ou suppression d'une ligne.

Analogie : une alarme de maison. On la configure une fois. Ensuite, a chaque fois que quelqu'un ouvre la porte (INSERT), l'alarme se declenche automatiquement, sans qu'on ait a intervenir. Le trigger fonctionne de la meme maniere : on le cree une fois, et il se declenche tout seul a chaque evenement.

Le declenchement est transparent : le programme qui fait l'INSERT ne sait pas qu'un trigger existe. Il fait son INSERT normalement, et le trigger s'execute en arriere-plan.

5.2 Les 6 moments possibles

Chaque trigger est associe a :

  • Un moment : BEFORE (avant) ou AFTER (apres)
  • Un evenement : INSERT, UPDATE ou DELETE

Ce qui donne 6 combinaisons :

MomentINSERTUPDATEDELETE
BEFOREBEFORE INSERTBEFORE UPDATEBEFORE DELETE
AFTERAFTER INSERTAFTER UPDATEAFTER DELETE

Quand choisir BEFORE ?

  • Pour valider ou modifier les donnees avant qu'elles soient ecrites.
  • Exemple : verifier qu'un prix n'est pas negatif avant insertion. Corriger automatiquement une valeur avant ecriture.

Quand choisir AFTER ?

  • Pour reagir a une modification deja effectuee.
  • Exemple : mettre a jour un total, ecrire dans une table d'historique.

5.3 NEW et OLD

Dans le corps d'un trigger, on accede aux valeurs de la ligne concernee avec les mots-cles NEW et OLD.

Mot-cleSignificationDisponible en
NEWLes nouvelles valeurs (celles qu'on est en train d'ecrire)INSERT, UPDATE
OLDLes anciennes valeurs (celles qui existaient avant)UPDATE, DELETE

Tableau recapitulatif complet :

EvenementOLDNEW
INSERTN'existe pas (il n'y avait pas de ligne avant)Les valeurs inserees
UPDATELes valeurs avant modificationLes valeurs apres modification
DELETELes valeurs de la ligne supprimeeN'existe pas (la ligne va disparaitre)

Exemples d'acces :

  • NEW.prix_ht : le nouveau prix HT (en INSERT ou UPDATE).
  • OLD.prix_ht : l'ancien prix HT (en UPDATE ou DELETE).
  • En BEFORE INSERT ou BEFORE UPDATE, on peut modifier les valeurs de NEW : SET NEW.prix_ht = 0;
  • On ne peut jamais modifier OLD. OLD est en lecture seule.

5.4 CREATE TRIGGER : syntaxe complete

sql
DELIMITER //

CREATE TRIGGER nom_trigger
{BEFORE | AFTER} {INSERT | UPDATE | DELETE}
ON nom_table
FOR EACH ROW
BEGIN
    -- corps du trigger
END //

DELIMITER ;
  • nom_trigger : nom unique dans la base.
  • FOR EACH ROW : obligatoire en MySQL. Le trigger s'execute pour chaque ligne affectee.
  • Le corps est entre BEGIN et END (sauf si une seule instruction, mais il est recommande de toujours utiliser BEGIN/END).

5.5 Cas d'utilisation 1 : historique / audit

Besoin : a chaque modification du prix d'un produit, enregistrer l'ancien et le nouveau prix dans la table historique_produit.

Table d'historique : deja creee dans le schema de reference (voir debut du document).

Trigger :

sql
DELIMITER //

CREATE TRIGGER trg_audit_prix_produit
AFTER UPDATE
ON produit
FOR EACH ROW
BEGIN
    IF OLD.prix_ht != NEW.prix_ht THEN
        INSERT INTO historique_produit
            (id_produit, ancien_prix, nouveau_prix, utilisateur)
        VALUES
            (NEW.id_produit, OLD.prix_ht, NEW.prix_ht, CURRENT_USER());
    END IF;
END //

DELIMITER ;

Explication :

  • AFTER UPDATE : le trigger se declenche apres chaque UPDATE sur la table produit.
  • IF OLD.prix_ht != NEW.prix_ht : on ne logue que si le prix a reellement change (un UPDATE peut modifier d'autres colonnes sans toucher au prix).
  • OLD.prix_ht : l'ancien prix (avant modification).
  • NEW.prix_ht : le nouveau prix (apres modification).
  • CURRENT_USER() : l'utilisateur MySQL qui a fait la modification.

Test :

sql
-- Inserer un produit
INSERT INTO produit (designation, prix_ht, stock) VALUES ('Casque audio', 50.00, 20);

-- Le modifier
UPDATE produit SET prix_ht = 55.00 WHERE designation = 'Casque audio';

-- Verifier l'historique
SELECT * FROM historique_produit;
-- On voit : ancien_prix = 50.00, nouveau_prix = 55.00

-- Modifier le stock (pas le prix)
UPDATE produit SET stock = 25 WHERE designation = 'Casque audio';

-- Verifier : pas de nouvelle ligne dans l'historique (le prix n'a pas change)
SELECT * FROM historique_produit;

5.6 Cas d'utilisation 2 : validation avec SIGNAL SQLSTATE

Besoin : empecher l'insertion d'un produit avec un prix negatif ou un stock negatif.

sql
DELIMITER //

CREATE TRIGGER trg_valider_produit
BEFORE INSERT
ON produit
FOR EACH ROW
BEGIN
    IF NEW.prix_ht < 0 THEN
        SIGNAL SQLSTATE '45000'
            SET MESSAGE_TEXT = 'Le prix HT ne peut pas etre negatif';
    END IF;

    IF NEW.stock < 0 THEN
        SIGNAL SQLSTATE '45000'
            SET MESSAGE_TEXT = 'Le stock ne peut pas etre negatif';
    END IF;
END //

DELIMITER ;

Explication :

  • BEFORE INSERT : le trigger s'execute avant l'insertion. Si on declenche une erreur, l'insertion est annulee.
  • SIGNAL SQLSTATE '45000' : declenche une erreur utilisateur. Le code '45000' est le code standard pour les erreurs definies par l'utilisateur.
  • MESSAGE_TEXT : le message d'erreur retourne au programme appelant.

Test :

sql
-- Ceci doit reussir
INSERT INTO produit (designation, prix_ht, stock) VALUES ('Clef USB', 10.00, 50);

-- Ceci doit echouer
INSERT INTO produit (designation, prix_ht, stock) VALUES ('Clef USB', -5.00, 50);
-- Erreur : Le prix HT ne peut pas etre negatif

-- Ceci doit echouer
INSERT INTO produit (designation, prix_ht, stock) VALUES ('Clef USB', 10.00, -3);
-- Erreur : Le stock ne peut pas etre negatif

Important : il faut aussi creer un trigger BEFORE UPDATE pour la meme validation, sinon un UPDATE pourrait mettre un prix negatif.

sql
DELIMITER //

CREATE TRIGGER trg_valider_produit_update
BEFORE UPDATE
ON produit
FOR EACH ROW
BEGIN
    IF NEW.prix_ht < 0 THEN
        SIGNAL SQLSTATE '45000'
            SET MESSAGE_TEXT = 'Le prix HT ne peut pas etre negatif';
    END IF;

    IF NEW.stock < 0 THEN
        SIGNAL SQLSTATE '45000'
            SET MESSAGE_TEXT = 'Le stock ne peut pas etre negatif';
    END IF;
END //

DELIMITER ;

5.7 Cas d'utilisation 3 : mise a jour automatique du stock

Besoin : quand on ajoute une ligne de commande (INSERT dans ligne_commande), diminuer automatiquement le stock du produit concerne.

sql
DELIMITER //

CREATE TRIGGER trg_diminuer_stock
AFTER INSERT
ON ligne_commande
FOR EACH ROW
BEGIN
    UPDATE produit
    SET stock = stock - NEW.quantite
    WHERE id_produit = NEW.id_produit;
END //

DELIMITER ;

Explication :

  • AFTER INSERT ON ligne_commande : se declenche apres chaque insertion dans ligne_commande.
  • NEW.quantite : la quantite commandee (valeur de la ligne inseree).
  • NEW.id_produit : le produit concerne.
  • Le stock du produit est diminue de la quantite commandee.

Test :

sql
-- Verifier le stock initial
SELECT id_produit, designation, stock FROM produit WHERE id_produit = 1;
-- Supposons stock = 100

-- Inserer une ligne de commande
INSERT INTO ligne_commande (id_commande, id_produit, quantite, prix_unitaire)
VALUES (1, 1, 3, 25.00);

-- Verifier le stock
SELECT id_produit, designation, stock FROM produit WHERE id_produit = 1;
-- stock = 97

5.8 Cas d'utilisation 4 : mise a jour du total de la commande

Besoin : quand on ajoute une ligne de commande, mettre a jour automatiquement le montant_total dans la table commande.

sql
DELIMITER //

CREATE TRIGGER trg_maj_total_commande
AFTER INSERT
ON ligne_commande
FOR EACH ROW
BEGIN
    UPDATE commande
    SET montant_total = montant_total + (NEW.quantite * NEW.prix_unitaire)
    WHERE id_commande = NEW.id_commande;
END //

DELIMITER ;

Test :

sql
-- Creer une commande
INSERT INTO commande (id_client) VALUES (1);
-- montant_total = 0 par defaut

SET @id_cmd = LAST_INSERT_ID();

-- Ajouter des lignes
INSERT INTO ligne_commande (id_commande, id_produit, quantite, prix_unitaire)
VALUES (@id_cmd, 1, 2, 25.00);

INSERT INTO ligne_commande (id_commande, id_produit, quantite, prix_unitaire)
VALUES (@id_cmd, 2, 1, 15.00);

-- Verifier le total
SELECT id_commande, montant_total FROM commande WHERE id_commande = @id_cmd;
-- montant_total = 65.00 (2*25 + 1*15)

5.9 Les pieges des triggers

Piege 1 : boucle infinie entre triggers

Si un trigger sur la table A modifie la table B, et qu'un trigger sur la table B modifie la table A, on obtient une boucle infinie. MySQL detecte cette situation et leve une erreur (profondeur maximale atteinte).

Exemple dangereux :

Trigger sur table A → UPDATE table B
Trigger sur table B → UPDATE table A → re-declenche le trigger sur A → boucle

Solution : concevoir les triggers pour eviter les modifications circulaires. En cas de doute, dessiner un schema des dependances entre triggers.

Piege 2 : ordre d'execution avec plusieurs triggers

Depuis MySQL 5.7, on peut avoir plusieurs triggers pour le meme evenement sur la meme table. L'ordre est controle par FOLLOWS et PRECEDES :

sql
CREATE TRIGGER trg_second
AFTER INSERT ON produit
FOR EACH ROW
FOLLOWS trg_premier
BEGIN
    -- s'execute apres trg_premier
END;

Si on ne precise rien, l'ordre depend de l'ordre de creation. En examen, eviter d'avoir plusieurs triggers pour le meme evenement sauf si explicitement demande.

Piege 3 : un trigger ne peut pas modifier sa propre table avec INSERT/UPDATE/DELETE

En MySQL, un trigger sur la table X ne peut pas faire INSERT INTO X, UPDATE X ou DELETE FROM X. Cela provoque une erreur. Le seul moyen de modifier la ligne en cours est d'utiliser SET NEW.colonne = valeur dans un trigger BEFORE.

Piege 4 : utiliser NEW dans DELETE ou OLD dans INSERT

  • NEW n'existe pas dans un trigger DELETE (il n'y a pas de nouvelle ligne).
  • OLD n'existe pas dans un trigger INSERT (il n'y avait pas d'ancienne ligne).

Utiliser l'un ou l'autre dans le mauvais contexte provoque une erreur.

5.10 DROP TRIGGER

sql
DROP TRIGGER IF EXISTS trg_audit_prix_produit;

5.11 Voir les triggers existants

sql
SHOW TRIGGERS;
SHOW TRIGGERS FROM nom_base;
SHOW CREATE TRIGGER nom_trigger;

Chapitre terminé !

Tu as lu ce chapitre jusqu'au bout. Passe au suivant tant que c'est frais.

chapitres
–
de série
–
du cours
–
Chapitre suivant : Transactions dans les procéduresRevoir le sommaire du cours