Работа с тази фиктивна таблица
CREATE TEMP TABLE foo (id int, my_num numeric);
INSERT INTO foo VALUES (1, 12.34)
Първо, опростих и почистих вашия пример:
-
Премахнат някои шумове, които не са от значение за въпроса.
-
RETURNS SETOF void
едва ли има смисъл. ИзползвамRETURNS void
вместо това. -
Използвам
text
вместоcharacter varying
, само за простота. -
Когато използвате динамичен SQL, имате за да се предпазя от SQL инжектиране, използвам
format()
с%I
в такъв случай. Има и други начини.
Основният проблем е, че SQL е много твърд с типове и идентификатори. Вие работите с динамична таблица име, както и с име на динамично поле на запис - нанонимен записа в оригиналния си пример. Pl/pgSQL не е добре оборудван да се справи с това. Postgres не знае какво е вътре анонимен запис. Само след като присвоите записа към добре познат тип можете ли да препратите отделни полета.
Ето един тясно свързан въпрос, опитвайки се да зададете поле на запис с динамично име:
Как да зададете стойност на полето за съставна променлива с помощта на динамичен SQL
Основна функция
CREATE OR REPLACE FUNCTION getrowdata1(table_name text, id int)
RETURNS void AS
$func$
DECLARE
srowdata record;
reqfield text := 'my_num'; -- assigning at declaration time for convenience
value numeric;
BEGIN
RAISE NOTICE 'id: %', id;
EXECUTE format('SELECT * FROM %I WHERE id = $1', table_name)
USING id
INTO srowdata;
RAISE NOTICE 'srowdata: %', srowdata;
RAISE NOTICE 'srowdatadata.my_num: %', srowdata.my_num;
/* This does not work, even with dynamic SQL
EXECUTE format('SELECT ($1).%I', reqfield)
USING srowdata
INTO value;
RAISE NOTICE 'value: %', value;
*/
END
$func$ LANGUAGE plpgsql;
Обадете се:
SELECT * from getrowdata1('foo', 1);
Коментираната част би предизвикала изключение:
не можа да идентифицира колона "my_num" в тип данни на записа:SELECT * fromgetrowdata(1,'foo')
hstore
Трябва да инсталирате допълнителния модул hstore за това. Веднъж на база данни с:
CREATE EXTENSION hstore;
Тогава всичко може да работи така:
CREATE OR REPLACE FUNCTION getrowdata2(table_name text, id int)
RETURNS void AS
$func$
DECLARE
hstoredata hstore;
reqfield text := 'my_num';
value numeric;
BEGIN
RAISE NOTICE 'id: %', id;
EXECUTE format('SELECT hstore(t) FROM %I t WHERE id = $1', table_name)
USING id
INTO hstoredata;
RAISE NOTICE 'hstoredata: %', hstoredata;
RAISE NOTICE 'hstoredata.my_num: %', hstoredata -> 'my_num';
value := hstoredata -> reqfield;
RAISE NOTICE 'value: %', value;
END
$func$ LANGUAGE plpgsql;
Обадете се:
SELECT * from getrowdata2('foo', 1);
Полиморфен тип
Алтернатива без инсталиране на допълнителни модули.
Тъй като избирате цял ред във вашата променлива за запис, има добре дефиниран тип за него по дефиниция. Използваи го. Ключовата дума е полиморфни типове .
CREATE OR REPLACE FUNCTION getrowdata3(_tbl anyelement, id int)
RETURNS void AS
$func$
DECLARE
reqfield text := 'my_num';
value numeric;
BEGIN
RAISE NOTICE 'id: %', id;
EXECUTE format('SELECT * FROM %s WHERE id = $1', pg_typeof(_tbl))
USING id
INTO _tbl;
RAISE NOTICE '_tbl: %', _tbl;
RAISE NOTICE '_tbl.my_num: %', _tbl.my_num;
EXECUTE 'SELECT ($1).' || reqfield -- requfield must be SQLi-safe or escape
USING _tbl
INTO value;
RAISE NOTICE 'value: %', value;
END
$func$ LANGUAGE plpgsql;
Обадете се:
SELECT * from getrowdata3(NULL::foo, 1);
-> SQLfiddle
-
(ab-) използвам входния параметър
_tbl
за три цели тук:- Осигурява добре дефинирания тип на записа
- Предоставя име на таблицата, автоматично квалифицирани по схема
- Служи като променлива.
-
Повече обяснение в този свързан отговор (последна глава):
Рефакторирайте функция PL/pgSQL, за да върнете изхода от различни SELECT заявки