PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Passando nomes de coluna dinamicamente para uma variável de registro no PostgreSQL


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 uso RETURNS void em vez de.

  • Eu uso text em vez de character varying , apenas por uma questão de simplicidade.

  • Ao usar SQL dinâmico, você tem para proteger contra injeção de SQL, eu uso format() 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