PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Passare i nomi di colonna in modo dinamico per una variabile record in PostgreSQL

Lavorare con questa tabella fittizia

CREATE TEMP TABLE foo (id int, my_num numeric);
INSERT INTO foo VALUES (1, 12.34)

Per prima cosa, ho semplificato e sanificato il tuo esempio:

  • Rimosso un po' di rumore irrilevante per la domanda.

  • RETURNS SETOF void ha poco senso. Uso RETURNS void invece.

  • Uso text invece di character varying , solo per semplicità.

  • Quando usi l'SQL dinamico, hai per proteggermi dall'iniezione SQL, utilizzo format() con %I in questo caso. Ci sono altri modi.

Il problema di base è che SQL è molto rigido con tipi e identificatori. Stai operando con tabella dinamica nome e con nome campo dinamico di un record - un anonimo registrare nel tuo esempio originale. Pl/pgSQL non è ben attrezzato per affrontare questo. Postgres non sa cosa c'è dentro un record anonimo. Solo dopo aver assegnato il record a un tipo noto puoi fare riferimento a singoli campi.
Ecco una domanda strettamente correlata, cercando di impostare un campo di un record con nome dinamico:
Come impostare il valore di un campo variabile composto utilizzando SQL dinamico

Funzione di base

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;

Chiama:

SELECT * from getrowdata1('foo', 1);

La parte commentata solleverebbe un'eccezione:

impossibile identificare la colonna "my_num" nel tipo di dati del record:SELECT * fromgetrowdata(1,'foo')

hstore

Devi installare il modulo aggiuntivo hstore per questo. Una volta per database con:

CREATE EXTENSION hstore;

Allora tutto potrebbe funzionare così:

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;

Chiama:

SELECT * from getrowdata2('foo', 1);

Tipo polimorfico

Alternativa senza installare moduli aggiuntivi.

Poiché selezioni un'intera riga nella variabile del record, esiste un tipo ben definito per esso per definizione. Usalo. La parola chiave è tipi polimorfici .

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;

Chiama:

SELECT * from getrowdata3(NULL::foo, 1);

-> SQLfiddle

  • Io (ab-) utilizzo il parametro di input _tbl per tre scopi qui:

    • Fornisce il tipo ben definito del record
    • Fornisce il nome della tabella, qualificato automaticamente per schema
    • Serve come variabile.
  • Maggiori spiegazioni in questa risposta correlata (ultimo capitolo):
    Refactoring di una funzione PL/pgSQL per restituire l'output di varie query SELECT