RECORD: wert an unbestimmter Stelle

Einen schönen guten Morgen,

Hintergrund:
In einer Triggerfunktion für onAfterDelete will ich bestimmte Aktionen für Spaltenwerte des gelöschten Eintrags (der mir ja in der Variablen OLD noch zur Verfügung steht) ausführen. Den Spaltennamen kenne ich erst zur Laufzeit.

Frage:
Wie kann ich auf den Wert eines record an einer in einer Variablen gespeicherten Stelle zugreifen (in etwa value := record.)? Oder gibt es irgendeinen würg-around?

Vielen Dank für eure Hilfe bei der Entfernung des Brettes vor dem Kopf

Gerd


Umgebung: PostgreSQL 8.1.3, Suse Linux 9.2

Komplettes Demo-Beispiel:

– Testtabelle
CREATE TABLE test
(
id int4 PRIMARY KEY,
name text
description text
);

– Funktion
CREATE FUNCTION get_column_value(id int4, colname text)
RETURNS text AS
$BODY$
DECLARE
thevalue text;
arecord record;
_sql text;
BEGIN
– record holen (ist klar)
SELECT * INTO arecord FROM test WHERE id=$1;

– (V1) thevalue := arecord.colname;
– runtime error: record “arecord” has no field “colname”

– (V2.1) _sql := ‘SELECT ’ || therecord || ‘.’ || colname || ’ AS value;’;
– runtime error: column “therecord” does not exist

_sql := ‘SELECT therecord .’ || colname || ’ AS value;’;
– runtime error: missing FROM-clause entry for table “therecord”

FOR arecord IN EXECUTE _sql
LOOP
thevalue := arecord.value;
END LOOP;
RETURN thevalue;
END;
$BODY$
LANGUAGE ‘plpgsql’ VOLATILE;



– AUFRUF
INSERT INTO test (id, name, description)
VALUES (1, ‘paul mueller’, ‘der mueller, paul’);

SELECT * FROM get_column_value(1, ‘name’);[/list]

Hallo gerd,

wenn das dass komplette Beispiel sein soll, kann es nicht funktionierern. Es fehlt CREATE TRIGGER! Die Funktion, die vom Trigger augerufen wird, muss immer RETURN AS TRIGGER definieren. Schau Dir nochmals die Doku an, da gibt es genügend Beispiele für die Triggerfunktionen.


Kann nicht sein, da CREATE TRIGGER sich auf einen bestimmte Tabelle bezieht, kennst Du auch die Spaltennamen der Tabelle.



mfg

fristo

Ich gehe mal davon aus, das er den Trigger nicht nur an einer Tabelle ansetzen möchte und darum dynamisch die Spaltennamen braucht.

Meines Wissens kann man in plpgsql nicht auf dynamische Spaltennamen in NEW/OLD zugreifen, das gleiche Problem hatte ich schon in tablelog seufz
Letztendlich hab ich das in C gelöst.

Hallo ads,

wenn man für mehrere Trigger ein und dieselbe Triggerfunktion aufruft, kann man innerhalb der Triggerfunktion durch Abfrage von TG_RELNAME den Namen der Tabelle feststellen, von dem der Trigger ausgelöst wurde. Mit dem Namen der Tabelle sollte man dann auch die Namen der Spalten kennen und NEW bzw OLD liefern mir die Zeile in der Tabelle. Der Rest des Codes in der Triggerfunktion beginnt dann mit

if TG_RELNAME = "???TABELLENNAME???"
...

mfg

fristo

fristo: schon richtig.

Wenn ich jedoch das Eingangsposting richtig interpretiere, sucht der OP eine Möglichkeit, die Spaltennamen herauszufinden (oder dynamisch vorzugeben) und dann die Werte aus OLD. auszulesen.
Und das geht nicht.

Hallo fristo,

danke für Deine Mühe, jedoch sollte das Beispiel mein Problem demonstrieren, und nicht die gesamte Triggerfunktion.

Wie man diese baut und aufruft ist mir bekannt.

Das Problem ist, dass ich die Triggerfunktion für verschiedene Tabellen anbinden möchte und innerhalb dieser (aus einer anderen fest definierten Tabelle) auf die enztsprechenden Spaltennamen komme.

Gerd

Ist mir auch bekannt und wäre eine - wenn auch recht dämliche - Alternative: ich müsste mir innerhalb dieser Funktion für alle möglichen Tabellen einen case-Zweig einrichten!

Gerd

Hallo ads,

jetzt muss ich mich wohl doch als newbi outen: vor etwa einem Monat die erste Begegnung mit postgre.
Kannst Du mir einen kurzen Überblick (oder Hinweis, wo zu finden) zur Einbindung von C code geben? (Am schönsten wäre natürlcih Deine fertige Lösung.)

Danke und beste Grüße

Gerd

gerd:

siehe:

http://pgfoundry.org/projects/tablelog/

Vielen Dank dafür, ich denke, damit kann ich mein Problem lösen.

Beste Grüße

Gerd

Hallo gerd,



wenn Du hier eine Frage zu einem Problem stellt, solltest Du das Problem vollständig beschreiben und nicht in Bruchstücken. Was Du in Deiner Frage nicht mitteilst, können wir nicht wissen! Woher sollen wir wissen, wie Du zur Laufzeit an den Spaltennamen der Tabellen kommst? Und einen Vorschlag als “wäre eine - wenn auch recht dämliche - Alternative” zu betiteln beschreibt eher Dein Wissensstand als Newbi.


Da Dir der Spaltenname erst zur Laufzeit, also während der Ausführung der Triggerfunktion, bekannt ist, kannst Du mittels Stringsfunktionen Dir den auszuführenden Code erstellen und mittels der Funktion EXECUTE ’ … ’ ausführen. Wegen einiger Restriktionen kann das eventuell nicht in der Triggerfunktion erfolgen. Dann muss der Code in separaten Funktion verlagert werden.

Wie so etwas aussieht, kannst Du aus den folgenden Beispiel entnehmen:

CREATE OR REPLACE FUNCTION public.copy_from_csv___sq &#40]



mfg

fristo

Hallo fristo,

Ich bitte höflichst um Entschuldigung für die doch möglicherweise unglückliche Ausdrucksweise und gelobe Besserung.

Nochmals Danke für die Unterstützung, und gleich nochmal die Frage:
wie müsste die Zeichenkette für das EXECUTE aussehen, die mir letzdendlich (innerhalb einer Triggerfunktion) den Wert von OLD an der Stelle colname holt (value:=OLD.)?

Beste Grüße

Gerd

Hallo Gerd,

zu Deimen Verständnis mal folgendes: Wenn Du eine Funktion in die Datenbank einspieltst, wird der Code der Funktion gewissermaßen kompiliert und damit werden alle Referenzen innerhalb des Codes statisch. Dadurch wird die Funktion beim Aufruf sehr schnell ausgeführt. Um dynamischen Code zu verarbeiten hat man dann die Funktion EXECUTE eingeführt. Diese macht nichts anderes als den Code vor jeden Aufruf zu kompilieren, mit dem Nachteil dass dadurch die gesamte Ausführung des Codes natürlich länger braucht.

Es gibt auch ein eigenes Thema “Executing Dynamic Commands” mit vielen Beispielen in der Doku.



Ja, so sieht der Syntax aus. Und dynamisch sollte es so aussehen.

EXECUTE 
'
'value&#58]
wo <colname> eine Variable ist, in der der Name der Spalte steht. (Beachte, das ich "AS $BODY$" und nicht "AS '" verwende)

Wie schon oben beschrieben, muss eventuell wegen Restriktionen der Code in separaten Funktion verlagert werden.

Zudem habe ich es jetzt nicht getestet, aber es zeigt Dir den Weg, den Du gehen musst.



mfg

fristo

Hallo fristo,

mit Deinen Angaben sieht jetzt die Testfunktion (aus dem Ursprungsposting) so aus:

CREATE OR REPLACE FUNCTION get_column_value(id int4, colname text)
  RETURNS text AS
$BODY$
DECLARE
  thevalue text;
  arecord record;
  _sql text;
BEGIN
  -- record holen (nur zur Demo, anstelle von arecord steht dann OLD/NEW)
  SELECT * INTO arecord FROM test WHERE id=$1;
  -- string für execute bauen
  _sql := 'thevalue:=therecord.' || colname;
  EXECUTE _sql;
  RETURN thevalue;
END;
$BODY$
ALTER FUNCTION get_column_value(id int4, colname text) OWNER TO geo_admin;

bei der Ausführung (mittels select * from get_column_value(1, ‘name’); ) erhalte ich die Meldung:

ERROR: syntax error at or near “thevalue” bei Zeichen 1
ANFRAGE: thevalue:=therecord.name

Hab ich doch ein Brett vor dem Kopf?

Hallo gerd,

kurze Frage an Dich: Ist das die Funktion die vom Trigger aufgerufen wird?



mfg


fristo

Hallo fristo,

gerade fiel mir ein möglicher work-aroud ein (temp. Tabelle erzeugen, Wert reinschreiben, Wert lesen temp. Tabelle löschen). Funktioniert aber auch nicht, Fehlermeldung zur Laufzeit:
ERROR: missing FROM-clause entry for table “arecord”
KONTEXT: SQL statement “INSERT INTO tt_temp (id, avalue) VALUES (1, arecord.name);”

Funktion:

CREATE OR REPLACE FUNCTION get_column_value(id int4, colname text)
  RETURNS text AS
$BODY$DECLARE
  thevalue text;
  arecord record;
  _sql text;
BEGIN
  -- record holen (nur zur Demo, anstelle von arecord steht dann OLD/NEW)
  SELECT * INTO arecord FROM test WHERE id=$1;
  -- temp Tabelle erzeugen
  CREATE TABLE tt_temp( _id int4 PRIMARY KEY, avalue text);
  -- Spaltenwert einfügen
  _sql := 'INSERT INTO tt_temp (id, avalue) VALUES (1, arecord.'||colname||');';
  EXECUTE _SQL;
  -- Zeile aus temp lesen
  SELECT * INTO arecord FROM tt_temp WHERE _id=1;
  thevalue := arecord.avalue;
  -- temp löschen
  DROP TABLE tt_temp;
  RETURN thevalue;
END;$BODY$
  LANGUAGE 'plpgsql' VOLATILE;

Nein, das ist eine Funktion, die das Problem (value:=record.) demonstrieren soll. Die damit (hoffentlich) gewonnenen Erfahreungen werden dann in die Triggerfunktion übertragen.

[quote=“gerd”]Hallo fristo,

mit Deinen Angaben sieht jetzt die Testfunktion (aus dem Ursprungsposting) so aus:

CREATE OR REPLACE FUNCTION get_column_value&#40]

bei der Ausführung (mittels  select * from get_column_value(1, 'name'); ) erhalte ich die Meldung:

ERROR:  syntax error at or near "thevalue" bei Zeichen 1
ANFRAGE:  thevalue:=therecord.name

Hab ich doch ein Brett vor dem Kopf?[/quote]
Das musst Du selbst entscheiden. Aber genauer hinschauen solltest Du schon!



Die Funktion liefert nur einen Wert zurück. Wieviel Werte willst Du denn mit dem Aufruf 

[code]select * from get_column_value(1, 'name');

zurück haben?


Brauchst Dich nur an den Syntax zu halten:

select get_column_value(1, 'name');

mfg

fristo

Hallo fristo,

danke für die SQL-Unterweisung. Aber das ändert an der Fehlöermeldung nichts - die wird durch das EXECUTE-Statement innerhalb der Funktion geworfen.

Gerd

Hallo gerd,

ersetze mal

_sql := ‘thevalue:=therecord.’ || colname;
EXECUTE _sql;

durch

EXECUTE 
' 
'thevalue:=therecord.' || colname; 
'

mfg

fristo