37.7. 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.

37.7.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.

37.7.1.1. RETURN

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 spécifiée; l'expression suivant la commande RETURN est cependant optionnelle et sera ignorée dans tous les cas.

37.7.1.2. RETURN NEXT

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 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 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'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 contrôlé 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.

37.7.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:

37.7.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 négligées.

Exemple :

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

37.7.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 évalué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;

37.7.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. 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.

37.7.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;

37.7.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.

37.7.3.1. LOOP

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

LOOP définit une boucle inconditionnelle répétée indéfiniment 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.

37.7.3.2. EXIT

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 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;  -- invalide; on ne peut pas utiliser EXIT hors d'un LOOP
    END IF;
END;

37.7.3.3. WHILE

[<<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;

37.7.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 évalué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 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.

37.7.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

        -- À 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 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.

Note : 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 après 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. Typiquement, la complainte ressemblera à missing ".." at end of SQL expression.