PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Dynamisches Übergeben von Spaltennamen für eine Datensatzvariable in PostgreSQL

Arbeiten mit dieser Dummy-Tabelle

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

Zuerst habe ich Ihr Beispiel vereinfacht und bereinigt:

  • Etwas Rauschen entfernt, das für die Frage irrelevant ist.

  • RETURNS SETOF void kaum sinnvoll. Ich verwende RETURNS void stattdessen.

  • Ich verwende text statt character varying , nur der Einfachheit halber.

  • Wenn Sie dynamisches SQL verwenden, haben Sie Zum Schutz vor SQL-Injection verwende ich format() mit %I in diesem Fall. Es gibt andere Möglichkeiten.

Das grundlegende Problem besteht darin, dass SQL mit Typen und Bezeichnern sehr starr ist. Sie arbeiten mit dynamischer Tabelle Namen sowie mit dynamischem Feldname eines Datensatzes - ein anonymer aufnehmen in Ihrem ursprünglichen Beispiel. Pl/pgSQL ist dafür nicht gut gerüstet. Postgres weiß nicht, was darin ist eine anonyme Aufzeichnung. Erst nachdem Sie den Datensatz einem bekannten Typ zugewiesen haben können Sie auf einzelne Felder verweisen.
Hier ist eine eng verwandte Frage, die versucht wird, einzustellen ein Feld eines Datensatzes mit dynamischem Namen:
So setzen Sie den Wert eines zusammengesetzten Variablenfelds mit dynamischem SQL

Grundfunktion

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;

Aufruf:

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

Der kommentierte Teil würde eine Ausnahme auslösen:

Konnte Spalte "my_num" im Datensatzdatentyp nicht identifizieren:SELECT * fromgetrowdata(1,'foo')

hstore

Sie müssen das zusätzliche Modul hstore installieren dafür. Einmal pro Datenbank mit:

CREATE EXTENSION hstore;

Dann könnte alles so funktionieren:

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;

Aufruf:

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

Polymorpher Typ

Alternative ohne Installation zusätzlicher Module.

Da Sie in Ihrer Datensatzvariablen eine ganze Zeile auswählen, gibt es einen wohldefinierten Typ dafür per Definition. Benutze es. Das Schlüsselwort ist polymorphe Typen .

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;

Aufruf:

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

-> SQLfiddle

  • Ich (ab-)verwende den Eingabeparameter _tbl für drei Zwecke hier:

    • Stellt den gut definierten Typ bereit des Datensatzes
    • Liefert den Namen der Tabelle, automatisch schemaqualifiziert
    • Dient als Variable.
  • Weitere Erklärungen in dieser verwandten Antwort (letztes Kapitel):
    Refaktorisieren Sie eine PL/pgSQL-Funktion, um die Ausgabe verschiedener SELECT-Abfragen zurückzugeben