Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Versionierung in neuer Tabelle und DELETE im Trigger

13 views
Skip to first unread message

Andreas Horn

unread,
Apr 19, 2013, 4:53:42 PM4/19/13
to
Hallo Allerseits,

es gibt eine Datentabelle und die dazu gehᅵrige Sicherungstabelle. In
der Datentabelle kann ich in einer Spalte den "Delinquenten" und seine
ᅵnderungs-DATETIME eintragen. Bei INSERT und bei UPDATE wird das dann im
after_insert_tr bzw. im after_update_tr mit den anderen Daten der
datentabelle an die Sicherungsdatei angehᅵngt. Wie geht so was denn bei
DELETE? Werte bei DELETE zu ᅵbergeben ist ja witzlos, weil sie mit dem
Datensatz ja sowieso gelᅵscht werden, deshalb lehnt das DBMS das ja auch
ab. Wie kann ich das sonst noch machen? Ich habe schon von dem
vorherigen setzen einer globalen Variablen gelesen, die dann im Trigger
ausgewertet werden kann - das gefᅵllt mir aber gar nicht. Oder muss ich
dafᅵr eine Pozedur schreiben, die dann auch den Lᅵschbefehl enthᅵlt? Das
missfᅵllt mir aber auch.

Hat jemand noch eine andere Idee?

Danke schon mal

Andreas

Thomas 'PointedEars' Lahn

unread,
Apr 19, 2013, 6:48:35 PM4/19/13
to
Andreas Horn wrote:

> es gibt eine Datentabelle und die dazu gehörige Sicherungstabelle. In
> der Datentabelle kann ich in einer Spalte den "Delinquenten" und seine
> Änderungs-DATETIME eintragen. Bei INSERT und bei UPDATE wird das dann im
> after_insert_tr bzw. im after_update_tr mit den anderen Daten der
> datentabelle an die Sicherungsdatei angehängt.

Vermutlich sind das Deine benutzerdefinierten Trigger-Namen. In dem Fall
solltest Du nicht davon ausgehen, dass das jeder ohne Erklärung versteht.

> Wie geht so was denn bei DELETE?

Genauso.

> Werte bei DELETE zu übergeben ist ja witzlos, weil sie mit dem Datensatz
> ja sowieso gelöscht werden,

Nein. Es gibt bloss keine *neuen* Werte.

> deshalb lehnt das DBMS das ja auch ab.

Tut es nicht.

> Wie kann ich das sonst noch machen? Ich habe schon von dem
> vorherigen setzen einer globalen Variablen gelesen, die dann im Trigger
> ausgewertet werden kann - das gefällt mir aber gar nicht. Oder muss ich
> dafür eine Pozedur schreiben, die dann auch den Löschbefehl enthält? Das
> missfällt mir aber auch.

CREATE TRIGGER `foo` BEFORE DELETE ON `bar`
FOR EACH ROW INSERT INTO `baz` (`bla`) VALUES (OLD.`bla`)

und anschliessendes

DELETE FROM `bar` WHERE …

funktioniert in MySQL 5.5 auf einer (MyISAM-)Tabelle namens `bar`
ausgezeichnet, d. h. der (MyISAM-)Tabelle `baz` wird ein entsprechender
Datensatz hinzugefügt. (Mit InnoDb funktioniert das sicher auch.)

--
PointedEars

Twitter: @PointedEars2
Please do not Cc: me. / Bitte keine Kopien per E-Mail.

Andreas Horn

unread,
Apr 22, 2013, 4:48:16 AM4/22/13
to
Hallo Thomas,

zuerst mal Danke für deine schnelle Antwort.

(Getreu dem Motto: Sind Sie mit meiner Antwort nicht zufrieden, stellen
Sie bitte eine andere Frage - alles noch mal und diesmal vielleicht besser.)

On 20.04.2013 00:48, Thomas 'PointedEars' Lahn wrote:
> Andreas Horn wrote:
>
>> es gibt eine Datentabelle und die dazu gehörige Sicherungstabelle. In
>> der Datentabelle kann ich in einer Spalte den "Delinquenten" und seine
>> Änderungs-DATETIME eintragen. Bei INSERT und bei UPDATE wird das dann im
>> after_insert_tr bzw. im after_update_tr mit den anderen Daten der
>> datentabelle an die Sicherungsdatei angehängt.
>
> Vermutlich sind das Deine benutzerdefinierten Trigger-Namen. In dem Fall
> solltest Du nicht davon ausgehen, dass das jeder ohne Erklärung versteht.
Ich dachte die Namen sind sprechend. Aber Du hast Recht, das ist nicht
immer und bei Jedem so und sollte immer eindeutig beschrieben werden -
will mich bessern (hoffentlich denke ich dann auch dran).
>
>> Wie geht so was denn bei DELETE?
>
> Genauso.
>
>> Werte bei DELETE zu übergeben ist ja witzlos, weil sie mit dem Datensatz
>> ja sowieso gelöscht werden,
>
> Nein. Es gibt bloss keine *neuen* Werte.
Hier ist das genau so, ich sollte mitteilen, dass ich **neue** Werte
meine. Auf die **alten** Werte kann ich ja mit OLD.spaltenname zugreifen.

>
>> deshalb lehnt das DBMS das ja auch ab.
>
> Tut es nicht.
>
>> Wie kann ich das sonst noch machen? Ich habe schon von dem
>> vorherigen setzen einer globalen Variablen gelesen, die dann im Trigger
>> ausgewertet werden kann - das gefällt mir aber gar nicht. Oder muss ich
>> dafür eine Pozedur schreiben, die dann auch den Löschbefehl enthält? Das
>> missfällt mir aber auch.
>
> CREATE TRIGGER `foo` BEFORE DELETE ON `bar`
> FOR EACH ROW INSERT INTO `baz` (`bla`) VALUES (OLD.`bla`)
>
> und anschliessendes
>
> DELETE FROM `bar` WHERE …
>
> funktioniert in MySQL 5.5 auf einer (MyISAM-)Tabelle namens `bar`
> ausgezeichnet, d. h. der (MyISAM-)Tabelle `baz` wird ein entsprechender
> Datensatz hinzugefügt. (Mit InnoDb funktioniert das sicher auch.)
>

So, und jetzt noch mal ausführlich, auch auf die Gefahr hin, dass so
viel Text niemand mehr liest.

Prinzip ist bei mir, dass ich für jede Datentabelle eine
Sicherungstabelle, an die durch einen AFTER Trigger die Veränderungen
angehängt werden.
Die Sicherungstabelle ist genau so aufgebaut, wie die Datentabelle, die
ID der Datentabelle ist darin aber kein UNIQUE und sie hat noch 2
zusätzliche Felder:
eine eigene ID und eine Spalte `action`, in die INSERT, UPDATE oder
DELETE eingetragen wird.


Meine (verkürzte) Datentabelle `tbl_geraetetyp` sieht so aus:
CREATE TABLE `tbl_geraetetyp` (
`id_geraet` int(11) unsigned NOT NULL,
`geraetebezeichnung` varchar(240) COLLATE latin1_german1_ci NOT NULL,
`geaendertam` datetime DEFAULT NULL,
`geaendertvon` varchar(240) COLLATE latin1_german1_ci DEFAULT NULL,
`version` int(11) unsigned NOT NULL DEFAULT '1',
PRIMARY KEY (`id_geraet`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_german1_ci;

Meine (verkürzte) Sicherungstabelle `stbl_geraetetyp` sieht so aus:
CREATE TABLE `tbl_geraetetyp` (
`ids_geraet` int(11) unsigned NOT NULL,
`id_geraet` int(11) unsigned NOT NULL,
`geraetebezeichnung` varchar(240) COLLATE latin1_german1_ci NOT NULL,
`geaendertam` datetime DEFAULT NULL,
`geaendertvon` varchar(240) COLLATE latin1_german1_ci DEFAULT NULL,
`version` int(11) unsigned NOT NULL DEFAULT '1',
PRIMARY KEY (`ids_geraet`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_german1_ci;

Mein (verkürzter) Trigger `tbl_geraetetyp_before_ins_tr` in der
Datentabelle `tbl_geraetetyp` für BEFORE INSERT sieht so aus:
BEGIN
DECLARE var_geaendertvon VARCHAR(240) DEFAULT '';
SET var_geaendertvon := SUBSTRING_INDEX(USER(),'@',1);
IF 1 = 1 AND NOT "spezialuser" = var_geaendertvon THEN
IF (NOT 'anwendungsname' = var_geaendertvon) THEN
SET NEW.`geaendertvon` := var_geaendertvon;
END IF;
SET NEW.`geaendertam` := NOW(),
NEW.`version` := 1;
END IF;
END;

Mein (verkürzter) Trigger `tbl_geraetetyp_after_ins_tr` in der
Datentabelle `tbl_geraetetyp` für AFTER INSERT sieht so aus:
BEGIN
DECLARE var_geaendertvon VARCHAR(240) DEFAULT '';
SET var_geaendertvon := SUBSTRING_INDEX(USER(),'@',1);
IF 1 = 1 AND NOT "spezialuser" = var_geaendertvon THEN
SET @aktion = "INSERT";
INSERT INTO `stbl_geraetetyp`
VALUES(
NULL,
NEW.`id_geraet`,
NEW.`geraetebezeichnung`,
NEW.`geaendertam`,
NEW.`geaendertvon`,
NEW.`version`,
@aktion
);
END IF;
END;

Mein (verkürzter) Trigger `tbl_geraetetyp_before_upd_tr` in der
Datentabelle `tbl_geraetetyp` für BEFORE UPDATE sieht so aus:
BEGIN
DECLARE var_geaendertvon VARCHAR(240) DEFAULT '';
SET var_geaendertvon := SUBSTRING_INDEX(USER(),'@',1);
IF 1 = 1 AND NOT "spezialuser" = var_geaendertvon THEN
IF (NOT 'anwendungsname' = var_geaendertvon) THEN
SET NEW.`geaendertvon` := var_geaendertvon;
END IF;
SET NEW.`geaendertam` := NOW(),
NEW.`version` := OLD.`version` + 1;
END IF;
END;

Mein (verkürzter) Trigger `tbl_geraetetyp_after_upd_tr` in der
Datentabelle `tbl_geraetetyp` für AFTER UPDATE sieht so aus:
BEGIN
DECLARE var_geaendertvon VARCHAR(240) DEFAULT '';
SET var_geaendertvon := SUBSTRING_INDEX(USER(),'@',1);
IF 1 = 1 AND NOT "spezialuser" = var_geaendertvon THEN
SET @aktion = "UPDATE";
INSERT INTO `stbl_geraetetyp`
VALUES(
NULL,
NEW.`id_geraet`,
NEW.`geraetebezeichnung`,
NEW.`geaendertam`,
NEW.`geaendertvon`,
NEW.`version`,
@aktion
);
END IF;
END;

Einen Trigger `tbl_geraetetyp_before_del_tr` gibt es nicht.

Mein (verkürzter) Trigger `tbl_geraetetyp_after_del_tr` in der
Datentabelle `tbl_geraetetyp` für AFTER DELETE sieht so aus:
BEGIN
DECLARE var_geaendertvon VARCHAR(240) DEFAULT '';
SET var_geaendertvon := SUBSTRING_INDEX(USER(),'@',1);
IF 1 = 1 AND NOT "spezialuser" = var_geaendertvon THEN
SET @aktion = "DELETE";
INSERT INTO `stbl_geraetetyp`
VALUES(
NULL,
OLD.`id_geraet`,
OLD.`geraetebezeichnung`,
NOW(),
var_geaendertvon,
OLD.`version`,
@aktion
);
END IF;
END;

Ich kann nicht davon ausgehen, dass derjenige, welcher einen Datensatz
angelegt oder zuletzt editiert hat hat, auch derjenige ist, der ihn
löscht - und genau den möchte ich fragen können, wenn ich mit irgend
etwas nach dem Löschen nicht klar komme.

Leider meldet sich die Anwendung nicht mit dem Login-Namen des Benutzers
an, sondern mit dem Namen der Anwendung 'anwendungsname', so dass die
Datenbank den Login-Namen des Benutzers explizit genannt kriegen muss.

Bei INSERT und UPDATE kann man dazu Ausdrücke mitgeben:

INSERT tbl_name
SET col_name1=expr1 [, col_name2=expr2 ...]
WHERE where_condition;

oder

INSERT INTO tbl_name
VALUES (
expr,
...
);

UPDATE tbl_name
SET col_name1=expr1 [, col_name2=expr2 ...]
WHERE where_condition;

Das geht bei DELETE aber nicht:

DELETE FROM tbl_name
WHERE where_condition;

Und jetzt endlich kommt meine Frage noch mal:

Wie kriege ich an den Trigger `tbl_geraetetyp_after_del_tr` für AFTER
DELETE den Login-Namen des Benutzers übergeben, damit er in der
Sicherungstabelle `stbl_geraetetyp` eingetragen werden kann.

Danke

Andreas



Thomas 'PointedEars' Lahn

unread,
Apr 22, 2013, 9:54:42 AM4/22/13
to
Andreas Horn wrote:

> […]
> Einen Trigger `tbl_geraetetyp_before_del_tr` gibt es nicht.
>
> Mein (verkürzter) Trigger `tbl_geraetetyp_after_del_tr` in der
> Datentabelle `tbl_geraetetyp` für AFTER DELETE sieht so aus:
> BEGIN
> DECLARE var_geaendertvon VARCHAR(240) DEFAULT '';
> SET var_geaendertvon := SUBSTRING_INDEX(USER(),'@',1);
> IF 1 = 1 AND NOT "spezialuser" = var_geaendertvon THEN
> SET @aktion = "DELETE";
> INSERT INTO `stbl_geraetetyp`
> VALUES(
> NULL,
> OLD.`id_geraet`,
> OLD.`geraetebezeichnung`,
> NOW(),
> var_geaendertvon,
> OLD.`version`,
> @aktion
> );
> END IF;
> END;

MySQL kann man auch lesbar schreiben, zum Beispiel:

BEGIN
DECLARE var_geaendertvon VARCHAR(240) DEFAULT '';
SET var_geaendertvon := SUBSTRING_INDEX(USER(), '@', 1);

(Es fe lt die Angabe, welche MySQL-Version verwendet wird.)

A) Gar nicht; ein Trigger ist im Unterschied zu einer Prozedur oder
Funktion statischer Code, der keine Parameter akzeptiert. Du musst
das also ohne Trigger lösen.

B) Indem Du im Trigger auf eine globale Benutzervariable zugreifst,
die Du vorher z. B. mit „SET @app_username = '…';“ setzt.

C) Indem Du zuerst mit UPDATE den Namen des Löschenden in ein
entsprechendes Feld des Datensatzes einträgst, und erst dann den
Datensatz löschst. Im DELETE-Trigger kannst Du dann mit OLD.`eraser`
o. ä. auf den Namen zugreifen, z. B. für eine INSERT-Anweisung.

D) Indem Du für jeden für die Aktion berechtigten Applikationsbenutzer
einen entsprechenden Datenbankbenutzer mit eingeschränkten
Berechtigungen erstellst und mit diesen auf die Datenbank zugreifst.
Dann kannst Du mit USER() den Namen im Trigger abfragen (so wie Du es
bereits getan hast).

Einen „AFTER DELETE“-Trigger halte ich hier weiterhin für einen Logikfehler,
denn so wird der Datensatz wird auch dann gelöscht, wenn der Trigger
fehlschlug. Schlägt hingegen ein „BEFORE DELETE“-Trigger fehl, wird der
Datensatz nicht gelöscht; somit kann dann keine Information verlorengehen.

<http://dev.mysql.com/doc/refman/5.6/en/trigger-syntax.html>

Andreas Horn

unread,
Apr 22, 2013, 4:37:37 PM4/22/13
to
sorry, das stimmt natürlich.

>> Und jetzt endlich kommt meine Frage noch mal:
>>
>> Wie kriege ich an den Trigger `tbl_geraetetyp_after_del_tr` für AFTER
>> DELETE den Login-Namen des Benutzers übergeben, damit er in der
>> Sicherungstabelle `stbl_geraetetyp` eingetragen werden kann.
>
> (Es fe lt die Angabe, welche MySQL-Version verwendet wird.)
>
> A) Gar nicht; ein Trigger ist im Unterschied zu einer Prozedur oder
> Funktion statischer Code, der keine Parameter akzeptiert. Du musst
> das also ohne Trigger lösen.
hierfür müsste ich mich also mit SP beschäftigen

>
> B) Indem Du im Trigger auf eine globale Benutzervariable zugreifst,
> die Du vorher z. B. mit „SET @app_username = '…';“ setzt.
das wollte ich tunlichst vermeiden

>
> C) Indem Du zuerst mit UPDATE den Namen des Löschenden in ein
> entsprechendes Feld des Datensatzes einträgst, und erst dann den
> Datensatz löschst. Im DELETE-Trigger kannst Du dann mit OLD.`eraser`
> o. ä. auf den Namen zugreifen, z. B. für eine INSERT-Anweisung.
das müsste ich dann mit Transaction kapseln

>
> D) Indem Du für jeden für die Aktion berechtigten Applikationsbenutzer
> einen entsprechenden Datenbankbenutzer mit eingeschränkten
> Berechtigungen erstellst und mit diesen auf die Datenbank zugreifst.
> Dann kannst Du mit USER() den Namen im Trigger abfragen (so wie Du es
> bereits getan hast).
und das hier habe ich nicht verstanden

>
> Einen „AFTER DELETE“-Trigger halte ich hier weiterhin für einen Logikfehler,
> denn so wird der Datensatz wird auch dann gelöscht, wenn der Trigger
> fehlschlug. Schlägt hingegen ein „BEFORE DELETE“-Trigger fehl, wird der
> Datensatz nicht gelöscht; somit kann dann keine Information verlorengehen.
>
> <http://dev.mysql.com/doc/refman/5.6/en/trigger-syntax.html>
>
dieser Tipp ist einleuchtend

Da coden nicht mein eigentlicher Job ist, sondern nur
"Heimsport für meien Job", werde ich wohl noch 'ne Weile dazu brauchen,
um mich hier ranzutasten.

Danke Thomas!

Andreas

Thomas 'PointedEars' Lahn

unread,
Apr 22, 2013, 6:54:27 PM4/22/13
to
Andreas Horn wrote:

> Am 22.04.2013 15:54, schrieb Thomas 'PointedEars' Lahn:
>> Andreas Horn wrote:
>>> Wie kriege ich an den Trigger `tbl_geraetetyp_after_del_tr` für AFTER
>>> DELETE den Login-Namen des Benutzers übergeben, damit er in der
>>> Sicherungstabelle `stbl_geraetetyp` eingetragen werden kann.
>>
>> (Es fe lt die Angabe, welche MySQL-Version verwendet wird.)
>>
>> A) Gar nicht; ein Trigger ist im Unterschied zu einer Prozedur oder
>> Funktion statischer Code, der keine Parameter akzeptiert. Du musst
>> das also ohne Trigger lösen.
>
> hierfür müsste ich mich also mit SP beschäftigen

Nicht unbedingt; es hängt davon ab, wie weit Du die Applikation anpassen
kannst/willst.

>> B) Indem Du im Trigger auf eine globale Benutzervariable zugreifst,
>> die Du vorher z. B. mit „SET @app_username = '…';“ setzt.
>
> das wollte ich tunlichst vermeiden

Weshalb? Genau für solche Fälle gibt es Benutzervariablen. Die Variable
muss nur nach Aufbau der Verbindung von der Applikation gesetzt werden und
existiert dann (AFAIK) für den Rest der Verbindung. Das lässt sich gut in
der Applikationsroutine unterbringen, welche die Verbindung aufbaut (zum
Beispiel nach der MySQL-Anweisung, die unabhängig von der
Serverkonfiguration die richtige Zeichencodierung für Verbindungen setzt –
bei mir in der Regel “SET NAMES utf8”).

>> C) Indem Du zuerst mit UPDATE den Namen des Löschenden in ein
>> entsprechendes Feld des Datensatzes einträgst, und erst dann den
>> Datensatz löschst. Im DELETE-Trigger kannst Du dann mit
>> OLD.`eraser` o. ä. auf den Namen zugreifen, z. B. für eine
>> INSERT-Anweisung.
>
> das müsste ich dann mit Transaction kapseln

Das wäre sinnvoll, ist aber ebenfalls nicht zwingend.

>> D) Indem Du für jeden für die Aktion berechtigten Applikationsbenutzer
>> einen entsprechenden Datenbankbenutzer mit eingeschränkten
>> Berechtigungen erstellst und mit diesen auf die Datenbank
>> zugreifst. Dann kannst Du mit USER() den Namen im Trigger abfragen
>> (so wie Du es bereits getan hast).
>
> und das hier habe ich nicht verstanden

Ich habe Dich so verstanden, dass der Benutzername in der Applikation nicht
dem Benutzernamen in der Datenbank entspricht, weil Datenbankzugriffe der
Applikation nur mit einem bestimmten anderen Datenbankbenutzer stattfinden
(dessen Anmeldename dann USER() liefert).

Wenn Du aber für jeden solchen Datenbankzugriff mit einem Datenbankbenutzer
zugreifst, der dem jeweiligen Applikationsbenutzer entspricht, liefert
USER() den richtigen Benutzernamen. Dieser gespiegelte Benutzer sollte
natürlich jeweils nur die unbedingt nötigen Berechtigungen auf die Datenbank
haben, und Du bräuchtest nur Datenbankbenutzer einzurichten für die
Applikationsbenutzer, die z. B. auch löschen dürfen.

Diese Variante hat den Buchstaben D, weil sie natürlich gegenüber den
vorherigen Varianten einige Nachteile hat. Zum Beispiel müsstest Du von
Applikationsseite her Datenbankbenutzer erstellen können, damit Du das nicht
manuell doppelt pflegen musst. Entsprechend gut abgesichert müsste diese
Applikationsfunktionalität sein, ggf. sogar in einer eigenen, besonders
gesicherten Applikation untergebracht sein.

> Danke Thomas!

Gern geschehen.

Bitte lies und beherzige aber das nächste Mal <http://learn.to/quote>.
Insbesondere sollte zwischen altem, *sinnvoll gekürzten* und neuem Text eine
Leerzeile stehen; das ist wesentlich besser lesbar.

Andreas Horn

unread,
Apr 29, 2013, 3:05:29 PM4/29/13
to
Hallo Thomas,

... nur zur Info: Die AFTER-Trigger der über 20 Tabellen sind Deiner
Empfehlung entsprechung jetzt umgestellt auf BEFORE-Trigger, mühsam
ernährt sich das Eich-"Hörnchen" ...

... und jetzt fange ich an, die anderen Vorschläge zu untersuchen ...

Andreas

Thomas 'PointedEars' Lahn

unread,
Apr 29, 2013, 3:17:13 PM4/29/13
to
Andreas Horn wrote:

> ... nur zur Info: Die AFTER-Trigger der über 20 Tabellen sind Deiner
> Empfehlung entsprechung jetzt umgestellt auf BEFORE-Trigger, mühsam
> ernährt sich das Eich-"Hörnchen" ...

Ich hatte lediglich empfohlen, die *DELETE*-Trigger auf BEFORE umzustellen,
damit beim Löschen (hier: Verschieben) keine Daten verlorengehen.

Wie hast Du die Umstellung realisiert?
0 new messages