Arbeta med det här dummybordet
CREATE TEMP TABLE foo (id int, my_num numeric);
INSERT INTO foo VALUES (1, 12.34)
Först förenklade och sanerade jag ditt exempel:
-
Tog bort en del brus som är irrelevant för frågan.
-
RETURNS SETOF void
är knappast vettigt. Jag använderRETURNS void
istället. -
Jag använder
text
istället för attcharacter varying
, bara för enkelhetens skull. -
När du använder dynamisk SQL har för att skydda mot SQL-injektion använder jag
format()
med%I
I detta fall. Det finns andra sätt.
Det grundläggande problemet är att SQL är väldigt stel med typer och identifierare. Du arbetar med dynamisk tabell namn samt med dynamiskt fältnamn på en post - en anonym spela in i ditt ursprungliga exempel. Pl/pgSQL är inte väl rustad för att hantera detta. Postgres vet inte vad som finns inuti ett anonymt register. Först efter att du tilldelat posten till en välkänd typ kan du referera till enskilda fält.
Här är en närbesläktad fråga som försöker ställa ett fält i en post med dynamiskt namn:
Hur man ställer in värdet på ett sammansatt variabelfält med hjälp av dynamisk SQL
Grundläggande funktion
CREATE OR REPLACE FUNCTION getrowdata1(table_name text, id int)
RETURNS void AS
$func$
DECLARE
srowdata record;
reqfield text := 'my_num'; -- assigning at declaration time for convenience
value numeric;
BEGIN
RAISE NOTICE 'id: %', id;
EXECUTE format('SELECT * FROM %I WHERE id = $1', table_name)
USING id
INTO srowdata;
RAISE NOTICE 'srowdata: %', srowdata;
RAISE NOTICE 'srowdatadata.my_num: %', srowdata.my_num;
/* This does not work, even with dynamic SQL
EXECUTE format('SELECT ($1).%I', reqfield)
USING srowdata
INTO value;
RAISE NOTICE 'value: %', value;
*/
END
$func$ LANGUAGE plpgsql;
Ring:
SELECT * from getrowdata1('foo', 1);
Den kommenterade delen skulle ta upp ett undantag:
kunde inte identifiera kolumnen "my_num" i postens datatyp:SELECT * fromgetrowdata(1,'foo')
hstore
Du måste installera tilläggsmodulen hstore för detta. En gång per databas med:
CREATE EXTENSION hstore;
Då skulle allt kunna fungera så här:
CREATE OR REPLACE FUNCTION getrowdata2(table_name text, id int)
RETURNS void AS
$func$
DECLARE
hstoredata hstore;
reqfield text := 'my_num';
value numeric;
BEGIN
RAISE NOTICE 'id: %', id;
EXECUTE format('SELECT hstore(t) FROM %I t WHERE id = $1', table_name)
USING id
INTO hstoredata;
RAISE NOTICE 'hstoredata: %', hstoredata;
RAISE NOTICE 'hstoredata.my_num: %', hstoredata -> 'my_num';
value := hstoredata -> reqfield;
RAISE NOTICE 'value: %', value;
END
$func$ LANGUAGE plpgsql;
Ring:
SELECT * from getrowdata2('foo', 1);
Polymorf typ
Alternativ utan att installera ytterligare moduler.
Eftersom du väljer en hel rad i din postvariabel finns det en väldefinierad typ för det per definition. Använd den. Nyckelordet är polymorfa typer .
CREATE OR REPLACE FUNCTION getrowdata3(_tbl anyelement, id int)
RETURNS void AS
$func$
DECLARE
reqfield text := 'my_num';
value numeric;
BEGIN
RAISE NOTICE 'id: %', id;
EXECUTE format('SELECT * FROM %s WHERE id = $1', pg_typeof(_tbl))
USING id
INTO _tbl;
RAISE NOTICE '_tbl: %', _tbl;
RAISE NOTICE '_tbl.my_num: %', _tbl.my_num;
EXECUTE 'SELECT ($1).' || reqfield -- requfield must be SQLi-safe or escape
USING _tbl
INTO value;
RAISE NOTICE 'value: %', value;
END
$func$ LANGUAGE plpgsql;
Ring:
SELECT * from getrowdata3(NULL::foo, 1);
-> SQLfiddle
-
Jag (ab-)använder indataparametern
_tbl
för tre syften här:- Tillhandahåller den väldefinierade typen protokollet
- Tillhandahåller namnet i tabellen, automatiskt schemakvalificerad
- Fungerar som variabel.
-
Mer förklaring i detta relaterade svar (sista kapitlet):
Refaktorera en PL/pgSQL-funktion för att returnera utdata från olika SELECT-frågor