Ответ 1
Динамический SQL и RETURN
тип
(я сохранил лучшее для последнего, продолжаю читать!)
Вы хотите выполнить динамический SQL. В принципе, это просто в plpgsql с помощью EXECUTE
. Вам не нужен курсор - на самом деле, большую часть времени вам лучше без явных курсоров.
Найдите примеры SO с поиском.
Проблема, с которой вы столкнулись: вы хотите вернуть записи еще undefined типа. Функция должна объявлять возвращаемый тип с помощью 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) 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$ LANGUAGE plpgsql;
Как это работает?
-
Вместо переменных
_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()
. Параметр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
)
Как мы должны определить тип RETURN
функции, я прибегаю к типу ARRAY
в этом случае, который может содержать переменное число значений. Кроме того, я возвращаю массив с именами столбцов, чтобы вы могли также анализировать имена из результата:
CREATE OR REPLACE FUNCTION data_of(_id integer)
RETURNS TABLE (datahora timestamp, names text[], values float8[] ) 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$ LANGUAGE plpgsql;
Различные полные типы таблиц
Если вы действительно пытаетесь вернуть все столбцы таблицы (например, одну из таблиц на связанной странице, затем используйте это простое, очень мощное решение с полиморфным типом:
CREATE OR REPLACE FUNCTION data_of(_tbl_type anyelement, _id int)
RETURNS SETOF anyelement 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$ LANGUAGE plpgsql;
Вызов:
SELECT * FROM data_of(NULL::pcdmet, 17);
Замените pcdmet
в вызове любым другим именем таблицы.
Как это работает?
-
anyelement
- это псевдо-тип данных, полиморфный тип, местозаполнитель для любого типа данных, не относящихся к массиву. Все вхожденияanyelement
в функции оцениваются одним и тем же типом, указанными во время выполнения. Предоставляя значение определенного типа в качестве аргумента функции, мы неявно определяем тип возврата. -
PostgreSQL автоматически определяет тип строки (составной тип данных) для каждой созданной таблицы, поэтому для каждой таблицы есть четко определенный тип. Это включает временные таблицы, которые удобны для ad-hoc-использования.
-
Любой тип может быть
NULL
. Таким образом, мы передаем значениеNULL
, отбрасываем тип таблицы. -
Теперь функция возвращает четко определенный тип строки, и мы можем использовать
SELECT * FROM data_of(...)
для разложения строки и получения отдельных столбцов. -
pg_typeof(_tbl_type)
возвращает имя таблицы как тип идентификатора объектаregtype
. При автоматическом преобразовании вtext
идентификаторы автоматически дублируются и имеют сертификат, если необходимо. Поэтому SQL-инъекция не является возможной. Это может даже иметь дело с табличными именами гдеquote_ident()
не работает.