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