Dynamisk SQL och RETURN
typ
Du vill köra dynamisk SQL . I princip är det enkelt i plpgsql med hjälp av EXECUTE
. Du behöver inte en markör. Faktum är att du oftast har det bättre utan explicita markörer.
Problemet du stöter på:du vill returnera poster av ännu odefinierad typ . En funktion måste deklarera sin returtyp i RETURNS
klausul (eller med OUT
eller INOUT
parametrar). I ditt fall skulle du behöva falla tillbaka till anonyma poster, eftersom nummer , namn och typer av returnerade kolumner varierar. Gilla:
CREATE FUNCTION data_of(integer)
RETURNS SETOF record AS ...
Detta är dock inte särskilt användbart. Du måste tillhandahålla en kolumndefinitionslista med varje samtal. Gilla:
SELECT * FROM data_of(17)
AS foo (colum_name1 integer
, colum_name2 text
, colum_name3 real);
Men hur skulle du ens göra detta när du inte känner till kolumnerna i förväg?
Du kan använda mindre strukturerade dokumentdatatyper som json
, jsonb
, hstore
eller xml
. Se:
- Hur lagrar man en datatabell i databasen?
Men för denna frågas syfte, låt oss anta att du vill returnera individuella, korrekt skrivna och namngivna kolumner så mycket som möjligt.
Enkel lösning med fast returtyp
Kolumnen datahora
verkar vara en given, jag antar datatypen timestamp
och att det alltid finns ytterligare två kolumner med olika namn och datatyp.
Namn vi överger till förmån för generiska namn i returtypen.
Typer vi överger också och castar allt till text
sedan varje datatyp kan castas till text
.
CREATE OR REPLACE FUNCTION data_of(_id integer)
RETURNS TABLE (datahora timestamp, col2 text, col3 text)
LANGUAGE plpgsql AS
$func$
DECLARE
_sensors text := 'col1::text, col2::text'; -- cast each col to text
_type text := 'foo';
BEGIN
RETURN QUERY EXECUTE '
SELECT datahora, ' || _sensors || '
FROM ' || quote_ident(_type) || '
WHERE id = $1
ORDER BY datahora'
USING _id;
END
$func$;
Variablerna _sensors
och _type
kan vara indataparametrar istället.
Notera RETURNS TABLE
klausul.
Notera användningen av RETURN QUERY EXECUTE
. Det är ett av de mer eleganta sätten att returnera rader från en dynamisk fråga.
Jag använder ett namn för funktionsparametern, bara för att göra USING
sats av RETURN QUERY EXECUTE
mindre förvirrande. $1
i SQL-strängen hänvisar inte till funktionsparametern utan till värdet som skickas med USING
klausul. (Båda råkar vara $1
i deras respektive omfattning i detta enkla exempel.)
Notera exempelvärdet för _sensors
:varje kolumn casts till typ text
.
Den här typen av kod är mycket sårbar för SQL-injektion . Jag använder quote_ident()
för att skydda sig mot det. Slår ihop ett par kolumnnamn i variabeln _sensors
förhindrar användningen av quote_ident()
(och är vanligtvis en dålig idé!). Se till att inga dåliga saker kan finnas där på något annat sätt, till exempel genom att individuellt köra kolumnnamnen genom quote_ident()
istället. En VARIADIC
parameter kommer att tänka på ...
Enklare sedan PostgreSQL 9.1
Med version 9.1 eller senare kan du använda format()
för att ytterligare förenkla:
RETURN QUERY EXECUTE format('
SELECT datahora, %s -- identifier passed as unescaped string
FROM %I -- assuming the name is provided by user
WHERE id = $1
ORDER BY datahora'
,_sensors, _type)
USING _id;
Återigen, individuella kolumnnamn kunde escapes korrekt och skulle vara det rena sättet.
Variabelt antal kolumner som delar samma typ
Efter att din fråga har uppdaterats ser det ut som att din returtyp har
- ett variabelt nummer av kolumner
- men alla kolumner av samma typ
double precision
(aliasfloat8
)
Använd en ARRAY
skriv i det här fallet för att kapsla ett variabelt antal värden. Dessutom returnerar jag en array med kolumnnamn:
CREATE OR REPLACE FUNCTION data_of(_id integer)
RETURNS TABLE (datahora timestamp, names text[], values float8[])
LANGUAGE plpgsql AS
$func$
DECLARE
_sensors text := 'col1, col2, col3'; -- plain list of column names
_type text := 'foo';
BEGIN
RETURN QUERY EXECUTE format('
SELECT datahora
, string_to_array($1) -- AS names
, ARRAY[%s] -- AS values
FROM %s
WHERE id = $2
ORDER BY datahora'
, _sensors, _type)
USING _sensors, _id;
END
$func$;
Olika kompletta tabelltyper
För att faktiskt returnera alla kolumner i en tabell , det finns en enkel, kraftfull lösning som använder en polymorf typ :
CREATE OR REPLACE FUNCTION data_of(_tbl_type anyelement, _id int)
RETURNS SETOF anyelement
LANGUAGE plpgsql AS
$func$
BEGIN
RETURN QUERY EXECUTE format('
SELECT *
FROM %s -- pg_typeof returns regtype, quoted automatically
WHERE id = $1
ORDER BY datahora'
, pg_typeof(_tbl_type))
USING _id;
END
$func$;
Ring (viktigt!):
SELECT * FROM data_of(NULL::pcdmet, 17);
Ersätt pcdmet
i samtalet med något annat tabellnamn.
Hur fungerar det här?
anyelement
är en pseudodatatyp, en polymorf typ, en platshållare för alla icke-arraydatatyper. Alla förekomster av anyelement
i funktionen utvärdera till samma typ som angavs vid körning. Genom att ange ett värde av en definierad typ som argument till funktionen, definierar vi implicit returtypen.
PostgreSQL definierar automatiskt en radtyp (en sammansatt datatyp) för varje skapad tabell, så det finns en väldefinierad typ för varje tabell. Detta inkluderar tillfälliga tabeller, vilket är bekvämt för ad hoc-användning.
Vilken typ som helst kan vara NULL
. Lämna in en NULL
värde, cast till tabelltypen:NULL::pcdmet
.
Nu returnerar funktionen en väldefinierad radtyp och vi kan använda SELECT * FROM data_of()
för att dekomponera raden och få enskilda kolumner.
pg_typeof(_tbl_type)
returnerar namnet på tabellen som objektidentifierare typ regtype
. När den konverteras automatiskt till text
, identifierare är automatiskt dubbla citattecken och schemakvalificerade vid behov, försvara sig mot SQL-injektion automatiskt. Detta kan till och med hantera schemakvalificerade tabellnamn där quote_ident()
skulle misslyckas. Se:
- Tabellnamn som en PostgreSQL-funktionsparameter