Chapitre 56 PL/pgSQL : 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.
56.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.
RETURN expression;
RETURN accompagné d'une expression termine la fonction et
renvoit le valeur d'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'éxécution sera
lancée. Notez que si vous avez déclaré la fonction comme renvoyant
void, une instruction RETURN doit être quand même
spécifiée ; 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 items 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 some_func();
En fait la fonction est utilisée comme table source dans une clause
FROM.
RETURN NEXT n'effectue pas vraiment de renvoi ; il sauvegarde
simplement les valeurs des expressions (ou variables de type
record ou ligne, d'après le type approprié au type de données
renvoyé). L'exécution continue alors avec la prochaîne 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.
Remarque.
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'a 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
controlé par la variable de configuration sort_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.
56.2 Contrôles conditionnels
Les instructions IF vous permettent d'exécuter des commandes
basées sur certaines conditions. PL/pgSQL a quatre formes de
IF :
-
IF ... THEN
IF ... THEN ... ELSE
IF ... THEN ... ELSE IF
IF ... THEN ... ELSIF ... 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 true. Autrement, ils seront
négligés.
Exemple :
IF v_user_id <> 0 THEN
UPDATE users SET email = v_email WHERE user_id = v_user_id;
END IF;
56.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 ensemble d'instructions alternatif à
exécuter si la condition est evaluée à false.
Exemples :
IF parentid IS NULL OR parentid = ''''
THEN
RETURN fullname;
ELSE
RETURN hp_true_filename(parentid) || ''/'' || fullname;
END IF;
IF v_count > 0 THEN
INSERT INTO users_count (count) VALUES (v_count);
RETURN ''t'';
ELSE
RETURN ''f'';
END IF;
56.2.3 IF-THEN-ELSE IF
Les instructions IF peuvent être imbriquées, 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;
Lorsque vous utilisez cette forme, vous imbriquez une instruction
IF dans la partie ELSE d'une instruction IF
extérieure. Ansi 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.
56.2.4 IF-THEN-ELSIF-ELSE
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 number = 0 THEN
result := ''zero'';
ELSIF number > 0 THEN
result := ''positif'';
ELSIF number < 0 THEN
result := ''negatif'';
ELSE
-- hmm, la seule possibilité est que le nombre soit null
result := ''NULL'';
END IF;
56.3 Boucles Simples
Grâce aux instructions LOOP, EXIT, WHILE, and
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éfiniement
jusqu'a 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 ou 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.
Exemples :
LOOP
-- quelques traitements
IF count > 0 THEN
EXIT; -- sortie de boucle
END IF;
END LOOP;
LOOP
-- quelques traitements
EXIT WHEN count > 0;
END LOOP;
BEGIN
-- quelques traitements
IF stocks > 100000 THEN
EXIT; -- invalide; on ne peut pas utiliser EXIT hors d'un LOOP
END IF;
END;
[<<label>>]
WHILE expression LOOP
instructions
END LOOP;
L'instruction WHILE repète une séquence d'instructions aussi
longtemps que l'expression conditionnelle est évaluée à vrai. La
condition est vérifée juste avant chaque entrée dans le corps de la
boucle.
Par exemple :
WHILE amount_owed > 0 AND gift_certificate_balance > 0 LOOP
-- quelques traitements ici
END LOOP;
WHILE NOT boolean_expression LOOP
-- quelques traitements ici
END LOOP;
56.3.4 FOR (variante avec entier)
[<<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 evaluées une fois en entrant dans la boucle. Le pas de
l'itération est normalement de 1, mais est -1 quand
REVERSE is spécifié.
Quelques exemples de boucles FOR avec entiers :
FOR i IN 1..10 LOOP
-- quelques expressions ici
RAISE NOTICE ''i is %'', i;
END LOOP;
FOR i IN REVERSE 10..1 LOOP
-- quelques expressions ici
END LOOP;
56.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 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 (une commande SELECT) et le corps de la
boucle et 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
-- A 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 apres la boucle.
L'instruction FOR-IN-EXECUTE est un moyen d'itérer sur des
enregistrements :
[<<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.
Remarque.
L'analyseur PL/pgSQL fait actuellement la distinction entre les deux
sortes de boucles FOR (avec entier ou résultat de requête) en
verifiant si la variable cible mentionnée juste apres le FOR
a été déclarée comme une variable record ou ligne. Si non, elle est
présumée être une boucle FOR avec entier. Ceci peut produire
des messages d'erreurs asser peu intuitifs quand le vrai problème
est, disons, que l'on a mal orthographié le nom de la variable
suivant le FOR.