PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Renvoyer une table dynamique avec des colonnes inconnues de la fonction PL/pgSQL

Ceci est difficile à résoudre, car SQL exige de connaître le type de retour au moment de l'appel .
De plus, une fonction plpgsql doit avoir un type de retour bien défini .

Si vous choisissez de renvoyer des enregistrements anonymes , vous obtenez ce que vous avez défini :des enregistrements anonymes. Postgres ne sait pas ce qu'il y a dedans. Par conséquent, une liste de définition de colonne est requise pour décomposer le type.

Il existe différentes solutions de contournement, en fonction des exigences exactes. Si vous avez un moyen de connaître le type de retour au moment de l'appel , je suggère des types polymorphes comme indiqué dans le dernier chapitre de cette réponse ("Divers types de tables complètes") :
Refactoriser une fonction PL/pgSQL pour renvoyer la sortie de diverses requêtes SELECT

Mais cela ne couvre pas l'ajout d'une autre colonne au type de retour au moment de l'exécution dans la fonction . Ce n'est tout simplement pas possible. Je voudrais repenser toute l'approche .

En ce qui concerne votre approche actuelle, la chose la plus proche à laquelle je puisse penser serait une table temporaire (ou un curseur), que vous interrogez dans un deuxième appel au sein d'une transaction unique .

Vous avez quelques autres problèmes dans votre code . Voir les notes ci-dessous.

Preuve de concept

CREATE OR REPLACE FUNCTION f_tbl_plus_infowindow (_tbl regclass) -- regclass!
  RETURNS void AS  -- no direct return type
$func$
DECLARE
   -- appending _tmp for temp table
   _tmp text := quote_ident(_tbl::text || '_tmp');
BEGIN

-- Create temp table only for duration of transaction
EXECUTE format(
   'CREATE TEMP TABLE %s ON COMMIT DROP AS TABLE %s LIMIT 0', _tmp, _tbl);

IF EXISTS (
   SELECT 1
   FROM   pg_attribute a
   WHERE  a.attrelid = _tbl
   AND    a.attname  = 'infowindow'
   AND    a.attisdropped = FALSE)
THEN
   EXECUTE format('INSERT INTO %s SELECT * FROM %s', _tmp, _tbl);
ELSE
  -- This is assuming a NOT NULL column named "id"!
   EXECUTE format($x$
      ALTER  TABLE %1$s ADD COLUMN infowindow text;
      INSERT INTO %1$s
      SELECT *, 'ID: ' || id::text
      FROM   %2$s $x$
     ,_tmp, _tbl);
END IF;

END
$func$ LANGUAGE plpgsql;

L'appel doit être en une seule transaction. Vous devrez peut-être démarrer une transaction explicite, selon votre client.

BEGIN;
SELECT f_tbl_plus_infowindow ('tbl');
SELECT * FROM tbl_tmp;  -- do something with the returned rows
ROLLBACK;               -- or COMMIT, does not matter here

SQL Fiddle.

Vous pouvez également laisser la table temporaire vivre pendant la durée de la session. Méfiez-vous cependant des collisions de noms avec des appels répétés.

Remarques

  • Utilisez des noms de paramètre au lieu de l'ancien ALIAS commande.

  • Pour réellement "par défaut" au schéma actuel, utilisez la requête plus simple que j'affiche. Utilisation de regclass fait l'affaire automatiquement. Détails :

    • Nom de table en tant que paramètre de fonction PostgreSQL

    De plus, cela évite également les erreurs de syntaxe et une éventuelle injection SQL à partir de noms de table non standard (ou malformés de manière malveillante) dans votre code d'origine.

  • Le code dans votre ELSE clause ne fonctionnerait pas du tout.

  • TABLE tbl; est essentiellement l'abréviation de SELECT * FROM tbl; .

  • Détails sur format() dans le manuel.