Oracle PL/SQL – Trigger

Fach: Informationstechnologie – Datenbanksysteme
Schulstufe: 12. Schulstufe – HTL Informatik
Voraussetzungen: Prozeduren, Funktionen, Packages, Exceptions
Autor: HTL Pinkafeld – IF/IT



1. Was sind Trigger?

1.1 Definition

Ein Trigger ist ein gespeicherter PL/SQL-Block, der automatisch ausgeführt wird, wenn ein bestimmtes Ereignis in der Datenbank eintritt. Im Gegensatz zu Prozeduren wird ein Trigger nicht explizit aufgerufen.

Ereignis (INSERT / UPDATE / DELETE)
    ↓
Oracle erkennt: Trigger ist definiert
    ↓
Trigger-Code wird automatisch ausgeführt
    ↓
Normale Verarbeitung setzt fort (oder wird abgebrochen)

1.2 Trigger-Typen

Typ Ausgelöst durch Typische Anwendung
DML-Trigger INSERT, UPDATE, DELETE Auditing, Validierung, Ableitung
INSTEAD OF DML auf Views Änderbare komplexe Views
DDL-Trigger CREATE, ALTER, DROP Schema-Schutz, Änderungsprotokoll
System-Trigger LOGON, LOGOFF, STARTUP, SHUTDOWN Session-Logging, Initialisierung

1.3 Anwendungsfälle


2. DML-Trigger erstellen

2.1 Syntax

CREATE [OR REPLACE] TRIGGER triggername
    {BEFORE | AFTER | INSTEAD OF}
    {INSERT | UPDATE [OF spalte] | DELETE} [OR {INSERT | UPDATE | DELETE}] ...
    ON tabellenname
    [REFERENCING {NEW AS neu | OLD AS alt}]
    [FOR EACH ROW]
    [WHEN (bedingung)]
DECLARE
    -- Variablendeklarationen (optional)
BEGIN
    -- Trigger-Code
EXCEPTION
    -- Fehlerbehandlung (optional)
END [triggername];
/

2.2 Einfacher AFTER INSERT Trigger

-- Jedes neue Mitglied in einer Audit-Tabelle protokollieren:
CREATE OR REPLACE TRIGGER trg_emp_insert
AFTER INSERT ON emp
FOR EACH ROW
BEGIN
    INSERT INTO emp_audit (aktion, empno, ename, sal, zeitpunkt, benutzer)
    VALUES ('INSERT', :NEW.empno, :NEW.ename, :NEW.sal, SYSTIMESTAMP, USER);
END trg_emp_insert;
/

-- Test:
INSERT INTO emp (empno, ename, job, deptno, sal, hiredate)
VALUES (9001, 'NEWUSER', 'CLERK', 10, 2000, SYSDATE);
COMMIT;

-- Audit-Eintrag prüfen:
SELECT * FROM emp_audit WHERE empno = 9001;

2.3 BEFORE INSERT – Wert setzen

-- Primärschlüssel automatisch aus Sequence befüllen:
CREATE OR REPLACE TRIGGER trg_emp_pk
BEFORE INSERT ON emp
FOR EACH ROW
WHEN (NEW.empno IS NULL)    -- Nur wenn kein Wert angegeben
BEGIN
    :NEW.empno := emp_seq.NEXTVAL;
END;
/

-- Test (empno wird automatisch gesetzt):
INSERT INTO emp (ename, job, deptno, sal, hiredate)
VALUES ('AUTOUSER', 'ANALYST', 20, 3500, SYSDATE);
COMMIT;

3. Zeilen- vs. Statement-Trigger

3.1 Statement-Trigger (Standard)

Ein Statement-Trigger wird einmal pro DML-Anweisung ausgeführt – unabhängig davon, wie viele Zeilen betroffen sind. Kein FOR EACH ROW:

CREATE OR REPLACE TRIGGER trg_dept_access
BEFORE INSERT OR UPDATE OR DELETE ON dept
BEGIN
    -- Außerhalb der Geschäftszeiten keine Änderungen erlauben:
    IF TO_NUMBER(TO_CHAR(SYSDATE, 'HH24')) NOT BETWEEN 8 AND 17 THEN
        RAISE_APPLICATION_ERROR(
            -20100,
            'Änderungen an DEPT nur zwischen 08:00 und 17:00 erlaubt!'
        );
    END IF;
    IF TO_CHAR(SYSDATE, 'DY', 'NLS_DATE_LANGUAGE=ENGLISH') IN ('SAT', 'SUN') THEN
        RAISE_APPLICATION_ERROR(-20101, 'Keine Änderungen am Wochenende!');
    END IF;
END trg_dept_access;
/

3.2 Zeilenorientierter Trigger mit FOR EACH ROW

Ein Row-Level Trigger wird für jede betroffene Zeile einmal ausgeführt. Bei UPDATE emp SET sal = sal * 1.1 – 14 Mitarbeiter → 14 Trigger-Aufrufe:

CREATE OR REPLACE TRIGGER trg_sal_check
BEFORE UPDATE OF sal ON emp
FOR EACH ROW
BEGIN
    -- Gehaltskürzung um mehr als 20% verhindern:
    IF :NEW.sal < :OLD.sal * 0.80 THEN
        RAISE_APPLICATION_ERROR(
            -20200,
            'Gehaltskürzung um mehr als 20% nicht erlaubt! (' ||
            :OLD.sal || ' → ' || :NEW.sal || ')'
        );
    END IF;
END trg_sal_check;
/

3.3 Ausführungsreihenfolge

DML-Statement (z.B. UPDATE auf 5 Zeilen)
    ↓
BEFORE Statement-Trigger  (1×)
    ↓
    Für jede betroffene Zeile:
    ├─ BEFORE Row-Trigger  (5×)
    ├─ Zeile wird geändert
    └─ AFTER Row-Trigger   (5×)
    ↓
AFTER Statement-Trigger   (1×)

4. :NEW und :OLD Pseudorecords

4.1 Verfügbarkeit

Zeitpunkt :OLD :NEW
BEFORE INSERT NULL Neuer Wert (änderbar)
AFTER INSERT NULL Eingefügter Wert
BEFORE UPDATE Alter Wert Neuer Wert (änderbar)
AFTER UPDATE Alter Wert Neuer Wert
BEFORE DELETE Alter Wert NULL
AFTER DELETE Alter Wert NULL

Wichtig: :NEW kann nur in BEFORE-Triggern geändert werden!

4.2 Beispiel: Vollständiges Audit

-- Audit-Tabelle erstellen:
CREATE TABLE emp_audit (
    audit_id   NUMBER GENERATED ALWAYS AS IDENTITY,
    zeitpunkt  TIMESTAMP DEFAULT SYSTIMESTAMP,
    benutzer   VARCHAR2(30) DEFAULT USER,
    aktion     VARCHAR2(10),
    empno      NUMBER,
    alt_ename  VARCHAR2(10),
    neu_ename  VARCHAR2(10),
    alt_sal    NUMBER,
    neu_sal    NUMBER,
    alt_deptno NUMBER,
    neu_deptno NUMBER
);

-- Audit-Trigger:
CREATE OR REPLACE TRIGGER trg_emp_audit
AFTER INSERT OR UPDATE OR DELETE ON emp
FOR EACH ROW
DECLARE
    v_aktion VARCHAR2(10);
BEGIN
    IF    INSERTING THEN v_aktion := 'INSERT';
    ELSIF UPDATING  THEN v_aktion := 'UPDATE';
    ELSIF DELETING  THEN v_aktion := 'DELETE';
    END IF;

    INSERT INTO emp_audit
        (aktion, empno, alt_ename, neu_ename, alt_sal, neu_sal, alt_deptno, neu_deptno)
    VALUES
        (v_aktion,
         COALESCE(:OLD.empno, :NEW.empno),
         :OLD.ename,  :NEW.ename,
         :OLD.sal,    :NEW.sal,
         :OLD.deptno, :NEW.deptno);
END trg_emp_audit;
/

4.3 INSERTING, UPDATING, DELETING

Wenn ein Trigger für mehrere Ereignisse definiert ist, kann mit diesen Prädikaten unterschieden werden:

CREATE OR REPLACE TRIGGER trg_multi
BEFORE INSERT OR UPDATE OR DELETE ON emp
FOR EACH ROW
BEGIN
    IF INSERTING THEN
        :NEW.hiredate := NVL(:NEW.hiredate, SYSDATE);  -- Datum automatisch setzen
    ELSIF UPDATING('SAL') THEN
        -- Nur bei SAL-Updates aktiv
        DBMS_OUTPUT.PUT_LINE('Gehalt von ' || :OLD.sal || ' auf ' || :NEW.sal);
    ELSIF DELETING THEN
        IF :OLD.job = 'PRESIDENT' THEN
            RAISE_APPLICATION_ERROR(-20300, 'Präsident kann nicht gelöscht werden!');
        END IF;
    END IF;
END;
/

5. WHEN-Klausel in Triggern

5.1 Bedingter Trigger

Die WHEN-Klausel filtert Zeilen, für die der Trigger-Body ausgeführt wird. Innerhalb von WHEN werden :NEW und :OLD ohne Doppelpunkt geschrieben:

CREATE OR REPLACE TRIGGER trg_hochgehalt
AFTER INSERT OR UPDATE OF sal ON emp
FOR EACH ROW
WHEN (NEW.sal > 5000)   -- Kein : vor NEW/OLD in WHEN!
BEGIN
    DBMS_OUTPUT.PUT_LINE(
        'Achtung: ' || :NEW.ename || ' hat ein Gehalt von ' || :NEW.sal
    );
END;
/

6. Compound Triggers

6.1 Motivation

Das Mutating Table Problem tritt auf, wenn ein Row-Trigger auf dieselbe Tabelle zugreift, die gerade geändert wird. Compound Triggers lösen dieses Problem:

CREATE OR REPLACE TRIGGER trg_emp_compound
FOR INSERT OR UPDATE ON emp
COMPOUND TRIGGER

    -- Package-ähnliche Sektion für Variablen:
    TYPE t_sal_tab IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
    v_salaries t_sal_tab;
    v_idx      PLS_INTEGER := 0;

    -- Zeitpunkt 1: Vor dem Statement (einmalig)
    BEFORE STATEMENT IS
    BEGIN
        v_idx := 0;
        v_salaries.DELETE;
    END BEFORE STATEMENT;

    -- Zeitpunkt 2: Vor jeder Zeile
    BEFORE EACH ROW IS
    BEGIN
        NULL;
    END BEFORE EACH ROW;

    -- Zeitpunkt 3: Nach jeder Zeile – Daten sammeln
    AFTER EACH ROW IS
    BEGIN
        v_idx := v_idx + 1;
        v_salaries(v_idx) := :NEW.sal;
    END AFTER EACH ROW;

    -- Zeitpunkt 4: Nach dem Statement – Auswertung
    AFTER STATEMENT IS
        v_gesamt NUMBER := 0;
    BEGIN
        FOR i IN 1..v_idx LOOP
            v_gesamt := v_gesamt + v_salaries(i);
        END LOOP;
        DBMS_OUTPUT.PUT_LINE('Gesamtgehalt geändert/eingefügt: ' || v_gesamt);
    END AFTER STATEMENT;

END trg_emp_compound;
/

7. INSTEAD OF Trigger auf Views

7.1 Motivation

Komplexe Views (mit JOINs, GROUP BY, DISTINCT) sind normalerweise nicht direkt änderbar. Ein INSTEAD OF-Trigger interceptiert DML-Operationen auf der View:

-- Komplexe View:
CREATE OR REPLACE VIEW vw_emp_dept AS
    SELECT e.empno, e.ename, e.sal, e.deptno, d.dname, d.loc
    FROM   emp e JOIN dept d ON e.deptno = d.deptno;

-- INSTEAD OF Trigger:
CREATE OR REPLACE TRIGGER trg_empdet_insert
INSTEAD OF INSERT ON vw_emp_dept
FOR EACH ROW
DECLARE
    v_cnt NUMBER;
BEGIN
    -- Prüfen ob Abteilung existiert:
    SELECT COUNT(*) INTO v_cnt FROM dept WHERE deptno = :NEW.deptno;
    IF v_cnt = 0 THEN
        RAISE_APPLICATION_ERROR(-20400, 'Abteilung ' || :NEW.deptno || ' existiert nicht.');
    END IF;

    -- Tatsächliche Einfügung in Basistabelle:
    INSERT INTO emp (empno, ename, sal, deptno, job, hiredate)
    VALUES (:NEW.empno, :NEW.ename, :NEW.sal, :NEW.deptno, 'CLERK', SYSDATE);
END;
/

-- INSERT auf die View funktioniert jetzt:
INSERT INTO vw_emp_dept (empno, ename, sal, deptno)
VALUES (9100, 'VIEWUSER', 2500, 20);
COMMIT;

8. DDL- und System-Trigger

8.1 DDL-Trigger

Reagiert auf DDL-Operationen (CREATE, ALTER, DROP, RENAME):

-- Tabellen-Drops protokollieren:
CREATE OR REPLACE TRIGGER trg_schema_protect
BEFORE DROP ON SCHEMA
BEGIN
    IF ORA_DICT_OBJ_TYPE = 'TABLE' THEN
        INSERT INTO ddl_log (aktion, objekt, benutzer, zeitpunkt)
        VALUES (ORA_SYSEVENT, ORA_DICT_OBJ_NAME, ORA_LOGIN_USER, SYSTIMESTAMP);
        COMMIT;

        -- Produktionstabellen schützen:
        IF ORA_DICT_OBJ_NAME IN ('EMP', 'DEPT', 'SALGRADE') THEN
            RAISE_APPLICATION_ERROR(-20500,
                'Schutz: Tabelle ' || ORA_DICT_OBJ_NAME || ' darf nicht gelöscht werden!');
        END IF;
    END IF;
END;
/

8.2 System-Trigger (LOGON/LOGOFF)

-- Login-Protokoll:
CREATE OR REPLACE TRIGGER trg_logon_audit
AFTER LOGON ON DATABASE
BEGIN
    INSERT INTO session_log (benutzer, zeitpunkt, ip_adresse, event)
    VALUES (
        SYS_CONTEXT('USERENV', 'SESSION_USER'),
        SYSTIMESTAMP,
        SYS_CONTEXT('USERENV', 'IP_ADDRESS'),
        'LOGIN'
    );
    COMMIT;
EXCEPTION
    WHEN OTHERS THEN NULL;  -- Fehler im Login-Trigger darf Session nicht blockieren!
END;
/

8.3 Ereignis-Funktionen in DDL/System-Triggern

Funktion Bedeutung
ORA_SYSEVENT Name des auslösenden Ereignisses (CREATE, DROP, …)
ORA_DICT_OBJ_TYPE Typ des betroffenen Objekts (TABLE, INDEX, …)
ORA_DICT_OBJ_NAME Name des betroffenen Objekts
ORA_DICT_OBJ_OWNER Schema-Eigentümer
ORA_LOGIN_USER Aktuell angemeldeter Benutzer

9. Trigger verwalten

9.1 Data Dictionary

-- Alle eigenen Trigger anzeigen:
SELECT trigger_name, trigger_type, triggering_event,
       table_name, status
FROM   user_triggers
ORDER  BY table_name, trigger_name;

-- Trigger-Code anzeigen:
SELECT trigger_body
FROM   user_triggers
WHERE  trigger_name = 'TRG_EMP_AUDIT';

9.2 Trigger aktivieren/deaktivieren

-- Einzelnen Trigger deaktivieren (z.B. für Bulk-Load):
ALTER TRIGGER trg_emp_audit DISABLE;

-- Einzelnen Trigger aktivieren:
ALTER TRIGGER trg_emp_audit ENABLE;

-- Alle Trigger einer Tabelle deaktivieren:
ALTER TABLE emp DISABLE ALL TRIGGERS;

-- Alle Trigger einer Tabelle aktivieren:
ALTER TABLE emp ENABLE ALL TRIGGERS;

9.3 Trigger löschen

DROP TRIGGER trg_emp_audit;
DROP TRIGGER trg_sal_check;

9.4 Trigger kompilieren

ALTER TRIGGER trg_emp_audit COMPILE;

10. Zusammenfassung und Ausblick

10.1 Trigger-Typen und Zeitpunkte

Trigger-Typ Zeitpunkt FOR EACH ROW :NEW/:OLD Typischer Einsatz
BEFORE Statement Vor dem DML Nein Nein Zugriffsschutz
BEFORE Row Vor jeder Zeile Ja Ja (änderbar) Werte setzen
AFTER Row Nach jeder Zeile Ja Ja (nur lesen) Auditing
AFTER Statement Nach dem DML Nein Nein Statistiken
INSTEAD OF Statt DML Ja Ja Views änderbar machen
Compound Alle Zeitpunkte Gemischt Ja Mutating Table Fix
DDL Schema-Änderungen Nein Nein Schema-Schutz
System DB-Ereignisse Nein Nein Login-Logging

10.2 Best Practices

10.3 Kurs-Abschluss

Damit ist der Oracle PL/SQL Kurs abgeschlossen. Die behandelten Themen:

Kapitel Inhalt
1 Einführung Anonymer Block, Variablen, %TYPE, %ROWTYPE, SELECT INTO
2 Kontrollstrukturen IF, CASE, LOOP, WHILE, FOR, EXIT, CONTINUE
3 Cursor Implizit, explizit, Cursor-FOR, REF CURSOR, BULK COLLECT
4 Exceptions Vordefiniert, benutzerdefiniert, RAISE_APPLICATION_ERROR
5 Prozeduren/Funktionen Stored Subprogramme, IN/OUT-Parameter, Overloading
6 Packages Spec/Body, Package-Variablen, Standard-Packages
7 Trigger DML, INSTEAD OF, DDL, System-Trigger

Weiterführend: Oracle Advanced PL/SQL – Collections, Object Types, Native Dynamic SQL (EXECUTE IMMEDIATE), Fine-Grained Auditing, Virtual Private Database

Vorheriges ThemaPackages Ende der ReiheKein weiteres Thema