After Update Trigger: Modified Date

MysterioJN

SQL-Guru
Beiträge
158
Hallo zusammen,

folgender Sachverhalt:

Tabelle: Produkte

Spalten:
ID = PK,
ImShop = bit(true/false)
ShopDatum = datetime


Ziel: Immer wenn in einem Datensatz in der Tabelle "Prdoukte" das Feld "ImShop" = true gesetzt wird, soll er mir in das Feld "ShopDatum" das aktuelle Datum/Zeit eintragen (=getdate())

Problem:
Ich hab leider nur keinen Peil, wie der Code für einen Trigger aussehen muss.
Ist es vlt. möglich, das sich jemand kurz die Mühe macht und mir es für das obige Beispiel hier kurz darstellt?

Ich hoffe echt auf Euch. Komme mit den Tutorials auf YouTube nicht weiter.

Beste Grüße

Marco
 
Werbung:
Edit:

Ziel:
Immer wenn in einem Datensatz in der Tabelle "Prdoukte" das Feld "ImShop" = true gesetzt wird, soll er mir NACH AKTUALISIERUNG IN DER SELBEN TABELLE in das Feld "ShopDatum" das aktuelle Datum/Zeit eintragen (=getdate())
 
Also was ich jetzt hinbekommen habe (zumindest schon mal ein erster Triggererfolg) ist bei genereller Datenzeilenänderung ein Mod-Datum auszuwerfen.

Code:
CREATE TRIGGER updateModified
ON Produkte
AFTER UPDATE
AS
   UPDATE Produkte
   SET Modified = getdate()
   FROM Inserted i
   WHERE Produke.ID = i.ID

Wie bekomme ich das jetzt mit einer Bedingung hin. Also wenn NUR in einer bestimmten Spalte eine Datensatzänderung gemacht wurde, das er mir ein Datum auswerfen soll.
Datums-Zielfeld wäre: ShopDatum
Wenn Bedingung: ImShop = true
 
Hab noch einen alten Beitrag gefunden hier der mich !endlich! zum Ziel geführt hat:

Code:
CREATE TRIGGER Tr_Produkte
ON Produkte
FOR INSERT, UPDATE
AS
BEGIN
UPDATE Produkte
SET ShopDatum = getdate()
FROM Produkte
INNER JOIN INSERTED i
ON i.ID= Produkte.ID
INNER JOIN DELETED d
ON d.ID= Produkte.ID
WHERE i.ImShop= 1
AND ( d.ImShop= 0
OR d.ImShop IS NULL )
END
 
Zuletzt bearbeitet:
Noch offenes Ziel 2: Wenn Produkte.ImShop wieder von 1 auf 0 geändert wird, dann (neue Spalte) Produkte.ShopDatumRaus = getdate()

Bekomme immer den Fehler: Die maximale Schachtelungsebene wurde erreicht.

Wie muss ich das Ende umstellen?
WHERE i.ImShop= 1
AND ( d.ImShop= 0
OR d.ImShop IS NULL )
 
Eigentlich kannst du mit zwei UPDATE-Anweisungen (eine für ShopDatum und eine für ShopDatumRaus) im selben Trigger wie oben mit Join und WHERE-Teil dein Datum setzen. Die Fehlermeldung deutet eher darauf hin das da zwei Trigger gegenseitig feuern oder einer immer wieder, hast du noch weitere Trigger auf Produkte?
 
Dank dir. Glaub das war auch mal dein Lösungsvorschlag der mir irgendwie untergegangen ist.

Es tut mir schrecklich leid, aber die Syntax habe ich irgenwie noch nicht ganz verstanden mit den Where-Bedingungen in einem Trigger.
Wie müsste ich das denn einbauen in den selben Trigger?

Ja ich hab noch einen anderen Trigger (modified bei irgendeiner Änderung) auf der Tabelle. Aber in Kombination mit dem Mod-Trigger und dem ImShop = 1 = Datum in shopDatum-Trigger klappt es problemlos.

Nur wenn ich dann einen weiteren Trigger einbaue bei ImShop = 0 dann Datum in Shop-Datum-Raus meckert er.

Nicht wundern über die Spalten/Tabellennamen. Die sind natürlich sauber in der DB. Nur hier abgeändert um es verständlicher zu machen was "ich" brauche.
 
Eigentlich ist das recht einfach mit den Triggern, man muss nur immer genau überlegen was eigentlich passiert.

Bei einem INSERT, UPDATE Trigger wie oben passiert folgendes:
Du schreibst ein oder mehrere Datensätze in die Tabelle oder du aktualisierst einen oder mehrere Datensätze in der Tabelle. Danach feuert der Trigger einmal. Du führst also den Code aus der zwischen AS BEGIN und END steht.

Als Besonderheit hast du in diesem Kontext Zugriff auf zwei Tabellen. In INSERTED stehen die Datensätze, die du eingefügt hast bzw. das, was durch UPDATE in die Tabelle geschrieben wurde. In DELETED das was vorher in der Tabelle stand. Hier ist eventuell schon dein Fehler zu suchen, mehr dazu gleich.

Der Code joint jetzt in einem UPDATE-Statement die eigentliche Tabelle, die ja schon verändert wurde, per INNER JOIN mit dem was verändert wurde. Betroffen sind also nur Datensätze die auch vorher verändert oder neu geschrieben wurden. Jetzt machst du noch einen INNER JOIN mit DELETED, es sind also nur Datensätze betroffen, die vorher schon da waren. Das ist natürlich schlecht, denn: Der Trigger würde auf einen INSERT vermutlich unerwartet reagieren, daher würde ich es an der Stelle erstmal mit einem LEFT JOIN versuchen und testen, ob dein Fehler bei INSERT oder bei UPDATE noch auftritt:
Code:
CREATE TRIGGER Tr_Produkte
ON Produkte
FOR INSERT, UPDATE
AS
BEGIN
   UPDATE Produkte
   SET Produkte.ShopDatum = getdate()
   FROM Produkte
   INNER JOIN INSERTED i
   ON i.ID= Produkte.ID
   LEFT JOIN DELETED d
   ON d.ID= Produkte.ID
   WHERE i.ImShop = 1
   AND ( d.ImShop = 0
   OR d.ImShop IS NULL )
END
Der Trigger setzt ShopDatum = getdate() bei neuen Datensätzen (INSERT) wenn ImShop = 1 ist (d.ImShop ist dann übrigens nicht existent aber aufgrund des LEFT JOINs NULL) oder bei einem alten Datensatz wenn ImShop vorder 0 oder NULL war (hier existiert ein Eintrag in DELETED und wird unter d.ImShop auch geprüft).

Du solltest nicht beliebig Trigger auf eine Tabelle legen, du kannst die Reihenfolge nur schwer bis gar nicht beeinflussen. Jeder Trigger der wie hier die Daten der Tabelle nachträglich ändert, würde sich und andere Trigger bei diesem Änderungsvorgang erneut auslösen. Das führt zu Cascaden die per Default bei 30 Auslösern von der DB gestoppt werden. In diesem Fall würde ich alles in den selben Trigger packen, entscheidend ist ja der Auslöser.
Code:
CREATE TRIGGER Tr_Produkte
ON Produkte
FOR INSERT, UPDATE
AS
BEGIN
   UPDATE Produkte
   SET Produkte.ShopDatum = getdate()
   FROM Produkte
   INNER JOIN INSERTED i
   ON i.ID= Produkte.ID
   LEFT JOIN DELETED d
   ON d.ID= Produkte.ID
   WHERE i.ImShop = 1
   AND ( d.ImShop = 0
   OR d.ImShop IS NULL )

   UPDATE Produkte
   SET Produkte.ShopDatumRaus = getdate()
   FROM Produkte
   INNER JOIN INSERTED i
   ON i.ID= Produkte.ID
   INNER JOIN DELETED d
   ON d.ID= Produkte.ID
   WHERE ( i.ImShop = 0
   OR i.ImShop IS NULL )
   AND d.ImShop = 1
END
Man beachte: Im zweiten Teil nehme ich wieder INNER JOIN. Das heißt wird hier ein Datensatz per INSERT geschrieben mit ImShop = 0 oder NULL wird ShopDatumRaus nicht gesetzt, das UPDATE-Statement betrifft keine Datensätze. Das kann man natürlich auch wieder mit LEFT JOIN machen, je nachdem was gewollt ist.

Eigentlich einfach aber immer wieder Knoten im Hirn.
 
Es war tatsächlich das Problem das ich mehrere Trigger hatte. Bin jetzt wie du gesagt hast hingegangen und hab es gemäß deiner sehr ausführlichen Erklärung (VIELEN DANK DAFÜR!!) in einen gepackt und es klappt. Habe nun noch weitere Triggerabfragen in diesen Einen gepackt. Mit Trigger hebt man die Datenbank auf eine ganz neue Ebene; die mir sehr gefällt und viel Handarbeit/Fehler vermeidet.

Hab das nun so verstanden:
INSERTED und DELETED sind nur fiktive "Zwischenspeicher-Tabellen" richtig? Diese werden nicht physikalisch abgelegt.
Deleted brauche ich also um eine vorherigen Stand mit einem aktuellen Stand INSERTED vergleichen zu können

Denn es scheint z.B. beim generellen Ändern von Daten unter Zeitangabe in das Feld modified auch ohne Deleted zu klappen. (Hat das System mir so "abgeändert")

Ich bin dir wirklich unglaublich dankbar für deine Hilfe!!! Hab endlich wieder neue Freude an der Datenbank entdeckt, die hinterher 100+ Personen bedienen sollen.
Vorallem kann man somit viel Willkür durch Anwender vermeiden. Echt toll.

Beste Grüße
Marco
 
Werbung:
Richtig, INSERTED und DELETED gibt es immer im Trigger-Kontext. Bei einem Join kann man zum testen immer die Haupttabelle einsetzen und enthält alle betroffenen Datensätze. Man darf nur nicht den Fehler machen und davon ausgehen das nur ein Datensatz zurück geliefert wird, MSSQL kennt kein FOR EACH ROW wie ein MySQL z.B. wo man den Trigger pro Datensatz ausführt.

Trigger sind was feines, leider sind sie nicht perfekt und erfordern viel Geduld. Ich habe in meinem CRM sehr viele Trigger im Einsatz die Businesslogik abbilden (also Eingaben prüfen, etc.) das machen einige aus Prinzip nicht. In dem Fall kann ich aber die UI nicht selbst gestalten, sie gibt allerdings Fehlermeldungen der DB zurück und darüber baue ich Logik nach.

Das verursacht leider auch Kaskaden und ist super scheisse zu debuggen. Daher rate ich immer zu möglichst wenig Triggern sofern das möglich ist.

Für Eingabeprüfung empfehle ich dir den INSTEAD OF-Trigger, da kann man Leerschritte bereinigen oder sonstwas tun und dann den Datensatz aus INSERTED selbst in die Tabelle schreiben oder aktualisieren. Außerdem feuert er garantiert vor allen anderen. Einen AFTER-Trigger nehme ich dann zum loggen.
 
Zurück
Oben