Dynamische SQL en RETURN
typ
U wilt dynamische SQL uitvoeren . In principe is dat eenvoudig in plpgsql met behulp van EXECUTE
. Je hebt geen nodig een cursor. In feite ben je meestal beter af zonder expliciete cursors.
Het probleem waar u tegenaan loopt:u wilt records van een nog niet gedefinieerd type retourneren . Een functie moet zijn retourtype declareren in de RETURNS
clausule (of met OUT
of INOUT
parameters). In jouw geval zou je terug moeten vallen op anonieme gegevens, omdat nummer , namen en typen van de geretourneerde kolommen variëren. Vind ik leuk:
CREATE FUNCTION data_of(integer)
RETURNS SETOF record AS ...
Dit is echter niet bijzonder handig. U moet bij elke oproep een kolomdefinitielijst opgeven. Vind ik leuk:
SELECT * FROM data_of(17)
AS foo (colum_name1 integer
, colum_name2 text
, colum_name3 real);
Maar hoe zou u dit zelfs doen als u de kolommen niet van tevoren kent?
U kunt minder gestructureerde documentgegevenstypen gebruiken, zoals json
, jsonb
, hstore
of xml
. Zie:
- Hoe bewaar ik een gegevenstabel in de database?
Maar laten we voor het doel van deze vraag aannemen dat u zoveel mogelijk individuele, correct getypte en benoemde kolommen wilt retourneren.
Eenvoudige oplossing met vast retourtype
De kolom datahora
lijkt een gegeven te zijn, neem ik aan dat gegevenstype timestamp
en dat er altijd nog twee kolommen zijn met verschillende namen en gegevenstypes.
Namen we zullen afzien van generieke namen in het retourtype.
Typen we zullen het ook opgeven en alles naar text
casten sinds elke gegevenstype kan worden gecast naar 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$;
De variabelen _sensors
en _type
kunnen in plaats daarvan invoerparameters zijn.
Let op de RETURNS TABLE
clausule.
Let op het gebruik van RETURN QUERY EXECUTE
. Dat is een van de elegantere manieren om rijen uit een dynamische zoekopdracht te retourneren.
Ik gebruik een naam voor de functieparameter, alleen om de USING
clausule van RETURN QUERY EXECUTE
minder verwarrend. $1
in de SQL-string verwijst niet naar de functieparameter maar naar de waarde doorgegeven met de USING
clausule. (Beide zijn toevallig $1
in hun respectievelijke bereik in dit eenvoudige voorbeeld.)
Let op de voorbeeldwaarde voor _sensors
:elke kolom wordt gecast om text
te typen .
Dit soort code is erg kwetsbaar voor SQL-injectie . Ik gebruik quote_ident()
ertegen te beschermen. Een paar kolomnamen samenvoegen in de variabele _sensors
voorkomt het gebruik van quote_ident()
(en is meestal een slecht idee!). Zorg ervoor dat er op een andere manier geen slechte dingen in kunnen zitten, bijvoorbeeld door de kolomnamen afzonderlijk door quote_ident()
te laten lopen in plaats van. Een VARIADIC
parameter komt in me op ...
Eenvoudiger sinds PostgreSQL 9.1
Met versie 9.1 of hoger kunt u format()
. gebruiken om verder te vereenvoudigen:
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;
Nogmaals, individuele kolomnamen zouden correct kunnen worden ontsnapt en zou de schone manier zijn.
Variabel aantal kolommen met hetzelfde type
Nadat uw vraag is bijgewerkt, lijkt het erop dat uw retourtype
. heeft- een variabel getal kolommen
- maar alle kolommen van hetzelfde type
double precision
(aliasfloat8
)
Gebruik een ARRAY
typ in dit geval om een variabel aantal waarden te nesten. Bovendien retourneer ik een array met kolomnamen:
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$;
Verschillende complete tafeltypes
Om daadwerkelijk alle kolommen van een tabel terug te geven , is er een eenvoudige, krachtige oplossing met een 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$;
Bel (belangrijk!):
SELECT * FROM data_of(NULL::pcdmet, 17);
Vervang pcdmet
in het gesprek met een andere tabelnaam.
Hoe werkt dit?
anyelement
is een pseudo-gegevenstype, een polymorf type, een tijdelijke aanduiding voor elk niet-array-gegevenstype. Alle exemplaren van anyelement
in de functie evalueren naar hetzelfde type dat tijdens runtime is opgegeven. Door een waarde van een gedefinieerd type als argument aan de functie te geven, definiëren we impliciet het retourtype.
PostgreSQL definieert automatisch een rijtype (een samengesteld gegevenstype) voor elke gemaakte tabel, dus er is een goed gedefinieerd type voor elke tabel. Dit omvat tijdelijke tabellen, wat handig is voor ad-hocgebruik.
Elk type kan NULL
zijn . Dien een NULL
in waarde, cast naar het tabeltype:NULL::pcdmet
.
Nu retourneert de functie een goed gedefinieerd rijtype en kunnen we SELECT * FROM data_of()
gebruiken om de rij te ontleden en individuele kolommen te krijgen.
pg_typeof(_tbl_type)
geeft de naam van de tabel terug als object-ID type regtype
. Wanneer automatisch geconverteerd naar text
, ID's worden automatisch dubbele aanhalingstekens en schema-gekwalificeerd indien nodig, automatische verdediging tegen SQL-injectie. Dit kan zelfs omgaan met schema-gekwalificeerde tabelnamen waarbij quote_ident()
zou mislukken. Zie:
- Tabelnaam als een PostgreSQL-functieparameter