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

Returner dynamisk tabel med ukendte kolonner fra PL/pgSQL-funktionen

Dette er svært at løse, fordi SQL kræver at kende returtypen på opkaldstidspunktet .
En plpgsql-funktion skal også have en veldefineret returtype .

Hvis du vælger at returnere anonyme poster , får du det, du definerede:anonyme optegnelser. Postgres ved ikke, hvad der er indeni. Derfor er en kolonnedefinitionsliste påkrævet for at dekomponere typen.

Der er forskellige løsninger, afhængigt af nøjagtige krav. Hvis du har nogen måde at kende returtypen på opkaldstidspunktet , foreslår jeg polymorfe typer som beskrevet i det sidste kapitel af dette svar ("Forskellige komplette tabeltyper"):
Refaktorer en PL/pgSQL-funktion for at returnere output fra forskellige SELECT-forespørgsler

Men det dækker ikke tilføjelse af endnu en kolonne til returtypen ved runtime inde i funktionen . Det er bare ikke muligt. Jeg ville genoverveje hele tilgangen .

Hvad angår din nuværende tilgang, ville det tætteste, jeg kan komme i tanke om, være en midlertidig tabel (eller en markør), som du forespørger på i et andet opkald inden for en enkelt transaktion .

Du har et par andre problemer i din kode . Se bemærkninger nedenfor.

Proof of 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;

Opkaldet skal være i en enkelt transaktion. Du skal muligvis starte en eksplicit transaktion, afhængigt af din klient.

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.

Alternativt kan du lade det midlertidige bord leve under sessionens varighed. Vær dog på vagt over for navnekollisioner med gentagne opkald.

Bemærkninger

  • Brug parameternavne i stedet for det forældede ALIAS kommando.

  • For faktisk at "standard" til det aktuelle skema, brug den enklere forespørgsel, jeg viser. Brug af regclass gør tricket automatisk. Detaljer:

    • Tabelnavn som en PostgreSQL-funktionsparameter

    Derudover undgår dette også syntaksfejl og mulig SQL-injektion fra ikke-standardiserede (eller ondsindede forkerte) tabelnavne i din originale kode.

  • Koden i din ELSE klausul ville slet ikke virke.

  • TABLE tbl; er grundlæggende en forkortelse for SELECT * FROM tbl; .

  • Detaljer om format() i manualen.




  1. Hvordan afinstallerer/fjerner jeg Oracle 11g (klient)?

  2. Kald en sæt-returnerende funktion med et array-argument flere gange

  3. SQL:valg af rækker, hvor kolonneværdien er ændret fra forrige række

  4. Konverter 'smalldatetime' til 'datetime2' i SQL Server (T-SQL-eksempler)