web-dev-qa-db-fra.com

Comment conserver un ensemble de résultats sans garder la transaction ouverte?

La documentation suivante explique comment voir le Refcursor renvoyé d'une fonction, ici , comme celui-ci:

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

BEGIN;
SELECT reffunc('funccursor');
FETCH ALL IN funccursor;
COMMIT;

Cela fonctionne pour moi. Cependant, si je veux conserver les résultats sur mon écran, je dois garder la transaction ouverte. Lorsque j'exécute de commettre, mon jeu de résultats est jeté. Lorsque j'exécute à la fois de chercher et de s'engager en même temps, le premier jeu de résultats est supprimé.

Existe-t-il un moyen de commettre la transaction mais gardez le résultat? La version de PGADMIN est 1.18.1.

5
A-K

Lorsqu'un curseur est défini sur le niveau SQL avec DÉCLARER , il y a une option WITH HOLD Cela permet de continuer à exister après avoir commis la transaction en cours. Citant le doc:

Avec Hold Spécifie que le curseur peut continuer à être utilisé après la transaction qui l'a créée comme contraire

D'autre part, un Refcurseur ouvert par une fonction PLPGSQL est fermé à la fin de la transaction. Citant le PLPGSQL DOC :

Tous les portails sont implicitement fermés à la fin de la transaction. Par conséquent, une valeur Refcursor est utilisée pour faire référence à un curseur ouvert uniquement jusqu'à la fin de la transaction.

Pour créer un curseur dans une fonction PLPGGSQL pouvant être utilisée en dehors de la transaction "parent", il s'agit simplement d'une question de syntaxe. Vous voulez la mise en œuvre SQL d'un curseur, pas la variante PLPGSQL. Pour cela, EXECUTE doit être utilisé.

Par exemple, voici le squelette d'une fonction similaire à la vôtre, mais en utilisant des curseurs de niveau SQL qui survivent à la transaction:

CREATE FUNCTION dyncursor(name text) RETURNS VOID AS
$$
DECLARE
  query text;
BEGIN
  query='SELECT 1 as col1, 2 as col2'; -- sample query
  EXECUTE 'DECLARE ' || quote_ident(name) || ' CURSOR WITH HOLD FOR ' || query;
END
$$ language plpgsql;

Démo:

 Test => Commencez; 
 Test => Sélectionnez Dyncursor ('FOO'); 
 Dyncursor [.____] ----------- 
 [.____] (1 rangée) 
 Test => commit; 
 Test => Fetch tout de foo; 
 Col1 | col2 
 ------ + ------ 
 1 | 2 [.____] (1 rangée) 
 Test => Fermer FOO; 
 Fermer le curseur [.____]
8
Daniel Vérité

pgadmin est juste une interface graphique. Largement non pertinent pour cette question. Il arrive que une ou plusieurs sessions soient liées à une fenêtre de l'éditeur SQL et se terminent lorsque la fenêtre est fermée.

Si vous souhaitez conserver la transaction ouverte, ne faites pas COMMIT (ou ROLLBACK), encore.

Si vous voulez vraiment "garder" THA Résultat, écrivez-le à une table - éventuellement A TEMPORARY ou UNLOGGED table Si vous n'avez pas besoin de persister de façon permanente. Une table temporaire vit et meurt avec la session par défaut.

SQL et PL/PGSQL sont plutôt stricts avec leur système de type et certaines choses qui semblent possibles ne sont pas implémentées (encore). Mais ce que vous essayez de faire peut probablement être résolu sans curseurs. Si vous devez fournir un nom de table à une fonction de manière dynamique, utilisez un polymorphe Type de retour qui est enchaîné à un paramètre d'entrée:

CREATE OR REPLACE FUNCTION f_dynamic_select(_tbl anyelement)
 RETURNS SETOF anyelement AS
$func$
BEGIN

RETURN QUERY EXECUTE
'SELECT * FROM ' || pg_typeof(_tbl);

END
$func$ LANGUAGE plpgsql;

Appel:

SELECT * FROM  f_dynamic_select(NULL::my_schema.my_table)

Ce formulaire est automatiquement sécurisé contre l'injection SQL, car pg_typeof() Retourne une valeur regtype qui est automatiquement échappée (si Necesaray) lorsqu'il est automatiquement converti en text pendant la concaténation de la chaîne.

J'ai écrit une réponse étroitement liée à traiter avec des types polymorphes seulement hier. Il a plus d'explications et de liens:
[.____] Insérer des valeurs d'une variable d'enregistrement dans une table

Normalement, une simple et simple SELECT _ ferait le travail. C'est une condition rare que les noms de table doivent être fournis de manière dynamique.

SELECT * FROM my_schema.my_table
4