sql >> Base de Datos >  >> RDS >> PostgreSQL

Pasar nombres de columna dinámicamente para una variable de registro en PostgreSQL

Trabajando con esta mesa ficticia

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

Primero, simplifiqué y desinfecté tu ejemplo:

  • Se eliminó algo de ruido que es irrelevante para la pregunta.

  • RETURNS SETOF void apenas tiene sentido. Yo uso RETURNS void en su lugar.

  • Yo uso text en lugar de character varying , solo por el bien de la simplicidad.

  • Cuando usa SQL dinámico, tiene para protegerme contra la inyección de SQL, uso format() con %I en este caso. Hay otras formas.

El problema básico es que SQL es muy rígido con tipos e identificadores. Estás operando con tabla dinámica nombre así como con nombre de campo dinámico de un registro - un anónimo grabar en tu ejemplo original. Pl/pgSQL no está bien equipado para lidiar con esto. Postgres no sabe lo que hay dentro un registro anónimo. Solo después de asignar el registro a un tipo conocido ¿puede hacer referencia a campos individuales?
Aquí hay una pregunta estrechamente relacionada, tratando de establecer un campo de un registro con nombre dinámico:
Cómo establecer el valor de un campo variable compuesto usando SQL dinámico

Función 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;

Llamar:

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

La parte comentada generaría una excepción:

no se pudo identificar la columna "my_num" en el tipo de datos de registro:SELECT * fromgetrowdata(1,'foo')

hstore

Necesitas instalar el módulo adicional hstore para esto. Una vez por base de datos con:

CREATE EXTENSION hstore;

Entonces todo podría funcionar así:

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;

Llamar:

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

Tipo polimórfico

Alternativa sin instalar módulos adicionales.

Dado que selecciona una fila completa en su variable de registro, hay un tipo bien definido por definición. úsalo La palabra clave es 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;

Llamar:

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

-> violín SQL

  • Yo (ab-) uso el parámetro de entrada _tbl para tres propósitos aquí:

    • Proporciona el tipo bien definido del registro
    • Proporciona el nombre de la tabla, automáticamente calificado por esquema
    • Sirve como variable.
  • Más explicación en esta respuesta relacionada (último capítulo):
    Refactorizar una función PL/pgSQL para devolver el resultado de varias consultas SELECT