Triggerfunktion BEFORE INSERT löst sich nie aus!

Nicht wirklich.
Schreib mal in deine Funktion ein paar Debugmeldungen, das kannst du mittels:

RAISE NOTICE 'ich bin gesprächig';

tun. Dann wirst du sehen, dass die Triggerfunktion höchstwahrscheinlich (hab es nicht probiert) nicht aufgerufen wird.

Du kannst das NOT NULL immer noch in deinem Trigger forcieren, aber wenn du die Domain schon als NOT NULL definierst, musst du Daten übergeben.

Danke ads,

Postgres verhält sich gegen Firebird ein bisschen anders. Kann aber damit leben :slight_smile:

Bitte schön :wink:

Das ist aber nicht ganz konsistent zu dem Verhalten von Postgres zu normalen NOT NULL attributen … hmm

Diese kann man durchaus in einem BEFORE INSERT trigger initialisieren … ohne sie vorher mit was nicht-nullem zu fuellen.

Obs nicht ein ist?

Das Attribut ist ein Attribut der Domain.
Was du meinst, sind Constraints.

Beide werden unterschiedlich gehandhabt. Bei der Domain ist das Attribut quasi “fest dabei” und muss erfüllt werden. Der Constraint wird erst später überprüft.

Ich sehe da nicht wirklich eine Inkonsistenz, weil zwei verschiedene Features angesprochen werden. Gut, die haben beide den gleiche Namen, aber das war es auch schon.

natuerlich constraints - auf ein attribut → not null …

Beide werden unterschiedlich gehandhabt.

schade …

Bei der Domain ist das Attribut quasi “fest dabei” und muss erfüllt werden. Der Constraint wird erst später überprüft.

natuerlich muss auch das not-null constraint auf ein attribut letztendlich erfuellt sein. das ist ja das schoene an dem before-trigger, dass ich dort dafuer sorgen kann … der wert eines attributes ist ja bekanntlich “ohne wert” solange er nicht in den speicher geschrieben wurde - was nach dem before trigger i.d.R. passiert.

aber was meinst du, dass ein attribut bei einer domain “dabei sei” … so eine domain erfuellt doch eher die funktion eines types.

Ich sehe da nicht wirklich eine Inkonsistenz, …

soeine domain ist ja letztendlich kaum mehr als ein satz herkoemmlicher “db-werkzeuge” um ein attribut zu definieren - also z.B. dessen wertebereich und regelwerke dazu - wie z.B. nullable oder nicht.

ich meine - mit der selben argumentation, mit der man das verhalten der domain vor/in/bei einem before-insert trigger begründen kann, koennte man auch für eine strikte erfuellung eines schnoeden not-null constraints vor dem before-trigger plädieren … oder eben umgekehrt - wie der OP “erwarten” dass die regeln der domain nach dem before-trigger gecheckt werden

immerhin definiert man das not null in der domain ja auch als constraint - ich haette auch erwartet, dass dies zu dem gleichen zeitpunkt wie alle anderen constraints sichergestellt wird - das wäre konsequent / konsistent.

gläube mir - du würdest es auch hübscher finden :slight_smile:

für den schluessel wenigstenst eine domain zu definieren ist doch garnicht so dumm! wenn mir später einfaellt das mein schluessel oversized oder zu wenig ist, dann gaebe es (so die theorie) wenige stellen wo ich eingreifen müsste.

cool waere natuerlich wenn man sich einen pk-domain erstellen koennte, die auch den pseudo-typ serial verwenden wuerde … die muesste sicherstellen, dass fuer die anwendung der domain eine sequence erzeugt wird … waeren wir bei “wuensch dir was”, dann wäre für fk’s auch eine domain denkbar - incl dem fk-constraint und so weiter …

geht halt leider nicht :slight_smile:

symbiont: wie üblich faselst du ungeprüfte Sachen durch die Gegend.

Der BEFORE-Trigger wird erst nach den formalen Prüfungen für den Datentyp ausgelöst. Wenn du dir die Mühe gemacht hättest, das Eingangsbeispiel um einige Debugging-Statements zu erweitern, würdest du das gesehen haben. Bzw. würdest du gesehen haben, dass der Trigger eben nicht aufgerufen wird.



koennte man auch für eine strikte erfuellung eines schnoeden not-null constraints vor dem before-trigger plädieren

Das eine ist eine formale Voraussetzung für den definierten Datentyp, das andere ist ein Constraint. Aber nur für dich schauen wir doch mal, was passiert, wenn man das gleiche mit einem regulär vorhandenen Datentyp probiert:

CREATE OR REPLACE FUNCTION t_test ()
  RETURNS TRIGGER AS $$
BEGIN
  RAISE NOTICE 'spalte (vorher): %', NEW.spalte;
  NEW.spalte := 1;
  RAISE NOTICE 'spalte (danach): %', NEW.spalte;
  RETURN NEW;
END;
$$ LANGUAGE 'plpgsql';

CREATE TABLE test (spalte INTEGER);

CREATE TRIGGER t_trigger
BEFORE INSERT ON test
FOR EACH ROW
EXECUTE PROCEDURE t_test();

Ein Test mit einem Integer, um die Funktionsfähigkeit zu demonstrieren:

t7=# INSERT INTO test (spalte) VALUES ('5');
NOTICE:  spalte (vorher): 5
NOTICE:  spalte (danach): 1
INSERT 0 1
t7=# SELECT spalte FROM test;
 spalte
--------
      1
(1 row)

Und nun der große Praxistext:

t7=# INSERT INTO test (spalte) VALUES ('abc');
ERROR:  invalid input syntax for integer: "abc"

Wow, kein Trigger, einfach nur falsche Werte. PostgreSQL lehnt die ungültigen Eingaben von vorneherein ab. Das gleiche passiert auch bei einer Domain.


immerhin definiert man das not null in der domain ja auch als constraint

Das ist der größte Schmarrn, den du (zumindest heute) erzählt hast.
Aber die Details habe ich dir in meinem letzten Posting schon erklärt, ich mag mich ungern wiederholen. Lies die Unterschiede einfach dort mal nach.


gläube mir - du würdest es auch hübscher finden > :slight_smile: >

Nein.
Und noch weniger hübsch finde ich es, wenn du meine Meinung bestimmen möchtest.

ads: wie üblich faselst du halbgares und debattierst ueber unverstandenes …

ich sagte nicht, dass typen wie domains sind, sondern dass domains keine attribute sind und in ihrer “idee” eher einem typ entsprechen - was du offenbar ja auch bestaetigst

dass auch die “formalen” bedingungen fuer typen VOR dem before geprueft werden ist ja wunderschoen - aber sagt genau nichts darueber aus, ob es nicht “praktischer” sein koennte die einzelnen formalen bedingungen für domains genau dort zu pruefen, wo es fuer jeden teil der domain denn auch einzeln geschehen wuerde - wie wenn man keine domain benutzte und alles einzeln definiert. das ziel der domain wuerde trotzdem erfuellt.

du bist mir einer

und natuerlich ist NOT NULL ein constraint - obs technisch als “postgres-xy-4711-constraint” realisiert wird ist schnuppe - formal (weils dir so schoen gefaellt) ist es ein constraint … wenn du kruemelkacken willst - gern - das kann ich auch :slight_smile: denn ein constrait ist und bleibt eine “einschränkung” ob du zum pruefen einen index nehmen musst eine funktion oder deine oma fragen - ist voellig egal

[nachtrag]
betrachten wirs doch mal “andersrum” … domains funktionieren wie sie funktionieren (es gibt dafuer sinnvolle argumente) - wie möchtest du dann aber den fakt argumentieren, dass die schnöde einschränkung “NOT NULL” an einem attribut, das gute postgres bis “nach dem before-insert trigger” nicht die bohne interessiert? (glücklicher weise!)

Schreib deine Idee auf die Entwickler-Mailingliste.
Oder noch besser: schreib einen Patch.



die einzelnen formalen bedingungen für domains genau dort zu pruefen, wo es fuer jeden teil der domain denn auch einzeln geschehen wuerde

Parse error: genau das passiert doch.



wie wenn man keine domain benutzte und alles einzeln definiert. das ziel der domain wuerde trotzdem erfuellt.

Ist dann aber keine (in sich geschlossene) Domain mehr sondern ein Constraint und der darf dementsprechend lazy sein.


Nun ja, wie gesagt, ich erwarte deine Diskussion oder deinen Patch auf der Mailingliste.

CREATE OR REPLACE FUNCTION t_test ()
  RETURNS TRIGGER AS $$
BEGIN
  RAISE NOTICE 'spalte (vorher): %', NEW.spalte;
  NEW.spalte := 1;
  RAISE NOTICE 'spalte (danach): %', NEW.spalte;
  RETURN NEW;
END;
$$ LANGUAGE 'plpgsql';

CREATE TABLE test (spalte INTEGER);

CREATE TRIGGER t_trigger
BEFORE INSERT ON test
FOR EACH ROW
EXECUTE PROCEDURE t_test();

Da muss ich mich jetzt mal dran hängen, weil ich auch gerade dabei bin, von Firebird auf PostgreSQL umzusteigen und weil ich heute genau an diesem Punkt hängengeblieben bin: ist denn “EXECUTE PROCEDURE” in PostgreSQL überhaupt richtig? In der Dokumentation finde ich es nämlich nicht, aber vielleicht habe ich ungeschickt gesucht.

Im Augenblick habe ich den Eindruck, dass Trigger und gespeicherte Prozeduren ein Haupt-Stolperstein beim Umstieg sind.

Die Sache mit als NOT NULL deklarierten Domänen habe ich in PostgreSQL noch nicht erforscht. Aber aus Firebird bin ich schon daran gewöhnt, eine Domäne als NOT NULL zu deklarieren und entsprechende Werte in Datenbanktabellen durch Trigger einsetzen zu lassen (geht kaum anders, weil Firebird kein SERIAL kennt). Wäre also ein weiterer potentieller Stolperer.

Beste Grüße
Sibylle



test=# \h create trigger
Command:     CREATE TRIGGER
Description: define a new trigger
Syntax:
CREATE TRIGGER name { BEFORE | AFTER } { event [ OR ... ] }
    ON table [ FOR [ EACH ] { ROW | STATEMENT } ]
    EXECUTE PROCEDURE funcname ( arguments )

Andreas

Inzwischen hab ich’s auch gefunden, man soll abends keine Fragen mehr stellen. Und die Sache mit der als NOT NULL definierten Domäne habe ich auch nachvollzogen. Mal sehen, wo das nächste Umstiegshindernis auftaucht.

Danke, Gruß,
Sibylle

Wenn du all die Sachen in Textform zusammenschreibst, kann man sich darüber unterhalten, ob und wie man so etwas weiterverwerten kann.

“All die Sachen” = der in diesem Thread zutage getretene Unterschied zwischen Firebird und PostgreSQL bei der Behandlung von Domain-Definitionen einerseits, Constraints in der Spaltendefinition andererseits. War es so gemeint?

Aufschreiben muss ich mir das in jedem Fall, sonst trete ich ja immer in die gleichen Fußangeln.

Schöne Grüße,
Sibylle

Nicht nur du dir, sondern für alle veröffentlichen.

Mit einigen Details, welche Probleme auftreten können, worauf man achten muss, wie man bestimmte Probleme umgeht oder beseitigt.

Hier im Forum gibt es eine FAQ Kategorie, dort wäre so etwas gut aufgehoben.

Ich habe jetzt mal zusammengeschrieben, was mir bis jetzt aufgefallen und so einigermaßen klar geworden ist. Im Wesentlichen also das, was in diesem Thread besprochen wurde. Vielleicht regt es andere zum Weiterschreiben an, ich bin bestimmt noch nicht in sämtliche Fallen getrappt.

Wenn ich richtig sehe, kann ich selbst nichts in die FAQ-Kategorie schreiben, deshalb mache ich aus dem bisher vorhandenen Text einfach mal einen weiteren Beitrag in diesem Thread und bitte um Verschiebung. Für kritisches Lesen und Korrigieren wäre ich sehr dankbar. Insbesondere mein sehr kurzer Punkt 2 (Ausführen von gespeicherten Funktionen mit Rückgabetyp void) macht mir Kummer: es gelingt mir nicht mehr, auch nur das Wenige dazu in der Dokumentation wiederzufinden, was ich m.E. schon mal gefunden hatte.

Und was noch ganz fehlt: die Migration einer fertigen Datenbank. Das habe ich schlicht noch nicht ausprobiert. Statt dessen habe ich als erstes eine Datenbank ganz neu aufgebaut, deren Firebird-Version sowieso eine gründliche Umstrukturierung gebraucht hätte.

Gruß,
Sibylle

  1. NOT-NULL-Domain und fehlende Eingabewerte

Häufige Situation: ein Tabellenfeld braucht einen Eingabewert, der aber bei Bedarf von einem Trigger erzeugt wird (z.B. automatisch erzeugte Primärschlüssel). Das kann praktischer sein als ein Defaultwert, denn wenn im INSERT-Befehl ein Feld nicht weggelassen, sondern mit NULL belegt wird, z.B. von automatisch erzeugten SQL-Befehlen, dann hilft der Defaultwert nichts, der Trigger dagegen schon. Im Fall Firebird kommt noch dazu, dass es den Datentyp “SERIAL” nicht gibt, hier kommt für die Primärschlüssel-Erzeugung nur der Trigger in Frage.

In Firebird ist es egal, ob für ein solches Feld eine als NOT NULL deklarierte Domain benutzt wird oder NOT NULL in der Spaltendefinition steht: in beiden Fällen wird der Trigger ausgeführt, das Feld wird belegt und der ergänzte Datensatz kommt in die Tabelle.

Das ist in PostgreSQL anders! Die Trigger-Konstruktion funktioniert hier nur mit einem NOT-NULL-Constraint in der Spaltendefinition, nicht mit einer entsprechend deklarierten Domain. Für Primärschlüssel braucht man keinen Trigger, hier bietet sich SERIAL an, allerdings muss man auch da aufpassen, dass einem kein “hilfreicher” SQL-Builder eine NULL in den INSERT-Befehl schreibt.

Zeigen kann man das zum Beispiel so:

Vorbedingung:

Definition einer NOT-NULL-Domain:

CREATE DOMAIN id_domain INTEGER NOT NULL;

In der Tabellendefinition wird sie benutzt:

CREATE TABLE numtest(
idc char(3) primary key,
nummer id_domain,
);

Im “BEFORE INSERT”-Trigger wird die Eingabe für das Feld nummer auf einen legalen Wert gesetzt. In PostgreSQL sieht der Trigger so aus:

CREATE OR REPLACE FUNCTION domain_test() RETURNS trigger AS $$
    BEGIN
        RAISE NOTICE 'nummer (vorher) %', NEW.nummer;
        NEW.nummer = 99;
        RAISE NOTICE 'nummer (nachher) %', NEW.nummer;
        RETURN NEW;
    END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER domain_test
  BEFORE INSERT
  ON numtest
  FOR EACH ROW
  EXECUTE PROCEDURE domain_test();

Dass dieser Trigger nicht besonders sinnvoll ist, ist ganz klar, er dient nur der Demonstration.

Verschiedene Eingaben:

Legaler Wert:

INSERT INTO numtest (idc. nummer) VALUES ('A00', 5);

Ausgabe: HINWEIS: nummer (vorher): 5
HINWEIS: nummer (nachher): 99
Satz eingefügt, mit nummer = 99.

Falscher Datentyp:

INSERT INTO numtest (idc, nummer) VALUES ('A01', 'abc');

Ausgabe: FEHLER: Ungültige Eingabesyntax für ganze Zahl ‘abc’
Satz nicht eingefügt

Wert fehlt:

INSERT INTO numtest (idc) VALUES ('A02');

Ausgabe: FEHLER: Domäne id_domain erlaubt keine NULL-Werte

Wird das Feld nummer nicht mehr mit der Domäne id_domain, sondern als “integer not null” deklariert, gibt es für die Eingaben 5 und ‘abc’ die gleichen Ausgaben. Ausgabe für den INSERT-Befehl ohne das Feld nummer:

HINWEIS: nummer (vorher):
HINWEIS: nummer (nachher): 99

Fazit: ist eine Domain als “NOT NULL” definiert, dann dürfen die Eingaben für entsprechende Felder von vornherein nicht NULL sein. Korrekturversuche in einem “BEFORE INSERT”-Trigger kommen zu spät, dieser Trigger wird gar nicht ausgeführt. Wird der NOT-NULL-Constraint in der Spaltendefinition angegeben, kann ein fehlender Wert im Trigger ergänzt werden.


Wer das ganze Spielchen mit Firebird nachvollziehen will:

Firebird hat so etwas wie RAISE NOTICE nicht, aber dieser Trigger eignet sich so einigermaßen:

ALTER TRIGGER BI_NUMTEST ACTIVE
BEFORE INSERT POSITION 0
AS 
BEGIN 
    NEW.remark = 'Vorher: ' || COALESCE(NEW.amount_no2, 'NULL'); 
    NEW.amount_no2 = 99;
    NEW.remark = NEW.remark || ' Nachher: ' || NEW.amount_no2;
END^

Die gleichen Eingaben in Firebird: hier macht es keinen Unterschied, ob die Domäne als “NOT NULL” deklariert ist oder die Spalte. Eine Eingabe mit dem falschen Typ (‘abc’ statt einer Zahl) wird auch hier zurückgewiesen und der Satz nicht eingefügt. Eine fehlende Eingabe wird im Trigger ergänzt und der Satz wird mit diesem Wert eingefügt.


2. Ausführen von Funktionen

Den Unterschied zwischen ausführbaren und selektierbaren Prozeduren, der in Firebird wichtig ist, kennt PostgreSQL nicht. Es gibt (abgesehen von Triggerprozeduren) nur Funktionen, die als Rückgabetyp void haben können - das entspricht am ehesten der ausführbaren Prozedur von Firebird. Aufgerufen werden alle Funktionen mit SELECT, eine Funktion mit Rückgabetyp void kann mit “SELECT meinefunktion(parameterliste)” oder auch mit “SELECT * FROM meinefunktion(parameterliste)” aufgerufen werden. In der Dokumentation sind solche Funktionen stiefmütterlich behandelt (oder nur schwer zu finden?).

Danke schön.

Ich habe aus deinem Posting mal einen FAQ-Beitrag erstellt:
http://www.pg-forum.de/h-ufig-gestellte-fragen-faq/3783-stolpersteine-bei-der-migration-von-firebird-nach-postgresql.html

Allerdings habe ich den letzten Satz entfernt und statt dessen die dafür passende Dokumentation verlinkt. Auf der dort angegebenen Seite sind all die angesprochenen Punkte imho gut und mit Beispielen erklärt.

Danke. Das war genau die Stelle; das void-Beispiel fehlt in der deutschsprachigen Version. Es ist wahrscheinlich generell ratsam, das englische Original zu benutzen, richtig?