PostgreSQLLa base de données la plus sophistiquée au monde.
Documentation PostgreSQL 16.6 » Programmation serveur » PL/pgSQL -- Langage de procédures SQL » Curseurs

43.7. Curseurs #

Plutôt que d'exécuter la totalité d'une requête à la fois, il est possible de créer un curseur qui encapsule la requête, puis en lit le résultat quelques lignes à la fois. Une des raisons pour faire de la sorte est d'éviter les surcharges de mémoire quand le résultat contient un grand nombre de lignes (cependant, les utilisateurs PL/pgSQL n'ont généralement pas besoin de se préoccuper de cela puisque les boucles FOR utilisent automatiquement un curseur en interne pour éviter les problèmes de mémoire). Un usage plus intéressant est de renvoyer une référence à un curseur qu'une fonction a créé, permettant à l'appelant de lire les lignes. C'est un moyen efficace de renvoyer de grands ensembles de lignes à partir des fonctions.

43.7.1. Déclaration de variables curseur #

Tous les accès aux curseurs dans PL/pgSQL se font par les variables curseur, qui sont toujours du type de données spécial refcursor. Un des moyens de créer une variable curseur est de simplement la déclarer comme une variable de type refcursor. Un autre moyen est d'utiliser la syntaxe de déclaration de curseur qui est en général :

nom [ [ NO ] SCROLL ] CURSOR [ ( arguments ) ] FOR requête;
    

(FOR peut être remplacé par IS pour la compatibilité avec Oracle). Si SCROLL est spécifié, le curseur sera capable d'aller en sens inverse ; si NO SCROLL est indiqué, les récupérations en sens inverses seront rejetées ; si rien n'est indiqué, cela dépend de la requête. arguments est une liste de paires de nom type-de-donnée qui définit les noms devant être remplacés par les valeurs des paramètres dans la requête donnée. La valeur effective à substituer pour ces noms sera indiquée plus tard lors de l'ouverture du curseur.

Quelques exemples :

DECLARE
    curs1 refcursor;
    curs2 CURSOR FOR SELECT * FROM tenk1;
    curs3 CURSOR (cle integer) FOR SELECT * FROM tenk1 WHERE unique1 = cle;
    

Ces variables sont toutes trois du type de données refcursor mais la première peut être utilisée avec n'importe quelle requête alors que la seconde a une requête complètement spécifiée qui lui est déjà liée, et la dernière est liée à une requête paramétrée (cle sera remplacée par un paramètre de valeur entière lors de l'ouverture du curseur). La variable curs1 est dite non liée puisqu'elle n'est pas liée à une requête particulière.

L'option SCROLL ne peut pas être utilisée quand la requête du curseur utilise FOR UPDATE/SHARE. De plus, il est préférable d'utiliser NO SCROLL avec une requête qui implique des fonctions volatiles. L'implémentation de SCROLL suppose que relire la sortie de la requête donnera des résultats cohérents, ce qu'une fonction volatile ne peut pas faire.

43.7.2. Ouverture de curseurs #

Avant qu'un curseur puisse être utilisé pour rapatrier des lignes, il doit être ouvert (c'est l'action équivalente de la commande SQL DECLARE CURSOR). PL/pgSQL dispose de trois formes pour l'instruction OPEN, dont deux utilisent des variables curseur non liées et la dernière une variable curseur liée.

Note

Les variables des curseurs liés peuvent aussi être utilisés sans les ouvrir explicitement, via l'instruction FOR décrite dans Section 43.7.4. Une boucle FOR ouvrira le curseur puis le fermera de nouveau quand la boucle se terminera.

Ouvrir un curseur implique de créer une structure de données interne appelée portail, qui contient l'état d'exécution de la requête du curseur. Un portail a un nom, qui doit être unique dans la session pour la durée de l'existence du portail. Par défaut, PL/pgSQL affectera un nom unique chaque portail qu'il crée. Néanmoins, si vous affectez une chaîne de caractères non NULL à une variable curseur, cette chaîne sera utilisée comme nom de portail. Cette fonctionnalité peut être utilisée comme décrit dans Section 43.7.3.5.

43.7.2.1. OPEN FOR requête #

OPEN var_curseur_nonlie [ [ NO ] SCROLL ] FOR requete;
    

La variable curseur est ouverte et reçoit la requête spécifiée à exécuter. Le curseur ne peut pas être déjà ouvert, et il doit avoir été déclaré comme une variable de curseur non lié (c'est-à-dire comme une simple variable refcursor). La requête doit être un SELECT ou quelque chose d'autre qui renvoie des lignes (comme EXPLAIN). La requête est traitée de la même façon que les autres commandes SQL dans PL/pgSQL : les noms de variables PL/pgSQL sont substitués et le plan de requête est mis en cache pour une possible ré-utilisation. Quand une variable PL/pgSQL est substituée dans une requête de type curseur, la valeur qui est substituée est celle qu'elle avait au moment du OPEN ; les modifications ultérieures n'auront pas affectées le comportement du curseur. Les options SCROLL et NO SCROLL ont la même signification que pour un curseur lié.

Exemple :

OPEN curs1 FOR SELECT * FROM foo WHERE cle = ma_cle;
     

43.7.2.2. OPEN FOR EXECUTE #

OPEN var_curseur_nonlie [ [ NO ] SCROLL ] FOR EXECUTE requete
                                     [ USING expression [, ... ] ];
    

La variable curseur est ouverte et reçoit la requête spécifiée à exécuter. Le curseur ne peut pas être déjà ouvert et il doit avoir été déclaré comme une variable de curseur non lié (c'est-à-dire comme une simple variable refcursor). La requête est spécifiée comme une expression chaîne de la même façon que dans une commande EXECUTE. Comme d'habitude, ceci donne assez de flexibilité pour que le plan de la requête puisse changer d'une exécution à l'autre (voir la Section 43.11.2), et cela signifie aussi que la substitution de variable n'est pas faite sur la chaîne de commande. Comme avec la commande EXECUTE, les valeurs de paramètre peuvent être insérées dans la commande dynamique avec format() ou USING. Les options SCROLL et NO SCROLL ont la même signification que pour un curseur lié.

Exemple :

OPEN curs1 FOR EXECUTE format('SELECT * FROM %I WHERE nom_colonne = $1', ma_table) USING valeur_clef;
     

Dans cet exemple, le nom de la table est inséré dans la requête via la fonction format(). La valeur de la colonne nom_colonne utilisée pour la comparaison est insérée via le paramètre USING, c'est la raison pour laquelle elle n'a pas besoin d'être échappée.

43.7.2.3. Ouverture d'un curseur lié #

OPEN var_curseur_lié [ ( [ nom_argument := ] valeur_argument [, ...] ) ];
    

Cette forme d'OPEN est utilisée pour ouvrir une variable curseur à laquelle la requête est liée au moment de la déclaration. Le curseur ne peut pas être déjà ouvert. Une liste des expressions arguments doit apparaître si et seulement si le curseur a été déclaré comme acceptant des arguments. Ces valeurs seront remplacées dans la requête.

Le plan de requête pour un curseur lié est toujours considéré comme pouvant être mis en cache ; il n'y a pas d'équivalent de la commande EXECUTE dans ce cas. Notez que SCROLL et NO SCROLL ne peuvent pas être indiqués dans OPEN car le comportement du curseur était déjà déterminé.

Les valeurs des arguments peuvent être passées en utilisant soit la notation en position soit la notation nommée. Dans la première, tous les arguments sont indiqués dans l'ordre. Dans la seconde, chaque nom d'argument est indiqué en utilisant := pour la séparer de l'expression de l'argument. De façon similaire à l'appel de fonctions, décrit dans Section 4.3, il est aussi autorisé de mixer notation en position et notation nommée.

Voici quelques exemples (ils utilisent les exemples de déclaration de curseur ci-dessus) :

OPEN curs2;
OPEN curs3(42);
OPEN curs3(key := 42);
     

Comme la substitution de variable est faite sur la requête d'un curseur lié, il existe en fait deux façons de passer les valeurs au curseur : soit avec un argument explicite pour OPEN soit en référençant implicitement une variable PL/pgSQL dans la requête. Néanmoins, seules les variables déclarées avant que le curseur lié ne soit déclaré lui seront substituées. Dans tous les cas, la valeur passée est déterminée au moment de l'exécution de la commande OPEN. Par exemple, une autre façon d'obtenir le même effet que l'exemple curs3 ci-dessus est la suivante :

DECLARE
    key integer;
    curs4 CURSOR FOR SELECT * FROM tenk1 WHERE unique1 = key;
BEGIN
    key := 42;
    OPEN curs4;
     

43.7.3. Utilisation des curseurs #

Une fois qu'un curseur a été ouvert, il peut être manipulé grâce aux instructions décrites ci-dessous.

Ces manipulations n'ont pas besoin de se dérouler dans la même fonction que celle qui a ouvert le curseur. Vous pouvez renvoyer une valeur refcursor à partir d'une fonction et laisser l'appelant opérer sur le curseur (d'un point de vue interne, une valeur refcursor est simplement la chaîne de caractères du nom d'un portail contenant la requête active pour le curseur. Ce nom peut être passé à d'autres, affecté à d'autres variables refcursor et ainsi de suite, sans déranger le portail).

Tous les portails sont implicitement fermés à la fin de la transaction. C'est pourquoi une valeur refcursor est utilisable pour référencer un curseur ouvert seulement jusqu'à la fin de la transaction.

43.7.3.1. FETCH #

FETCH [ direction { FROM | IN } ] curseur INTO cible;
    

FETCH récupère la prochaine ligne (dans la direction indiquée) à partir d'un curseur et la place dans une cible, qui peut être une variable ligne, une variable record ou une liste de variables simples séparées par des virgules, comme dans un SELECT INTO. S'il n'y a pas de ligne convenable, la cible est mise à NULL. Comme avec SELECT INTO, la variable spéciale FOUND peut être lue pour voir si une ligne a été récupérée. Si aucune ligne n'est obtenue, le curseur est positionné après la dernière ligne ou avant la première ligne, suivant la direction du mouvement.

La clause direction peut être une des variantes suivantes autorisées pour la commande SQL FETCH sauf celles qui peuvent récupérer plus d'une ligne ; nommément, cela peut être NEXT, PRIOR, FIRST, LAST, ABSOLUTE nombre, RELATIVE nombre, FORWARD ou BACKWARD. Omettre direction est identique à spécifier NEXT. Quand la syntaxe utilise un count, le count peut être une expression de type integer (contrairement à la commande SQL FETCH, qui autorise seulement une constante de type integer). Les valeurs direction qui nécessitent d'aller en sens inverse risquent d'échouer sauf si le curseur a été déclaré ou ouvert avec l'option SCROLL.

curseur doit être le nom d'une variable refcursor qui référence un portail de curseur ouvert.

Exemples :

FETCH curs1 INTO rowvar;
FETCH curs2 INTO foo, bar, baz;
FETCH LAST FROM curs3 INTO x, y;
FETCH RELATIVE -2 FROM curs4 INTO x;
     

43.7.3.2. MOVE #

MOVE [ direction { FROM | IN } ] curseur;
    

MOVE repositionne un curseur sans récupérer de données. MOVE fonctionne comme la commande FETCH sauf qu'elle ne fait que repositionner le curseur et ne renvoie donc pas les lignes du déplacement. La clause direction peut valoir toute variante autorisée dans la commande SQL FETCH, incluant celles qui récupèrent plus d'une ligne ; le curseur est positionné sur la dernière ligne. (Néanmoins, le cas où la clause direction est simplement une expression count sans mot clé est maintenant obsolète dans PL/pgSQL. La syntaxe est ambigu dans le cas où la clause direction est omise et donc cela pourrait échouer si count n'est pas une constante.) Comme avec SELECT INTO, la variable spéciale FOUND peut être lue pour vérifier s'il y avait bien les lignes correspondant au déplacement. Si aucune ligne n'est obtenue, le curseur est positionné après la dernière ligne ou avant la première ligne, suivant la direction du mouvement.

Exemples :

MOVE curs1;
MOVE LAST FROM curs3;
MOVE RELATIVE -2 FROM curs4;
MOVE FORWARD 2 FROM curs4;
     

43.7.3.3. UPDATE/DELETE WHERE CURRENT OF #

UPDATE table SET ... WHERE CURRENT OF curseur;
DELETE FROM table WHERE CURRENT OF curseur;
    

Quand un curseur est positionné sur une ligne d'une table, cette ligne peut être mise à jour ou supprimée en utilisant le curseur qui identifie la ligne. Il existe des restrictions sur ce que peut être la requête du curseur (en particulier, pas de regroupement) et il est mieux d'utiliser FOR UPDATE dans le curseur. Pour des informations supplémentaires, voir la page de référence DECLARE.

Un exemple :

UPDATE foo SET valdonnee = mavaleur WHERE CURRENT OF curs1;
     

43.7.3.4. CLOSE #

CLOSE curseur;
    

CLOSE ferme le portail sous-tendant un curseur ouvert. Ceci peut être utilisé pour libérer des ressources avant la fin de la transaction ou pour libérer la variable curseur pour pouvoir la réouvrir.

Exemple :

CLOSE curs1;
     

43.7.3.5. Renvoi de curseurs #

Les fonctions PL/pgSQL peuvent renvoyer des curseurs à l'appelant. Ceci est utile pour renvoyer plusieurs lignes ou colonnes, spécialement avec des ensembles de résultats très grands. Pour cela, la fonction ouvre le curseur et renvoie le nom du curseur à l'appelant (ou simplement ouvre le curseur en utilisant un nom de portail spécifié par ou autrement connu par l'appelant). L'appelant peut alors récupérer les lignes à partir du curseur. Le curseur peut être fermé par l'appelant ou il sera fermé automatiquement à la fin de la transaction.

Le nom du portail utilisé pour un curseur peut être spécifié par le développeur ou peut être généré automatiquement. Pour spécifier un nom de portail, affectez simplement une chaîne à la variable refcursor avant de l'ouvrir. La valeur de la variable refcursor sera utilisée par OPEN comme nom du portail sous-jacent. Néanmoins, si la valeur de la variable refcursor est NULL (c'est le cas par défaut), OPEN génère automatiquement un nom qui n'entre pas en conflit avec tout portail existant et l'affecte à la variable refcursor.

Note

Avant PostgreSQL 16, les variables de curseurs liés étaient initialisées pour contenir leurs propres noms, plutôt que d'être laisséses à NULL, pour que le nom du portail sous-jacent soit le même que le nom de la variable curseur par défaut. Ceci a été changé parce que cela créait trop de risques de conflits entre des curseurs nommés de façon similaire dans des fonctions différentes.

L'exemple suivant montre une façon de fournir un nom de curseur par l'appelant :

CREATE TABLE test (col text);
INSERT INTO test VALUES ('123');

CREATE FUNCTION fonction_reference(refcursor) RETURNS refcursor AS $$
BEGIN
    OPEN $1 FOR SELECT col FROM test;
    RETURN $1;
END;
$$ LANGUAGE plpgsql;

BEGIN;
SELECT fonction_reference('curseur_fonction');
FETCH ALL IN curseur_fonction;
COMMIT;
     

L'exemple suivant utilise la génération automatique du nom du curseur :

CREATE FUNCTION fonction_reference2() RETURNS refcursor AS $$
DECLARE
    ref refcursor;
BEGIN
    OPEN ref FOR SELECT col FROM test;
    RETURN ref;
END;
$$ LANGUAGE plpgsql;

-- Il faut être dans une transaction pour utiliser les curseurs.
BEGIN;
SELECT fonction_reference2();

   fonction_reference2
--------------------------
 <unnamed cursor 1>
(1 row)

FETCH ALL IN "<unnamed cursor 1>";
COMMIT;
     

L'exemple suivant montre une façon de renvoyer plusieurs curseurs à une seule fonction :

CREATE FUNCTION ma_fonction(refcursor, refcursor) RETURNS SETOF refcursor AS $$
BEGIN
    OPEN $1 FOR SELECT * FROM table_1;
    RETURN NEXT $1;
    OPEN $2 FOR SELECT * FROM table_2;
    RETURN NEXT $2;
END;
$$ LANGUAGE plpgsql;

-- doit être dans une transaction pour utiliser les curseurs.
BEGIN;

SELECT * FROM ma_fonction('a', 'b');

FETCH ALL FROM a;
FETCH ALL FROM b;
COMMIT;
     

43.7.4. Boucler dans les résultats d'un curseur #

C'est une variante de l'instruction FOR qui permet l'itération sur les lignes renvoyées par un curseur. La syntaxe est :

[ <<label>> ]
FOR var_record IN var_curseur_lié [ ( [ nom_argument := ] valeur_argument [, ...] ) ] LOOP
    instructions
END LOOP [ label ];
    

La variable curseur doit avoir été liée à une requête lors de sa déclaration et il ne peut pas être déjà ouvert. L'instruction FOR ouvre automatiquement le curseur, et il ferme le curseur en sortie de la boucle. Une liste des expressions de valeurs des arguments doit apparaître si et seulement si le curseur a été déclaré prendre des arguments. Ces valeurs seront substituées dans la requête, de la même façon que lors d'un OPEN (voir Section 43.7.2.3).

La variable var_record est définie automatiquement avec le type record et existe seulement dans la boucle (toute définition existante d'un nom de variable est ignorée dans la boucle). Chaque ligne renvoyée par le curseur est successivement affectée à la variable d'enregistrement et le corps de la boucle est exécuté.