Aller au contenu
Réussir mon BTS SIO

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

Par exemple

Chapitre 222 minAvancé

Créer une procédure stockée MySQL : DELIMITER, CALL, IN et OUT

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

À la fin de ce chapitre, tu sauras

  • créer une procédure avec DELIMITER, dans n'importe quel client ;
  • l'appeler avec CALL et récupérer ses résultats ;
  • choisir entre un paramètre IN, OUT et INOUT ;
  • lister, afficher, modifier et supprimer tes procédures.

4 exercices corrigés2 schémas

Une procédure stockée se crée avec CREATE PROCEDURE nom(paramètres) BEGIN … END et s'exécute avec CALL nom(valeurs). Elle reçoit des valeurs par ses paramètres IN, en renvoie par ses paramètres OUT, et peut aussi renvoyer des lignes comme un SELECT. Ce chapitre écrit tes premières procédures sur la base parc, explique la commande DELIMITER qui déroute tous les débutants, et montre comment gérer les procédures existantes.

Ta première procédure

Le service informatique affiche chaque matin les tickets en attente, les plus urgents d'abord. Plutôt que de copier la requête dans chaque outil, on l'enregistre dans une procédure :

sql
DELIMITER //

CREATE PROCEDURE tickets_en_attente()
BEGIN
  SELECT t.id_ticket, t.titre, t.priorite, t.statut,
         CONCAT(e.prenom, ' ', e.nom) AS demandeur
  FROM ticket t
  JOIN employe e ON e.id_employe = t.id_demandeur
  WHERE t.statut <> 'resolu'
  ORDER BY FIELD(t.priorite, 'critique', 'haute', 'normale', 'basse'), t.date_ouverture;
END //

DELIMITER ;

CALL tickets_en_attente();
id_tickettitreprioritestatutdemandeur
13Lenteurs réseau à l'entrepôtcritiqueen_coursNadia Faure
11Accès VPN impossiblehauteen_coursSofiane Benali
7Installer Sage pour Paul MartinnormaleouvertPaul Martin
14Batterie à remplacernormaleouvertHélène Garnier
6Ventilateur bruyantbasseen_coursLucas Girard
12Clavier cassébasseouvertLéa Rousseau

FIELD(valeur, liste…) renvoie la position de la valeur dans la liste : 1 pour critique, 4 pour basse. Trier sur ce nombre range les priorités dans l'ordre métier au lieu de l'ordre alphabétique.

Ce que tu dois retenir de la syntaxe :

  • les parenthèses suivent toujours le nom, même sans paramètre : tickets_en_attente() ;
  • le corps est un bloc BEGIN … END qui contient une ou plusieurs instructions, chacune terminée par un point-virgule ;
  • CREATE PROCEDURE enregistre la procédure dans la base courante (parc), il ne l'exécute pas ;
  • CALL l'exécute. Les parenthèses de CALL sont facultatives sans paramètre, mais écris-les toujours.

DELIMITER : le piège du point-virgule

Le client mysql lit ce que tu tapes et l'envoie au serveur instruction par instruction : il coupe à chaque point-virgule. Or le corps d'une procédure contient lui-même des points-virgules. Sans précaution, le client coupe au premier et envoie au serveur une instruction tronquée :

sql
CREATE PROCEDURE tickets_en_attente()
BEGIN
  SELECT id_ticket, titre FROM ticket WHERE statut <> 'resolu';
END;
text
ERROR 1064 (42000): You have an error in your SQL syntax; … near '' at line 3
ERROR 1064 (42000): You have an error in your SQL syntax; … near 'END' at line 1

Le serveur a reçu CREATE PROCEDURE … BEGIN SELECT … sans le END (première erreur), puis un END tout seul (seconde erreur). La commande DELIMITER change le caractère de fin d'instruction du client le temps de la création :

Sans DELIMITER : le client coupe au premier point-virguleCREATE PROCEDURE p()BEGINSELECT … FROM ticket;END;coupureCREATE … BEGIN SELECT …ERROR 1064 : il manque ENDENDERROR 1064 : END isoléAvec DELIMITER // : une seule instruction jusqu'à //DELIMITER //CREATE PROCEDURE p()BEGINSELECT … FROM ticket;END //DELIMITER ;CREATE PROCEDURE p() … ENDenvoyée d'un bloc au serveurprocédure créée
DELIMITER change le séparateur du client, pas celui du serveur
  • DELIMITER // : désormais, une instruction se termine par // ;
  • tout ce qui précède END //, points-virgules compris, part en une seule instruction ;
  • DELIMITER ; rétablit le séparateur habituel. Ne l'oublie pas : sinon, tes requêtes suivantes attendent un // et le client affiche -> sans rien exécuter.

// et $$ sont les deux séparateurs les plus courants ; choisis une suite de caractères qui n'apparaît jamais dans ton code. DELIMITER est une commande du client, pas du SQL : le serveur ne la reçoit jamais. Conséquences pratiques :

  • une procédure dont le corps tient en une seule instruction, sans BEGIN … END, n'a pas besoin de DELIMITER :

    sql
    CREATE PROCEDURE nb_tickets_ouverts()
      SELECT COUNT(*) AS nb_ouverts FROM ticket WHERE statut <> 'resolu';
    
  • dans le code d'une application (PDO, MySqlConnector, mysql2), on n'écrit jamais DELIMITER : le pilote envoie chaque chaîne au serveur comme une seule instruction, CREATE PROCEDURE … END compris. Un script de migration qui crée des procédures depuis une application se découpe donc à la main, une instruction par appel.

La syntaxe de CREATE PROCEDURE

sql
CREATE [DEFINER = compte] PROCEDURE [IF NOT EXISTS] nom_procedure (
  [IN | OUT | INOUT] nom_parametre TYPE,
  …
)
[COMMENT 'texte']
[SQL SECURITY { DEFINER | INVOKER }]
BEGIN
  -- déclarations (DECLARE), puis instructions
END

Les clauses DEFINER et SQL SECURITY décident avec quels droits la procédure s'exécute : le chapitre 11 leur est consacré. COMMENT documente la procédure ; le commentaire s'affiche dans SHOW PROCEDURE STATUS.

Des conventions de nommage évitent bien des erreurs : un verbe pour une procédure qui agit (cloturer_ticket, installer_logiciel), un nom pour une procédure qui affiche (tickets_en_attente), le préfixe p_ pour les paramètres et v_ pour les variables locales. Le préfixe n'est pas une coquetterie : il évite le piège décrit plus bas.

Pour créer une procédure, ton compte a besoin du privilège CREATE ROUTINE sur la base. MySQL donne ensuite automatiquement à son créateur les droits EXECUTE (l'appeler) et ALTER ROUTINE (la modifier ou la supprimer).

Les paramètres IN, OUT et INOUT

Un paramètre a un mode qui dit dans quel sens circule la valeur.

ModeSensCe que voit la procédure au départCe que voit l'appelant à la fin
IN (par défaut)De l'appelant vers la procédureLa valeur passéeRien : une modification dans la procédure reste invisible
OUTDe la procédure vers l'appelantNULL, quelle que soit la valeur passéeLa dernière valeur affectée
INOUTDans les deux sensLa valeur passéeLa valeur modifiée
Session de l'appelantProcédure11une valeurINla valeur entre, rien ne ressortp_id_technicienvaut 11@nbvaut 6 après l'appelOUTla valeur calculée ressortp_nb_interventionsdémarre à NULL@total65 avant, 635 aprèsINOUTentre, est modifiée, ressortp_total65 + 570 = 635
IN entre, OUT sort, INOUT entre et ressort

Un paramètre IN. La liste des tickets d'un demandeur :

sql
DELIMITER //
CREATE PROCEDURE tickets_du_demandeur(IN p_id_employe INT)
BEGIN
  SELECT id_ticket, titre, statut, date_ouverture
  FROM ticket
  WHERE id_demandeur = p_id_employe
  ORDER BY date_ouverture;
END //
DELIMITER ;

CALL tickets_du_demandeur(3);   -- Julie Lefèvre
id_tickettitrestatutdate_ouverture
1Écran noir au démarrageresolu2026-09-02 08:47:00
10Écran noir au démarrageresolu2026-10-12 08:50:00

Des paramètres OUT. Le nombre d'interventions et le temps passé par un technicien. SELECT … INTO range le résultat d'une requête dans des variables ou des paramètres, dans l'ordre des colonnes :

sql
DELIMITER //
CREATE PROCEDURE charge_technicien(
  IN  p_id_technicien    INT,
  OUT p_nb_interventions INT,
  OUT p_minutes          INT
)
BEGIN
  SELECT COUNT(*), COALESCE(SUM(duree_min), 0)
  INTO p_nb_interventions, p_minutes
  FROM intervention
  WHERE id_technicien = p_id_technicien;
END //
DELIMITER ;

CALL charge_technicien(11, @nb, @minutes);   -- Chloé Lambert
SELECT @nb AS interventions, @minutes AS minutes;
interventionsminutes
6295

À l'appel, on passe des variables de session, préfixées par @ : elles appartiennent à ta connexion et gardent leur valeur jusqu'à sa fermeture. Le SELECT qui suit lit ce que la procédure y a écrit. Avec Moussa Diallo (12), tu obtiendrais 7 interventions et 450 minutes ; avec Antoine Mercier (10), 4 et 510. Pour un employé qui n'est jamais intervenu, COUNT(*) vaut 0 et COALESCE transforme le SUM vide (NULL) en 0.

Un paramètre INOUT. Une procédure qui ajoute à un total le temps passé sur un ticket. L'appelant l'appelle plusieurs fois avec la même variable :

sql
DELIMITER //
CREATE PROCEDURE cumuler_duree(INOUT p_total INT, IN p_id_ticket INT)
BEGIN
  DECLARE v_duree INT;

  SELECT COALESCE(SUM(duree_min), 0) INTO v_duree
  FROM intervention
  WHERE id_ticket = p_id_ticket;

  SET p_total = p_total + v_duree;
END //
DELIMITER ;

SET @total = 0;
CALL cumuler_duree(@total, 1);   -- + 65 minutes
CALL cumuler_duree(@total, 9);   -- + 570 minutes
SELECT @total;                   -- 635

DECLARE crée une variable locale, qui n'existe que pendant l'exécution de la procédure ; SET lui affecte une valeur. Le chapitre 3 les détaille.

Le piège du paramètre qui porte le nom d'une colonne

Que renvoie cette procédure pour le ticket 1, qui a deux interventions ?

sql
DELIMITER //
CREATE PROCEDURE nb_interventions(IN id_ticket INT)
BEGIN
  SELECT COUNT(*) AS nb FROM intervention WHERE id_ticket = id_ticket;
END //
DELIMITER ;

CALL nb_interventions(1);
nb
17

Dix-sept : toutes les interventions de la base. Dans une routine, un nom ambigu désigne d'abord la variable locale, puis le paramètre, et seulement ensuite la colonne. Les deux côtés du WHERE désignent donc le paramètre, et 1 = 1 est toujours vrai. Avec un DELETE ou un UPDATE, ce piège efface ou modifie toute la table. Préfixe tes paramètres (p_id_ticket), ou qualifie les colonnes par leur table (intervention.id_ticket).

Une procédure qui renvoie plusieurs résultats

Chaque SELECT sans INTO d'une procédure renvoie un jeu de résultats à l'appelant. Une procédure peut en renvoyer plusieurs :

sql
DELIMITER //
CREATE PROCEDURE bilan_ticket(IN p_id_ticket INT)
BEGIN
  SELECT id_ticket, titre, statut FROM ticket WHERE id_ticket = p_id_ticket;

  SELECT date_intervention, duree_min, compte_rendu
  FROM intervention
  WHERE id_ticket = p_id_ticket
  ORDER BY date_intervention;
END //
DELIMITER ;

CALL bilan_ticket(9);
id_tickettitrestatut
9Rançongiciel suspectéresolu
date_interventionduree_mincompte_rendu
2026-10-05 08:20:00240Poste isolé du réseau, analyse antivirus
2026-10-05 13:30:00180Réinstallation complète du poste
2026-10-06 09:00:00150Données restaurées depuis la sauvegarde

Le client mysql affiche les deux tableaux l'un après l'autre. Une application doit les lire dans l'ordre et passer de l'un à l'autre explicitement (chapitre 12). C'est pratique, mais un appelant qui oublie un jeu de résultats bloque la connexion : préfère des paramètres OUT quand tu renvoies quelques valeurs isolées.

Lister, afficher, modifier et supprimer

sql
SHOW PROCEDURE STATUS WHERE Db = 'parc';     -- les procédures de la base
SHOW CREATE PROCEDURE charge_technicien;     -- le code complet, tel qu'il a été enregistré

-- Même information, en SQL normalisé : information_schema
SELECT routine_name AS nom, routine_type AS type, definer, security_type
FROM information_schema.routines
WHERE routine_schema = 'parc'
ORDER BY routine_name;

Pour supprimer une procédure : DROP PROCEDURE IF EXISTS charge_technicien;. IF EXISTS évite une erreur si elle n'existe pas, ce qui rend un script rejouable.

Pour modifier son code, il n'y a pas d'autre moyen que de la supprimer puis de la recréer. ALTER PROCEDURE ne change que ses caractéristiques (commentaire, SQL SECURITY) :

sql
ALTER PROCEDURE charge_technicien COMMENT 'Nombre d''interventions et minutes d''un technicien';

Un script de déploiement commence donc par DROP PROCEDURE IF EXISTS, puis CREATE PROCEDURE, et se termine par les GRANT EXECUTE : supprimer une procédure supprime aussi les droits donnés dessus, et la recréer ne les rétablit pas. Depuis MySQL 8.0.29, CREATE PROCEDURE IF NOT EXISTS crée la procédure seulement si elle n'existe pas encore : si elle existe, MySQL ne la remplace pas et se contente d'une note (1304, PROCEDURE … already exists). Ce n'est donc pas un moyen de mettre à jour du code.

Exercices

Exercice 1 sur 4 Les postes d'un service Réussi À revoir

Écris une procédure postes_du_service(p_code VARCHAR(8)) qui affiche le numéro d'inventaire, le type, la marque, le modèle et le nom de l'utilisateur de chaque poste affecté à un salarié du service dont le code est passé en paramètre, triés par numéro d'inventaire. Appelle-la pour le service 'COMM'.

Voir la réponseMasquer la réponse de l'exercice 1
sql
DELIMITER //
CREATE PROCEDURE postes_du_service(IN p_code VARCHAR(8))
BEGIN
  SELECT p.num_inventaire, p.type_poste, p.marque, p.modele,
         CONCAT(e.prenom, ' ', e.nom) AS utilisateur
  FROM poste p
  JOIN employe e ON e.id_employe = p.id_employe
  JOIN service s ON s.id_service = e.id_service
  WHERE s.code = p_code
  ORDER BY p.num_inventaire;
END //
DELIMITER ;

CALL postes_du_service('COMM');
num_inventairetype_postemarquemodeleutilisateur
PC-0004portableHPEliteBook 840Sofiane Benali
PC-0005portableHPEliteBook 840Camille Roux
PC-0006portableLenovoThinkPad T14Thomas Petit
PC-0015fixeHPProDesk 400Sofiane Benali

Sofiane Benali apparaît deux fois : il a deux postes. Les postes en stock (id_employe à NULL) disparaissent avec la jointure interne, ce qui est voulu ici.

Tu avais trouvé ?

Exercice 2 sur 4 Paramètres de sortie Réussi À revoir

Écris une procédure licences_logiciel(IN p_id_logiciel INT, OUT p_installees INT, OUT p_restantes INT) qui renvoie le nombre d'installations d'un logiciel et le nombre de licences restantes (NULL pour un logiciel libre). Donne l'appel et le résultat pour Odoo (5) et pour Firefox (3).

Voir la réponseMasquer la réponse de l'exercice 2
sql
DELIMITER //
CREATE PROCEDURE licences_logiciel(
  IN  p_id_logiciel INT,
  OUT p_installees  INT,
  OUT p_restantes   INT
)
BEGIN
  SELECT COUNT(*) INTO p_installees
  FROM installation
  WHERE id_logiciel = p_id_logiciel;

  SELECT nb_licences - p_installees INTO p_restantes
  FROM logiciel
  WHERE id_logiciel = p_id_logiciel;
END //
DELIMITER ;

CALL licences_logiciel(5, @inst, @rest);
SELECT @inst, @rest;   -- 7 et 1
CALL licences_logiciel(3, @inst, @rest);
SELECT @inst, @rest;   -- 14 et NULL

Pour Firefox, nb_licences est NULL, et NULL - 14 vaut NULL : le résultat dit « pas de limite » sans code particulier. Un paramètre OUT peut servir dans la suite de la procédure, comme p_installees dans la seconde requête.

Tu avais trouvé ?

Exercice 3 sur 4 Trouve les erreurs Réussi À revoir

Ce script, tapé dans le client mysql, devait créer une procédure qui clôture un ticket. Trouve les deux erreurs et dis ce qui se passe à cause de chacune.

sql
DELIMITER //
CREATE PROCEDURE cloturer(id_ticket INT)
BEGIN
  UPDATE ticket SET statut = 'resolu', date_cloture = NOW()
  WHERE id_ticket = id_ticket;
END;
DELIMITER ;
Voir la réponseMasquer la réponse de l'exercice 3
  1. END; au lieu de END //. Le client attend toujours // : il garde tout le texte en mémoire, y compris la ligne DELIMITER ;, qu'il ne traite plus comme une commande, et affiche ->. Quand le // finit par arriver, il envoie le tout au serveur : le END; termine l'instruction et la procédure est créée (le client affiche Query OK), puis le serveur répond par l'erreur 1064 sur le texte DELIMITER ; qui suivait. L'erreur fait croire que rien n'a marché, alors que la procédure existe, avec l'erreur 2 dedans, et que le séparateur du client est toujours //. Le séparateur étant encore //, vérifie avec SHOW PROCEDURE STATUS WHERE Db = 'parc' //, supprime la procédure avec DROP PROCEDURE cloturer //, puis rétablis le séparateur avec DELIMITER ;.
  2. Le paramètre porte le nom de la colonne. Une fois l'erreur 1 corrigée, la procédure se crée sans message d'erreur, mais WHERE id_ticket = id_ticket compare le paramètre à lui-même : c'est toujours vrai, et CALL cloturer(6) clôture tous les tickets de la base. Renomme-le p_id_ticket et écris son mode : IN p_id_ticket INT.

La seconde erreur est la plus grave, parce qu'elle ne produit aucun message. Il manque aussi des contrôles (le ticket existe-t-il ? est-il déjà résolu ?) et l'intervention qui accompagne une clôture : le chapitre 7 écrit la version complète.

Tu avais trouvé ?

Exercice 4 sur 4 Ce que voit l'appelant Réussi À revoir

Qu'affichent les deux SELECT ?

sql
DELIMITER //
CREATE PROCEDURE doubler(IN p_a INT, OUT p_b INT, INOUT p_c INT)
BEGIN
  SET p_a = p_a * 2;
  SET p_b = p_b * 2;
  SET p_c = p_c * 2;
END //
DELIMITER ;

SET @a = 5, @b = 5, @c = 5;
CALL doubler(@a, @b, @c);
SELECT @a, @b, @c;
Voir la réponseMasquer la réponse de l'exercice 4
@a@b@c
5NULL10
  • @a reste 5 : la modification de p_a, paramètre IN, ne ressort pas.
  • @b vaut NULL : p_b, paramètre OUT, démarre à NULL dans la procédure, et NULL * 2 vaut NULL.
  • @c vaut 10 : p_c entre avec 5 et ressort doublé.

Tu avais trouvé ?

Chapitre terminé !

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

chapitres
–
de série
–
du cours
–
Chapitre suivant : Variables, IF, CASE et boucles WHILE en SQL procéduralRevoir le sommaire du cours