Динамичен SQL и RETURN
тип
Искате да изпълните динамичен SQL . По принцип това е просто в plpgsql с помощта на EXECUTE
. Нямате нужни курсор. Всъщност през повечето време е по-добре без изрични курсори.
Проблемът, с който се сблъсквате:искате да връщате записи от все още недефиниран тип . Функцията трябва да декларира своя тип връщане в RETURNS
клауза (или с OUT
или INOUT
параметри). Във вашия случай ще трябва да се върнете към анонимните записи, защото номер , имена и типове на върнатите колони варират. Като:
CREATE FUNCTION data_of(integer)
RETURNS SETOF record AS ...
Това обаче не е особено полезно. Трябва да предоставите списък с дефиниции на колони с всяко повикване. Като:
SELECT * FROM data_of(17)
AS foo (colum_name1 integer
, colum_name2 text
, colum_name3 real);
Но как бихте направили това, когато не знаете колоните предварително?
Бихте могли да използвате по-малко структурирани типове данни за документи като json
, jsonb
, hstore
или xml
. Вижте:
- Как да съхранявам таблица с данни в база данни?
Но за целите на този въпрос нека приемем, че искате да върнете отделни, правилно въведени и именувани колони, доколкото е възможно.
Просто решение с фиксиран тип връщане
Колоната datahora
изглежда като даденост, ще приема тип данни timestamp
и че винаги има още две колони с различно име и тип данни.
Имена ще се откажем в полза на общите имена в връщания тип.
Типове ние също ще изоставим и ще прехвърлим всичко към text
от всеки типът данни може да бъде прехвърлен към 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$;
Променливите _sensors
и _type
вместо това могат да бъдат входни параметри.
Обърнете внимание на RETURNS TABLE
клауза.
Обърнете внимание на използването на RETURN QUERY EXECUTE
. Това е един от по-елегантните начини за връщане на редове от динамична заявка.
Използвам име за параметъра на функцията, само за да направя USING
клауза на RETURN QUERY EXECUTE
по-малко объркващо. $1
в SQL низ не се отнася до параметъра на функцията, а до стойността, предадена с USING
клауза. (И двете са $1
в съответния им обхват в този прост пример.)
Обърнете внимание на примерната стойност за _sensors
:всяка колона се прехвърля към тип text
.
Този вид код е много уязвим към SQL инжекция . Използвам quote_ident()
за да се предпази от него. Обединяване на няколко имена на колони в променливата _sensors
предотвратява използването на quote_ident()
(и обикновено е лоша идея!). Уверете се, че там не може да има лоши неща по друг начин, например като стартирате поотделно имената на колоните чрез quote_ident()
вместо. A VARIADIC
параметър идва на ум ...
По-просто от PostgreSQL 9.1
С версия 9.1 или по-нова можете да използвате format()
за допълнително опростяване:
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;
Отново, имената на отделните колони могат да бъдат екранирани правилно и това би било чистият начин.
Променлив брой колони, споделящи един и същи тип
След актуализиране на въпроса ви изглежда, че типът ви връщане е
- променлива число от колони
- но всички колони от един и същи тип
double precision
(псевдонимfloat8
)
Използвайте ARRAY
въведете в този случай, за да вложите променлив брой стойности. Освен това връщам масив с имена на колони:
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$;
Различни типове пълни таблици
За действително връщане на всички колони на таблица , има просто, мощно решение, използващо полиморфен тип :
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$;
Обадете се (важно!):
SELECT * FROM data_of(NULL::pcdmet, 17);
Заменете pcdmet
в повикването с всяко друго име на таблица.
Как работи това?
anyelement
е псевдо тип данни, полиморфен тип, заместител за всеки тип данни без масив. Всички поява на anyelement
във функцията се оценява до същия тип, предоставен по време на изпълнение. Чрез предоставяне на стойност от дефиниран тип като аргумент на функцията, ние имплицитно дефинираме връщания тип.
PostgreSQL автоматично дефинира тип ред (композитен тип данни) за всяка създадена таблица, така че има добре дефиниран тип за всяка таблица. Това включва временни таблици, което е удобно за ad hoc използване.
Всеки тип може да бъде NULL
. Подайте NULL
стойност, прехвърлена към типа на таблицата:NULL::pcdmet
.
Сега функцията връща добре дефиниран тип ред и можем да използваме SELECT * FROM data_of()
за разлагане на реда и получаване на отделни колони.
pg_typeof(_tbl_type)
връща името на таблицата като тип идентификатор на обект regtype
. При автоматично преобразуване в text
, идентификаторите са автоматично двойни кавички и квалифицирани по схема ако е необходимо, автоматично се защитава срещу SQL инжекция. Това може дори да се справи с квалифицирани по схема имена на таблици, където quote_ident()
би се провалил. Вижте:
- Име на таблица като параметър на функцията на PostgreSQL