Dynamické SQL a RETURN
typ
Chcete spustit dynamické SQL . V plpgsql je to v principu jednoduché s pomocí EXECUTE
. nepotřebujete kurzor. Ve skutečnosti je vám většinou lépe bez explicitních kurzorů.
Problém, na který narazíte:chcete vrátit záznamy dosud nedefinovaného typu . Funkce musí deklarovat svůj návratový typ v RETURNS
klauzule (nebo s OUT
nebo INOUT
parametry). Ve vašem případě byste se museli vrátit k anonymním záznamům, protože číslo , jména a typy vrácených sloupců se liší. Jako:
CREATE FUNCTION data_of(integer)
RETURNS SETOF record AS ...
To však není nijak zvlášť užitečné. S každým voláním musíte poskytnout seznam definic sloupců. Jako:
SELECT * FROM data_of(17)
AS foo (colum_name1 integer
, colum_name2 text
, colum_name3 real);
Ale jak byste to vůbec udělali, když předem neznáte sloupce?
Mohli byste použít méně strukturované datové typy dokumentů, jako je json
, jsonb
, hstore
nebo xml
. Viz:
- Jak uložit tabulku dat do databáze?
Ale pro účely této otázky předpokládejme, že chcete co nejvíce vrátit jednotlivé, správně zadané a pojmenované sloupce.
Jednoduché řešení s pevným typem návratu
Sloupec datahora
Zdá se, že je to dané, budu předpokládat datový typ timestamp
a že vždy existují dva další sloupce s různým názvem a typem dat.
Jména opustíme ve prospěch obecných jmen v návratovém typu.
Typy také opustíme a přeneseme vše na text
od každého datový typ lze přetypovat na 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$;
Proměnné _sensors
a _type
místo toho mohou být vstupní parametry.
Všimněte si RETURNS TABLE
doložka.
Všimněte si použití RETURN QUERY EXECUTE
. To je jeden z elegantnějších způsobů, jak vrátit řádky z dynamického dotazu.
Používám název pro parametr funkce, abych vytvořil USING
klauzule RETURN QUERY EXECUTE
méně matoucí. $1
v řetězci SQL neodkazuje na parametr funkce, ale na hodnotu předávanou pomocí USING
doložka. (Oba jsou náhodou $1
v jejich příslušném rozsahu v tomto jednoduchém příkladu.)
Všimněte si vzorové hodnoty pro _sensors
:každý sloupec je přetypován do typu text
.
Tento druh kódu je velmi zranitelný vůči vložení SQL . Používám quote_ident()
chránit před tím. Shrnutí několika názvů sloupců v proměnné _sensors
zabraňuje použití quote_ident()
(a obvykle je to špatný nápad!). Zajistěte, aby se tam žádné špatné věci nemohly dostat jiným způsobem, například individuálním spuštěním názvů sloupců pomocí quote_ident()
namísto. A VARIADIC
parametr mě napadá ...
Jednodušší od PostgreSQL 9.1
S verzí 9.1 nebo novější můžete použít format()
pro další zjednodušení:
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;
Opět platí, že názvy jednotlivých sloupců by mohly být správně escapovány a byl by to čistý způsob.
Proměnný počet sloupců sdílejících stejný typ
Po aktualizaci vaší otázky to vypadá, že váš typ návratu má
- proměnná číslo sloupců
- ale všechny sloupce stejného typu
double precision
(jinakfloat8
)
Použijte ARRAY
zadejte v tomto případě vnořte proměnný počet hodnot. Navíc vracím pole s názvy sloupců:
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$;
Různé kompletní typy tabulek
Chcete-li skutečně vrátit všechny sloupce tabulky , existuje jednoduché a výkonné řešení využívající polymorfní 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$;
Zavolejte (důležité!):
SELECT * FROM data_of(NULL::pcdmet, 17);
Nahraďte pcdmet
ve volání s jakýmkoli jiným názvem tabulky.
Jak to funguje?
anyelement
je pseudo datový typ, polymorfní typ, zástupný symbol pro jakýkoli datový typ bez pole. Všechny výskyty anyelement
ve funkci vyhodnotit na stejný typ poskytnutý za běhu. Dodáním hodnoty definovaného typu jako argumentu funkci implicitně definujeme návratový typ.
PostgreSQL automaticky definuje typ řádku (složený datový typ) pro každou vytvořenou tabulku, takže pro každou tabulku existuje dobře definovaný typ. To zahrnuje dočasné tabulky, což je vhodné pro použití ad-hoc.
Jakýkoli typ může být NULL
. Odevzdejte NULL
hodnotu, přetypovat do tabulky typu:NULL::pcdmet
.
Nyní funkce vrací dobře definovaný typ řádku a můžeme použít SELECT * FROM data_of()
rozložit řádek a získat jednotlivé sloupce.
pg_typeof(_tbl_type)
vrátí název tabulky jako typ identifikátoru objektu regtype
. Po automatickém převedení na text
, jsou identifikátory automaticky uváděny do dvojitých uvozovek a jsou kvalifikovány podle schématu v případě potřeby automatická obrana proti SQL injection. To se dokonce může vypořádat s názvy tabulek kvalifikovanými pro schéma, kde quote_ident()
by selhal. Viz:
- Název tabulky jako parametr funkce PostgreSQL