Oracle PL/SQL – Exceptions (Fehlerbehandlung)

Fach: Informationstechnologie – Datenbanksysteme
Schulstufe: 12. Schulstufe – HTL Informatik
Voraussetzungen: PL/SQL Einführung, Kontrollstrukturen, Cursor
Autor: HTL Pinkafeld – IF/IT



1. Was sind Exceptions?

1.1 Definition

Eine Exception (Ausnahme) ist ein Laufzeitfehler, der den normalen Programmablauf unterbricht. PL/SQL bietet einen strukturierten Mechanismus, um solche Fehler abzufangen und darauf zu reagieren, ohne das Programm abstürzen zu lassen.

BEGIN
    -- Normaler Programmfluss
    ...
    -- Fehler tritt auf (z.B. ORA-01403)EXCEPTION
    WHEN NO_DATA_FOUND THEN   ← Gezielter Handler
        ...
    WHEN OTHERS THEN          ← Globaler Handler
        ...
END;

1.2 Ohne Exception-Handling

BEGIN
    -- Wenn kein Mitarbeiter mit empno=9999 → ORA-01403
    SELECT ename INTO v_name FROM emp WHERE empno = 9999;
    DBMS_OUTPUT.PUT_LINE(v_name);
END;
/
-- Ergebnis: ORA-01403: no data found → Programm bricht ab

1.3 Mit Exception-Handling

DECLARE
    v_name VARCHAR2(10);
BEGIN
    SELECT ename INTO v_name FROM emp WHERE empno = 9999;
    DBMS_OUTPUT.PUT_LINE(v_name);
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        DBMS_OUTPUT.PUT_LINE('Mitarbeiter nicht gefunden – kein Fehler!');
END;
/
-- Programm läuft sauber durch

2. Vordefinierte Oracle-Exceptions

2.1 Häufig verwendete Exceptions

Exception ORA-Fehler Auslöser
NO_DATA_FOUND ORA-01403 SELECT INTO findet 0 Zeilen
TOO_MANY_ROWS ORA-01422 SELECT INTO findet > 1 Zeile
ZERO_DIVIDE ORA-01476 Division durch Null
DUP_VAL_ON_INDEX ORA-00001 Unique-Constraint verletzt
VALUE_ERROR ORA-06502 Typkonvertierung / Längenfehler
INVALID_NUMBER ORA-01722 Ungültige Zahl-Konvertierung
INVALID_CURSOR ORA-01001 Cursor-Operation auf ungültigem Cursor
CURSOR_ALREADY_OPEN ORA-06511 Cursor bereits geöffnet
NOT_LOGGED_ON ORA-01012 Keine aktive DB-Verbindung
PROGRAM_ERROR ORA-06501 Interner PL/SQL-Fehler
ROWTYPE_MISMATCH ORA-06504 Inkompatible Cursor-/Variablentypen
STORAGE_ERROR ORA-06500 Speicherproblem
TIMEOUT_ON_RESOURCE ORA-00051 Timeout beim Warten auf Ressource
LOGIN_DENIED ORA-01017 Ungültiger Benutzername/Passwort

2.2 Beispiele für häufige Exceptions

DECLARE
    v_erg NUMBER;
    v_name VARCHAR2(5);
BEGIN
    -- ZERO_DIVIDE
    v_erg := 100 / 0;
EXCEPTION
    WHEN ZERO_DIVIDE THEN
        DBMS_OUTPUT.PUT_LINE('Fehler: Division durch Null!');
END;
/
DECLARE
    v_name VARCHAR2(5) := 'Pinkafeld';  -- 9 Zeichen in 5-Zeichen-Variable
BEGIN
    NULL;
EXCEPTION
    WHEN VALUE_ERROR THEN
        DBMS_OUTPUT.PUT_LINE('Wert zu lang für die Variable!');
END;
/

3. Der EXCEPTION-Abschnitt

3.1 Struktur

EXCEPTION
    WHEN exception_name1 THEN
        -- Handler für exception_name1
    WHEN exception_name2 OR exception_name3 THEN
        -- Handler für zwei Exceptions gleichzeitig
    WHEN OTHERS THEN
        -- Alles andere

3.2 Mehrere Handler

DECLARE
    v_empno emp.empno%TYPE := &empno;  -- Eingabe in SQL*Plus
    v_row   emp%ROWTYPE;
BEGIN
    SELECT * INTO v_row FROM emp WHERE empno = v_empno;

    DBMS_OUTPUT.PUT_LINE('Name:   ' || v_row.ename);
    DBMS_OUTPUT.PUT_LINE('Gehalt: ' || v_row.sal);

EXCEPTION
    WHEN NO_DATA_FOUND THEN
        DBMS_OUTPUT.PUT_LINE('Mitarbeiter ' || v_empno || ' nicht gefunden.');
    WHEN TOO_MANY_ROWS THEN
        DBMS_OUTPUT.PUT_LINE('Mehrere Treffer – WHERE-Klausel präzisieren!');
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('Unbekannter Fehler: ' || SQLERRM);
END;
/

3.3 Exception im Schleifenkontext

Nach einer Exception verlässt PL/SQL den aktuellen Block. Um Schleifen weiterzuführen, müssen Blöcke verschachtelt werden:

BEGIN
    FOR r IN (SELECT empno FROM emp WHERE deptno = 20) LOOP
        BEGIN  -- Innerer Block fängt den Fehler
            -- Hier irgendeine kritische Operation
            IF r.empno = 7788 THEN
                RAISE ZERO_DIVIDE;  -- Simulierter Fehler
            END IF;
            DBMS_OUTPUT.PUT_LINE('Verarbeitet: ' || r.empno);
        EXCEPTION
            WHEN ZERO_DIVIDE THEN
                DBMS_OUTPUT.PUT_LINE('Fehler bei ' || r.empno || ' – weiter...');
        END;
    END LOOP;
END;
/

4. OTHERS – Globaler Fänger

4.1 WHEN OTHERS als Sicherheitsnetz

WHEN OTHERS fängt alle nicht explizit behandelten Fehler ab:

DECLARE
    v_val NUMBER;
BEGIN
    v_val := TO_NUMBER('abc');  -- Ungültige Konvertierung
EXCEPTION
    WHEN INVALID_NUMBER THEN
        DBMS_OUTPUT.PUT_LINE('Ungültige Zahl eingegeben.');
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('Fehlercode:  ' || SQLCODE);
        DBMS_OUTPUT.PUT_LINE('Fehlermeldung: ' || SQLERRM);
END;
/

4.2 Best Practice: OTHERS nie schweigen lassen

-- SCHLECHT: Fehler werden verschluckt
EXCEPTION
    WHEN OTHERS THEN NULL;

-- GUT: Fehler protokollieren, dann weiterwerfen
EXCEPTION
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('Fehler: ' || SQLERRM);
        RAISE;  -- Exception weitergeben an den aufrufenden Block

5. SQLCODE und SQLERRM

5.1 SQLCODE

SQLCODE gibt den numerischen Fehlercode zurück:

5.2 SQLERRM

SQLERRM gibt die zugehörige Fehlermeldung zurück. Kann auch mit einem Fehlercode aufgerufen werden:

DECLARE
    v_code    NUMBER;
    v_message VARCHAR2(500);
BEGIN
    SELECT sal / 0 INTO v_code FROM dual;
EXCEPTION
    WHEN OTHERS THEN
        v_code    := SQLCODE;
        v_message := SQLERRM;
        DBMS_OUTPUT.PUT_LINE('Code:    ' || v_code);
        DBMS_OUTPUT.PUT_LINE('Meldung: ' || v_message);

        -- Fehlermeldung für bekannte Codes nachschlagen:
        DBMS_OUTPUT.PUT_LINE(SQLERRM(-1403));  -- → ORA-01403: no data found
END;
/

6. Benutzerdefinierte Exceptions

6.1 Deklaration und Auslösung

Eigene Exceptions werden im DECLARE-Abschnitt deklariert und mit RAISE ausgelöst:

DECLARE
    -- Eigene Exception deklarieren
    e_ungültiges_gehalt   EXCEPTION;
    e_kein_manager        EXCEPTION;

    v_sal  emp.sal%TYPE := -500;
BEGIN
    -- Geschäftsregel prüfen
    IF v_sal < 0 THEN
        RAISE e_ungültiges_gehalt;   -- Eigene Exception auslösen
    END IF;

    DBMS_OUTPUT.PUT_LINE('Gehalt OK: ' || v_sal);

EXCEPTION
    WHEN e_ungültiges_gehalt THEN
        DBMS_OUTPUT.PUT_LINE('Fehler: Gehalt darf nicht negativ sein!');
    WHEN e_kein_manager THEN
        DBMS_OUTPUT.PUT_LINE('Fehler: Kein Manager für diese Abteilung.');
END;
/

6.2 Praxisbeispiel: Geschäftsregeln validieren

DECLARE
    e_min_gehalt   EXCEPTION;
    e_max_stunden  EXCEPTION;

    v_gehalt    NUMBER := 1200;
    v_stunden   NUMBER := 55;
    c_min_sal   CONSTANT NUMBER := 1700;
    c_max_h     CONSTANT NUMBER := 50;
BEGIN
    IF v_gehalt < c_min_sal THEN
        RAISE e_min_gehalt;
    END IF;

    IF v_stunden > c_max_h THEN
        RAISE e_max_stunden;
    END IF;

    DBMS_OUTPUT.PUT_LINE('Daten gültig.');

EXCEPTION
    WHEN e_min_gehalt THEN
        DBMS_OUTPUT.PUT_LINE('Gehalt unter Mindestlohn (' || c_min_sal || ' €)!');
    WHEN e_max_stunden THEN
        DBMS_OUTPUT.PUT_LINE('Überschreitung der Maximalstunden (' || c_max_h || 'h)!');
END;
/

7. RAISE_APPLICATION_ERROR

7.1 Benutzerdefinierte Fehlernummern

Mit RAISE_APPLICATION_ERROR können eigene ORA-Fehlermeldungen mit benutzerdefinierten Nummern erzeugt werden. Der Fehlercode muss im Bereich -20000 bis -20999 liegen:

PROCEDURE gehalt_pruefen (p_sal NUMBER) IS
BEGIN
    IF p_sal < 0 THEN
        RAISE_APPLICATION_ERROR(
            -20001,
            'Gehalt darf nicht negativ sein. Eingabe: ' || p_sal
        );
    ELSIF p_sal > 50000 THEN
        RAISE_APPLICATION_ERROR(
            -20002,
            'Gehalt überschreitet das Maximum von 50.000. Eingabe: ' || p_sal
        );
    END IF;
END;
/

7.2 Aufruf und Behandlung

BEGIN
    gehalt_pruefen(-100);
EXCEPTION
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('Fehler ' || SQLCODE || ': ' || SQLERRM);
        -- Ausgabe: Fehler -20001: ORA-20001: Gehalt darf nicht negativ sein. Eingabe: -100
END;
/

7.3 Vergleich: RAISE vs. RAISE_APPLICATION_ERROR

Merkmal RAISE RAISE_APPLICATION_ERROR
Fehlercode Oracle-intern oder PL/SQL -20000 bis -20999 (benutzerdefiniert)
Fehlermeldung Vordefiniert Frei definierbar
Im SQLERRM sichtbar Ja Ja
Gibt Kontrolle ab Ja Ja
Typischer Einsatz Interne Exceptions API-Fehler für aufrufende Schichten

8. PRAGMA EXCEPTION_INIT

8.1 ORA-Fehler einer Exception zuordnen

Mit PRAGMA EXCEPTION_INIT kann einem Oracle-Fehlercode eine benannte Exception zugewiesen werden:

DECLARE
    e_deadlock    EXCEPTION;
    PRAGMA EXCEPTION_INIT(e_deadlock, -60);  -- ORA-00060: deadlock detected

    e_fk_verletzt EXCEPTION;
    PRAGMA EXCEPTION_INIT(e_fk_verletzt, -2292);  -- ORA-02292: integrity constraint violated

BEGIN
    DELETE FROM dept WHERE deptno = 10;  -- Hat noch Mitarbeiter!

EXCEPTION
    WHEN e_fk_verletzt THEN
        DBMS_OUTPUT.PUT_LINE('Abteilung kann nicht gelöscht werden – hat noch Mitarbeiter!');
    WHEN e_deadlock THEN
        DBMS_OUTPUT.PUT_LINE('Deadlock erkannt – Transaktion zurückgerollt.');
END;
/

8.2 Wichtige ORA-Fehlercodes

ORA-Code Beschreibung
ORA-00001 Unique Constraint verletzt
ORA-00060 Deadlock erkannt
ORA-01400 NULL-Wert für NOT NULL-Spalte
ORA-02291 FK-Referenz nicht gefunden (Elternteil fehlt)
ORA-02292 FK-Abhängigkeit vorhanden (Kind existiert noch)
ORA-04091 Table is mutating (Trigger-Problem)

9. Exception-Weiterleitung und Scope

9.1 Exceptions in verschachtelten Blöcken

BEGIN
    BEGIN          -- Innerer Block
        RAISE NO_DATA_FOUND;
    EXCEPTION
        WHEN NO_DATA_FOUND THEN
            DBMS_OUTPUT.PUT_LINE('Innerer Block: gefangen');
            RAISE;  -- Weiterwerfen an äußeren Block
    END;
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        DBMS_OUTPUT.PUT_LINE('Äußerer Block: auch gefangen');
END;
/

9.2 Exception-Sichtbarkeit

Benutzerdefinierte Exceptions sind nur im Block sichtbar, in dem sie deklariert wurden:

BEGIN
    DECLARE
        e_lokal EXCEPTION;
    BEGIN
        RAISE e_lokal;
    EXCEPTION
        WHEN e_lokal THEN
            DBMS_OUTPUT.PUT_LINE('Lokal gefangen');
    END;

    -- RAISE e_lokal;  -- FEHLER: e_lokal hier nicht sichtbar
END;
/

9.3 Transaktionssicherheit in Exception-Handlern

DECLARE
    v_empno NUMBER := 9001;
BEGIN
    INSERT INTO emp (empno, ename, deptno, sal, hiredate, job)
    VALUES (v_empno, 'TESTUSER', 10, 2500, SYSDATE, 'CLERK');

    UPDATE dept SET dname = 'INVALID' WHERE deptno = 999;  -- Kein Effekt

    COMMIT;
    DBMS_OUTPUT.PUT_LINE('Transaktion erfolgreich.');

EXCEPTION
    WHEN OTHERS THEN
        ROLLBACK;  -- Alle DML dieser Transaktion zurückrollen
        DBMS_OUTPUT.PUT_LINE('Fehler – Rollback durchgeführt: ' || SQLERRM);
END;
/

10. Zusammenfassung und Ausblick

10.1 Exception-Typen im Überblick

Typ Deklaration Auslösung Beispiel
Vordefiniert Automatisch Automatisch durch Oracle NO_DATA_FOUND
Nicht benannt Automatisch Automatisch durch Oracle ORA-00904
Benutzerdefiniert EXCEPTION im DECLARE RAISE e_ungültiges_gehalt
Mit PRAGMA EXCEPTION + PRAGMA EXCEPTION_INIT Automatisch ORA-02292 benannt
Anwendungsfehler Keine RAISE_APPLICATION_ERROR -20001

10.2 Checkliste gutes Exception-Handling

10.3 Ausblick

Das nächste Kapitel behandelt gespeicherte Prozeduren und Funktionen – wiederverwendbare PL/SQL-Programme in der Datenbank:

Vorheriges ThemaCursor Nächstes ThemaProzeduren & Funktionen