sql >> Databasteknik >  >> RDS >> PostgreSQL

Returnera dynamisk tabell med okända kolumner från PL/pgSQL-funktionen

Detta är svårt att lösa, eftersom SQL kräver att man känner till returtypen vid samtalstid .
En plpgsql-funktion måste också ha en väldefinierad returtyp .

Om du väljer att returnera anonyma poster , får du vad du definierade:anonyma poster. Postgres vet inte vad som finns inuti. Därför är en kolumndefinitionslista obligatorisk för att dekomponera typen.

Det finns olika lösningar, beroende på exakta krav. Om du har något sätt att veta returtypen vid samtalstid , jag föreslår polymorfa typer som beskrivs i det sista kapitlet i detta svar ("Olika kompletta tabelltyper"):
Refaktorera en PL/pgSQL-funktion för att returnera utdata från olika SELECT-frågor

Men det täcker inte att lägga till ytterligare en kolumn till returtypen vid körning inuti funktionen . Det är bara inte möjligt. Jag skulle ompröva hela tillvägagångssättet .

När det gäller ditt nuvarande tillvägagångssätt är det närmaste jag kan komma på en tillfällig tabell (eller en markör), som du frågar efter i ett andra samtal inom en enskild transaktion .

Du har ett par andra problem i din kod . Se anteckningarna nedan.

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;

Samtalet måste vara i en enda transaktion. Du kan behöva starta en explicit transaktion, beroende på 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-fiol.

Alternativt kan du låta det tillfälliga bordet leva under hela sessionen. Var dock försiktig med att namnge kollisioner med upprepade samtal.

Anteckningar

  • Använd parameternamn istället för det föråldrade ALIAS kommando.

  • För att faktiskt "ställa in som standard" till det aktuella schemat, använd den enklare frågan jag visar. Använder regclass gör susen automatiskt. Detaljer:

    • Tabellnamn som en PostgreSQL-funktionsparameter

    Dessutom undviker detta också syntaxfel och eventuell SQL-injektion från icke-standardiserade (eller uppsåtligt felaktiga) tabellnamn i din ursprungliga kod.

  • Koden i din ELSE klausul skulle inte fungera alls.

  • TABLE tbl; är i princip förkortning för SELECT * FROM tbl; .

  • Detaljer om format() i manualen.




  1. PostgreSQL-fråga för att lista alla tabellnamn?

  2. Hur ställer man in ORACLE_HOME-variabeln korrekt på Ubuntu 9.x?

  3. SQLite-gräns

  4. Val av lagringsmotor:Aria