Aller au contenu
Réussir mon BTS SIO

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

Par exemple

Chapitre 1613 minDébutant

Procédures stockées et triggers (aperçu)

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

À la fin de ce chapitre, tu sauras

  • distinguer procédure stockée, fonction et trigger ;
  • appeler et tester une procédure stockée ;
  • lire un trigger et dire ce qu'il fait ;
  • choisir entre la base et l'application pour une règle de gestion.

3 exercices corrigés

Une procédure stockée, une fonction stockée ou un trigger est un programme SQL enregistré dans la base et exécuté par le serveur MySQL. Ce chapitre montre à quoi ils servent, avec un exemple de chaque sur la base d'exemple parc ; le cours Procédures stockées et triggers enseigne à les écrire.

Procédures, fonctions et triggers

ObjetAppelUsage
Procédure stockéeExplicite, avec CALLUn traitement en plusieurs étapes : clôturer un ticket, archiver un mois
Fonction stockéeDans une requête, comme UPPER()Un calcul réutilisable qui renvoie une valeur
TriggerAutomatique, avant ou après un INSERT, un UPDATE ou un DELETEUne règle à appliquer quelle que soit l'origine de la modification : audit, contrôle
ÉvènementPlanifié par le serveurUne tâche périodique : purger les vieux tickets chaque nuit

Un exemple de chaque

Une procédure qui clôture un ticket dans une transaction, et annule tout en cas d'erreur :

sql
DELIMITER //
CREATE PROCEDURE cloturer_ticket(
  IN p_id_ticket     INT,
  IN p_id_technicien INT,
  IN p_duree_min     SMALLINT,
  IN p_compte_rendu  VARCHAR(255)
)
BEGIN
  DECLARE EXIT HANDLER FOR SQLEXCEPTION
  BEGIN
    ROLLBACK;
    RESIGNAL;            -- renvoie l'erreur d'origine à l'appelant
  END;

  START TRANSACTION;
  INSERT INTO intervention (id_ticket, id_technicien, date_intervention, duree_min, compte_rendu)
  VALUES (p_id_ticket, p_id_technicien, NOW(), p_duree_min, p_compte_rendu);
  UPDATE ticket SET statut = 'resolu', date_cloture = NOW()
  WHERE id_ticket = p_id_ticket;
  COMMIT;
END //
DELIMITER ;

CALL cloturer_ticket(6, 12, 25, 'Ventilateur remplacé');

Une fonction qui renvoie le temps passé sur un ticket :

sql
DELIMITER //
CREATE FUNCTION duree_totale_ticket(p_id_ticket INT) RETURNS INT
READS SQL DATA
BEGIN
  DECLARE v_total INT;
  SELECT COALESCE(SUM(duree_min), 0) INTO v_total
  FROM intervention
  WHERE id_ticket = p_id_ticket;
  RETURN v_total;
END //
DELIMITER ;

SELECT id_ticket, titre, duree_totale_ticket(id_ticket) AS minutes
FROM ticket
WHERE id_ticket IN (1, 7, 9);
id_tickettitreminutes
1Écran noir au démarrage65
7Installer Sage pour Paul Martin0
9Rançongiciel suspecté570

La mention READS SQL DATA est obligatoire quand le journal binaire est actif (le cas par défaut) : sans elle, ni DETERMINISTIC ni NO SQL, MySQL refuse de créer la fonction (erreur 1418).

Un trigger qui applique une règle que les clés étrangères ne savent pas exprimer : seul un membre du service informatique peut être technicien d'une intervention.

sql
DELIMITER //
CREATE TRIGGER trg_intervention_technicien
BEFORE INSERT ON intervention
FOR EACH ROW
BEGIN
  IF NOT EXISTS (SELECT 1
                 FROM employe e
                 JOIN service s ON s.id_service = e.id_service
                 WHERE e.id_employe = NEW.id_technicien AND s.code = 'INFO') THEN
    SIGNAL SQLSTATE '45000'
      SET MESSAGE_TEXT = 'Seul un membre du service informatique peut intervenir';
  END IF;
END //
DELIMITER ;
text
ERROR 1644 (45000): Seul un membre du service informatique peut intervenir

SIGNAL lève une erreur personnalisée ; l'INSERT est annulé. Un trigger BEFORE peut aussi corriger NEW avant l'enregistrement (SET NEW.titre = TRIM(NEW.titre);).

Dans la base ou dans l'application ?

Dans la base (procédures, triggers)Dans l'application
La règle s'applique à tous les accès : application, client SQL, script d'importLa logique est dans le même langage et le même dépôt que le reste du code
Moins d'allers-retours réseau pour un traitement lourdPlus facile à tester, à déboguer et à versionner
Droits très fins : EXECUTE sur une procédure au lieu de UPDATE sur une tableIndépendante du SGBD : changer de base ne réécrit pas les règles
Le code SQL procédural varie fortement d'un SGBD à l'autreLa règle est contournable par qui accède directement à la base

Le compromis courant : les contraintes d'intégrité (clés, CHECK, triggers de contrôle et d'audit) dans la base, qui en est la gardienne ; la logique métier riche dans l'application. Les sujets d'E7 SLAM donnent souvent un trigger à lire, à compléter ou à corriger : sache lire OLD, NEW, BEFORE, AFTER et SIGNAL.

Si le sujet est en SQL Server

Plusieurs sujets récents d'E7 SLAM sont écrits pour SQL Server. Trois différences reviennent.

Les triggers. Un trigger T-SQL s'exécute une fois par instruction, pas une fois par ligne : il n'y a ni FOR EACH ROW, ni OLD, ni NEW. À la place, deux pseudo-tables contiennent toutes les lignes touchées : deleted (les lignes avant la modification) et inserted (après). Pour un UPDATE, on les joint sur la clé. L'audit des changements de statut devient :

sql
-- SQL Server (T-SQL)
CREATE TRIGGER trg_ticket_audit_statut ON ticket
AFTER UPDATE
AS
BEGIN
  SET NOCOUNT ON;
  INSERT INTO audit_ticket (id_ticket, ancien_statut, nouveau_statut, modifie_par, modifie_le)
  SELECT i.id_ticket, d.statut, i.statut, SUSER_SNAME(), SYSDATETIME()
  FROM inserted i
  JOIN deleted d ON d.id_ticket = i.id_ticket
  WHERE i.statut <> d.statut;
END;
GO

Un UPDATE qui modifie dix tickets déclenche le trigger une seule fois, avec dix lignes dans inserted et deleted : écris toujours le corps pour un ensemble de lignes. Pour refuser une modification, THROW 50001, N'Message', 1; joue le rôle de SIGNAL : l'erreur annule l'instruction. Il n'y a pas de trigger BEFORE ; un trigger INSTEAD OF remplace l'instruction.

Les comptes. SQL Server sépare la connexion au serveur (LOGIN) et l'utilisateur d'une base (USER), sans partie hôte :

sql
-- SQL Server (T-SQL)
CREATE LOGIN app_parc WITH PASSWORD = 'Mot-de-passe-long-et-unique-2026';
GO
USE parc;
GO
CREATE USER app_parc FOR LOGIN app_parc;
GRANT SELECT ON SCHEMA::dbo TO app_parc;           -- toutes les tables du schéma dbo
GRANT INSERT, UPDATE ON dbo.ticket TO app_parc;
CREATE ROLE r_technicien;
ALTER ROLE r_technicien ADD MEMBER app_parc;       -- l'équivalent de GRANT rôle TO compte

Les lots. GO sépare les lots de SSMS et de sqlcmd (chiffrer une colonne avec SQL Server). Un CREATE TRIGGER, CREATE PROCEDURE ou CREATE VIEW doit être la première instruction de son lot, et une colonne créée par ALTER TABLE n'est utilisable que dans le lot suivant.

Exercices

Exercice 1 sur 3 Appeler et tester la procédure Réussi À revoir

  1. Clôture le ticket 11 avec la procédure cloturer_ticket : 20 minutes de Moussa Diallo (12), compte rendu « Certificat renouvelé ».
  2. Que se passe-t-il avec CALL cloturer_ticket(12, 9, 15, 'Clavier changé'); si le trigger trg_intervention_technicien existe ? Dans quel état est le ticket 12 ensuite ?
Voir la réponseMasquer la réponse de l'exercice 1
sql
CALL cloturer_ticket(11, 12, 20, 'Certificat renouvelé');

Pour le second appel, Inès Bonnet (9) est à la logistique : le trigger lève l'erreur 45000 sur l'INSERT. Le gestionnaire EXIT HANDLER de la procédure exécute ROLLBACK, puis RESIGNAL renvoie l'erreur à l'appelant, qui voit ERROR 1644 (45000): Seul un membre du service informatique peut intervenir. Le ticket 12 reste au statut ouvert, sans intervention ni date de clôture.

Tu avais trouvé ?

Exercice 2 sur 3 Une fonction de comptage Réussi À revoir

Écris une fonction nb_tickets_en_attente(p_id_employe INT) qui renvoie le nombre de tickets non résolus ouverts par un employé, puis utilise-la pour afficher les employés qui en ont au moins un.

Voir la réponseMasquer la réponse de l'exercice 2
sql
DELIMITER //
CREATE FUNCTION nb_tickets_en_attente(p_id_employe INT) RETURNS INT
READS SQL DATA
BEGIN
  DECLARE v_nb INT;
  SELECT COUNT(*) INTO v_nb
  FROM ticket
  WHERE id_demandeur = p_id_employe AND statut <> 'resolu';
  RETURN v_nb;
END //
DELIMITER ;

SELECT nom, prenom, nb_tickets_en_attente(id_employe) AS en_attente
FROM employe
WHERE nb_tickets_en_attente(id_employe) > 0
ORDER BY nom;

Le résultat liste Benali, Faure, Garnier, Girard, Martin et Rousseau, avec 1 ticket chacun. La fonction est appelée pour chaque employé : sur une grande table, une jointure avec GROUP BY serait plus rapide. Une fonction stockée sert surtout à partager un calcul entre plusieurs requêtes.

Tu avais trouvé ?

Exercice 3 sur 3 Lire un trigger Réussi À revoir

On trouve ce trigger dans la base :

sql
DELIMITER //
CREATE TRIGGER trg_ticket_cloture
BEFORE UPDATE ON ticket
FOR EACH ROW
BEGIN
  IF NEW.statut = 'resolu' AND OLD.statut <> 'resolu' THEN
    SET NEW.date_cloture = NOW();
  END IF;
END //
DELIMITER ;
  1. Que fait-il, et quand ?
  2. Pourquoi est-il déclaré BEFORE et non AFTER ?
  3. Un technicien rouvre le ticket 9 (UPDATE ticket SET statut = 'en_cours' WHERE id_ticket = 9;). Quel problème apparaît ? Corrige le trigger.
Voir la réponseMasquer la réponse de l'exercice 3
  1. Avant chaque modification d'une ligne de ticket, il regarde si le statut passe à resolu alors qu'il ne l'était pas. Si oui, il renseigne lui-même la date de clôture avec l'heure courante, quelle que soit la valeur envoyée par l'application.
  2. Seul un trigger BEFORE peut modifier NEW, la ligne qui va être enregistrée. Dans un trigger AFTER, NEW est en lecture seule : SET NEW.date_cloture provoquerait une erreur à la création du trigger.
  3. Le ticket 9 repasse « en cours » mais garde sa date de clôture du 6 octobre : la base dit qu'un ticket en cours est clos. On efface la date quand le ticket quitte le statut résolu :
sql
DELIMITER //
CREATE TRIGGER trg_ticket_cloture
BEFORE UPDATE ON ticket
FOR EACH ROW
BEGIN
  IF NEW.statut = 'resolu' AND OLD.statut <> 'resolu' THEN
    SET NEW.date_cloture = NOW();
  ELSEIF NEW.statut <> 'resolu' THEN
    SET NEW.date_cloture = NULL;
  END IF;
END //
DELIMITER ;

Pour remplacer un trigger existant, supprime-le d'abord avec DROP TRIGGER trg_ticket_cloture;.

Tu avais trouvé ?

Chapitre terminé !

Fais les 3 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : Sauvegarder une base : mysqldumpRevoir le sommaire du cours