Documentation PostgreSQL 8.0.25 | ||||
---|---|---|---|---|
Pr�c�dent | Arri�re rapide | Chapitre 35. PL/pgSQL - Langage de proc�dures SQL | Avance rapide | Suivant |
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.
Il y a deux commandes disponibles qui vous permettent de renvoyer des donn�es d'une fonction : RETURN et RETURN NEXT.
RETURN expression;
RETURN accompagn� d'une expression termine la fonction et renvoie le valeur de l'expression � l'appelant. Cette forme est � utiliser 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 transtyp� vers le type de retour de la fonction, comme d�crit pour les assignations. Pour renvoyer une valeur composite (ligne), vous devez �crire une variable record ou ligne comme expression.
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 premier niveau sans avoir rencontr� d'instruction RETURN une erreur d'ex�cution sera lanc�e.
Notez que si vous avez d�clar� la fonction comme renvoyant void, une instruction RETURN doit �tre quand m�me fournie ; l'expression suivant la commande RETURN est cependant optionnelle et sera ignor�e dans tous les cas.
RETURN NEXT expression;
Lorsqu'une fonction PL/pgSQL est d�clar�e renvoyer SETOF type quelconque, la proc�dure � suivre est l�g�rement diff�rente. Dans ce cas, les �l�ments individuels � renvoyer sont sp�cifi�s dans les commandes RETURN NEXT, et ensuite une commande RETURN finale sans arguments est utilis�e pour indiquer que la fonction a termin� son ex�cution. RETURN NEXT peut �tre utilis� avec des types scalaires et des types composites de donn�es; dans ce dernier cas, une <<�table�>> enti�re de r�sultats sera renvoy�e.
Les fonctions qui utilisent RETURN NEXT devraient �tre appel�es d'apr�s le mod�le suivant :
SELECT * FROM une_fonction();
En fait, la fonction doit �tre utilis�e comme une table source dans une clause FROM.
RETURN NEXT n'effectue pas vraiment de renvoi; il sauvegarde simplement les valeurs des expressions. L'ex�cution continue alors avec la prochaine instruction dans la fonction PL/pgSQL. Lorsque des commandes RETURN NEXT successives sont renvoy�es, l'ensemble des r�sultats est �labor�. Un RETURN final, qui ne devrait pas avoir d'argument, provoque la sortie du contr�le de la fonction.
Note�: L'impl�mentation actuelle de RETURN NEXT pour PL/pgSQL emmagasine 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 des r�sultats entier soit g�n�r�. Une version future de PL/pgSQL pourra permettre 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 devraient envisager l'augmentation de ce param�tre.
Les instructions IF vous permettent d'ex�cuter des commandes bas�es sur certaines conditions. PL/pgSQL a cinq formes de IF :
IF ... THEN
IF ... THEN ... ELSE
IF ... THEN ... ELSE IF
IF ... THEN ... ELSIF ... THEN ... ELSE
IF ... THEN ... ELSEIF ... THEN ... ELSE
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 n�glig�es.
Exemple :
IF v_id_utilisateur <> 0 THEN UPDATE utilisateurs SET email = v_email WHERE id_utilisateur = v_id_utilisateur; END IF;
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 ensemble d'instructions alternatif � ex�cuter si la condition est �valu�e � false.
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;
Les instructions IF peuvent �tre imbriqu�es, comme dans l'exemple suivant :
IF demo_ligne.sexe = 'm' THEN joli_sexe := ''man''; ELSE IF demo_ligne.sexe = 'f' THEN joli_sexe := 'woman'; END IF; END IF;
Lorsque vous utilisez cette forme, vous imbriquez une instruction IF dans la partie ELSE d'une instruction IF ext�rieure. Ainsi, vous avez besoin d'une instruction END IF pour chaque IF imbriqu� et une pour le IF-ELSE parent. Ceci fonctionne mais devient fastidieux quand il y a de nombreuses alternatives � traiter. Consid�rez alors la forme suivante.
IF expression-booleenne THEN instructions [ ELSIF expression-booleenne THEN instructions [ ELSIF expression-booleenne THEN instructions ...]] [ ELSE instructions ] END IF;
IF-THEN-ELSIF-ELSE fournit une m�thode plus pratique pour v�rifier de nombreuses alternatives en une instruction. Elle est �quivalente formellement aux commandes IF-THEN-ELSE-IF-THEN imbriqu�es, mais un seul END IF est n�cessaire.
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;
ELSEIF est un alias pour ELSIF.
Gr�ce aux instructions LOOP, EXIT, WHILE et FOR vous pouvez faire en sorte que vos fonctions PL/pgSQL r�p�tent une s�rie de commandes.
[<<label>>]
LOOP
instructions
END LOOP;
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 dans le cas de boucles imbriqu�es pour d�finir quel niveau d'imbrication doit s'achever.
EXIT [ label ] [ WHEN expression ];
Si aucun label n'est donn�, la boucle la plus imbriqu�e se termine et l'instruction suivant END LOOP est ex�cut�e ensuite. 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 pr�sent, la sortie de boucle ne s'effectue que si les conditions sp�cifi�es sont true, autrement le contr�le passe � l'instruction suivant le EXIT.
EXIT peut �tre utilis� pour causer un d�part rapide de tout type de boucles ; il n'est pas limit� en utilisation aux boucles sans condition.
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; BEGIN -- quelques traitements IF stocks > 100000 THEN EXIT; -- cause la sortie (EXIT) du bloc BEGIN END IF; END;
[<<label>>]
WHILE expression LOOP
instructions
END LOOP;
L'instruction WHILE r�p�te une s�quence d'instructions aussi longtemps que l'expression conditionnelle est �valu�e � vrai. La condition 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 expression_booleenne LOOP -- quelques traitements ici END LOOP;
[<<label>>] FOR nom IN [ REVERSE ] expression .. expression LOOP instruction END LOOP;
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. Les deux expressions donnant les limites inf�rieures et sup�rieures de la plage sont �valu�es une fois en entrant dans la boucle. Le pas de l'it�ration est normalement de 1 mais vaut -1 quand REVERSE est sp�cifi�.
Quelques exemples de boucles FOR avec entiers :
FOR i IN 1..10 LOOP -- quelques calculs ici RAISE NOTICE 'i is %', i; END LOOP; FOR i IN REVERSE 10..1 LOOP -- quelques calculs ici END LOOP;
Si la limite basse est plus grande que la limite haute (ou moins grande que, dans le cas du REVERSE case), le corps de la boucle n'est pas ex�cut� du tout. Aucune erreur n'est renvoy�e.
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 record_ou_ligne IN requ�te LOOP
instructions
END LOOP;
La variable record ou ligne est successivement assign�e � chaque ligne r�sultant de la requ�te (qui doit �tre une commande SELECT) et le corps de la boucle est ex�cut� pour chaque ligne. Voici un exemple :
CREATE FUNCTION cs_refresh_mviews() RETURNS integer AS $$ DECLARE mviews RECORD; BEGIN PERFORM cs_log('Refreshing materialized views...'); FOR mviews IN SELECT * FROM cs_materialized_views ORDER BY sort_key LOOP -- � pr�sent "mviews" contient un enregistrement de cs_materialized_views PERFORM cs_log('Refreshing materialized view ' || quote_ident(mviews.mv_name) || '...'); EXECUTE 'TRUNCATE TABLE ' || quote_ident(mviews.mv_name); EXECUTE 'INSERT INTO ' || quote_ident(mviews.mv_name) || ' ' || mviews.mv_query; END LOOP; PERFORM cs_log('Done refreshing materialized views.'); RETURN 1; END; $$ LANGUAGE plpgsql;
Si la boucle est termin�e par une instruction EXIT, la derni�re valeur ligne assign�e est toujours accessible apr�s la boucle.
L'instruction FOR-IN-EXECUTE est un moyen d'it�rer sur des lignes :
[<<label>>]
FOR record_ou_ligne IN EXECUTE expression_texte LOOP
instructions
END LOOP;
Ceci est identique � la forme pr�c�dente, � ceci pr�s que l'expression SELECT 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 programmeur de choisir la vitesse d'une requ�te pr�planifi�e, ou la flexibilit� d'une requ�te dynamique, uniquement avec la simple instruction EXECUTE.
Note�: L'analyseur PL/pgSQL distingue actuellement deux types de boucles FOR (entier ou r�sultat d'une requ�te) en v�rifiant si .. appara�t � l'ext�rieur des parenth�ses entre IN et LOOP. Si .. n'est pas trouv�, la boucle est suppos�e �tre une boucle entre des lignes. Une mauvaise saisie de .. am�nera donc une plainte du type <<�loop variable of loop over rows must be a record or row variable�>> (NdT : une variable de boucle d'une boucle sur des enregistrements doit �tre un enregistrement ou une variable de type ligne) plut�t qu'une simple erreur de syntaxe comme vous pourriez vous y attendre.
Par d�faut, toute erreur survenant dans une fonction PL/pgSQL annule l'ex�cution de la fonction et, en fait, aussi de la transaction qui l'entoure. Vous pouvez r�cup�rer les erreurs et les surpasser 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_gestionnaire [ WHEN condition [ OR condition ... ] THEN instructions_gestionnaire ... ] 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_gestionnaire 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 peuvent �tre n'importe laquelle parmi celles list�es dans l'Annexe A. 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.
Si une nouvelle erreur survient � l'int�rieur des instructions_gestionnaire 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 reste comme elles �taient 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 'caught 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�: 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.
Exemple 35-1. Exceptions avec UPDATE/INSERT
Cet exemple utilise la gestion des exceptions pour r�aliser soit un UPDATE soit un INSERT suivant le cas.
CREATE TABLE base (a INT PRIMARY KEY, b TEXT); CREATE FUNCTION fusionne_base(cle INT, donnee TEXT) RETURNS VOID AS $$ BEGIN LOOP UPDATE base SET b = donnee WHERE a = cle; IF found THEN RETURN; END IF; BEGIN INSERT INTO base(a,b) VALUES (cle, donnee); RETURN; EXCEPTION WHEN unique_violation THEN -- ne fait rien END; END LOOP; END; $$ LANGUAGE plpgsql; SELECT fusionne_base(1, 'david'); SELECT fusionne_base(1, 'dennis');
Pr�c�dent | Sommaire | Suivant |
Instructions de base | Niveau sup�rieur | Curseurs |