Informatique · PL/pgSQL (PostgreSQL)

Procédures et fonctions

Calculer un prix TTC, augmenter un salaire, transférer de l'argent entre deux comptes : plutôt que de réécrire ces opérations dans chaque programme, on les range dans la base de données, sous un nom. Une fonction quand on veut obtenir une valeur, une procédure quand on veut agir.

appelant fonction calculer_ttc 100 108.10 appelant procédure augmenter_salaire 42, 10 aucun retour employes : Léa 2000 → 2200
Une procédure n'a pas de valeur de retour. Peut-elle quand même renvoyer quelque chose à celui qui l'appelle ? Garde ta réponse en tête, on la vérifie dans la notion 3.

Après cette fiche, tu sais

  • choisir entre une fonction et une procédure, et appeler chacune correctement (dans un SELECT ou avec CALL) ;
  • écrire une fonction PL/pgSQL complète : paramètres, valeur par défaut, RETURNS et RETURN ;
  • utiliser les paramètres IN, OUT et INOUT, et prévoir ce que voit l'appelant ;
  • intercepter une erreur, renvoyer plusieurs lignes et piloter une transaction dans une procédure.
Notion 1 · Fonction ou procédure

Obtenir une valeur, ou agir

En PostgreSQL, une fonction (CREATE FUNCTION) renvoie une valeur et s'utilise à l'intérieur d'une requête. Une procédure (CREATE PROCEDURE, depuis PostgreSQL 11) n'a pas de valeur de retour, s'appelle seule avec CALL, et peut valider ou annuler des transactions pendant son exécution.1

FUNCTIONPROCEDURE
Valeur de retouroui : RETURNS type (éventuellement void)non, mais des paramètres OUT possibles
Appeldans une requête : SELECT f(…)seule : CALL p(…)
COMMIT / ROLLBACKjamaisoui, sous conditions (notion 6)
Usage typiquecalculer, transformer, liremodifier des données, traitements par lots
Scène 1

Le trajet de la donnée

Fais défiler : un appel de fonction, puis un appel de procédure. Regarde ce qui revient, ou pas, vers l'appelant.

Scène 1 · Trajet

Ce qui revient vers l'appelant

appelant SELECT calculer_ttc(100) → 108.10 CALL augmenter_ salaire(42, 10) aucune valeur fonction calculer_ttc RETURN round(…) procédure augmenter_salaire UPDATE employes Léa · 2000.00 100 108.10 42, 10

Exercice · à toi

Fonction ou procédure ?

Cinq besoins. À chaque fois, une contrainte décide pour toi : laquelle faut-il écrire ?

Notion 2 · Écrire une fonction

RETURNS dans l'en-tête, RETURN dans le corps

Une fonction qui calcule un prix TTC, avec par défaut le taux normal de la TVA suisse, 8,1 %.5

Créer la fonctionPL/pgSQL
CREATE OR REPLACE FUNCTION calculer_ttc(prix NUMERIC, tva NUMERIC DEFAULT 0.081)
RETURNS NUMERIC
AS $$
BEGIN
    RETURN round(prix * (1 + tva), 2);   -- arrondi au centime
END;
$$ LANGUAGE plpgsql;
L'utiliserSQL
SELECT calculer_ttc(100);                 -- 108.10
SELECT calculer_ttc(100, 0.026);          -- 102.60 (taux réduit)
SELECT calculer_ttc(tva => 0.026, prix => 20);   -- 20.52, paramètres nommés
SELECT nom, calculer_ttc(prix) AS prix_ttc FROM produits;

-- dans un bloc PL/pgSQL : affectation à une variable
DO $$
DECLARE v_total NUMERIC;
BEGIN
    v_total := calculer_ttc(100, 0.026);
    RAISE NOTICE 'v_total = %', v_total;    -- v_total = 102.60
END $$;
RETURNS et RETURN

RETURNS NUMERIC, dans l'en-tête, déclare le type renvoyé ; RETURN …;, dans le corps, renvoie la valeur et termine la fonction.

DEFAULT

Un paramètre avec valeur par défaut peut être omis à l'appel. Tous les paramètres d'entrée qui le suivent doivent aussi en avoir une.1

$$ … $$

Le corps est une chaîne délimitée par des dollars : elle peut contenir des apostrophes sans qu'on doive les doubler.3

CREATE OR REPLACE

Remplace une fonction existante sans casser les vues ou déclencheurs qui l'utilisent ; mais on ne peut pas changer ainsi son type de retour.3

Piège : la fonction qui oublie son RETURN

PostgreSQL accepte la création d'une fonction RETURNS integer dont une branche n'a pas de RETURN ; l'erreur tombe à l'exécution : control reached end of function without RETURN. Seules les fonctions RETURNS void ou à paramètres OUT peuvent s'en passer.2

Exercice · remettre dans l'ordre

Reconstruis la fonction

Les sept lignes de calculer_ttc ont été mélangées. Touche une ligne, puis celle avec laquelle l'échanger.

Notion 3 · Procédures et paramètres

CALL, et des valeurs qui entrent ou sortent

La table des exemplesemployes(id, nom, salaire) contient : 42 Léa 2000, 7 Noah 5200, 3 Zoé 6100, 15 Lina 4800, 9 Elias 4300, 11 Maé 3900. Les résultats indiqués en commentaire ont été obtenus avec elle.
Une procédure qui agitPL/pgSQL
CREATE OR REPLACE PROCEDURE augmenter_salaire(
    p_employe_id INTEGER,
    p_pourcentage NUMERIC
)
AS $$
BEGIN
    UPDATE employes
    SET salaire = round(salaire * (1 + p_pourcentage / 100), 2)
    WHERE id = p_employe_id;

    RAISE NOTICE 'Salaire augmenté de %', p_pourcentage || '%';
END;
$$ LANGUAGE plpgsql;

CALL augmenter_salaire(42, 10);                                 -- Léa : 2000.00 → 2200.00
CALL augmenter_salaire(p_employe_id => 42, p_pourcentage => 10); -- paramètres nommés
Piège : appeler une procédure comme une fonction

SELECT augmenter_salaire(42, 10); échoue : augmenter_salaire(integer, integer) is a procedure. Une procédure s'appelle avec CALL, jamais dans une requête.

Les trois modes de paramètres

IN (par défaut)

La valeur entre. En PL/pgSQL, le paramètre est une variable locale : on peut la réaffecter, mais l'appelant n'en voit rien.

OUT

La valeur sort : la procédure l'écrit, l'appelant la récupère. À l'appel direct, on écrit NULL à sa place.1

INOUT

Aller-retour : la valeur entre, la procédure peut la modifier, et elle ressort.

Résultat d'un CALL

Si la procédure a des paramètres OUT ou INOUT, CALL renvoie une ligne avec leurs valeurs.1

Scène 2

Le sens de circulation

Fais défiler : un paramètre IN, puis OUT, puis INOUT. Les valeurs ont été vérifiées dans PostgreSQL 18.

Scène 2 · Paramètres

IN : la valeur entre

appelant procédure p IN integer v = 42 p := p + 1 p = 43, copie p_nom OUT varchar v_nom = ? écrit 'Noah' p_cpt INOUT integer v = 10 p_cpt + 1 42

Réponse à ma question : oui. Une procédure n'a pas de RETURN de valeur, mais ses paramètres OUT et INOUT reviennent à l'appelant.
OUT en pratiquePL/pgSQL
CREATE OR REPLACE PROCEDURE infos_employe(
    p_id IN INTEGER,
    p_nom OUT VARCHAR,
    p_salaire OUT NUMERIC
)
AS $$
BEGIN
    SELECT nom, salaire INTO p_nom, p_salaire
    FROM employes WHERE id = p_id;
END;
$$ LANGUAGE plpgsql;

CALL infos_employe(7, NULL, NULL);   -- une ligne : p_nom = Noah, p_salaire = 5200

DO $$
DECLARE v_nom VARCHAR; v_sal NUMERIC;
BEGIN
    CALL infos_employe(7, v_nom, v_sal);
    RAISE NOTICE '% gagne %', v_nom, v_sal;  -- Noah gagne 5200
END $$;
Et en Oracle PL/SQL ?Les modes s'écrivent IN, OUT et IN OUT (en deux mots). Un paramètre IN y agit comme une constante : le sous-programme ne peut pas changer sa valeur, le compilateur refuse l'affectation.4 PostgreSQL, lui, accepte l'affectation mais la garde locale.
Notion 4 · Intercepter une erreur

BEGIN … EXCEPTION WHEN … THEN

Un bloc PL/pgSQL peut avoir une section EXCEPTION : si une instruction du bloc lève une erreur, PostgreSQL abandonne le reste du bloc, annule ce que le bloc a modifié et cherche une clause WHEN correspondante.2 Fais défiler : la flèche suit l'exécution.

appel SELECT diviser(10, 4); → 2.5000000000000000
Étape 1 sur 5 · tout va bien

diviser(10, 4) : la ligne RETURN a / b; s'exécute et renvoie 2.5. La section EXCEPTION n'est jamais visitée.

Étape 2 sur 5 · l'erreur

diviser(10, 0) : a / b lève l'erreur division_by_zero. Le reste du bloc est abandonné.

Étape 3 sur 5 · le saut

L'exécution saute à la section EXCEPTION, et la clause WHEN division_by_zero correspond.

Étape 4 sur 5 · le rattrapage

Le gestionnaire affiche un avis et renvoie NULL : l'appel réussit, avec un résultat vide.

Étape 5 sur 5 · sans EXCEPTION

Sans cette section, l'erreur remonterait jusqu'à l'appelant : toute la requête échouerait avec division by zero.

Ne pas en mettre partoutUn bloc avec une section EXCEPTION est nettement plus coûteux à exécuter qu'un bloc sans : on ne l'utilise que si on sait quoi faire de l'erreur.2 WHEN OTHERS, qui attrape tout, cache souvent de vrais bogues.
Notion 5 · Renvoyer plusieurs lignes

Une fonction qui se lit comme une table

RETURNS TABLE (…) (ou RETURNS SETOF type) déclare un résultat de plusieurs lignes. Dans le corps, RETURN QUERY et RETURN NEXT ne quittent pas la fonction : ils ajoutent des lignes au résultat, et c'est la fin de la fonction, ou un RETURN sans argument, qui le renvoie.2

Les n meilleurs salairesPL/pgSQL
CREATE OR REPLACE FUNCTION get_top_salaries(n INTEGER)
RETURNS TABLE(nom VARCHAR, salaire NUMERIC) AS $$
BEGIN
    RETURN QUERY
    SELECT e.nom, e.salaire      -- alias e : évite le conflit avec nom et salaire
    FROM employes e
    ORDER BY e.salaire DESC
    LIMIT n;
END;
$$ LANGUAGE plpgsql;

SELECT * FROM get_top_salaries(3);
  nom  | salaire
-------+---------
 Zoé   |    6100
 Noah  |    5200
 Lina  |    4800
Piège : le conflit de noms

Les colonnes de RETURNS TABLE (nom, salaire) deviennent des variables dans le corps. Une requête qui écrit nom sans préfixe devient ambiguë : on qualifie les colonnes par un alias (e.nom).

Notion 6 · Transactions

COMMIT dans une procédure, jamais dans une fonction

Une procédure appelée par CALL (comme un bloc DO) peut terminer la transaction en cours avec COMMIT ou ROLLBACK ; une nouvelle commence aussitôt.2 Trois limites : jamais dans une fonction ; jamais dans un bloc qui a une section EXCEPTION, qui forme une sous-transaction ; jamais si le CALL est lui-même dans une transaction ouverte par BEGIN.1 Dans ces trois cas, PostgreSQL 18 répond invalid transaction termination.

Un transfert entre deux comptesPL/pgSQL
-- comptes(id, solde) avec la contrainte CHECK (solde >= 0)
CREATE OR REPLACE PROCEDURE transferer_fonds(
    p_from INTEGER,
    p_to INTEGER,
    p_montant NUMERIC
)
AS $$
BEGIN
    UPDATE comptes SET solde = solde - p_montant WHERE id = p_from;
    UPDATE comptes SET solde = solde + p_montant WHERE id = p_to;
    COMMIT;
    RAISE NOTICE 'Transfert de % effectué', p_montant;
END;
$$ LANGUAGE plpgsql;
Scène 3

Deux transferts, un refusé

Fais défiler : un transfert de 200 CHF réussit, puis un transfert de 400 CHF viole la contrainte CHECK. Regarde les soldes.

Scène 3 · Transaction

Avant les transferts

soldes compte 1 500 compte 2 100 échelle : 600 CHF pour toute la barre

Remarque le deuxième transfert : l'erreur arrive au premier UPDATE, avant le COMMIT. Toute la transaction en cours est annulée, aucun compte n'est débité à moitié.
Exercice · classer

Accepté ou refusé par PostgreSQL ?

Sept situations, toutes testées dans PostgreSQL 18. Range chacune.

Habitudes de pro

Six bonnes pratiques

  • préfixer les paramètres (p_) et les variables (v_) : ils ne se confondent plus avec les colonnes ;
  • déclarer les variables avec %TYPE : v_sal employes.salaire%TYPE suit le type de la colonne s'il change un jour3 ;
  • ne pas réaffecter un paramètre IN : c'est trompeur en PL/pgSQL et refusé en Oracle ;
  • pour une requête dont on ignore le résultat, écrire PERFORM : un SELECT sans INTO provoque l'erreur query has no destination for result data ;
  • n'intercepter que les erreurs qu'on sait traiter, et nommer la condition (division_by_zero) plutôt que OTHERS ;
  • arrondir les montants (round(…, 2)) : les calculs en NUMERIC allongent vite le nombre de décimales.
Entraînement

Trois exercices corrigés

Tables utilisées : eleves(id, nom, classe) et notes(eleve_id, note), notes de 1 à 6. Tous les corrigés ont été exécutés dans PostgreSQL 18.

1Écris une fonction moyenne_classe(p_classe) qui renvoie la moyenne des notes d'une classe, arrondie au centième.Voir le corrigé
CREATE OR REPLACE FUNCTION moyenne_classe(p_classe TEXT)
RETURNS NUMERIC AS $$
DECLARE
    v_moyenne NUMERIC;
BEGIN
    SELECT round(avg(n.note), 2) INTO v_moyenne
    FROM notes n JOIN eleves e ON e.id = n.eleve_id
    WHERE e.classe = p_classe;
    RETURN v_moyenne;
END;
$$ LANGUAGE plpgsql;

SELECT moyenne_classe('2M3');   -- 4.75 avec les notes 5.5, 4.5, 4 et 5

Pour une classe sans notes, avg renvoie NULL, et la fonction aussi.

2Écris une procédure ajouter_note(p_eleve, p_note) qui refuse une note hors de l'échelle 1 à 6, puis l'enregistre.Voir le corrigé
CREATE OR REPLACE PROCEDURE ajouter_note(p_eleve INTEGER, p_note NUMERIC)
AS $$
BEGIN
    IF p_note < 1 OR p_note > 6 THEN
        RAISE EXCEPTION 'Note % hors de l''échelle 1 à 6', p_note;
    END IF;
    INSERT INTO notes (eleve_id, note) VALUES (p_eleve, p_note);
END;
$$ LANGUAGE plpgsql;

CALL ajouter_note(3, 5.5);   -- enregistrée
CALL ajouter_note(3, 7);     -- ERREUR : Note 7 hors de l'échelle 1 à 6

Dans une chaîne SQL, l'apostrophe se double (l''échelle). Une procédure convient : on agit sur la table, sans valeur à renvoyer.

3Cette fonction se crée sans erreur, mais plante dès qu'on l'appelle. Pourquoi, et comment la corriger ?Voir le corrigé
CREATE OR REPLACE FUNCTION nb_eleves(p_classe TEXT)
RETURNS INTEGER AS $$
BEGIN
    SELECT count(*) FROM eleves WHERE classe = p_classe;
END;
$$ LANGUAGE plpgsql;

Deux défauts : le SELECT n'a pas d'INTO (erreur query has no destination for result data), et il manque le RETURN. Version corrigée :

CREATE OR REPLACE FUNCTION nb_eleves(p_classe TEXT)
RETURNS INTEGER AS $$
DECLARE
    v_nb INTEGER;
BEGIN
    SELECT count(*) INTO v_nb FROM eleves WHERE classe = p_classe;
    RETURN v_nb;
END;
$$ LANGUAGE plpgsql;
À retenir

La fiche en huit lignes

FonctionRETURNS dans l'en-tête, RETURN dans le corps, s'utilise dans une requête.
Procédurepas de valeur de retour, s'appelle seule avec CALL.
ParamètresIN entre (copie locale en PL/pgSQL), OUT sort, INOUT fait l'aller-retour.
DEFAULTparamètre facultatif ; ceux qui suivent en ont aussi un.
ErreursEXCEPTION WHEN condition THEN ; bloc coûteux, à réserver aux erreurs qu'on sait traiter.
Plusieurs lignesRETURNS TABLE ou SETOF ; RETURN QUERY et RETURN NEXT ajoutent des lignes.
TransactionsCOMMIT et ROLLBACK seulement dans une procédure appelée par CALL, hors bloc EXCEPTION et hors transaction ouverte.
PiègesRETURN oublié, SELECT sans INTO, procédure appelée par SELECT.
Dernière étape : le quiz. Douze questions tirées au hasard, différentes à chaque partie. Si tu te trompes, lis bien l'explication.
Quiz final

Teste-toi

Sources

  1. PostgreSQL 18 Documentation : User-Defined Procedures (différences avec les fonctions), CALL (paramètres OUT, transaction ouverte), CREATE PROCEDURE (modes, valeurs par défaut). procédures, CALL, CREATE PROCEDURE (postgresql.org)
  2. PostgreSQL 18 Documentation, PL/pgSQL : Transaction Management et Control Structures (RETURN, RETURN QUERY, interception des erreurs, coût d'un bloc EXCEPTION). transactions, structures de contrôle (postgresql.org)
  3. PostgreSQL 18 Documentation : CREATE FUNCTION (OR REPLACE, chaînes entre dollars) et PL/pgSQL Declarations (%TYPE). CREATE FUNCTION, déclarations (postgresql.org)
  4. Oracle, Database PL/SQL Language Reference 19c, « Subprogram Parameters » (modes IN, OUT, IN OUT). docs.oracle.com
  5. Confédération suisse, Loi fédérale régissant la taxe sur la valeur ajoutée (LTVA), RS 641.20, art. 25 (taux normal 8,1 %, taux réduit 2,6 %). fedlex.admin.ch

Fiche relue et sourcée en septembre 2026. Tout le code a été exécuté dans PostgreSQL 18.

Glisser pour continuer vers Déclencheurs et curseurs
Bachelor Informatique