Trabalhando com esta mesa fictícia
CREATE TEMP TABLE foo (id int, my_num numeric);
INSERT INTO foo VALUES (1, 12.34)
Primeiro, simplifiquei e higienizei seu exemplo:
-
Removido algum ruído que é irrelevante para a pergunta.
-
RETURNS SETOF void
dificilmente faz sentido. Eu usoRETURNS void
em vez de.
-
Eu usotext
em vez decharacter varying
, apenas por uma questão de simplicidade.
-
Ao usar SQL dinâmico, você tem para proteger contra injeção de SQL, eu usoformat()
com%I
nesse caso. Existem outras maneiras.
O problema básico é que o SQL é muito rígido com tipos e identificadores. Você está operando com tabela dinâmica nome, bem como com nome de campo dinâmico de um registro - um anônimo registro no seu exemplo original. Pl/pgSQL não está bem equipado para lidar com isso. Postgres não sabe o que está dentro um registro anônimo. Somente depois de atribuir o registro a um tipo bem conhecido você pode fazer referência a campos individuais.
Aqui está uma pergunta intimamente relacionada, tentando definir um campo de um registro com nome dinâmico:
Como definir valor de campo de variável composta usando SQL dinâmico
Função básica
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;
Ligar:
SELECT * from getrowdata1('foo', 1);
A parte comentada levantaria uma exceção:
não foi possível identificar a coluna "my_num" no tipo de dados do registro:SELECT * fromgetrowdata(1,'foo')
hstore
Você precisa instalar o módulo adicional hstore por esta. Uma vez por banco de dados com:
CREATE EXTENSION hstore;
Então tudo poderia funcionar assim:
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;
Ligar:
SELECT * from getrowdata2('foo', 1);
Tipo polimórfico
Alternativa sem instalar módulos adicionais.
Como você seleciona uma linha inteira em sua variável de registro, há um tipo bem definido para isso por definição. Use-o. A palavra-chave é tipos polimórficos .
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;
Ligar:
SELECT * from getrowdata3(NULL::foo, 1);
-> SQLfiddle
-
Eu (ab-) uso o parâmetro de entrada_tbl
por três propósitos aqui:
- Fornece o tipo bem definido do registro
- Fornece o nome da tabela, automaticamente qualificado pelo esquema
- Serve como variável.
-
Mais explicações nesta resposta relacionada (último capítulo):
Refatorar uma função PL/pgSQL para retornar a saída de várias consultas SELECT