Trigger im SQL Server

Auch wenn Access und die Jet-Engine bereits zahlreiche Möglichkeiten einer “richtigen” Datenbank enthalten, fehlen immer noch wichtige Funktionen: So finden Sie etwa beim Umstieg auf SQL Server-Backends viel Bekanntes, aber auch Unbekanntes – zum Beispiel Trigger. Im vorliegenden Beitrag stellen wir den Nutzen von und den Umgang mit Triggern vor.

Welche Aufgaben haben Trigger im SQL Server eigentlich Laut übersetzungshilfe beschreibt das Substantiv “Trigger” den Auslöser, den Anstoß oder den Abzugsbügel und das Verb entsprechend ansteuern, auslösen, einleiten.

Ein Trigger löst also bei bestimmten Aktionen etwas aus. Da wir uns im SQL Server befinden, kann also nur ein SQL-Befehl etwas auslösen und das kann hier natürlich auch nur wieder ein SQL-Statement sein. Damit realisieren Trigger etwa die Funktionalität, die wir vom Client und dabei etwa aus Access als VBA-Ereignisprozedur kennen. Mit Ereignisprozeduren können Sie beispielsweise beim Einfügen eines Datensatzes eine Aktion auslösen und diesen Vorgang unter bestimmten Bedingungen abbrechen (s. Listing 1).

Listing 1: Eine Ereignisprozedur in VBA

Private Sub Form_BeforeInsert(Cancel As Integer)

If Name = "Doofmann" Then

MsgBox "Solche Namen wollen wir hier nicht!"

Cancel = True

End If

End Sub

Die unterschiedlichen Client-Programmierumgebungen unterstützen unterschiedliche Ereignisse. Was in VBA unter Access geht, ist vielleicht unter ADO.NET nicht verfügbar, und andersherum, unter ADO und C++ wiederum finden Sie wieder ganz andere Möglichkeiten.

Wenn aber die unterschiedlichen Clients solche Funktionen kennen, warum brauchen wir sie dann noch einmal im SQL Server In einer richtigen Client-Server-Umgebung ist es eben nicht sichergestellt, dass alle Veränderungen an den Daten einer Datenbank über denselben Client abgewickelt werden. Wenn Sie eine Access-Anwendung auf einer SQL Server 2005-Datenbank programmieren und vor dem Löschen eines Datensatzes diesen unbedingt in eine “Ist-gelöscht-worden”-Tabelle schreiben möchten, dann ist die Ereignisprozedur in Access nicht der richtige Weg, da jeder Berechtigte direkt auf die SQL Server-Datenbank zugreifen und ein DELETE FROM tblMeineDaten WHERE ID=9 absetzen kann. Der Datensatz wäre ohne Dokumentation verschwunden (die Syntax “DELETE * FROM…”, also mit “*” ist im übrigen Access-Dialekt und nicht ANSI SQL).

Die Funktionalität wird also vom Client auf den Server verschoben. ähnliches kennen Sie schon von der referentiellen Integrität. Auch das Löschen von Master-Datensätzen bei existierenden Detail-Datensätzen kann man mit einer client-seitigen Ereignisprozedur überwachen und gegebenenfalls verhindern, doch Access kann dies auch automatisch unterlassen.

Im SQL Server haben Sie noch viel mehr Möglichkeiten: Angenommen, Sie möchten die erwähnte Funktionalität umsetzen und jeden Datensatz vor dem Löschen in eine Dokumentations- oder Historisierungstabelle schreiben, dann sind Trigger genau das Richtige für Sie.

Zum Nachvollziehen der Beispiele bauen Sie am besten direkt eine kleine Testdatenbank auf. Dazu können Sie das Skript aus Listing 2 verwenden, das Sie am einfachsten über das SQL Server Management Studio des SQL Servers 2005 eingeben und ausgeführen (weitere Informationen siehe [1]). Die Syntax wird aber auch vom SQL Server 2000 unterstützt, die ersten Beispiele können Sie daher auch auf diesem nachvollziehen und das Skript beispielsweise im Query Analyser des SQL Servers 2000 ausführen. Wenn Sie die Beispiele wieder löschen wollen, löschen Sie einfach die gesamte Test-Datenbank mit dem Befehl DROP DATABASE TestTrigger. Falls Sie die Fehlermeldung erhalten, dass die Datenbank nicht gelöscht werden kann, weil sie in Benutzung ist (wahrscheinlich von Ihnen selbst durch die Tests), hilft der folgende kleine Trick beim Schließen aller bestehenden Verbindungen:

USE master
go
ALTER DATABASE TriggerTest SET SINGLE_USER WITH ROLLBACK IMMEDIATE
go
DROP DATABASE TriggerTest

Das Skript aus Listing 2 erledigt Folgendes: Der Befehl USE wechselt den aktuellen Datenbankkontext, weil es im SQL Server in einer Instanz mehrere Datenbanken geben kann (und meistens auch gibt). Nach dem Erstellen der Datenbank TestTrigger erstellt das Skript mit der CREATE TABLE-Anweisung zwei Tabellen. Die erste Tabelle MyData enthält die Ausgangsdaten, die zweite Tabelle MyDataHistory soll die Löschvorgänge mit Uhrzeit (DEFAULT entspricht dem Standardwert eines Feldes unter Access) und aktuellem Benutzer dokumentieren. Der Befehl GO beendet einen Batch, wobei zur Vereinfachung beinahe alle Befehle in einem einzigen Batch untergebracht sind. Die Anweisungen können dann einzeln markiert und nacheinander ausgeführt werden (wenn Sie F5 oder STRG+E drücken, wird sowohl im Query Analyser des SQL Server 2000 als auch im SQL Server Management Studio des SQL Server 2005 nur die Markierung ausgeführt).

Listing 2: Erstellen der benötigten Objekte für die Beispiele

-- SETUP der Beispieldatenbank ----------
USE master
go
CREATE DATABASE TriggerTest
GO
USE TriggerTest
GO
CREATE TABLE MyData
    ID int identity Primary Key,
    Name nvarchar(80)
)
GO
INSERT INTO MyData VALUES ('Müller')
INSERT INTO MyData VALUES ('Maier')
INSERT INTO MyData VALUES ('Schmidt')
INSERT INTO MyData VALUES ('Schulz')
INSERT INTO MyData VALUES ('Schäfer')
GO
SELECT * FROM myData
GO
CREATE TABLE MyDataHistory
    ID int,
    Name nvarchar(80),
    DeletedAt datetime DEFAULT GETDATE(),
    DeletedFrom nvarchar(80) DEFAULT SYSTEM_USER
)

Ein einfacher Trigger

Einen Trigger erstellt man wie andere Objekte in SQL mit dem CREATE-Befehl, hier mit CREATE TRIGGER. Trigger wie der geplante werden immer für eine bestimmte Tabelle erstellt. Diese Tabelle legt man mit dem Schlüsselwort ON fest (s. Listing 3).

Listing 3: Erstellen eines einfachen Triggers

CREATE TRIGGER DataHistory ON myData FOR DELETE

AS

BEGIN

INSERT INTO MyDataHistory (ID, Name) SELECT ID, Name FROM deleted

END

Beachten Sie das Schlüsselwort FOR DELETE. Genauso können Sie auch Trigger für das Einfügen und ändern erstellen, dann verwenden Sie das Schlüsselwort FOR INSERT oder FOR UPDATE. Es gibt auch gleichnamige gemeinsame Trigger.

Löschen Sie nun mit den folgenden Anweisungen einmal ein paar Datensätze:

DELETE FROM MyData where ID=2
DELETE FROM MyData where ID=5
GO

Schauen Sie sich das Ergebnis in den beiden Tabellen an:

SELECT * FROM myData
SELECT * FROM myDataHistory

Interessant ist hier der Befehl, der im Trigger selbst ausgeführt wird: INSERT INTO… SELECT … FROM deleted. Um welche Tabelle handelt es sich denn bei deleted

Dies ist keine tatsächlich im SQL Server vorhandene Tabelle. Die Abfrage SELECT * FROM deleted wird vom SQL Server dementsprechend mit der Fehlermeldung Invalid object name 'deleted” beantwortet.

Die Trigger-Tabellen

Der SQL Server stellt die Tabelle deleted allein im Kontext eines Triggers bereit und auch nur für die Dauer der Durchführung des Triggers. Es handelt sich tatsächlich um eine Tabelle, wenn auch keine im üblichen Sinne, und nicht etwa um einen einzelnen Datensatz, denn in einem SQL-Statement können durchaus mehrere Datensätze gelöscht werden. Für jedes Statement wird der Trigger aber nur einmal ausgelöst. Wenn Sie drei Datensätze wie im unten stehenden Listing löschen, wird der Trigger trotzdem nur einmal ausgelöst, die Tabelle deleted enthält dann eben drei Datensätze, wie wir einfach nachweisen können.

Bemerkenswert ist, dass der SQL Server beim Löschen von Datensätzen auch zwei Meldungen erzeugt (siehe Bild 1). (3 row(s) affected) bezieht sich bei der ersten Meldung auf das Einfügen in die History-Tabelle und bei der zweiten Meldung auf den Löschvorgang in MyData. Ob dies tatsächlich die richtige Reihenfolge ist, lässt sich leicht prüfen. Dazu ändern Sie den Trigger wie folgt, was dazu führt, dass der Datensatz mit dem Wert Beethoven für das Feld Name nicht in die History-Tabelle geschrieben wird.

ALTER TRIGGER [dbo].[DataHistory] ON [dbo].[MyData] FOR DELETE
AS
BEGIN
INSERT INTO MyDataHistory (ID, NAME)
SELECT ID, Name FROM deleted WHERE Name<>'Beethoven'
END

Trigger01.bmp

Bild 1: Ausgabe im SQL Server Management Studio

Dementsprechend sollten nun beim Löschen dreier Datensätze zwei Meldungen erscheinen, von denen die eine drei und die andere zwei Datensätze betrifft. Wie Bild 2 zeigt, führt der SQL Server tatsächlich zunächst die im Trigger enthaltenen Operationen und erst dann den eigentlichen Löschvorgang durch.

Trigger02.bmp

Bild 2: Der Trigger fügt erst die gelöschten Datensätze ein und löscht diese dann aus der Originaltabelle.

So jedenfalls scheint es. Die Aktion des Triggers wird aber laut Dokumentation erst nach der eigentlichen Aktion des SQL-Statements ausgeführt. Alle Trigger im SQL Server 2000 und 2005 sind standardmäßig AFTER-Trigger. Das Schlüsselwort FOR ist dabei gleichbedeutend mit AFTER.

INSERT- und UPDATE-Trigger

Wir haben bislang immer mit einem DELETE-Trigger experimentiert, wie verhalten sich die anderen DML (Data Manipulation Language)-Trigger, die beim Einfügen (Insert) und Aktualisieren (Update) ausgeführt werden

Im Prinzip genauso, nur werden unterschiedliche Tabellen innerhalb der Trigger vom SQL Server verwaltet. Um dies nachzuvollziehen, erweitern wir die History-Tabelle um einen Eintrag, der anzeigt, ob die Daten gelöscht, eingefügt oder geändert wurden (s. Listing 4).

Listing 4: Historisieren eingefügter Datensätze

ALTER TABLE MyDataHistory ADD Type nvarchar(8)
GO
UPDATE MyDataHistory SET Type='delete'
GO
ALTER TRIGGER dbo.DataHistory ON dbo.MyData FOR DELETE
AS
BEGIN
     INSERT INTO MyDataHistory (ID, Name, Type)
     Select ID, Name, 'delete' FROM deleted 
END
GO
CREATE TRIGGER dbo.InsertHistory ON dbo.MyData AFTER INSERT
AS
BEGIN
    INSERT INTO MyDataHistory (ID, Name, Type) 
    Select ID, Name, 'insert' FROM inserted 
END
GO
INSERT INTO MyData VALUES ('Wagner')
GO
SELECT * FROM MyData
SELECT * FROM MyDataHistory

Wie Sie sehen, wird beim Einfügen innerhalb des INSERT-Triggers eine Tabelle inserted bereitgestellt. Und beim UPDATE-Trigger Falsch geraten! Es gibt keine Tabelle namens updated. Stattdessen benutzt der Update-Trigger beide von den anderen Triggern verwendeten Tabellen. In der Tabelle inserted werden die neuen Werte abgelegt und in der Tabelle deleted die gelöschten (s. Listing 5).

Listing 5: Update-Trigger greifen auf die beiden Tabellen deleted und inserted zu.

Ende des frei verfügbaren Teil. Wenn Du mehr lesen möchtest, hole Dir ...

den kompletten Artikel im PDF-Format mit Beispieldatenbank

diesen und alle anderen Artikel mit dem Jahresabo

Schreibe einen Kommentar