PostgreSQLLa base de données la plus sophistiquée au monde.

Version anglaise

39.6. Structures de contrĂŽle

Les structures de contrĂŽle sont probablement la partie la plus utile (et importante) de PL/pgSQL. GrĂące aux structures de contrĂŽle de PL/pgSQL, vous pouvez manipuler les donnĂ©es PostgreSQLℱ de façon trĂšs flexible et puissante.

39.6.1. Retour d'une fonction

Il y a deux commandes disponibles qui vous permettent de renvoyer des donnĂ©es d'une fonction : RETURN et RETURN NEXT.

39.6.1.1. RETURN

RETURN expression;

RETURN accompagnĂ© d'une expression termine la fonction et renvoie le valeur de l'expression Ă  l'appelant. Cette forme doit ĂȘtre utilisĂ©e avec des fonctions PL/pgSQL qui ne renvoient pas d'ensemble de valeurs.

Lorsqu'elle renvoie un type scalaire, n'importe quelle expression peut ĂȘtre utilisĂ©e. Le rĂ©sultat de l'expression sera automatiquement converti vers le type de retour de la fonction, comme dĂ©crit pour les affectations. Pour renvoyer une valeur composite (ligne), vous devez Ă©crire une variable record ou ligne comme expression.

Si vous déclarez la fonction avec des paramÚtres en sortie, écrivez seulement RETURN sans expression. Les valeurs courantes des paramÚtres en sortie seront renvoyées.

Si vous dĂ©clarez que la fonction renvoie void, une instruction RETURN peut ĂȘtre utilisĂ©e pour quitter rapidement la fonction ; mais n'Ă©crivez pas d'expression aprĂšs RETURN.

La valeur de retour d'une fonction ne peut pas ĂȘtre laissĂ©e indĂ©finie. Si le contrĂŽle atteint la fin du bloc de haut niveau de la fonction, sans parvenir Ă  une instruction RETURN, une erreur d'exĂ©cution survient. NĂ©anmoins, cette restriction ne s'applique pas aux fonctions sans paramĂštre de sortie et aux fonctions renvoyant void. Dans ces cas, une instruction RETURN est automatiquement exĂ©cutĂ©e si le bloc de haut niveau est terminĂ©.

39.6.1.2. RETURN NEXT et RETURN QUERY

RETURN NEXT expression;
RETURN QUERY requete;
RETURN QUERY EXECUTE command-string [ USING expression [, ...] ];

Quand une fonction PL/pgSQL dĂ©clare renvoyer SETOF un_certain_type, la procĂ©dure Ă  suivre est un peu diffĂ©rente. Dans ce cas, les Ă©lĂ©ments individuels Ă  renvoyer sont spĂ©cifiĂ©s par une sĂ©quence de commandes RETURN NEXT ou RETURN QUERY, suivies de la commande finale RETURN sans argument qui est utilisĂ©e pour indiquer la fin de l'exĂ©cution de la fonction. RETURN NEXT peut ĂȘtre utilisĂ© avec des types de donnĂ©es scalaires comme composites ; avec un type de rĂ©sultat composite, une « table Â» entiĂšre de rĂ©sultats sera renvoyĂ©e. RETURN QUERY ajoute les rĂ©sultats de l'exĂ©cution d'une requĂȘte Ă  l'ensemble des rĂ©sultats de la fonction. RETURN NEXT et RETURN QUERY peuvent ĂȘtre utilisĂ©s dans la mĂȘme fonction, auquel cas leurs rĂ©sultats seront concatĂ©nĂ©es.

RETURN NEXT et RETURN QUERY ne quittent pas réellement la fonction -- elles ajoutent simplement zéro ou plusieurs lignes à l'ensemble de résultats de la fonction. L'exécution continue ensuite avec l'instruction suivante de la fonction PL/pgSQL. Quand plusieurs commandes RETURN NEXT et/ou RETURN QUERY successives sont exécutées, l'ensemble de résultats augmente. Un RETURN, sans argument, permet de quitter la fonction mais vous pouvez aussi continuer jusqu'à la fin de la fonction.

RETURN QUERY dispose d'une variante RETURN QUERY EXECUTE, qui spĂ©cifie la requĂȘte Ă  exĂ©cuter dynamiquement. Les expressions de paramĂštres peuvent ĂȘtre insĂ©rĂ©es dans la chaĂźne calculĂ©e via USING, de la mĂȘme façon que le fait la commande EXECUTE.

Si vous dĂ©clarez la fonction avec des paramĂštres en sortie, Ă©crivez RETURN NEXT sans expression. À chaque exĂ©cution, les valeurs actuelles des variables paramĂštres en sortie seront sauvegardĂ©es pour un renvoi Ă©ventuel en tant que rĂ©sultat en sortie. Notez que vous devez dĂ©clarer la fonction en tant que SETOF record quand il y a plusieurs paramĂštres en sortie, ou SETOF un_certain_type quand il y a un seul paramĂštre en sortie, et de type un_certain_type, pour crĂ©er une fonction SRF avec des paramĂštres en sortie.

Voici un exemple d'une fonction utilisant RETURN NEXT :

CREATE TABLE truc (id_truc INT, sousid_truc INT, nom_truc TEXT);
INSERT INTO truc VALUES (1, 2, 'trois');
INSERT INTO truc VALUES (4, 5, 'six');

CREATE OR REPLACE FUNCTION obtenirTousLesTrucs() RETURNS SETOF foo AS
$BODY$
DECLARE
    r truc%rowtype;
BEGIN
    FOR r IN SELECT * FROM truc
    WHERE id_truc > 0
    LOOP
        -- quelques traitements
        RETURN NEXT r; -- renvoie la ligne courante du SELECT
    END LOOP;
    RETURN;
END
$BODY$
LANGUAGE 'plpgsql' ;

SELECT * FROM obtenirTousLesTrucs();
[Note]

Note

L'implĂ©mentation actuelle de RETURN NEXT et de RETURN QUERY pour PL/pgSQL rĂ©cupĂšre la totalitĂ© de l'ensemble des rĂ©sultats avant d'effectuer le retour de la fonction, comme vu plus haut. Cela signifie que si une fonction PL/pgSQL produit une structure rĂ©sultat trĂšs grande, les performances peuvent ĂȘtre faibles : les donnĂ©es seront Ă©crites sur le disque pour Ă©viter un Ă©puisement de la mĂ©moire mais la fonction en elle-mĂȘme ne renverra rien jusqu'Ă  ce que l'ensemble complet des rĂ©sultats soit gĂ©nĂ©rĂ©. Une version future de PL/pgSQL permettra aux utilisateurs de dĂ©finir des fonctions renvoyant des ensembles qui n'auront pas cette limitation. Actuellement, le point auquel les donnĂ©es commencent Ă  ĂȘtre Ă©crites sur le disque est contrĂŽlĂ© par la variable de configuration work_mem. Les administrateurs ayant une mĂ©moire suffisante pour enregistrer des ensembles de rĂ©sultats plus importants en mĂ©moire doivent envisager l'augmentation de ce paramĂštre.

39.6.2. ContrĂŽles conditionnels

Les instructions IF et CASE vous permettent d'exĂ©cuter des commandes basĂ©es sur certaines conditions. PL/pgSQL a trois formes de IF :

  • IF ... THEN

  • IF ... THEN ... ELSE

  • IF ... THEN ... ELSIF ... THEN ... ELSE

et deux formes de CASE :

  • CASE ... WHEN ... THEN ... ELSE ... END CASE

  • CASE WHEN ... THEN ... ELSE ... END CASE

39.6.2.1. IF-THEN

IF expression-booleenne THEN
    instructions
END IF;

Les instructions IF-THEN sont la forme la plus simple de IF. Les instructions entre THEN et END IF seront exécutées si la condition est vraie. Autrement, elles seront ignorées.

Exemple :

IF v_id_utilisateur <> 0 THEN
    UPDATE utilisateurs SET email = v_email WHERE id_utilisateur = v_id_utilisateur;
END IF;

39.6.2.2. IF-THEN-ELSE

IF expression-booleenne THEN
    instructions
ELSE
    instructions
END IF;

Les instructions IF-THEN-ELSE s'ajoutent au IF-THEN en vous permettant de spĂ©cifier un autre ensemble d'instructions Ă  exĂ©cuter si la condition n'est pas vraie (notez que ceci inclut le cas oĂč la condition s'Ă©value Ă  NULL.).

Exemples :

IF id_parent IS NULL OR id_parent = ''
THEN
    RETURN nom_complet;
ELSE
    RETURN hp_true_filename(id_parent) || '/' || nom_complet;
END IF;
IF v_nombre > 0 THEN 
    INSERT INTO nombre_utilisateurs (nombre) VALUES (v_nombre);
    RETURN 't';
ELSE
    RETURN 'f';
END IF;

39.6.2.3. IF-THEN-ELSIF

IF expression-booleenne THEN
    instructions
[ ELSIF expression-booleenne THEN
    instructions
[ ELSIF expression-booleenne THEN
    instructions
    ...
]
]
[ ELSE
    instructions ]
END IF;

Quelques fois, il existe plus de deux alternatives. IF-THEN-ELSIF fournit une méthode agréable pour vérifier différentes alternatives. Les conditions IF sont testées successivement jusqu'à trouver la bonne. Alors les instructions associées sont exécutées, puis le contrÎle est passé à la prochaine instruction aprÚs END IF. (Toute autre condition IF n'est pas testée.) Si aucune des conditions IF n'est vraie, alors le bloc ELSE (s'il y en a un) est exécuté.

Voici un exemple :

IF nombre = 0 THEN
    resultat := 'zero';
ELSIF nombre > 0 THEN 
    resultat := 'positif';
ELSIF nombre < 0 THEN
    resultat := 'negatif';
ELSE
    -- hmm, la seule possibilité est que le nombre soit NULL
    resultat := 'NULL';
END IF;

Le mot clé ELSIF peut aussi s'écrire ELSEIF.

Une façon alternative d'accomplir la mĂȘme tĂąche est d'intĂ©grer les instructions IF-THEN-ELSE, comme dans l'exemple suivant :

IF demo_row.sex = 'm' THEN
    pretty_sex := 'man';
ELSE
    IF demo_row.sex = 'f' THEN
        pretty_sex := 'woman';
    END IF;
END IF;

Néanmoins, cette méthode requiert d'écrire un END IF pour chaque IF, donc c'est un peu plus compliqué que d'utiliser ELSIF quand il y a beaucoup d'autres alternatives.

39.6.2.4. CASE simple

CASE expression_recherche
    WHEN expression [, expression [ ... ]] THEN
      instructions
  [ WHEN expression [, expression [ ... ]] THEN
      instructions
    ... ]
  [ ELSE
      instructions ]
END CASE;

La forme simple de CASE fournit une exĂ©cution conditionnelle basĂ©e sur l'Ă©galitĂ© des opĂ©randes. L'expression-recherche est Ă©valuĂ©e (une fois) puis comparĂ©e successivement Ă  chaque expression dans les clauses WHEN. Si une correspondance est trouvĂ©e, alors les instructions correspondantes sont exĂ©cutĂ©es, puis le contrĂŽle est passĂ© Ă  la prochaine instruction aprĂšs END CASE. (Les autres expressions WHEN ne sont pas testĂ©es.) Si aucune correspondance n'est trouvĂ©e, les instructions du bloc ELSE sont exĂ©cutĂ©es ; s'il n'y a pas de bloc ELSE, une exception CASE_NOT_FOUND est levĂ©e.

Voici un exemple simple :

CASE x
    WHEN 1, 2 THEN
        msg := 'un ou deux';
    ELSE
        msg := 'autre valeur que un ou deux';
END CASE;

39.6.2.5. CASE recherchĂ©

CASE
    WHEN expression_booléenne THEN
      instructions
  [ WHEN expression_booléenne THEN
      instructions
    ... ]
  [ ELSE
      instructions ]
END CASE;

La forme recherché de CASE fournit une exécution conditionnelle basée sur la vérification d'expressions booléennes. Chaque expression-booléenne de la clause WHEN est évaluée à son tour jusqu'à en trouver une qui est validée (true). Les instructions correspondantes sont exécutées, puis le contrÎle est passé à la prochaine instruction aprÚs END CASE. (Les expressions WHEN suivantes ne sont pas testées.) Si aucun résultat vrai n'est trouvé, les instructions du bloc ELSE sont exécutées. Si aucun bloc ELSE n'est présent, une exception CASE_NOT_FOUND est levée.

Voici un exemple :

CASE
    WHEN x BETWEEN 0 AND 10 THEN
        msg := 'valeur entre zéro et dix';
    WHEN x BETWEEN 11 AND 20 THEN
        msg := 'valeur entre onze et vingt';
END CASE;

Cette forme de CASE est entiÚrement équivalente à IF-THEN-ELSIF, sauf pour la rÚgle qui dit qu'atteindre une clause ELSE omise résulte dans une erreur plutÎt que ne rien faire.

39.6.3. Boucles simples

Grùce aux instructions LOOP, EXIT, CONTINUE, WHILE FOR et FOREACH, vous pouvez faire en sorte que vos fonctions PL/pgSQL répÚtent une série de commandes.

39.6.3.1. LOOP

[<<label>>]
LOOP
    instructions
END LOOP [ label ];

LOOP dĂ©finit une boucle inconditionnelle rĂ©pĂ©tĂ©e indĂ©finiment jusqu'Ă  ce qu'elle soit terminĂ©e par une instruction EXIT ou RETURN. Le label optionnel peut ĂȘtre utilisĂ© par les instructions EXIT et CONTINUE dans le cas de boucles imbriquĂ©es pour dĂ©finir la boucle impliquĂ©e.

39.6.3.2. EXIT

EXIT [ label ] [ WHEN expression-booléenne ];

Si aucun label n'est donnĂ©, la boucle la plus imbriquĂ©e se termine et l'instruction suivant END LOOP est exĂ©cutĂ©e. Si un label est donnĂ©, ce doit ĂȘtre le label de la boucle, du bloc courant ou d'un niveau moins imbriquĂ©. La boucle ou le bloc nommĂ© se termine alors et le contrĂŽle continue avec l'instruction situĂ©e aprĂšs le END de la boucle ou du bloc correspondant.

Si WHEN est spécifié, la sortie de boucle ne s'effectue que si expression-booléenne est vraie. Sinon, le contrÎle passe à l'instruction suivant le EXIT.

EXIT peut ĂȘtre utilisĂ© pour tous les types de boucles ; il n'est pas limitĂ© aux boucles non conditionnelles.

Lorsqu'il est utilisĂ© avec un bloc BEGIN, EXIT passe le contrĂŽle Ă  la prochaine instruction aprĂšs la fin du bloc. Notez qu'un label doit ĂȘtre utilisĂ© pour cela ; un EXIT sans label n'est jamais pris en compte pour correspondre Ă  un bloc BEGIN. (Ceci est un changement de la version 8.4 de PostgreSQLℱ. Auparavant, il Ă©tait permis de faire correspondre un EXIT sans label avec un bloc BEGIN.)

Exemples :

LOOP
    -- quelques traitements
    IF nombre > 0 THEN
        EXIT;  -- sortie de boucle
    END IF;
END LOOP;

LOOP
    -- quelques traitements
    EXIT WHEN nombre > 0;
END LOOP;

<<un_bloc>>
BEGIN
    -- quelques traitements
    IF stocks > 100000 THEN
        EXIT un_bloc;  -- cause la sortie (EXIT) du bloc BEGIN
    END IF;
    -- les traitements ici seront ignorés quand stocks > 100000
END;

39.6.3.3. CONTINUE

CONTINUE [ label ] [ WHEN expression-booléenne ];

Si aucun label n'est donnĂ©, la prochaine itĂ©ration de la boucle interne est commencĂ©e. C'est-Ă -dire que toutes les instructions restantes dans le corps de la boucle sont ignorĂ©es et le contrĂŽle revient Ă  l'expression de contrĂŽle de la boucle pour dĂ©terminer si une autre itĂ©ration de boucle est nĂ©cessaire. Si le label est prĂ©sent, il spĂ©cifie le label de la boucle dont l'exĂ©cution va ĂȘtre continuĂ©e.

Si WHEN est spécifié, la prochaine itération de la boucle est commencée seulement si l'expression-booléenne est vraie. Sinon, le contrÎle est passé à l'instruction aprÚs CONTINUE.

CONTINUE peut ĂȘtre utilisĂ© avec tous les types de boucles ; il n'est pas limitĂ© Ă  l'utilisation des boucles inconditionnelles.

Exemples :

LOOP
    -- quelques traitements
    EXIT WHEN nombre > 100;
    CONTINUE WHEN nombre < 50;
    -- quelques traitements pour nombre IN [50 .. 100] 
END LOOP;

39.6.3.4. WHILE

[<<label>>]
WHILE expression-booléenne LOOP
    instructions
END LOOP [ label ];

L'instruction WHILE répÚte une séquence d'instructions aussi longtemps que expression-booléenne est évaluée à vrai. L'expression est vérifiée juste avant chaque entrée dans le corps de la boucle.

Par exemple :

WHILE montant_possede > 0 AND balance_cadeau > 0 LOOP
    -- quelques traitements ici
END LOOP;

WHILE NOT termine LOOP
    -- quelques traitements ici
END LOOP;

39.6.3.5. FOR (variante avec entier)

[<<label>>]
FOR nom IN [ REVERSE ] expression .. expression [ BY expression ] LOOP
    instruction
END LOOP [ label ];

Cette forme de FOR crée une boucle qui effectue une itération sur une plage de valeurs entiÚres. La variable nom est automatiquement définie comme un type integer et n'existe que dans la boucle (toute définition de la variable est ignorée à l'intérieur de la boucle). Les deux expressions donnant les limites inférieures et supérieures de la plage sont évaluées une fois en entrant dans la boucle. Si la clause BY n'est pas spécifiée, l'étape d'itération est de 1, sinon elle est de la valeur spécifiée dans la clause BY, qui est évaluée encore une fois à l'entrée de la boucle. Si REVERSE est indiquée, alors la valeur de l'étape est soustraite, plutÎt qu'ajoutée, aprÚs chaque itération.

Quelques exemples de boucles FOR avec entiers :

FOR i IN 1..10 LOOP
    -- prend les valeurs 1,2,3,4,5,6,7,8,9,10 dans la boucle
END LOOP;

FOR i IN REVERSE 10..1 LOOP
    -- prend les valeurs 10,9,8,7,6,5,4,3,2,1 dans la boucle
END LOOP;

FOR i IN REVERSE 10..1 BY 2 LOOP
    -- prend les valeurs 10,8,6,4,2 dans la boucle
END LOOP;

Si la limite basse est plus grande que la limite haute (ou moins grande dans le cas du REVERSE), le corps de la boucle n'est pas exécuté du tout. Aucune erreur n'est renvoyée.

Si un label est attachĂ© Ă  la boucle FOR, alors la variable entiĂšre de boucle peut ĂȘtre rĂ©fĂ©rencĂ©e avec un nom qualifiĂ© en utilisant ce label.

39.6.4. Boucler dans les rĂ©sultats de requĂȘtes

En utilisant un type de FOR diffĂ©rent, vous pouvez itĂ©rer au travers des rĂ©sultats d'une requĂȘte et par lĂ -mĂȘme manipuler ces donnĂ©es. La syntaxe est la suivante :

[<<label>>]
FOR cible IN requĂȘte LOOP
    instructions
END LOOP [ label ];

La cible est une variable de type record, row ou une liste de variables scalaires sĂ©parĂ©es par une virgule. La cible est affectĂ©e successivement Ă  chaque ligne rĂ©sultant de la requĂȘte et le corps de la boucle est exĂ©cutĂ© pour chaque ligne. Voici un exemple :

CREATE FUNCTION cs_rafraichir_vuemat() RETURNS integer AS $$
DECLARE
    vues_mat RECORD;
BEGIN
    RAISE NOTICE 'Rafraichissement des vues matérialisées...';

    FOR vues_mat IN SELECT * FROM cs_vues_materialisees ORDER BY cle_tri LOOP

        -- À prĂ©sent vues_mat contient un enregistrement de cs_vues_materialisees

            RAISE NOTICE 'Rafraichissement de la vue matérialisée %s ...', quote_ident(mviews.mv_name);
            EXECUTE 'TRUNCATE TABLE  ' || quote_ident(vues_mat.vm_nom);
            EXECUTE 'INSERT INTO '
                    || quote_ident(vues_mat.vm_nom) || ' '
                    || vues_mat.vm_requete;
    END LOOP;

    RAISE NOTICE 'Fin du rafraichissement des vues matérialisées.';
    RETURN 1;
END;
$$ LANGUAGE plpgsql;

Si la boucle est terminée par une instruction EXIT, la derniÚre valeur ligne affectée est toujours accessible aprÚs la boucle.

La requĂȘte utilisĂ©e dans ce type d'instruction FOR peut ĂȘtre toute commande SQL qui renvoie des lignes Ă  l'appelant : SELECT est le cas le plus commun mais vous pouvez aussi utiliser INSERT, UPDATE ou DELETE avec une clause RETURNING. Certaines commandes comme EXPLAIN fonctionnent aussi.

Les variables PL/pgSQL sont substituĂ©es dans le texte de la requĂȘte et le plan de requĂȘte est mis en cache pour une rĂ©utilisation possible. C'est couvert en dĂ©tail dans la Section 39.10.1, « Substitution de variables Â» et dans la Section 39.10.2, « Mise en cache du plan Â».

L'instruction FOR-IN-EXECUTE est un moyen d'itĂ©rer sur des lignes :

[<<label>>]
FOR target IN EXECUTE text_expression [ USING expression [, ...] ] LOOP
    instructions
END LOOP [ label ];

Ceci est identique Ă  la forme prĂ©cĂ©dente, Ă  ceci prĂšs que l'expression de la requĂȘte source est spĂ©cifiĂ©e comme une expression chaĂźne, Ă©valuĂ©e et replanifiĂ©e Ă  chaque entrĂ©e dans la boucle FOR. Ceci permet au dĂ©veloppeur de choisir entre la vitesse d'une requĂȘte prĂ©planifiĂ©e et la flexibilitĂ© d'une requĂȘte dynamique, uniquement avec l'instruction EXECUTE. Comme avec EXECUTE, les valeurs de paramĂštres peuvent ĂȘtre insĂ©rĂ©es dans la commande dynamique via USING.

Une autre façon de spĂ©cifier la requĂȘte dont les rĂ©sultats devront ĂȘtre itĂ©rĂ©s est de la dĂ©clarer comme un curseur. Ceci est dĂ©crit dans Section 39.7.4, « Boucler dans les rĂ©sultats d'un curseur Â».

39.6.5. Boucler dans des tableaux

La boucle FOREACH ressemble beaucoup Ă  une boucle FOR mais, au lieu d'itĂ©rer sur les lignes renvoyĂ©es par une requĂȘtes SQL, elle itĂšre sur les Ă©lĂ©ments d'une valeur de type tableau. (En gĂ©nĂ©ral, FOREACH est fait pour boucler sur les composants d'une expression composite ; les variantes pour boucler sur des composites en plus des tableaux pourraient ĂȘtre ajoutĂ©es dans le futur.) L'instruction FOREACH pour boucler sur un tableau est :

[ <<label>> ]
FOREACH target [ SLICE nombre ] IN ARRAY expression LOOP
    instructions
END LOOP [ label ];

Sans SLICE ou si SLICE 0 est indiquĂ©, la boucle itĂšre au niveau des Ă©lĂ©ments individuels du tableau produit par l'Ă©valuation de l'expression. La variable cible se voit affectĂ©e chaque valeur d'Ă©lĂ©ment en sĂ©quence, et le corps de la boucle est exĂ©cutĂ© pour chaque Ă©lĂ©ment. Voici un exemple de boucle sur les Ă©lĂ©ments d'un tableau d'entiers :

CREATE FUNCTION somme(int[]) RETURNS int8 AS $$
DECLARE
  s int8 := 0;
  x int;
BEGIN
  FOREACH x IN ARRAY $1
  LOOP
    s := s + x;
  END LOOP;
  RETURN s;
END;
$$ LANGUAGE plpgsql;

Les Ă©lĂ©ments sont parcourus dans l'ordre de leur stockage, quelque soit le nombre de dimensions du tableau. Bien que la cible est habituellement une simple variable, elle peut ĂȘtre une liste de variables lors d'une boucle dans un tableau de valeurs composites (des enregistrements). Dans ce cas, pour chaque Ă©lĂ©ment du tableau, les variables se voient affectĂ©es les colonnes de la valeur composite.

Avec une valeur SLICE positive, FOREACH itĂšre au travers des morceaux du tableau plutĂŽt que des Ă©lĂ©ments seuls. La valeur de SLICE doit ĂȘtre un entier constant, moins large que le nombre de dimensions du tableau. La variable cible doit ĂȘtre un tableau et elle reçoit les morceaux successifs de la valeur du tableau, oĂč chaque morceau est le nombre de dimensions indiquĂ©es par SLICE. Voici un exemple d'itĂ©ration sur des morceaux Ă  une dimension :

CREATE FUNCTION parcourt_lignes(int[]) RETURNS void AS $$
DECLARE
  x int[];
BEGIN
  FOREACH x SLICE 1 IN ARRAY $1
  LOOP
    RAISE NOTICE 'ligne = %', x;
  END LOOP;
END;
$$ LANGUAGE plpgsql;

SELECT parcourt_lignes(ARRAY[[1,2,3],[4,5,6],[7,8,9],[10,11,12]]);

NOTICE:  ligne = {1,2,3}
NOTICE:  ligne = {4,5,6}
NOTICE:  ligne = {7,8,9}
NOTICE:  ligne = {10,11,12}

39.6.6. Récupérer les erreurs

Par dĂ©faut, toute erreur survenant dans une fonction PL/pgSQL annule l'exĂ©cution de la fonction mais aussi de la transaction qui l'entoure. Vous pouvez rĂ©cupĂ©rer les erreurs en utilisant un bloc BEGIN avec une clause EXCEPTION. La syntaxe est une extension de la syntaxe habituelle pour un bloc BEGIN :

  [ <<label>> ]
  [ DECLARE
    declarations ]
  BEGIN
  instructions
  EXCEPTION
  WHEN condition [ OR condition ... ] THEN
  instructions_gestion_erreurs
  [ WHEN condition [ OR condition ... ] THEN
    instructions_gestion_erreurs
    ... ]
  END;

Si aucune erreur ne survient, cette forme de bloc exĂ©cute simplement toutes les instructions puis passe le contrĂŽle Ă  l'instruction suivant END. Mais si une erreur survient Ă  l'intĂ©rieur des instructions, le traitement en cours des instructions est abandonnĂ© et le contrĂŽle est passĂ© Ă  la liste d'EXCEPTION. Une recherche est effectuĂ©e sur la liste pour la premiĂšre condition correspondant Ă  l'erreur survenue. Si une correspondance est trouvĂ©e, les instructions_gestion_erreurs correspondantes sont exĂ©cutĂ©es puis le contrĂŽle est passĂ© Ă  l'instruction suivant le END. Si aucune correspondance n'est trouvĂ©e, l'erreur se propage comme si la clause EXCEPTION n'existait pas du tout : l'erreur peut ĂȘtre rĂ©cupĂ©rĂ©e par un bloc l'enfermant avec EXCEPTION ou, s'il n'existe pas, elle annule le traitement de la fonction.

Les noms des condition sont indiquĂ©es dans l'Annexe A, Codes d'erreurs de PostgreSQLℱ. Un nom de catĂ©gorie correspond Ă  toute erreur contenue dans cette catĂ©gorie. Le nom de condition spĂ©ciale OTHERS correspond Ă  tout type d'erreur sauf QUERY_CANCELED (il est possible, mais pas recommandĂ©, de rĂ©cupĂ©rer QUERY_CANCELED par son nom). Les noms des conditions ne sont pas sensibles Ă  la casse. De plus, une condition d'erreur peut ĂȘtre indiquĂ©e par un code SQLSTATE ; par exemple, ces deux cas sont Ă©quivalents :

        WHEN division_by_zero THEN ...
        WHEN SQLSTATE '22012' THEN ...

Si une nouvelle erreur survient Ă  l'intĂ©rieur des instructions_gestion_erreurs sĂ©lectionnĂ©es, elle ne peut pas ĂȘtre rĂ©cupĂ©rĂ©e par cette clause EXCEPTION mais est propagĂ©e en dehors. Une clause EXCEPTION l'englobant pourrait la rĂ©cupĂ©rer.

Quand une erreur est rĂ©cupĂ©rĂ©e par une clause EXCEPTION, les variables locales de la fonction PL/pgSQL restent dans le mĂȘme Ă©tat qu'au moment oĂč l'erreur est survenue mais toutes les modifications Ă  l'Ă©tat persistant de la base de donnĂ©es Ă  l'intĂ©rieur du bloc sont annulĂ©es. Comme exemple, considĂ©rez ce fragment :

INSERT INTO mon_tableau(prenom, nom) VALUES('Tom', 'Jones');
BEGIN
  UPDATE mon_tableau SET prenom = 'Joe' WHERE nom = 'Jones';
  x := x + 1;
  y := x / 0;
  EXCEPTION
    WHEN division_by_zero THEN
      RAISE NOTICE 'récupération de l''erreur division_by_zero';
RETURN x;
END;

Quand le contrÎle parvient à l'affectation de y, il échouera avec une erreur division_by_zero. Elle sera récupérée par la clause EXCEPTION. La valeur renvoyée par l'instruction RETURN sera la valeur incrémentée de x mais les effets de la commande UPDATE auront été annulés. La commande INSERT précédant le bloc ne sera pas annulée, du coup le résultat final est que la base de données contient Tom Jones et non pas Joe Jones.

[Astuce]

Astuce

Un bloc contenant une clause EXCEPTION est significativement plus coûteuse en entrée et en sortie qu'un bloc sans. Du coup, n'utilisez pas EXCEPTION sans besoin.

À l'intĂ©rieur d'un gestionnaire d'exceptions, la variable SQLSTATE contient le code d'erreur correspondant Ă  l'exception qui a Ă©tĂ© levĂ©e (rĂ©fĂ©rez-vous au Tableau A.1, « Codes d'erreur de PostgreSQLℱ Â» pour une liste des codes d'erreurs possibles). La variable SQLERRM contient le message d'erreur associĂ© avec l'exception. Ces variables sont indĂ©finies Ă  l'extĂ©rieur des gestionnaires d'exceptions.

Exemple 39.2. Exceptions avec UPDATE/INSERT

Cet exemple utilise un gestionnaire d'exceptions pour rĂ©aliser soit un UPDATE soit un INSERT, comme appropriĂ© :

CREATE TABLE base (a INT PRIMARY KEY, b TEXT);

CREATE FUNCTION fusionne_base(cle INT, donnee TEXT) RETURNS VOID AS
$$
BEGIN
    LOOP
        -- commençons par tenter la mise à jour de la clé
        UPDATE base SET b = donnee WHERE a = cle;
        IF found THEN
            RETURN;
        END IF;

        -- si elle n'est pas dispo, tentons l'insertion de la clé
        -- si quelqu'un essaie d'insĂ©rer la mĂȘme clĂ© en mĂȘme temps,
        -- il y aura une erreur pour violation de clé unique
        BEGIN
            INSERT INTO base(a,b) VALUES (cle, donnee);
            RETURN;
        EXCEPTION WHEN unique_violation THEN
            -- ne rien faire, et tente de nouveau la mise Ă  jour
        END;
    END LOOP;
END;
$$
LANGUAGE plpgsql;

SELECT fusionne_base(1, 'david');
SELECT fusionne_base(1, 'dennis');

Cet exemple suppose que l'erreur unique_violation est causĂ© par la requĂȘte INSERT, et non pas par une fonction trigger sur l'opĂ©ration INSERT pour cette table.