PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Restituisce una tabella dinamica con colonne sconosciute dalla funzione PL/pgSQL

Questo è difficile da risolvere, perché SQL richiede di conoscere il tipo restituito al momento della chiamata .
Inoltre, una funzione plpgsql deve avere un tipo restituito ben definito .

Se scegli di restituire record anonimi , ottieni ciò che hai definito:record anonimi. Postgres non sa cosa c'è dentro. Pertanto, è necessario un elenco di definizioni di colonna per scomporre il tipo.

Esistono varie soluzioni alternative, a seconda dei requisiti esatti. Se hai modo di conoscere il tipo di reso al momento della chiamata , suggerisco tipi polimorfici come descritto nell'ultimo capitolo di questa risposta ("Vari tipi di tabelle complete"):
Refactoring di una funzione PL/pgSQL per restituire l'output di varie query SELECT

Ma ciò non copre l'aggiunta di un'altra colonna al tipo restituito in fase di esecuzione all'interno della funzione . Non è possibile. Riconsidererei l'intero approccio .

Per quanto riguarda il tuo approccio attuale, la cosa più vicina a cui riesco a pensare sarebbe una tabella temporanea (o un cursore), che interroghi in una seconda chiamata all'interno di una singola transazione .

Hai un paio di altri problemi nel codice . Vedi le note sotto.

Prova del concetto

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;

La chiamata deve essere in un'unica transazione. Potrebbe essere necessario avviare una transazione esplicita, a seconda del cliente.

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.

In alternativa, puoi lasciare in vita il tavolo temporaneo per la durata della sessione. Tuttavia, fai attenzione alle collisioni di nomi con chiamate ripetute.

Note

  • Usa i nomi dei parametri invece dell'obsoleto ALIAS comando.

  • Per "predefinire" effettivamente lo schema corrente, usa la query più semplice che visualizzo. Usando regclass fa il trucco automaticamente. Dettagli:

    • Nome tabella come parametro di funzione PostgreSQL

    Inoltre, questo evita anche errori di sintassi e possibili iniezione SQL da nomi di tabelle non standard (o malformati) nel codice originale.

  • Il codice nel tuo ELSE la clausola non funzionerebbe affatto.

  • TABLE tbl; è fondamentalmente l'abbreviazione di SELECT * FROM tbl; .

  • Dettagli su format() nel manuale.