Dynamisk SQL og RETURN
type
Du ønsker at udføre dynamisk SQL . I princippet er det enkelt i plpgsql ved hjælp af EXECUTE
. Du behøver ikke behøve en markør. Faktisk er du det meste af tiden bedre stillet uden eksplicitte markører.
Det problem, du støder på:du vil returnere poster af endnu udefineret type . En funktion skal erklære sin returtype i RETURNS
klausul (eller med OUT
eller INOUT
parametre). I dit tilfælde ville du skulle falde tilbage til anonyme optegnelser, fordi nummer , navne og typer af returnerede kolonner varierer. Ligesom:
CREATE FUNCTION data_of(integer)
RETURNS SETOF record AS ...
Dette er dog ikke særlig nyttigt. Du skal angive en kolonnedefinitionsliste med hvert opkald. Ligesom:
SELECT * FROM data_of(17)
AS foo (colum_name1 integer
, colum_name2 text
, colum_name3 real);
Men hvordan ville du overhovedet gøre dette, når du ikke kender kolonnerne på forhånd?
Du kunne bruge mindre strukturerede dokumentdatatyper som json
, jsonb
, hstore
eller xml
. Se:
- Hvordan gemmer man en datatabel i databasen?
Men med henblik på dette spørgsmål, lad os antage, at du ønsker at returnere individuelle, korrekt indtastede og navngivne kolonner så meget som muligt.
Simpel løsning med fast returtype
Kolonnen datahora
synes at være en given, jeg antager datatypen timestamp
og at der altid er to kolonner mere med varierende navn og datatype.
Navne vi opgiver til fordel for generiske navne i returtypen.
Typer vi opgiver også og sender alt til text
siden hver datatypen kan castes til 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$;
Variablerne _sensors
og _type
kunne være inputparametre i stedet for.
Bemærk RETURNS TABLE
klausul.
Bemærk brugen af RETURN QUERY EXECUTE
. Det er en af de mere elegante måder at returnere rækker fra en dynamisk forespørgsel på.
Jeg bruger et navn til funktionsparameteren, bare for at lave USING
klausul af RETURN QUERY EXECUTE
mindre forvirrende. $1
i SQL-strengen henviser ikke til funktionsparameteren, men til den værdi, der sendes med USING
klausul. (Begge er tilfældigvis $1
i deres respektive omfang i dette simple eksempel.)
Bemærk eksempelværdien for _sensors
:hver kolonne castes til at skrive text
.
Denne type kode er meget sårbar over for SQL-injektion . Jeg bruger quote_ident()
at beskytte mod det. Slår et par kolonnenavne sammen i variablen _sensors
forhindrer brugen af quote_ident()
(og er typisk en dårlig idé!). Sørg for, at der ikke kan være dårlige ting derinde på en anden måde, for eksempel ved individuelt at køre kolonnenavnene gennem quote_ident()
i stedet. En VARIADIC
parameter kommer til at tænke på ...
Enklere siden PostgreSQL 9.1
Med version 9.1 eller nyere kan du bruge format()
for yderligere at forenkle:
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;
Igen kunne individuelle kolonnenavne undslippes korrekt og ville være den rene måde.
Variabelt antal kolonner, der deler samme type
Efter dit spørgsmål er opdateret ser det ud til, at din returtype har
- et variabelt tal af kolonner
- men alle kolonner af samme type
double precision
(aliasfloat8
)
Brug en ARRAY
skriv i dette tilfælde for at indlejre et variabelt antal værdier. Derudover returnerer jeg et array med kolonnenavne:
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$;
Forskellige komplette tabeltyper
For faktisk at returnere alle kolonner i en tabel , er der en enkel, kraftfuld løsning, der bruger en polymorf type :
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 (vigtigt!):
SELECT * FROM data_of(NULL::pcdmet, 17);
Erstat pcdmet
i opkaldet med et hvilket som helst andet tabelnavn.
Hvordan fungerer dette?
anyelement
er en pseudodatatype, en polymorf type, en pladsholder for enhver ikke-array-datatype. Alle forekomster af anyelement
i funktionen evaluere til den samme type, der blev angivet ved kørsel. Ved at angive en værdi af en defineret type som argument til funktionen, definerer vi implicit returtypen.
PostgreSQL definerer automatisk en rækketype (en sammensat datatype) for hver tabel oprettet, så der er en veldefineret type for hver tabel. Dette inkluderer midlertidige tabeller, hvilket er praktisk til ad hoc-brug.
Enhver type kan være NULL
. Aflever en NULL
værdi, cast til tabeltypen:NULL::pcdmet
.
Nu returnerer funktionen en veldefineret rækketype, og vi kan bruge SELECT * FROM data_of()
for at dekomponere rækken og få individuelle kolonner.
pg_typeof(_tbl_type)
returnerer navnet på tabellen som objektidentifikator type regtype
. Når den automatisk konverteres til text
, er identifikatorer automatisk dobbelte anførselstegn og skema-kvalificerede om nødvendigt forsvar mod SQL-injektion automatisk. Dette kan endda håndtere skema-kvalificerede tabelnavne, hvor quote_ident()
ville mislykkes. Se:
- Tabelnavn som en PostgreSQL-funktionsparameter