PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Dynamische Tabelle mit unbekannten Spalten aus der PL/pgSQL-Funktion zurückgeben

Dies ist schwer zu lösen, da SQL verlangt, den Rückgabetyp zur Aufrufzeit zu kennen .
Außerdem muss eine plpgsql-Funktion einen gut definierten Rückgabetyp haben .

Wenn Sie anonyme Datensätze zurückgeben möchten , erhalten Sie, was Sie definiert haben:anonyme Datensätze. Postgres weiß nicht, was drin ist. Daher ist eine Spaltendefinitionsliste erforderlich um den Typ zu zerlegen.

Abhängig von den genauen Anforderungen gibt es verschiedene Problemumgehungen. Wenn Sie eine Möglichkeit haben, den Rückgabetyp zur Aufrufzeit zu erfahren , schlage ich polymorphe Typen vor wie im letzten Kapitel dieser Antwort ("Verschiedene vollständige Tabellentypen") beschrieben:
Refaktorisieren Sie eine PL/pgSQL-Funktion, um die Ausgabe verschiedener SELECT-Abfragen zurückzugeben

Aber das deckt nicht das Hinzufügen einer weiteren Spalte zum Rückgabetyp zur Laufzeit innerhalb der Funktion ab . Das ist einfach nicht möglich. Ich würde den gesamten Ansatz überdenken .

Was Ihren aktuellen Ansatz betrifft, wäre das nächste, was mir einfällt, eine temporäre Tabelle (oder ein Cursor), die Sie in einem zweiten Aufruf abfragen innerhalb einer einzelnen Transaktion .

Sie haben ein paar andere Probleme in Ihrem Code . Siehe Anmerkungen unten.

Konzeptnachweis

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;

Der Aufruf muss in einer einzigen Transaktion erfolgen. Abhängig von Ihrem Client müssen Sie möglicherweise eine explizite Transaktion starten.

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

Alternativ können Sie die temporäre Tabelle für die Dauer der Sitzung aktiv lassen. Seien Sie jedoch vorsichtig bei Namenskollisionen bei wiederholten Aufrufen.

Notizen

  • Verwenden Sie Parameternamen anstelle des veralteten ALIAS Befehl.

  • Um das aktuelle Schema tatsächlich "standardmäßig" zu verwenden, verwenden Sie die einfachere Abfrage, die ich zeige. Verwenden von regclass macht den Trick automatisch. Einzelheiten:

    • Tabellenname als PostgreSQL-Funktionsparameter

    Außerdem werden dadurch auch Syntaxfehler und mögliche SQL-Injection vermieden aus nicht standardmäßigen (oder böswillig falsch formatierten) Tabellennamen in Ihrem Originalcode.

  • Der Code in Ihrem ELSE Klausel würde überhaupt nicht funktionieren.

  • TABLE tbl; ist im Grunde die Abkürzung für SELECT * FROM tbl; .

  • Details zu format() im Handbuch.