Der Exception-Teil eines PL/SQL Blocks

PL/SQL hat ein mächtiges Konzept zur Behandlung von Fehlern, bzw. Ausnahmen in Programmen. Es ist tatsächlich möglich, stabile Programme zu realisieren, die “abschusssicher” sind und Fehler in der Datenbank oder die Nutzung der Datenbank sichern.Die Ausnahmebehandlung ist ereignisbasierend. Alles, was getan werden muß, ist eine Ausnahmebedingung zu formulieren und schon wird, wann immer diese Bedingung zutrifft, der entsprechende Ausnahmebehandlungsteil, den Sie ebenfalls selbst formulieren, aufgerufen.

Ein PL/SQL Block besteht bekanntlich aus mehreren Teilen: dem Deklarationsteil, dem ausführbaren Teil und dem Fehlerbehandlungsteil (exception section).

Wenn ein Fehler im ausführbaren Teil des Blockes auftritt, wird die Programmkontrolle an den Exception Teil übergeben. PL/SQL durchsucht den Exception Teil nach einer Behandlung für den aufgetretenen Fehler.

Ein Fehler gilt als behandelt, wenn im Exception Teil ein sogenannter Exception Handler existiert. Das bedeutet, wenn es eine WHEN Klausel gibt, die der aufgetretenen Ausnahme (=Fehler) entspricht. Wenn dies zutrifft, werden die ausführbaren Anweisungen des Exception Handlers ausgeführt. Wenn es keine Behandlung gibt, aber eine WHEN OTHERS Klausel existiert, werden die hier formulierten Anweisungen ausgeführt. Wenn keine der obigen Bedingungen zutrifft, gilt die Ausnahme als unbehandelt und der Fehlercode mitsamt dem Fehlertext wird “hochgereicht”, im einfachsten Fall also dem Benutzer präsentiert.

Typen von Exceptions

Es gibt vier Typen von Exceptions.

  • Benannte System Exceptions. Dies sind Ausnahmen, die von ORACLE bereits benannt wurden und ausgelöst werden als Ergebnis eines Fehlers in PL/SQL Programmen oder innerhalb der Datenbank.
  • Benannte, durch den Programmierer definierte Exceptions. Diese Fehler werden ausgelöst durch Ihre eigene Programmierlogik (bspw. Wollen Sie einen Fehler auslösen, wenn das Gehalt eines Mitarbeiters kleiner null wird). Diese Ausnahmen können bei der Deklaration benannt werden und im ausführbaren Teil ausgelöst werden (RAISE).
  • Unbenannte System Exceptions. Diese Ausnahmen werden ebenfalls aufgrund eines Fehlers innerhalb von PL/SQL oder innerhalb der Datenbank ausgelöst, sind aber durch ORACLE nicht benannt. Daher ist ohne weitere Vorbereitung eine Reaktion Ihres Programms auf diese Fehler nicht möglich. Sie können diese ORACLE-Fehler mit Hilfe des Pragmas EXCEPTION_INIT an eine Ausnahme in Ihrem PL/SQL Programm binden und dann darauf reagieren.
  • Unbenannte, durch den Programmierer definierte Ausnahmen. Diese Ausnahmen werden durch den Entwickler definiert und ausgelöst. Sie werden durch die Prozedur RAISE_APPLICATION_ERROR ausgelöst und „hochgereicht“. Eine Reaktion innerhalb des gleichen Programms ist bei dieser Fehlerklasse nicht möglich.

Benannte System Exceptions

Beispiele für benannte Systemfehler sind z.B. NO_DATA_FOUND, DUP_VAL_ON_INDEX oder ZERO_DIVIDE-Ausnahmen. (die benannten Exceptions findet man am besten in der ORACLE Dokumentation. Sie sind allerdings nur ein ganz kleiner Teil der möglichen Fehler, die die Datenbank zurückliefern kann)

Beispiel:

DECLARE
    gehalt number;
BEGIN
-- wenn das folgende Select keinen Wert liefert, wird NO_DATA_FOUND
-- ausgelöst. Darauf reagiert der Exception-Teil mit einer
-- Ausgabe auf dem Bildschirm
 SELECT sal into gehalt from emp where empno=99;
EXCEPTION
  WHEN no_data_found THEN
    dbms_output.put_line('Diesen Mitarbeiter gibt es nicht');
END;

Benannte, durch den Programmierer definierte Exceptions

Hier haben wir ein Beispiel für eine durch den Programmierer benannte Exception. Ich habe eine Exception erzeugt mit Namen no_sales. Im ausführbaren Teil löse ich die Ausnahme explizit aus. Die Ausführung wird sofort im Exception Teil fortgesetzt.

DECLARE
  no_sales EXCEPTION;
BEGIN
  RAISE no_sales;
EXCEPTION
  WHEN no_sales THEN
    NULL;
  WHEN OTHERS THEN
-- der WHEN OTHERS Teil reagiert auf alle nicht behandelten Fehler
-- er muss am Ende der WHEN-Liste stehen!
    NULL;
END;

Die Ausnahme wird im Deklarationsteil definiert. Im ausführbaren Teil steht nur die Anweisung zum Auslösen der Ausnahme. PL/SQL durchsucht nun den Exception Teil nach dem Exception Handler der Ausnahme und führt die Anweisungen für den Handler aus (die NULL-Anweisung). Damit gilt der Fehler, oder die Ausnahme als behandelt.

Unbenannte System Exceptions

Wenn ein interner Fehler auftritt (bspw. Ein Extent kann nicht angelegt werden oder eine Berechtigung zur Manipulation von Tabellen fehlt), liefert ORACLE den entspr. Fehler, der anhand seiner Fehlernummer identifiziert wird. PL/SQL stellt hier keine vordefinierten Fehler zur Verfügung. Wir können aber einen eigenen Handler für solche Fehler schreiben. Dazu bindet man den ORACLE-Fehler an eine im Programm deklarierte Ausnahme. Das geschieht mit dem Pragma EXCEPTION_INIT.

DECLARE
  value_found EXCEPTION;
  PRAGMA exception_init(value_found, -00001);
BEGIN
  RAISE value_found;
EXCEPTION
  WHEN value_found THEN
    NULL;
END;

Das Beispiel ist nicht sinnvoll, zeigt jedoch das Binden der Fehler: ORACLEs? Fehler -00001 ist der Fehler “duplicate values found”, der bspw. beim Verletzen eines Unique Constraints auftritt. Wann immer dieser Fehler im ausführbaren Teil auftritt, wird der Exception Handler angesprungen. Das Beispiel zeigt, dass Sie aber nach wie vor solche Fehler auch selbst mit RAISE auslösen können. Sinnvoll ist das Beispiel deshalb nicht, weil es für den Fehler -00001 bereits eine von ORACLE vordefinierte Ausnahme gibt: DUP_VAL_ON_INDEX.

Verhalten und Propagation

Wenn eine Ausnahme in einem Block deklariert wird, ist sie lokal innerhalb des Blocks, aber global für alle Blöcke, die durch den Block umschlossen werden. Alle lokal deklarierten Ausnhamen haben Vorrang gegenüber den System Exceptions. Das bedeutet, wenn Sie eine Ausnahme no_data_found erzeugen, ersetzt diese Ausnahme das Standard-Verhalten der System Exception gleichen Namens. Es ist also keine gute Idee, eigene Ausnahmen genauso zu nennen, wie vordefinierte Exceptions.

Wenn eine Ausnahme ausgelöst wurde, sucht PL/SQL den entsprechenden Handler im aktuellen Block. Wird der Fehler dort nicht behandelt, propagiert PL/SQL den Fehler in den übergeordneten Block. Wird der Fehler auch hier nicht behandelt, wird weiterpropagiert. Gibt es keine weiteren Blöcke, in die “hinaufgereicht” werden kann, erhält der Benutzer einen unbehandelten Ausnahme-Fehler.

Exceptions auslösen

Ausnahmen werden automatisch ausgelöst, wenn ein Datenbankfehler oder PL/SQL-Fehler auftritt. Sie können aber auch explizit vom Entwickler ausgelöst werden mit Hilfe des RAISE Statements. Gleichermaßen kann der Entwickler die Prozedur RAISE_APPLICATION_ERROR nutzen, um Fehler an die Client-Applikation zu melden.

Exceptions behandeln

Wurde eine Ausnahme ausgelöst, bricht die normale Programmausführung ab und der Exception Teil erhält die Programmkontrolle. Aus dem Exception Teil ist es nicht möglich, in den ausführbaren Teil zurückzukehren. Das scheint eine enorme Einschränkung zu sein, lässt sich aber über geschachtelte Blöcke umgehen.

Wenn Sie die WHEN OTHERS Klausel im Exception Teil nutzen, muß dies der letzte Handler im Exception Teil sein. Denn PL/SQL scannt den Exception Teil von oben nach unten nach dem passenden Handler durch. Da WHEN OTHERS auf jede Form von Ausnahme reagiert, würden dahinterstehende Handler niemals angesprungen werden.

Unbehandelte Exceptions

Wenn eine Ausnahme ausgelöst wird und es keinen Handler im Exception Teil gibt, wird dies als unbehandelte Ausnahme bezeichnet und propagiert. Eine Anwendung mit gutem Design wird einen WHEN OTHERS Teil haben, um auch unerwartete Ausnahmen abzufangen.

Es gibt zwei Standards, die es erlauben, zu prüfen, welcher Fehler aufgetreten ist: SQLCODE und SQLERRM.

SQLCODE und SQLERRM

Die Funktion SQLCODE wird genutzt, um die Fehlernummer der letzten aufgetretenen Ausnahme zu ermitteln. Die Funktion SQLERRM erhält als Argument die Fehlernummer und liefert den dazugehörenden Fehlertext.

DECLARE
   gehalt number;
BEGIN
   select sal into gehalt from emp where empno=4711;
EXCEPTION
  WHEN OTHERS THEN
    dbms_output.put_line('Fehler: '||sqlcode);
    dbms_output.put_line('Fehlertext:'||sqlerrm(sqlcode));
END;

RAISE_APPLICATION_ERROR

Die RAISE_APPLICATION_ERROR Prozedur ist der einzige Weg, Fehler an die Client-Applikation zu senden. Sie wird häufig genutzt, um mit Hilfe von Triggern Datenbankfehler an den Client zu melden. Die Prozedur wird mit zwei Argumenten aufgerufen. Das erste Argument ist die Fehlernummer (Nummern zwischen -20001 und -21000 stehen zur freien Verfügung), das zweite Argument ein beliebiger Fehlertext. Unmittelbar nach Aufruf der Prozedur bricht das Programm ab.

Beispiel

BEGIN
   raise_application_error (-20001,'Abbruch mit  Fehler');
END; 

Übersteuerte Ausnahmen

Systemausnahmen müssen eigentlich nicht deklariert werden, da sie ja bereits deklariert sind, sobald PL/SQL ausgeführt wird. Wenn man jedoch – warum auch immer – eine eigene Ausnahme mit dem gleichen Namen wie eine Systemausnahme deklarieren will, ist dies möglich. Die benutzerdefinierte Ausnahme übersteuert die Systemausnahme.

declare
  dup_val_on_index exception;
begin 
  ...

Diese Ausnahme übersteuert die Systemausnahme. Sie wird nicht mehr ausgelöst, wenn ein doppelter Wert in eine Spalte mit einem UNIQUE INDEX eingefügt wird, was ja das normale Verhalten wäre.

Ausnahmen qualifizieren

Wenn Sie also aus irgendwelchen sadistischen Gründen eine benutzerdefinierte Ausnahme gleich einer Systemausnahme benennen, übersteuert diese die Systemausnahme. Trotzdem ist es möglich, zwischen beiden Ausnahmen zu unterscheiden. Sie müssen dazu lediglich genauer qualifiziert werden.

declare
  dup_val_on_index exception;
begin
 null;
exception
  when dup_val_on_index then
    dbms_output.put_line('This is our exception');
  when standard.dup_val_on_index then
    dbms_output.put_line('The named system exception');
end; 

Das obere Beispiel unterscheidet also zwischen der benutzerdefinierten und der Systemausnahme und erlaubt es PL/SQL, den richtigen Fehlerbehandlungsteil zu finden.

Aber das Übersteuern von Systemausnahmen ist generell eine schlechte Idee, also denken wir gar nicht erst an mögliche Konsequenzen und, schlimmer noch, an das aufwendige Debuggen.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.