Использовать вывод текста из функции в качестве нового запроса
Продолжая предыдущий случай, которому помогали @Erwin Brandstetter и @Craig Ringer, я исправил свой код следующим образом. Обратите внимание, что моя функция myresult()
выходы сейчас text
и не таблица (как, как было указано в предыдущем случае, нет смысла выводить табличный объект, поскольку нам нужно было бы определить все его столбцы впереди, что в принципе не поддается цели):
CREATE OR REPLACE FUNCTION myresult(mytable text, myprefix text)
RETURNS text AS
$func$
DECLARE
myoneliner text;
BEGIN
SELECT INTO myoneliner
'SELECT '
|| string_agg(quote_ident(column_name::text), ',' ORDER BY column_name)
|| ' FROM ' || quote_ident(mytable)
FROM information_schema.columns
WHERE table_name = mytable
AND column_name LIKE myprefix||'%'
AND table_schema = 'public'; -- schema name; might be another param
RAISE NOTICE 'My additional text: %', myoneliner;
RETURN myoneliner;
END
$func$ LANGUAGE plpgsql;
Вызов:
select myresult('dkj_p_k27ac','enri');
После запуска описанной выше процедуры я получаю текстовую строку, которая в основном является запросом. Я буду называть это далее "oneliner-output", просто для простоты.
'Oneline-output' выглядит следующим образом (я просто копирую / вставляю его из одной ячейки вывода, в которую я попал):
"SELECT enrich_d_dkj_p_k27ac,enrich_lr_dkj_p_k27ac,enrich_r_dkj_p_k27ac FROM dkj_p_k27ac"
- Обратите внимание, что двойные кавычки с обеих сторон утверждения были частью
myresult()
выход. Я не добавил их.
Теперь я гораздо лучше понимаю проблематичную идею мышления, заключающуюся в создании единой функции, которая одновременно создала бы "выходной поток" и выполнила бы его. Я могу скопировать / вставить 'oneliner-output' в новое окно запроса Postgres и выполнить его как обычный запрос, получая нужные столбцы и строки в моем окне вывода данных.
Я хотел бы, однако, автоматизировать этот шаг, чтобы избежать шага копирования / вставки. Есть ли способ в Postgres использовать text
вывод ('oneliner-output'), который я получаю от myresult()
функция, и выполнить это? Можно ли создать вторую функцию, которая получит вывод myresult()
и использовать его для выполнения запроса?
Вдобавок к этому, хотя я знаю, что следующий сценарий (здесь ниже) работает и фактически выводит именно нужные столбцы и строки:
-- DEALLOCATE stmt1; -- use this line after the first time 'stmt1' was created
prepare stmt1 as SELECT enrich_d_dkj_p_k27ac,enrich_lr_dkj_p_k27ac,enrich_r_dkj_p_k27ac FROM dkj_p_k27ac;
execute stmt1;
Я думал, что, возможно, что-то вроде следующего скриптинга может сработать после правильной настройки? Не уверен, как, хотя.
prepare stmt1 as THE_OUTPUT_OF_myresult(); execute stmt1;
Попытка с рекурсором
CREATE OR REPLACE FUNCTION show_mytable(ref refcursor) RETURNS refcursor AS $$
BEGIN
OPEN ref FOR SELECT enrich_d_dkj_p_k27ac,enrich_lr_dkj_p_k27ac,enrich_r_dkj_p_k27ac FROM dkj_p_k27ac; -- Open a cursor
RETURN ref; -- Return the cursor to the caller
END;
$$ LANGUAGE plpgsql;
Вызов:
BEGIN;
SELECT show_mytable('roy');
FETCH ALL IN "roy";
Эта процедура на самом деле работает и выплевывает нужные столбцы и строки, и еще раз, я должен предоставить точный оператор SELECT.
Я в основном хотел бы иметь возможность и вместо этого предоставить в качестве выходных данных моего myresult()
функция. Что-то вроде этого:
CREATE OR REPLACE FUNCTION show_mytable(ref refcursor) RETURNS refcursor AS $$
BEGIN
OPEN ref FOR myresult(); -- Open a cursor
RETURN ref; -- Return the cursor to the caller
END;
$$ LANGUAGE plpgsql;
Вызов:
BEGIN;
SELECT show_mytable('roy');
FETCH ALL IN "roy";
2 ответа
Трюк с PREPARE
не работает, так как не принимает * текстовую строку * (значение) как CREATE FUNCTION
делает, но действительное утверждение (код).
Для преобразования данных в исполняемый код необходимо использовать динамический SQL, т.е. EXECUTE
в функции plpgsql или DO
заявление. Это работает без проблем, пока тип возвращаемого значения не зависит от результата первой функции myresult()
, В противном случае вы вернулись, чтобы поймать 22, как указано в моем предыдущем ответе:
Важная часть заключается в том, чтобы как-то объявить тип возвращаемого значения (в данном случае тип строки). Вы можете создать TABLE
, TEMP TABLE
или же TYPE
с целью. Или вы можете использовать подготовленное заявление или рекурсор.
Решение с подготовленным заявлением
Вы были очень близки Недостающим элементом головоломки является подготовка сгенерированного запроса с использованием динамического SQL.
Функция, чтобы подготовить заявление динамически
Создайте эту функцию один раз. Это оптимизированная и безопасная версия вашей функции myresult()
:
CREATE OR REPLACE FUNCTION f_prep_query (_tbl regclass, _prefix text)
RETURNS void AS
$func$
BEGIN
IF EXISTS (SELECT 1 FROM pg_prepared_statements WHERE name = 'stmt_dyn') THEN
DEALLOCATE stmt_dyn;
END IF; -- you my or may not need this safety check
EXECUTE (
SELECT 'PREPARE stmt_dyn AS SELECT '
|| string_agg(quote_ident(attname), ',' ORDER BY attname)
|| ' FROM ' || _tbl
FROM pg_catalog.pg_attribute
WHERE attrelid = _tbl
AND attname LIKE _prefix || '%'
AND attnum > 0
AND NOT attisdropped
);
END
$func$ LANGUAGE plpgsql;
я использую regclass
для параметра имени таблицы _tbl
чтобы сделать его однозначным и безопасным против SQLi. Подробности:
Информационная схема не включает столбец oid системных каталогов, поэтому я переключился на pg_catalog.pg_attribute
вместо information_schema.columns
, Это тоже быстрее. Есть плюсы и минусы для этого:
Если подготовлено заявление с именем stmt_dyn
уже существовал, PREPARE
поднимет исключение. Если это приемлемо, снимите флажок в системном представлении pg_prepared_statements
и следующее DEALLOCATE
,
Более сложные алгоритмы позволяют управлять множеством подготовленных операторов за сеанс, или принимать имя подготовленного оператора в качестве дополнительного параметра, или даже использовать MD5-хэш строки запроса в качестве имени, но это выходит за рамки этого вопроса.
Быть в курсе, что PREPARE
работает за пределами объема транзакций, один раз PREPARE
успешно, подготовленный оператор существует на протяжении всего сеанса. Если транзакция переноса прервана, PREPARE
не влияет. ROLLBACK
не может удалить подготовленные заявления.
Динамическое выполнение запроса
Два запроса, но только один звонок на сервер. И очень эффективный тоже.
SELECT f_prep_query('tbl'::regclass, 'pre'::text);
EXECUTE stmt_dyn;
Проще и намного эффективнее для большинства простых случаев использования, чем создание временной таблицы или курсора и выбор / выборка из этого (что может быть другими вариантами).
Я думаю, что я тоже нашел решение, используя рекурсор.
Я был бы очень рад, если бы вы могли пройти через это, проверить и сказать мне, если вы думаете, что это "кошерный". Честно говоря, я не очень уверен, что я здесь придумал, так как я не очень знаком с синтаксисом. Но я скорее смог синтезировать это, используя различные примеры, которые я нашел в сети. Кажется, это работает для меня. Я был бы очень рад, если бы вы могли сформулировать это решение для меня и для других пользователей - и рассказать, что вы об этом думаете.
Сначала давайте создадим функцию, которая создает динамический SELECT
заявление:
CREATE OR REPLACE FUNCTION myresult2()
RETURNS text AS
$func$
DECLARE
myoneliner text;
mytable text := 'dkj_p_k27ac';
myprefix text := 'enri';
BEGIN
SELECT INTO myoneliner
'SELECT '
|| string_agg(quote_ident(column_name::text), ',' ORDER BY column_name)
|| ' FROM ' || quote_ident(mytable)
FROM information_schema.columns
WHERE table_name = mytable
AND column_name LIKE myprefix||'%'
AND table_schema = 'public'; -- schema name; might be another param
-- RAISE NOTICE 'My additional text: %', myoneliner; -- for debugging
RETURN myoneliner;
END
$func$ LANGUAGE plpgsql;
Теперь давайте создадим вторую функцию, которая может выполнять вывод строки TEXT первой функции. myresult2()
:
CREATE OR REPLACE FUNCTION show_mytable(ref refcursor)
RETURNS refcursor AS
$func$
DECLARE
mydynamicstatment text := myresult2();
BEGIN
OPEN ref FOR EXECUTE mydynamicstatment;
RETURN ref; -- return cursor to the caller
END;
$func$ LANGUAGE plpgsql;
Вызов:
BEGIN;
SELECT show_mytable('roy');
FETCH ALL IN "roy";