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

37.6. Instructions de base

Dans cette section ainsi que les suivantes, nous décrirons tous les types d'instructions explicitement compris par PL/pgSQL. Tout ce qui n'est pas reconnu comme l'un de ces types d'instruction est présumé être une commande SQL et est envoyé au moteur principal de bases de données pour être exécutée comme décrit dans Section 37.6.2, « Exécuter une requête sans résultats » et Section 37.6.3, « Exécuter une requête avec une seule ligne de résultats ».

37.6.1. Assignation

L'assignation d'une valeur à une variable PL/pgSQL ou à un champ row/record est écrite ainsi :

identifiant := expression;

Comme expliqué plus haut, l'expression dans une telle instruction est évaluée au moyen de la commande SQL SELECT envoyée au moteur principal de bases de données. L'expression ne doit manier qu'une seule valeur.

Si le type de données du résultat de l'expression ne correspond pas au type de donnée de la variable, ou que la variable a une taille ou une précision (comme char(20)), la valeur résultat sera implicitement convertie par l'interpréteur PL/pgSQL en utilisant la fonction d'écriture (output-function) du type du résultat, et la fonction d'entrée (input-function) du type de la variable. Notez que cela pourrait potentiellement conduire à des erreurs d'exécution générées par la fonction d'entrée si la forme de la chaîne de la valeur résultat n'est pas acceptable par la fonction d'entrée.

Exemples :

id_utilisateur := 20;
tax := sous_total * 0.06;

37.6.2. Exécuter une requête sans résultats

Pour toute requête qui ne renvoie pas de lignes, par exemple INSERT sans clause RETURNING, vous pouvez exécuter la requête à l'intérieur d'une fonction PL/pgSQL rien qu'en écrivant la requête.

Tout nom de variable PL/pgSQL apparaissant dans le texte de la variable est remplacé par un symbole de paramètre, puis la valeur actuelle de la variable est fournie comme valeur du paramètre à l'exécution. Ceci permet à la même requête textuelle de faire des choses différentes lors d'appels différents à la fonction.

[Note]

Note

Ce processus en deux étapes permet à PL/pgSQL de planifier la requête seulement une fois et de réutiliser la planification sur les exécutions suivantes. Par exemple, si vous écrivez

DECLARE
    cle TEXT;
    delta INTEGER;
BEGIN
    ...
    UPDATE matable SET val = val + delta WHERE id = cle;

le texte de la requête envoyée par le moteur SQL ressemblera à ceci

    UPDATE matable SET val = val + $1 WHERE id = $2;

Bien que vous n'ayez pas à y penser, il est utile de la savoir pour mieux comprendre les messages d'erreur de syntaxe.

[Attention]

Attention

PL/pgSQL substitutera pour tout identifiant une variable déclarée de la fonction. Du coup, une mauvaise idée serait d'utiliser un nom de variable identique à celui d'une table ou d'une colonne que vous avez besoin d'utiliser dans la fonction. Quelque fois, vous pouvez contourner ceci en utilisant des noms qualifiés dans la requête : PL/pgSQL ne substituera pas le nom qualifié foo.bar, même si foo ou bar est un nom de variable déclaré.

Quelque fois, il est utile d'évaluer une expression ou une requête SELECT mais sans récupérer le résultat, par exemple lors de l'appel d'une fonction qui a des effets de bord mais dont la valeur du résultat n'est pas utile. Pour faire cela en PL/pgSQL, utilisez l'instruction PERFORM :

PERFORM requête;

Ceci exécute la requête et ne tient pas compte du résultat. Écrivez la requête de la même façon que vous écririez une commande SELECT mais remplacez le mot clé initial SELECT avec PERFORM. Les variables PL/pgSQL seront substituées dans la requête comme d'habitude. De plus, la variable spéciale FOUND est configurée à true si la requête a produit au moins une ligne, false dans le cas contraire.

[Note]

Note

Vous pourriez vous attendre à ce que l'utilisation directe de SELECT aboutisse au même résultat mais, actuellement, la seule façon acceptée de le faire est d'utiliser PERFORM. Une commande SQL qui peut renvoyer des lignes comme SELECT sera rejetée comme une erreur si elle n'a pas de clause INTO, ce qui est discuté dans la section suivante.

Un exemple :

PERFORM creer_vuemat('cs_session_page_requests_mv', ma_requete);

37.6.3. Exécuter une requête avec une seule ligne de résultats

Le résultat d'une commande SQL ne ramenant qu'une seule ligne (mais avec une ou plusieurs colonnes) peut être affecté à une variable de type record, row ou à une liste de variables scalaires. Ceci se fait en écrivant la commande SQL de base et en ajoutant une clause INTO. Par exemple,

SELECT expressions_select INTO [STRICT] cible FROM ...;
INSERT ... RETURNING expressions INTO [STRICT] cible;
UPDATE ... RETURNING expressions INTO [STRICT] cible;
DELETE ... RETURNING expressions INTO [STRICT] cible;

cible peut être une variable de type record, row ou une liste de variables ou de champs record/row séparées par des virgules. Les variables PL/pgSQL seront substituées dans le reste de la requête comme d'habitude. Ceci fonctionne pour SELECT, INSERT/UPDATE/DELETE avec RETURNING, et les commandes utilitaires qui renvoient des résultats de type rowset (comme EXPLAIN). Sauf pour la clause INTO, la commande SQL est identique à celle qui aurait été écrite en dehors de PL/pgSQL.

[Astuce]

Astuce

Notez que cette interprétation de SELECT avec INTO est assez différente de la commande habituelle SELECT INTO où la cible INTO est une table nouvellement créée. Si vous voulez créer une table à partir du résultat d'un SELECT à l'intérieur d'une fonction PL/pgSQL, utilisez la syntaxe CREATE TABLE ... AS SELECT.

Si une ligne ou une liste de variables est utilisée comme cible, les colonnes du résultat de la requête doivent correspondre exactement à la structure de la cible (nombre de champs et types de données). Dans le cas contraire, une erreur sera rapportée à l'exécution. Quand une variable record est la cible, elle se configure automatiquement avec le type row des colonnes du résultat de la requête.

La clause INTO peut apparaître pratiquement partout dans la commande SQL. Elle est écrite soit juste avant soit juste après la liste d'expressions_select dans une commande SELECT, ou à la fin de la commande pour d'autres types de commande. Il est recommandé de suivre cette convention au cas où l'analyseur PL/pgSQL devient plus strict dans les versions futures.

Si STRICT n'est pas spécifié, alors cible sera configuré avec la première ligne renvoyée par la requête ou à NULL si la requête n'a renvoyé aucune ligne. (Notez que « la première ligne » n'est bien définie que si vous avez utilisé ORDER BY.) Toute ligne résultat après la première ligne est annulée. Vous pouvez vérifier la valeur de la variable spéciale FOUND (voir la Section 37.6.6, « Obtention du statut du résultat ») pour déterminer si une ligne a été renvoyée :

SELECT * INTO monrec FROM emp WHERE nom = mon_nom;
IF NOT FOUND THEN
    RAISE EXCEPTION 'employé % introuvable', mon_nom;
END IF;

Si l'option STRICT est indiquée, la requête doit renvoyer exactement une ligne. Dans le cas contraire, une erreur sera rapportée à l'exécution, soit NO_DATA_FOUND (aucune ligne) soit TOO_MANY_ROWS (plus d'une ligne). Vous pouvez utiliser un bloc d'exception si vous souhaitez récupérer l'erreur, par exemple :

BEGIN
    SELECT * INTO STRICT monrec FROM emp WHERE nom = mon_nom;
    EXCEPTION
        WHEN NO_DATA_FOUND THEN
            RAISE EXCEPTION 'employé % introuvable', mon_nom;
        WHEN TOO_MANY_ROWS THEN
            RAISE EXCEPTION 'employé % non unique', mon_nom;
END;

Une exécution réussie de la commande avec STRICT renvoie toujours true pour FOUND.

Pour INSERT/UPDATE/DELETE avec RETURNING, PL/pgSQL rapporte une erreur si plus d'une ligne est renvoyée, même quand STRICT n'est pas spécifié. Ceci est dû au fait qu'il n'y a pas d'option comme ORDER BY qui pourrait déterminer la ligne à renvoyer.

[Note]

Note

L'option STRICT correspond au comportement du SELECT INTO d'Oracle PL/SQL et des instructions relatives.

Pour gérer les cas où vous avez besoin de traiter plusieurs lignes de résultat à partir d'une requête SQL, voir la Section 37.7.4, « Boucler dans les résultats de requêtes ».

37.6.4. Ne rien faire du tout

Quelque fois, une instruction qui ne fait rien est utile. Par exemple, il indique qu'une partie de la chaîne if/then/else est délibérément vide. Pour cela, utilisez l'instruction :

NULL;

Par exemple, les deux fragments de code suivants sont équivalents :

BEGIN
  y := x / 0;
  EXCEPTION
  WHEN division_by_zero THEN
    NULL;  -- ignore l'erreur
  END;
BEGIN
  y := x / 0;
  EXCEPTION
  WHEN division_by_zero THEN  -- ignore l'erreur
  END;

Ce qui est préférable est une histoire de goût.

[Note]

Note

Dans le PL/SQL d'Oracle, les listes d'instructions vides ne sont pas autorisées et, du coup, les instructions NULL sont requises dans les situations telles que celles-ci. PL/pgSQL vous permet d'écrire simplement rien.

37.6.5. Exécuter des commandes dynamiques

Souvent vous voudrez générer des commandes dynamiques dans vos fonctions PL/pgSQL, c'est-à-dire des commandes qui impliquent différentes tables ou différents types de données à chaque fois qu'elles sont exécutées. Les tentatives normales de PL/pgSQL pour garder en cache les planifications des commandes ne fonctionneront pas dans de tels scénarios. Pour gérer ce type de problème, l'instruction EXECUTE est fournie :

EXECUTE chaîne-commande [ INTO [STRICT] cible ];

chaîne-commande est une expression manipulant une chaîne (de type text) contenant la commande à être exécutée et cible est une variable record ou ligne ou une liste de variables simples ou de champs de lignes/enregistrements séparées par des virgules.

Notez en particulier qu'aucune substitution de variable PL/pgSQL n'est faite sur la chaîne-commande calculée. Les valeurs des variables doivent être insérées dans la chaîne de commande lors de sa construction.

À la différence de toutes les autres commandes dans PL/pgSQL, une commande lancée par une instruction EXECUTE n'est pas préparée ni sauvée une seule fois pendant la durée de la session. À la place, la commande est préparée à chaque fois que l'instruction est lancée. La chaîne commande peut être créée dynamiquement à l'intérieur de la fonction pour agir sur des tables ou colonnes différentes.

La clause INTO spécifie où devraient être affectés les résultats d'une commande SQL renvoyant des lignes. Si une ligne ou une liste de variable est fournie, elle doit correspondre exactement à la structure des résultats de la requête (quand une variable de type record est utilisée, elle sera automatiquement typée pour correspondre à la structure du résultat). Si plusieurs lignes sont renvoyées, alors seule la première sera assignée à la variable INTO. Si aucune ligne n'est renvoyée, NULL est affectée à la variable INTO. Si aucune clause INTO n'est spécifiée, les résultats de la requête sont ignorés.

Si l'option STRICT est indiquée, une erreur est rapportée sauf si la requête produit exactement une ligne.

SELECT INTO n'est actuellement pas supporté à l'intérieur de EXECUTE.

En travaillant avec des commandes dynamiques, vous aurez souvent à gérer des échappements de guillemets simples. La méthode recommandée pour mettre entre guillemets un texte fixe dans le corps de votre fonction est d'utiliser les guillemets dollar (si votre code n'utilise pas les guillemets dollar, référez-vous à l'aperçu dans la Section 37.2.1, « Utilisation des guillemets simples (quotes) », ce qui peut vous faire gagner des efforts lors du passage de ce code à un schéma plus raisonnable).

Les valeurs dynamiques qui sont à insérer dans la requête construite requièrent une gestion spéciale car elles pourraient elles-même contenir des guillemets. Un exemple (ceci suppose que vous utilisez les guillemets dollar pour la fonction dans sa globalité, du coup les guillemets n'ont pas besoin d'être doublés) :

EXECUTE 'UPDATE tbl SET '
    || quote_ident(nom_colonne)
    || ' = '
    || quote_literal(nouvelle_valeur)
    || ' WHERE cle = '
    || quote_literal(valeur_cle);

Cet exemple démontre l'utilisation des fonctions quote_ident et quote_literal. Pour plus de sûreté, les expressions contenant les identifiants des colonnes et des tables doivent être passés à la fonction quote_ident. Les expressions contenant les valeurs devant être des chaînes dans la commande construite devraient être passées à quote_literal. Les deux font les étapes appropriées pour renvoyer le texte en entrée entouré par des guillemets doubles ou simples respectivement, avec tout caractère intégré spécial proprement échappé.

Notez que les guillemets dollar sont souvent utiles pour placer un texte fixe entre guillemets. Il serait une très mauvaise idée d'essayer de faire l'exemple ci-dessus de cette façon

    EXECUTE 'UPDATE tbl SET '
    || quote_ident(colname)
    || ' = $$'
    || newvalue
    || '$$ WHERE key = '
    || quote_literal(keyvalue);

car cela casserait si le contenu de newvalue pouvait contenir $$. La même objection s'appliquerait à tout délimiteur dollar que vous pourriez choisir. Donc, pour mettre un texte inconnu entre guillemets de façon sûr, vous devez utiliser quote_literal.

Un exemple bien plus important d'une commande dynamique et d'EXECUTE est disponible dans l'Exemple 37.6, « Portage d'une fonction qui crée une autre fonction de PL/SQL vers PL/pgSQL », qui construit et exécute une commande CREATE FUNCTION pour définir une nouvelle fonction.

37.6.6. Obtention du statut du résultat

Il y a plusieurs moyens pour déterminer l'effet d'une commande. La première méthode est d'utiliser GET DIAGNOSTICS :

GET DIAGNOSTICS variable = élément [ , ... ] ;

Cette commande permet la récupération des indicateurs d'état du système. Chaque élément est un mot clé identifiant une valeur d'état devant être affectée à la variable indiquée (qui doit être du bon type de donnée pour que l'affectation puisse se faire sans erreur.) Les éléments d'état actuellement disponibles sont ROW_COUNT, le nombre de lignes traitées par la dernière commande SQL envoyée au moteur SQL, et RESULT_OID, l'OID de la dernière ligne insérée par la commande SQL la plus récente. Notez que RESULT_OID n'est utile qu'après une commande INSERT dans une table contenant des OID.

Exemple :

GET DIAGNOSTICS var_entier = ROW_COUNT;

La seconde méthode permettant de déterminer les effets d'une commande est la variable spéciale nommée FOUND de type boolean. La variable FOUND est initialisée à false au début de chaque fonction PL/pgSQL. Elle est positionnée par chacun des types d'instructions suivants :

  • Une instruction SELECT INTO positionne FOUND à true si une ligne est affectée, false si aucune ligne n'est renvoyée.

  • Une instruction PERFORM positionne FOUND à true si elle produit (rejette) une ligne, faux si aucune ligne n'est produite.

  • Les instructions UPDATE, INSERT, et DELETE positionnent FOUND à true si au moins une ligne est affectée, false si aucune ligne n'est affectée.

  • Une instruction FETCH positionne FOUND à true si elle renvoie une ligne, false si aucune ligne n'est renvoyée.

  • La commande FOR positionne FOUND à true si elle effectue une itération une ou plusieurs fois, sinon elle renvoie false. Ceci s'applique aux trois variantes de l'instruction FOR (boucles FOR integer, FOR record-set, et FOR record-set dynamique). FOUND n'est positionnée de cette façon que quand la boucle FOR s'achève ; dans l'exécution de la chaîne, FOUND n'est pas modifiée par l'instruction FOR, bien qu'elle puisse être modifié par l'exécution d'autres instructions situées dans le corps de la boucle.

FOUND est une variable locale à l'intérieur de chaque fonction PL/pgSQL ; chaque changement qui y est fait n'affecte que la fonction en cours.