PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Passer dynamiquement des noms de colonne pour une variable d'enregistrement dans PostgreSQL

Travailler avec cette table factice

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

Tout d'abord, j'ai simplifié et aseptisé votre exemple :

  • Suppression de certains bruits sans rapport avec la question.

  • RETURNS SETOF void n'a guère de sens. J'utilise RETURNS void à la place.

  • J'utilise text au lieu de character varying , juste par souci de simplicité.

  • Lorsque vous utilisez SQL dynamique, vous avez pour me prémunir contre l'injection SQL, j'utilise format() avec %I dans ce cas. Il existe d'autres moyens.

Le problème de base est que SQL est très rigide avec les types et les identifiants. Vous travaillez avec tableau dynamique nom ainsi qu'avec nom de champ dynamique d'un enregistrement - un anonyme enregistrer dans votre exemple original. Pl/pgSQL n'est pas bien équipé pour gérer cela. Postgres ne sait pas ce qu'il y a à l'intérieur un enregistrement anonyme. Seulement après avoir attribué l'enregistrement à un type bien connu pouvez-vous référencer des champs individuels.
Voici une question étroitement liée, essayant de définir un champ d'un enregistrement avec un nom dynamique :
Comment définir la valeur d'un champ de variable composite à l'aide de SQL dynamique

Fonction de 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;

Appel :

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

La partie commentée lèverait une exception :

impossible d'identifier la colonne "my_num" dans le type de données d'enregistrement :SELECT * fromgetrowdata(1,'foo')

hstore

Vous devez installer le module supplémentaire hstore pour ça. Une fois par base de données avec :

CREATE EXTENSION hstore;

Alors tout pourrait fonctionner comme ceci :

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;

Appel :

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

Type polymorphe

Alternative sans installer de modules supplémentaires.

Puisque vous sélectionnez une ligne entière dans votre variable d'enregistrement, il existe un type bien défini pour cela par définition. Utilise le. Le mot clé est types polymorphes .

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;

Appel :

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

-> SQLfiddle

  • J'utilise (ab-)le paramètre d'entrée _tbl pour trois fins ici :

    • Fournit le type bien défini du dossier
    • Fournit le nom de la table, automatiquement qualifié par le schéma
    • Sert de variable.
  • Plus d'explications dans cette réponse connexe (dernier chapitre) :
    Refactoriser une fonction PL/pgSQL pour renvoyer la sortie de diverses requêtes SELECT