Le carnet
Chapitre 6 · encre charbon

PL/SQL — procédures, curseurs, exceptions

Du code stocké et appelable, des curseurs pour parcourir des résultats, et un gestionnaire d'erreurs robuste. Le PL/SQL qui rentre vraiment dans le moteur.

~ 35 min de lecture TP6 & TP7 — Producteur de vin 14 flashcards

1. Procédures et fonctions stockées

  • Déporter les traitements de manipulation vers le SGBD.
  • Ensemble d'instructions procédurales + requêtes SQL.
  • On les dit procédures/fonctions stockées.
  • Traitement identique et commun à tous les applicatifs.
  • Charge supplémentaire pour le SGBD (à arbitrer).
PROCEDURE IN / OUT / IN OUT exécute une action EXEC proc(args); ↪ ne « renvoie » rien FUNCTION IN / OUT / IN OUT · RETURN type calcule et RETURN une valeur SELECT fct(args) FROM dual; ↪ renvoie une valeur typée

Une fonction renvoie une valeur ; une procédure exécute un effet.

Déclaration

CREATE [OR REPLACE] PROCEDURE nom_procédure (paramètres)
IS
    <bloc de variables>
BEGIN
    <bloc de traitements>
END;

CREATE [OR REPLACE] FUNCTION nom_fonction (paramètres)
RETURN type
IS
    <bloc de variables>
BEGIN
    <bloc de traitements>
    RETURN <result>;
END;

Paramètres — trois modes

ModeSensUsage
INEntrée (lecture seule)Valeur reçue par la procédure
OUTSortieValeur renvoyée par paramètre
IN OUTEntrée + sortieModifié sur place
CREATE OR REPLACE PROCEDURE nom_procédure (
    p_1 IN     <type1> DEFAULT <valeur>,
    p_2 OUT    <type2>,
    p_3 IN OUT <type3>)
IS
    <bloc de variables>
BEGIN
    <bloc de traitements>
END;
⚠️ Types SANS taille Dans la signature d'une procédure/fonction, les types s'écrivent sans taille : NUMBER, VARCHAR2, et non pas NUMBER(6,2) ou VARCHAR2(50).
-- BON
CREATE OR REPLACE PROCEDURE ma_proc (
    p_1 IN NUMBER,
    p_2 IN VARCHAR2)

Variables, typage interne

CREATE [OR REPLACE] PROCEDURE nom_procédure (<paramètres>)
IS
    v_1 NUMBER;
    v_2 <table>.<colonne>%TYPE;
    v_3 <table>%ROWTYPE;
    TYPE t_rec IS RECORD (a t1, b t2);
    v_rec t_rec;
BEGIN
    <bloc de traitements>
END;

Appel

-- Dans un bloc PL/SQL
BEGIN
    v_res := f_nom(args);
    proc_nom(args);
END;

-- Depuis SQL*Plus
SQL> SELECT f_nom(args) FROM DUAL;
SQL> EXEC proc_nom(args);

Fonctions natives utiles

CatégorieExemples
Chaînesconcat (||), trim, length, substr
Numériquesceil, floor, round, trunc, sqrt, avg, max
Dateto_date, to_char, day, month, year, next_day
Transformationdecode, coalesce, nvl

2. Curseurs

Définition Un curseur est un objet qui permet de manipuler un ensemble de résultats d'une requête SELECT. Il contient les données de la requête, ligne par ligne.

Deux types de déclaration :

  • implicite : SELECT … INTO … FROM …; (résultat unique)
  • explicite : CURSOR c IS …; avec OPEN / FETCH / CLOSE (résultat multiple)

Curseurs implicites

BEGIN
    -- SELECT INTO direct (1 seule ligne attendue)
    SELECT nom, prenom INTO v_nom, v_prenom
    FROM clients
    WHERE id = p_id_client;

    -- ou via SQL dynamique
    EXECUTE IMMEDIATE r_req INTO v_res USING v_num;
END;

Curseurs explicites — cycle de vie

OPEN cur exécute la requête LOOP FETCH cur INTO v; EXIT WHEN cur%NOTFOUND; … traitements … END LOOP sortie CLOSE cur libère les ressources ⚠️ Ne pas oublier le CLOSE — sinon fuite de ressources

Cycle de vie d'un curseur explicite : OPEN → FETCH en boucle → CLOSE.

CREATE OR REPLACE PROCEDURE nom_procédure
IS
    CURSOR cur IS
        SELECT * FROM table;
    v_item cur%ROWTYPE;
BEGIN
    OPEN cur;                              -- ouverture
    LOOP
        FETCH cur INTO v_item;             -- récupère ligne
        EXIT WHEN cur%NOTFOUND;             -- fin du parcours
        <traitements>
    END LOOP;
    CLOSE cur;                             -- fermeture !
END;

Pseudo-attributs d'un curseur

AttributSens
cur%FOUNDVrai si un enregistrement a été récupéré au dernier FETCH
cur%NOTFOUNDVrai si aucun enregistrement n'a été récupéré (fin du parcours)
cur%ISOPENVrai si le curseur est ouvert
cur%ROWCOUNTNombre de FETCH effectués

Parcours automatique — FOR cursor LOOP

🎯 La forme la plus pratique
Le FOR ouvre, fetch et ferme automatiquement. Pas d'oubli de CLOSE, pas de gestion manuelle.
-- Curseur déclaré puis parcouru avec FOR
CREATE OR REPLACE PROCEDURE nom_procédure
IS
    CURSOR cur IS
        SELECTFROM …;
BEGIN
    FOR v_ligne IN cur LOOP
        <traitements>
    END LOOP;
END;

-- Curseur implicite (FOR sur SELECT direct)
BEGIN
    FOR v_ligne IN (SELECTFROM …) LOOP
        <traitements>
    END LOOP;
END;

Curseur paramétré

CREATE OR REPLACE PROCEDURE nom_procédure
IS
    CURSOR cur(p_num NUMBER) IS
        SELECT * FROM table WHERE no = p_num;
BEGIN
    OPEN cur(12);
    …
END;

3. Transactions PL/SQL

Permettent d'isoler un ensemble de modifications sur les données ; vérifient les propriétés ACID (revoir CM3).

Les 4 composantes principales

SAVEPOINT nom;Pose un point de sauvegarde
COMMIT;Valide la transaction
ROLLBACK;Annule la transaction (revient au dernier état stable connu)
SET TRANSACTION [READ ONLY|READ WRITE] [NAME 'tr_nom']Démarre une transaction nommée
BEGIN
    INSERT INTO …;
    INSERT INTO …;
    INSERT INTO …;
    INSERT INTO …;
    ROLLBACK;                -- Annule les 4 précédentes

    INSERT INTO …;
    INSERT INTO …;
    SAVEPOINT icionsauve;
    INSERT INTO …;
    INSERT INTO …;
    ROLLBACK TO icionsauve; -- Annule les 2 dernières
END;
BEGIN
    SET TRANSACTION NAME 'tr_nom';
    DELETE FROM …;
    INSERT INTO …;
    UPDATE …;
    COMMIT;
END;

Transaction autonome — PRAGMA AUTONOMOUS_TRANSACTION

  • Une procédure stockée / fonction devrait ne pas réaliser de validation. Charge au code appelant.
  • Mais on peut créer une transaction autonome via PRAGMA AUTONOMOUS_TRANSACTION qui s'engage indépendamment de la transaction appelante.
CREATE OR REPLACE PROCEDURE nom_procédure
IS
    PRAGMA AUTONOMOUS_TRANSACTION;
    <bloc variables>
BEGIN
    <traitements>
    COMMIT;  -- Commit indépendant de la transaction appelante
END;
📋 Cas d'usage typique
Journaliser une erreur dans une table d'audit même si la transaction métier rollback. Le log persiste ; le métier n'est pas affecté.

4. Gestion des exceptions

Bloc gestion d'erreurs

BEGIN
    <traitements à réaliser>
EXCEPTION
    WHEN ex1 THENWHEN ex2 THENWHEN OTHERS THEN-- fourre-tout
END;

Exceptions classiques Oracle

ExceptionCodeCause
DUP_VAL_ON_INDEXORA-00001Doublon sur clé primaire / unique
NO_DATA_FOUNDORA-01403SELECT INTO sans résultat
TOO_MANY_ROWSORA-01422SELECT INTO avec plusieurs résultats — il faut un curseur
ZERO_DIVIDEORA-01476Division par zéro
INVALID_NUMBERORA-01722Conversion en nombre impossible

Récupérer code et message d'erreur

BEGIN
    INSERT INTO …;
EXCEPTION
    WHEN DUP_VAL_ON_INDEX THEN
        DBMS_OUTPUT.PUT_LINE('Erreur n° '||SQLCODE||' - '||SQLERRM);
        UPDATE …;
END;
  • SQLCODE = code d'erreur Oracle (ORA-xxxxx)
  • SQLERRM = message d'erreur explicite

Créer ses propres exceptions

  1. Déclarer une variable EXCEPTION
  2. Lui associer un code via PRAGMA EXCEPTION_INIT(nom, code)
  3. La lever avec RAISE
  4. La capturer dans un WHEN
CREATE OR REPLACE PROCEDURE verifier_age(p_age NUMBER)
IS
    e_age_invalide EXCEPTION;
    PRAGMA EXCEPTION_INIT(e_age_invalide, -20001);
BEGIN
    IF p_age < 18 THEN
        RAISE e_age_invalide;
    END IF;
    DBMS_OUTPUT.PUT_LINE('Âge valide');
EXCEPTION
    WHEN e_age_invalide THEN
        DBMS_OUTPUT.PUT_LINE('Âge invalide');
END;

Lever une erreur ponctuelle

RAISE_APPLICATION_ERROR(code, message);
-- code ∈ [-20999, -20000] (strictement < -20000)

Exception levée puis gérée par une procédure appelante

-- Côté procédure qui lève
c_num_monexc CONSTANT NUMBER := -20042;
ex_monexception EXCEPTION;
PRAGMA EXCEPTION_INIT(ex_monexception, -20042);

BEGINEXCEPTION
    WHEN OTHERS THEN
        RAISE_APPLICATION_ERROR(c_num_monexc, 'Erreur id : '||p_id);
END;

-- Côté procédure appelante
BEGINEXCEPTION
    WHEN ex_monexception THEN
        DBMS_OUTPUT.PUT_LINE('Je gère l\'exception perso');
END;

5. Packages

  • Regroupe des fonctionnalités, traitements au sein d'une même entité.
  • Concerne : procédures/fonctions, exceptions, curseurs, variables/constantes.

Architecture en deux parties

Déclaration CREATE PACKAGE pkg IS … • signatures procédures • signatures fonctions • constantes / variables • exceptions 🔓 interface publique Body (description) CREATE PACKAGE BODY pkg IS … • corps des procédures • corps des fonctions • variables/cursors privés • helpers internes 🔒 implémentation

Le package sépare l'interface publique (declaration) de l'implémentation (body).

Déclaration (header)

CREATE [OR REPLACE] PACKAGE nom_pkg
IS
    [déclaration des variables/constantes;]
    [déclaration des exceptions;]
    [déclaration de procédures;]
    [déclaration de fonctions;]
    …
END nom_pkg;

Body

CREATE [OR REPLACE] PACKAGE BODY nom_pkg
IS
    [déclaration de variables (privées);]
    [déclaration et corps de procédures;]
    [déclaration et corps de fonctions;]
    …
END nom_pkg;

Exemple complet

CREATE OR REPLACE PACKAGE pkg_utils IS
    c_PI    CONSTANT NUMBER := 3.14159;
    vg_user VARCHAR2(10);
    PROCEDURE logue(p_str IN VARCHAR2);
    PROCEDURE init;
    FUNCTION  rech(p_id IN user.id%TYPE) RETURN user.usr_no%TYPE;
END pkg_utils;
/

CREATE OR REPLACE PACKAGE BODY pkg_utils IS
    PROCEDURE logue(p_str IN VARCHAR2) IS
    BEGIN
        DBMS_OUTPUT.PUT_LINE(p_str);
    END logue;

    PROCEDURE init IS
    BEGIN
        SELECT USER INTO vg_user FROM DUAL;
    END init;

    FUNCTION  rech(p_id IN user.id%TYPE) RETURN user.usr_no%TYPE IS
        v_no user.usr_no%TYPE;
    BEGIN
        SELECT usr_no INTO v_no FROM user WHERE id = p_id;
        RETURN v_no;
    END rech;
END pkg_utils;
/
🎁 Avantages des packages
• Organisation cohérente du code par domaine fonctionnel
• Encapsulation : exposer seulement ce qui est nécessaire
• Variables globales partagées sur la session
• Optimisation : tout le package est chargé en mémoire à la première utilisation

Réviser le chapitre

Pour vérifier ta compréhension

Quand préférer une fonction à une procédure ?

Quand on a besoin de renvoyer une valeur exploitable dans une expression — typiquement utilisable dans un SELECT (SELECT ma_fct(x) FROM dual). Si on veut seulement exécuter un effet de bord (modifier la base, écrire un log), on prend une procédure.

Pourquoi écrire NUMBER et non NUMBER(6,2) dans la signature ?

Parce que les types dans la signature d'une procédure/fonction Oracle s'écrivent sans taille. Sinon erreur de compilation. La taille s'applique seulement aux déclarations de variables dans le corps ou aux colonnes de tables.

Différence entre SELECT … INTO et un curseur explicite ?

SELECT … INTO : pour une seule ligne. Si 0 résultat → NO_DATA_FOUND, si plusieurs → TOO_MANY_ROWS. C'est le curseur implicite.
Curseur explicite : pour plusieurs lignes ou quand on veut contrôler finement le parcours (paramètres, attributs %ROWCOUNT, etc.).

Pourquoi utiliser FOR row IN cursor LOOP plutôt que OPEN/FETCH/CLOSE manuel ?

Pour la simplicité : pas d'oubli de CLOSE, pas de gestion de %NOTFOUND. Le FOR ouvre, fetch, ferme tout seul. Préférer cette forme dans 99% des cas — sauf si on a besoin du contrôle fin (sortir prématurément avec EXIT WHEN, lire %ROWCOUNT, etc.).

Quand utiliser PRAGMA AUTONOMOUS_TRANSACTION ?

Quand une procédure doit commiter indépendamment de la transaction qui l'appelle. Cas typique : journalisation d'audit. La transaction métier peut être annulée, mais on veut garder la trace de ce qui s'est passé dans la table de logs. La transaction autonome rend ça possible.

Quelle est la différence entre RAISE et RAISE_APPLICATION_ERROR ?

RAISE lève une exception déjà déclarée (ex : RAISE e_age_invalide). Elle peut être capturée par un WHEN e_age_invalide THEN dans le bloc englobant.
RAISE_APPLICATION_ERROR(code, msg) lève une erreur ponctuelle avec un code dans [-20999, -20000] et un message. Idéale pour signaler une erreur au client appelant.

Flashcards éclair

Clique pour retourner.

Procédure vs Fonction

Différence clé.

tourne →
Procédure : exécute un effet, pas de RETURN.
Fonction : RETURN type obligatoire, utilisable dans un SELECT.

3 modes de paramètres

Nomme-les.

tourne →
IN · OUT · IN OUT

Types dans signature

Quelle particularité ?

tourne →
SANS taille. NUMBER et pas NUMBER(6,2).

Curseur explicite

4 étapes.

tourne →
DECLARE (CURSOR c IS …) → OPENFETCH en boucle → CLOSE

Attributs curseur

Les 4.

tourne →
%FOUND
%NOTFOUND
%ISOPEN
%ROWCOUNT

Curseur FOR LOOP

Pattern simplifié.

tourne →
FOR v IN cur LOOPEND LOOP;
-- open/fetch/close auto

Curseur paramétré

Syntaxe.

tourne →
CURSOR c(p_id NUMBER) IS
  SELECTWHERE id = p_id;
OPEN c(12);

Exceptions classiques

3 à connaître.

tourne →
DUP_VAL_ON_INDEX (doublon clé)
NO_DATA_FOUND (rien trouvé)
TOO_MANY_ROWS (plusieurs lignes)

SQLCODE / SQLERRM

À quoi servent-ils ?

tourne →
SQLCODE = code Oracle (ORA-xxxxx)
SQLERRM = message d'erreur

Exception perso

3 étapes.

tourne →
e EXCEPTION;
PRAGMA EXCEPTION_INIT(e, -20001);
-- puis
RAISE e;

RAISE_APPLICATION_ERROR

Plage du code.

tourne →
Code dans [-20999, -20000] (strictement < -20000).

Transaction autonome

Pragma magique.

tourne →
PRAGMA AUTONOMOUS_TRANSACTION;

Package — 2 parties

Nommer.

tourne →
Déclaration (interface publique)
Body (description, implémentation)

Création package

Syntaxe minimale.

tourne →
CREATE OR REPLACE PACKAGE p IS
  PROCEDURE f;
END p;
/
CREATE OR REPLACE PACKAGE BODY p IS
  PROCEDURE f IS BEGINEND;
END p;

Quiz éclair

test rapide · 7 questions
0 / 7
1.Dans la signature d'une procédure Oracle, p_nom IN VARCHAR2(50) est :
  • Correct
  • Correct, mais déprécié
  • Incorrect : pas de taille dans la signature
  • Correct seulement avec %TYPE
Les types dans la signature s'écrivent sans taille. Erreur de compilation sinon.
2.Un SELECT … INTO qui ne renvoie aucune ligne lève :
  • TOO_MANY_ROWS
  • NO_DATA_FOUND
  • NULL est affecté aux variables
  • INVALID_CURSOR
Code ORA-01403. À gérer dans un bloc EXCEPTION.
3.Quel pseudo-attribut indique la fin d'un parcours de curseur ?
  • %FOUND
  • %ISOPEN
  • %ROWCOUNT
  • %NOTFOUND
Usage : EXIT WHEN cur%NOTFOUND;.
4.Pour gérer un curseur sans risque d'oubli, on utilise :
  • OPEN/FETCH/CLOSE manuel
  • Un trigger AFTER
  • FOR row IN cursor LOOP … END LOOP;
  • Une exception WHEN OTHERS
Le FOR cursor LOOP ouvre, fetch et ferme tout seul.
5.Quel code est valide pour RAISE_APPLICATION_ERROR ?
  • -19000
  • -20500
  • -21500
  • +20500
Plage valide : [-20999, -20000]. Seul -20500 est dans cette plage.
6.Pour journaliser une erreur même si la transaction métier rollback, on utilise :
  • PRAGMA AUTONOMOUS_TRANSACTION
  • SAVEPOINT
  • Un trigger AFTER
  • Une fonction renvoyant un booléen
La transaction autonome s'engage indépendamment de la transaction appelante.
7.Dans un package, où se trouve l'implémentation des procédures ?
  • Dans la déclaration
  • Dans un trigger associé
  • Dans le PACKAGE BODY
  • Dans un fichier externe
Le header (PACKAGE) contient les signatures publiques. Le body (PACKAGE BODY) contient les implémentations + variables privées.

Pour la feuille recto-verso

  • Procédure : CREATE PROCEDURE nom (p IN type, p OUT type, p IN OUT type) IS … BEGIN … END;
  • Fonction : ajoute RETURN type et instruction RETURN val;
  • Types SANS taille dans la signature
  • Appel : EXEC proc(args) · SELECT fct(args) FROM dual · ou direct dans un bloc PL/SQL
  • Curseur implicite : SELECT … INTO v FROM …; (1 ligne uniquement)
  • Curseur explicite : CURSOR c IS …; + OPEN c; LOOP FETCH c INTO v; EXIT WHEN c%NOTFOUND; … END LOOP; CLOSE c;
  • Curseur FOR : FOR row IN cursor LOOP … END LOOP; (open/close auto)
  • Curseur paramétré : CURSOR c(p NUMBER) IS …; · OPEN c(12);
  • Attributs : %FOUND, %NOTFOUND, %ISOPEN, %ROWCOUNT
  • Transaction : COMMIT; · ROLLBACK [TO sp]; · SAVEPOINT sp; · SET TRANSACTION NAME 'tr';
  • Transaction autonome : PRAGMA AUTONOMOUS_TRANSACTION; + COMMIT; en fin
  • Bloc EXCEPTION : EXCEPTION WHEN ex1 THEN … WHEN OTHERS THEN …
  • Exceptions classiques : DUP_VAL_ON_INDEX · NO_DATA_FOUND · TOO_MANY_ROWS
  • Codes : SQLCODE (numéro) · SQLERRM (message)
  • Créer exception : e EXCEPTION; PRAGMA EXCEPTION_INIT(e, -20001); · RAISE e;
  • RAISE_APPLICATION_ERROR(code, msg) · code ∈ [-20999, -20000]
  • Package = déclaration (public) + body (implémentation)
  • CREATE PACKAGE pkg IS … END pkg; puis CREATE PACKAGE BODY pkg IS … END pkg;