Oracle Database 10g PL/SQL Programmierung von Doris Heidenberger, Scott Urman, Ron Hardman, Michael McLaughlin 1. Auflage Hanser München 2005 Verlag C.H. Beck im Internet: www.beck.de ISBN 978 3 446 22849 8 Zu Inhaltsverzeichnis schnell und portofrei erhältlich bei beck-shop.de DIE FACHBUCHHANDLUNG Oracle Database 10g PL/SQL Programmierung Scott Urman, Ron Hardman, Michael McLaughlin ISBN 3-446-22849-7 Leseprobe Weitere Informationen oder Bestellungen unter http://www.hanser.de/3-446-22849-7 sowie im Buchhandel 8 8 Prozeduren, Funktionen und Packages erstellen In Kapitel 3 lernten wir zwei wichtige Arten von PL/SQL-Blöcken kennen: anonyme und benannte. Ein anonymer Block, der entweder mit DECLARE oder BEGIN beginnt, wird bei jedem Aufruf kompiliert. Er wird auch nicht in der Datenbank gespeichert und lässt sich nicht direkt aus anderen PL/SQL-Blöcken aufrufen. Die Konstrukte, die wir in diesem und den nächsten beiden Kapiteln untersuchen – Prozeduren, Funktionen, Packages und Trigger – sind benannte Blöcke und weisen diese Restriktionen nicht auf. Sie können in der Datenbank gespeichert und bei passender Gelegenheit ausgeführt werden. In diesem Kapitel untersuchen wir die Syntax für das Erstellen von Prozeduren, Funktionen und Packages. In Kapitel 9 zeigen wir, wie sie verwendet werden und beschreiben einige ihrer Auswirkungen. Kapitel 10 beschäftigt sich mit Datenbank-Triggern. 8.1 Prozeduren und Funktionen PL/SQL-Prozeduren und -Funktionen verhalten sich sehr ähnlich wie Prozeduren und Funktionen in anderen 3GLs (Sprachen der dritten Generation). Sie weisen viele gleiche Eigenschaften auf. Allgemein betrachtet bezeichnet man Prozeduren und Funktionen auch als Unterprogramme. Der folgende Code erstellt beispielsweise eine Prozedur in der Datenbank: –– Online verfügbar in AddNewAuthor.sql CREATE OR REPLACE PROCEDURE AddNewAuthor ( p_ID authors.ID%TYPE, p_FirstName authors.first_name%TYPE, p_LastName authors.last_name%TYPE) AS BEGIN 328 8 Prozeduren, Funktionen und Packages erstellen –– Insert a new row into the authors table, using the supplied –– arguments for the column values. INSERT INTO authors (id, first_name, last name) VALUES (p_ID, p_FirstName, p_LastName); END AddNewAuthor; Nach dem Erstellen der Prozedur rufen wir sie aus einem anderen PL/SQL-Block auf: –– Online verfügbar in AddNewAuthor.sql BEGIN AddNewAuthor(100, 'Zelda', 'Zudnik'); END; Dieses Beispiel zeigt einige wichtige Punkte auf: ■ Die Prozedur AddNewAuthor wird zuerst mit der Anweisung CREATE OR REPLACE PROCEDURE erstellt. Danach wird die Prozedur zuerst kompiliert und dann in kompilierter Form in der Datenbank gespeichert. Der kompilierte Code kann nun von anderen PL/SQL-Blöcken ausgeführt werden. (Auch der Quellcode der Prozedur wird in der Datenbank hinterlegt. Mehr dazu in Kapitel 9). ■ Beim Aufruf einer Prozedur können Parameter übergeben werden. Im obigen Beispiel wurden der Prozedur zur Laufzeit die ID des neuen Autors mit Vor- und Nachnamen übergeben. In der Prozedur hat der Parameter p_ID den Wert 100, p_FirstName den Wert 'Zelda' und p_LastName den Wert 'Zudnik', weil diese Literale beim Aufruf der Prozedur an sie übergeben wurden. Die Deklarationen %TYPE geben die Typen an, mit denen die Parameter für die Tabelle authors wie bei der Deklaration von Variablen übereinstimmen müssen. ■ Ein Prozeduraufruf ist eine PL/SQL-Anweisung. Die Prozedur lässt sich nicht als Teil eines Ausdrucks aufrufen. Beim Aufruf einer Prozedur wird die Steuerung an die erste ausführbare Anweisung in der Prozedur übergeben. Wird die Prozedur beendet, wird die Steuerung an die Anweisung unmittelbar nach dem Prozeduraufruf übergeben. In dieser Hinsicht verhalten sich PL/SQL-Prozeduren wie die Prozeduren anderer 3GLs. Funktionen werden als Teil eines Ausdrucks aufgerufen. ■ Eine Prozedur ist ein PL/SQL-Block mit einem deklarativen, ausführbaren und einem Exception-Bereich. Wie beim anonymen Block ist nur der ausführbare Bereich obligatorisch. AddNewAuthor besitzt lediglich einen ausführbaren Bereich. 8.1 Prozeduren und Funktionen 8.1.1 329 Unterprogramme erstellen Ähnlich wie andere Data-Dictionary-Objekte werden Unterprogramme mit der CREATE-Anweisung erstellt. Prozeduren werden mit CREATE PROCEDURE und Funktionen mit CREATE FUNCTION erstellt. In den folgenden Abschnitten untersuchen wir diese Anweisungen etwas genauer. Prozeduren erstellen Das folgende Übersichtsdiagramm zeigt die Basissyntax für die Anweisung CREATE OR REPLACE PROCEDURE: Procedure_name ist der Name der zu erstellenden Prozedur, argument ist der Name eines Prozedurparameters, type ist der Typ des dazugehörigen Parameters, und procedure_body ist ein PL/SQL-Block mit dem Prozedurcode. Im Abschnitt „Unterprogrammparameter“ erhalten Sie weitere Informationen zu Prozedur- und Funktionsparametern und zur Bedeutung der Schlüsselwörter IN, OUT, INOUT und NOCOPY. Die Argumentliste ist optional. In diesem Fall enthalten weder die Prozedurdeklaration noch der Prozeduraufruf Klammern. Für CREATE OR REPLACE PROCEDURE gibt es weitere Klauseln, auf die wir später in diesem Kapitel näher eingehen werden. Um den Code einer Prozedur zu ändern, muss diese gelöscht und neu angelegt werden. Da dies bei der Entwicklung von Prozeduren eine übliche Operation ist, lässt sich das mit Hilfe der Schlüsselwörter OR REPLACE in einem Schritt erledigen. Ist die Prozedur vorhanden, wird sie ohne Warnmeldung gelöscht. (Zum Löschen einer Prozedur verwenden Sie den Befehl DROP PROCEDURE). Sollte die Prozedur noch nicht vorhanden sein, wird sie einfach angelegt. Falls die Prozedur vorhanden ist, aber die Schlüsselwörter OR REPLACE vergessen wurden, gibt die CREATE-Anweisung den folgenden Oracle-Fehler zurück: „ORA-955: Name is already used by an existing object“. 330 8 Prozeduren, Funktionen und Packages erstellen Wie bei anderen CREATE-Anweisungen ist das Anlegen einer Prozedur eine DDLOperation. Damit wird vor und nach dem Anlegen der Prozedur ein implizites COMMIT ausgeführt. Man kann entweder das Schlüsselwort IS oder AS verwenden – beide sind äquivalent. Prozedurbodys Der Body einer Prozedur ist ein PL/SQL-Block mit einem deklarativen, einemausführbaren und einem Exception-Bereich. Der deklarative Bereich liegt zwischen den Schlüsselwörtern AS oder IS und dem Schlüsselwort BEGIN. Der ausführbare Bereich (nur dieser ist obligatorisch) liegt zwischen den Schlüsselwörtern BEGIN und EXCEPTION oder, falls der Exception-Bereich fehlt, zwischen den Schlüsselwörtern BEGIN und END. Der Exception-Bereich, falls vorhanden, liegt zwischen den Schlüsselwörtern EXCEPTION und END. Tipp: In der Deklaration einer Prozedur oder Funktion kommt das Schlüsselwort DECLARE nicht vor. Stattdessen wird das Schlüsselwort IS oder AS verwendet. Diese Syntax stammt ursprünglich aus Ada, worauf PL/SQL basiert. Die Struktur einer Anweisung zum Erstellen einer Prozedur sieht wie folgt aus: CREATE OR REPLACE PROCEDURE procedure_name [parameter_list] AS /* Hier ist der deklarative Bereich. */ BEGIN /* Hier ist der ausführbare Bereich. */ EXCEPTION /* Hier ist der Exception-Bereich. */ END [procedure_name]; Der Prozedurname kann in der Prozedurdeklaration optional nach der finalen ENDAnweisung aufgenommen werden. Steht nach dem END ein Identifier, muss er mit dem Prozedurnamen übereinstimmen. Tipp: Es entspricht einem guten Programmierstil, den Prozedurnamen nach dem letzten END anzugeben. Damit wird der Quellcode transparenter, die zusammengehörigen END- und CREATEAnweisungen werden hervorgehoben, und der PL/SQL-Compiler kann schon sehr früh falsche BEGIN-END-Paare ermitteln. 8.1 Prozeduren und Funktionen 331 Funktionen erstellen Funktionen und Prozeduren sind sehr ähnlich. Beide übernehmen Parameter in beliebigen Modi. Beide enthalten verschiedene Formen von PL/SQL-Blöcken mit deklarativem, ausführbarem und Exception-Bereich. Beide können in der Datenbank gespeichert oder in einem Block deklariert werden. Ein Prozeduraufruf ist aber eine PL/SQLAnweisung, während eine Funktion als Teil eines Ausdrucks aufgerufen wird. Die folgende Funktion gibt beispielsweise TRUE zurück, wenn das angegebene Buch drei Autoren aufweist, andernfalls FALSE: –– Online verfügbar in ThreeAuthors.sql CREATE OR REPLACE FUNCTION ThreeAuthors(p_ISBN IN books.isbn%TYPE) RETURN BOOLEAN AS v_Author3 books.author3%TYPE; BEGIN –– Select the third author for the supplied book into v_Author3. SELECT author3 INTO v_Author3 FROM books WHERE isbn = p_ISBN; –– If v_Author3 is NULL, that means that the book has less then 3 –– authors, so we can return false. Otherwise, return true. IF v_Author3 IS NULL THEN RETURN FALSE; ELSE RETURN TRUE; END IF; END ThreeAuthors; Die Funktion ThreeAuthors liefert einen Booleschen Wert. Die folgende SQL*PlusSession zeigt den Aufruf. Beachten Sie, dass der Funktionsaufruf keine Anweisung ist, sondern als Teil der IF-Anweisung innerhalb der Schleife erscheint. –– Online verfügbar in ThreeAuthors.sql SQL> BEGIN 2 FOR v_Rec IN (SELECT ISBN, title FROM books) LOOP 3 IF ThreeAuthors(v_Rec.ISBN) THEN 4 DBMS_OUTPUT.PUT_LINE('"' || v_Rec.title || '" has 3 authors'); 5 END IF; 6 END LOOP; 7 END; 8 / "Oracle DBA 101" has 3 authors "Oracle Performance Tuning 101" has 3 authors "Oracle9i: A Beginner's Guide" has 3 authors 332 8 Prozeduren, Funktionen und Packages erstellen "Oracle9i DBA 101" has 3 authors "Oracle Database 10g A Beginner's Guide" has 3 authors "Oracle E-Business Suite Financials Handbook" has 3 authors "Oracle E-Business Suite Manufacturing & Supply Chain Management" has 3 authors "Oracle Database 10g XML & SQL Design, Build, & Manage XML Applications in Java, C, C++, & PL/SQL" has 3 authors "Oracle PL/SQL Tips and Techniques" has 3 authors PL/SQL procedure successfully completed. Syntax von Funktionen Die Syntax zum Erstellen einer gespeicherten Funktion ähnelt der Syntax für eine Prozedur: Die folgende Übersicht zeigt die Syntax. Function_name ist der Name der Funktion, argument und type sind das Gleiche wie bei Prozeduren und return_type ist der Typ des Rückgabewertes der Funktion. function_ body ist ein PL/SQL-Block mit dem Quellcode der Funktion. Für einen Funktionsbody gelten die gleichen Regeln wie für einen Prozedurbody – so kann der Name der Funktion optional nach dem letzten END erscheinen. Wie auch bei Prozeduren ist die Argumentliste hier optional. In diesem Fall gibt es weder in der Funktionsdeklaration noch im Funktionsaufruf Klammern. Der Rückgabetyp der Funktion ist allerdings obligatorisch, da der Funktionsaufruf Teil eines Aus- 8.1 Prozeduren und Funktionen 333 drucks ist. Über den Funktionstyp wird der Typ des Ausdrucks ermittelt, der den Funktionsaufruf enthält. RETURN-Anweisungen Im Funktionsbody wird über die RETURN-Anweisung die Steuerung an die aufrufende Umgebung durch Übergabe eines Wertes zurückgegeben. Die allgemeine Syntax der RETURN-Anweisung lautet wie folgt: RETURN expression; wobei expression der zurückzugebende Wert ist. Bei der Ausführung von RETURN wird expression in den Typ konvertiert, der in der RETURN-Klausel der Funktionsdefinition angegeben wurde. An diesem Punkt kehrt die Steuerung unmittelbar zur aufrufenden Umgebung zurück. In einer Funktion kann es mehr als eine RETURN-Anweisung geben, obwohl nur eine ausgeführt wird. Es führt zu einem Fehler, wenn eine Funktion ohne Ausführung von RETURN endet. So enthält die im obigen Abschnitt vorgestellte Funktion ThreeAuthors zwei RETURN-Anweisungen. Welche ausgeführt wird, hängt davon ab, ob das betreffende Buch drei Autoren hat. In einer Funktion muss die RETURN-Anweisung mit einem Ausdruck verknüpft sein. RETURN kann auch in einer Prozedur verwendet werden. In diesem Fall besitzt die Anweisung kein Argument, was dazu führt, dass die Steuerung unmittelbar an die aufrufende Umgebung zurückgegeben wird. Die aktuellen Werte der formalen Parameter, die als OUT oder IN OUT deklariert sind, werden an die konkreten Parameter zurückgegeben, und die Ausführung wird bei der Anweisung fortgesetzt, die unmittelbar auf den Prozeduraufruf folgt. Prozeduren und Funktionen löschen So wie eine Tabelle gelöscht werden kann, können auch Prozeduren und Funktionen gelöscht werden. Damit wird die Prozedur oder Funktion aus dem Data Dictionary entfernt. Die Syntax zum Löschen einer Prozedur lautet: DROP PROCEDURE procedure_name; und zum Löschen einer Funktion: DROP FUNCTION function_name; wobei procedure_name der Name einer vorhandenen Prozedur und function_name der Name einer existierenden Funktion ist. Die folgende Anweisung löscht beispielsweise die Prozedur AddNewAuthor: DROP PROCEDURE AddNewAuthor; Handelt es sich bei dem zu löschenden Objekt um eine Funktion, müssen Sie DROP FUNCTION verwenden. Bei einer Prozedur setzen Sie DROP PROCEDURE ein. Wie CREATE ist auch DROP ein DDL-Befehl: Damit wird vor und nach der Anweisung ein 334 8 Prozeduren, Funktionen und Packages erstellen implizites COMMIT ausgeführt. Sollte das Unterprogramm nicht vorhanden sein, löst die DROP-Anweisung den Fehler „ORA-4043: Object does not exist“ aus. 8.1.2 Unterprogrammparameter Wie in anderen 3GLs können Sie Prozeduren und Funktionen erstellen, die Parameter übernehmen. Diese Parameter können verschiedene Modi besitzen und nach Wert (By Value) oder Referenz (By Reference) übergeben werden. In den folgenden Abschnitten betrachten wir dies etwas genauer. Parameter-Modi Die bereits bekannte Prozedur AddNewAuthor können wir aus folgendem anonymen PL/SQL-Block aufrufen: –– Online verfügbar als callANA.sql DECLARE –– Variables describing the new author v_NewFirstName authors.first_name%TYPE := 'Cynthia'; v_NewLastName authors.last_name%TYPE := 'Camino'; v_NewAuthorID authors.ID%TYPE := 100; BEGIN –– Add Cynthia Camino to the database AddNewAuthor(v_NewAuthorID, v_NewFirstName, v_NewLastName); END; Die in diesem Block deklarierten Variablen (v_NewAuthorID, v_NewFirstName, v_ NewLastName) werden an AddNewAuthor als Argumente übergeben. In diesem Kontext bezeichnet man sie als konkrete Parameter, während man die Parameter in der Prozedurdeklaration (p_ID, p_FirstName, p_LastName) formale Parameter nennt. Konkrete Parameter enthalten Werte, die an eine Prozedur beim Aufruf übergeben werden. Sie erhalten von der Prozedur bei der Rückkehr die Ergebnisse (in Abhängigkeit vom Modus). Die Werte der konkreten Parameter werden in der Prozedur verwendet. Die formalen Parameter sind die Patzhalter für die Werte der konkreten Parameter. Beim Aufruf der Prozedur werden den formalen Parametern die Werte der konkreten Parameter zugewiesen. Innerhalb der Prozedur werden sie über die formalen Parameter referenziert. Kehrt die Prozedur zurück, werden den konkreten Parametern die Werte der formalen Parameter zugewiesen. Diese Zuweisungen folgen den normalen Regeln für PL/SQL-Zuweisungen, inklusive der Typenkonvertierung (falls erforderlich). Formale Parameter können drei Modi besitzen – IN, OUT oder IN OUT. (Der Modifizierer NOCOPY wird im nächsten Abschnitt beschrieben.) Wird für einen formalen 8.1 Prozeduren und Funktionen 335 Parameter der Modus nicht angegeben, wird er standardmäßig auf IN eingestellt. Tabelle 8-1 enthält die Unterschiede zwischen den Modi. Tabelle 8-1: Parametermodi. Modus Beschreibung IN Die Werte des konkreten Parameters werden beim Aufruf der Prozedur an sie übergeben. Innerhalb der Prozedur agiert der formale Parameter wie eine PL/SQLKonstante – er wird als read-only betrachtet und kann nicht geändert werden. Wennbei Beendigung der Prozedur die Steuerung an die aufrufende Umgebung zurückgegeben wird, wird der konkrete Parameter nicht geändert. OUT Der Wert des konkreten Parameters wird beim Aufruf der Prozedur ignoriert. Innerhalb der Prozedur agiert der formale Parameter wie eine nicht initialisierte PL/SQL-Variable und hat damit den Wert NULL. Er kann gelesen und in ihn kann geschrieben werden. Wenn bei Beendigung der Prozedur die Steuerung an die aufrufende Umgebung zurückkehrt, werden die Inhalte des formalen Parameters dem konkreten Parameter zugewiesen. (Dieses Verhalten kann mit dem NOCOPYModifizierer geändert werden). IN OUT Dieser Modus ist eine Kombination von IN und OUT. Der Wert des konkreten Parameters wird beim Aufruf der Prozedur an sie übergeben. Innerhalb der Prozedur agiert der formale Parameter wie eine initialisierte Variable, in die geschrieben und aus der gelesen werden kann. Wenn bei Beendigung der Prozedur die Steuerung an die aufrufende Umgebung zurückkehrt, werden die Inhalte des formalen Parameters dem konkreten Parameter zugewiesen. (In Abhängigkeit von NOCOPY, wie bei OUT). IN-Parametern Werte zuweisen Betrachten Sie die folgende Prozedur, die einen einzelnen IN-Parameter verwendet: –– Online verfügbar in parameterModes.sql CREATE OR REPLACE PROCEDURE ModeIn ( p_InParameter IN NUMBER) AS v_LocalVariable NUMBER := 0; BEGIN DBMS_OUTPUT.PUT('Inside ModeIn: '); IF (p_InParameter IS NULL) THEN DBMS_OUTPUT.PUT_LINE('p_InParameter is NULL'); ELSE DBMS_OUTPUT.PUT_LINE('p_InParameter = ' || p_InParameter); END IF; /* Assign p_InParameter to v_LocalVariable. This is legal, since we are reading from an IN parameter and not writing to it. */ v_LocalVariable := p_InParameter; DBMS_OUTPUT.PUT('At end of ModeIn: '); 336 8 Prozeduren, Funktionen und Packages erstellen IF (p_InParameter IS NULL) THEN DBMS_OUTPUT.PUT_LINE('p_InParameter is NULL'); ELSE DBMS_OUTPUT.PUT_LINE('p_InParameter = ' || p_InParameter); END IF; END ModeIn; Die folgende SQL*Plus-Session zeigt einen erfolgreichen Aufruf von ModeIn: –– Online verfügbar in parameterModes.sql SQL> DECLARE 2 v_In NUMBER := 1; 3 BEGIN 4 – Call ModeIn with a variable, which should remain unchanged. 5 DBMS_OUTPUT.PUT_LINE('Before calling ModeIn, v_In = ' || v_In); 6 ModeIn(v_In); 7 DBMS_OUTPUT.PUT_LINE('After calling ModeIn, v_In = ' || v_In); 8 END; 9 / Before calling ModeIn, v_In = 1 Inside ModeIn: p_InParameter = 1 At end of ModeIn: p_InParameter = 1 After calling ModeIn, v_In = 1 PL/SQL procedure successfully completed. Wie zu erwarten, bleibt der Wert von v_In vor, während und nach dem Prozeduraufruf derselbe. OUT-Parametern Werte zuweisen Betrachten Sie nun die folgende Prozedur, die einen einzelnen OUT-Parameter verwendet: –– Online verfügbar in parameterModes.sql CREATE OR REPLACE PROCEDURE ModeOut ( p_OutParameter OUT NUMBER) AS v_LocalVariable NUMBER := 0; BEGIN DBMS_OUTPUT.PUT('Inside ModeOut: '); IF (p_OutParameter IS NULL) THEN DBMS_OUTPUT.PUT_LINE('p_OutParameter is NULL'); ELSE DBMS_OUTPUT.PUT_LINE('p_OutParameter = ' || p_OutParameter); END IF; /* Assign 7 to p_OutParameter. This is legal, since we are writing to an OUT parameter. */ p_OutParameter := 7; /* Assign p_OutParameter to v_LocalVariable. This is also legal, * since we are reading from an OUT parameter. */ v_LocalVariable := p_OutParameter; 8.1 Prozeduren und Funktionen 337 DBMS_OUTPUT.PUT('At end of ModeOut: '); IF (p_OutParameter IS NULL) THEN DBMS_OUTPUT.PUT_LINE('p_OutParameter is NULL'); ELSE DBMS_OUTPUT.PUT_LINE('p_OutParameter = ' || p_OutParameter); END IF; END ModeOut; Die folgende SQL*Plus-Session zeigt einen erfolgreichen Aufruf von ModeOUT: –– Online verfügbar in parameterModes.sql SQL> DECLARE 2 v_Out NUMBER := 1; 3 BEGIN 4 -- Call ModeOut with a variable, which should be modified. 5 DBMS_OUTPUT.PUT_LINE('Before calling ModeOut, v_Out = ' || v_Out); 6 ModeOut(v_Out); 7 DBMS_OUTPUT.PUT_LINE('After calling ModeOut, v_Out = ' || v_Out); 8 END; 9 / Before calling ModeOut, v_Out = 1 Inside ModeOut: p_OutParameter is NULL At end of ModeOut: p_OutParameter = 7 After calling ModeOut, v_Out = 7 PL/SQL procedure successfully completed. Aus dem obigen Beispiel lassen sich zwei Dinge ableiten – der formale Parameter p_ OutParameter ist NULL, selbst wenn der konkrete Parameter v_Out auf 1 initialisiert wurde. Der finale Wert des formalen Parameters p_OutParameter wird bei der Rückkehr aus der Prozedur in den konkreten Parameter v_Out kopiert. Hinweis: Löst die Prozedur einen Fehler aus, werden die Werte der formalen IN- und OUT-Parameter nicht in die dazugehörigen konkreten Parameter kopiert (gemäß NOCOPY). IN OUT-Parametern Werte zuweisen Betrachten Sie nun die folgende Prozedur, die einen einzelnen IN OUT-Parameter verwendet: –– Online verfügbar in parameterModes.sql CREATE OR REPLACE PROCEDURE ModeInOut ( p_InOutParameter IN OUT NUMBER) IS v_LocalVariable NUMBER := 0; BEGIN DBMS_OUTPUT.PUT('Inside ModeInOut: '); IF (p_InOutParameter IS NULL) THEN DBMS_OUTPUT.PUT_LINE('p_InOutParameter is NULL'); 338 8 Prozeduren, Funktionen und Packages erstellen ELSE DBMS_OUTPUT.PUT_LINE('p_InOutParameter = ' || p_InOutParameter); END IF; /* Assign p_InOutParameter to v_LocalVariable. This is legal, since we are reading from an IN OUT parameter. */ v_LocalVariable := p_InOutParameter; /* Assign 8 to p_InOutParameter. This is legal, since we are writing to an IN OUT parameter. */ p_InOutParameter := 8; DBMS_OUTPUT.PUT('At end of ModeInOut: '); IF (p_InOutParameter IS NULL) THEN DBMS_OUTPUT.PUT_LINE('p_InOutParameter is NULL'); ELSE DBMS_OUTPUT.PUT_LINE('p_InOutParameter = ' || p_InOutParameter); END IF; END ModeInOut; Die folgende SQL*Plus-Session zeigt einen erfolgreichen Aufruf von ModeInOut: –– Online verfügbar in parameterModes.sql SQL> DECLARE 2 v_InOut NUMBER := 1; 3 BEGIN 4 –– Call ModeInOut with a variable, which should be modified. 5 DBMS_OUTPUT.PUT_LINE('Before calling ModeInOut, v_InOut = ' || 6 v_InOut); 7 ModeInOut(v_InOut); 8 DBMS_OUTPUT.PUT_LINE('After calling ModeInOut, v_InOut = ' || 9 v_InOut); 10 END; 11 / Before calling ModeInOut, v_InOut = 1 Inside ModeInOut: p_InOutParameter = 1 At end of ModeInOut: p_InOutParameter = 8 After calling ModeInOut, v_InOut = 8 PL/SQL procedure successfully completed. Dies unterscheidet sich von der Ausgabe von ModeOut in einem wichtigen Aspekt: Der formale Parameter p_InOutParameter wird mit dem Wert der aktuellen Variable v_InOut initialisiert. Wie bei ModeOut wird aber der finale Wert des formalen Parameters p_InOutParameter bei der Rückkehr aus der Prozedur in die konkrete Parametervariable v_InOut kopiert. Literale oder Konstanten als konkrete Parameter Aufgrund dieses Kopiervorgangs müssen die konkreten Parameter, die den formalen Parametern OUT oder IN OUT entsprechen, eine Variable und keine Konstante oder ein Ausdruck sein. Es muss einen Ort geben, an dem der Rückgabewert gespeichert werden kann. Der 8.1 Prozeduren und Funktionen 339 PL/SQL-Compiler erkennt diese Situation und löst einen Fehler aus, wie die folgende SQL*Plus-Session zeigt: –– Online verfügbar in parameterModes.sql SQL> BEGIN 2 –– We cannot call ModeOut (or ModeInOut) with a constant, since 3 –– the actual parameter must identify a storage location. 4 ModeOut(3); 5 END; 6 / ModeOut(3); * ERROR at line 4: ORA-06502: line 4, column 11: PLS-00363: expression '3' cannot be used as an assignment target ORA-06550: line 4, column 3: PL/SQL: Statement ignored Wir können aber einen konkreten Parameter mit einem konstanten Wert ohne Fehler für einen formalen IN-Parameter verwenden: –– Online verfügbar in parameterModes.sql SQL> BEGIN 2 –– We can call ModeIn with a constant, though. 3 ModeIn(3); 4 END; 5 / Inside ModeIn: p_InParameter = 3 At end of ModeIn: p_InParameter = 3 PL/SQL procedure successfully completed. Wie zu erwarten, bleibt der formale Parameter p_In während der Ausführung der Prozedur unverändert. Modifikationen von IN-Parametern Zusätzlich zur Gültigkeitsprüfung der konkreten OUT-Parameter stellt der PL/SQL-Compiler auch sicher, dass kein formaler IN-Parameter geändert wurde, wie die folgende SQL*Plus-Sitzung zeigt: –– Online verfügbar in parameterModes.sql SQL> 2 3 4 5 6 7 8 CREATE OR REPLACE PROCEDURE IllegalModeIn ( p_InParameter IN NUMBER) AS BEGIN /* Assign 7 to p_InParameter. This is ILLEGAL, since we are writing to an IN parameter. */ p_InParameter := 7; END IllegalModeIn; / 340 8 Prozeduren, Funktionen und Packages erstellen Warning: Procedure created with compilation errors. SQL> show errors Errors for PROCEDURE ILLEGALMODEIN: LINE/COL ERROR –––– –––––––––––––––––––––––––––––––6/3 PLS-00363: expression 'P_INPARAMETER' cannot be used as an assignment target 6/3 PL/SQL: Statement ignored Constraints für formale Parameter Beim Aufruf einer Prozedur werden die Werte der konkreten Parameter übergeben. Diese Parameter werden mit den formalen Parametern in der Prozedur referenziert. Auch die Constraints für die Variablen werden als Teil des Parameterübergabeverfahrens übergeben. In der Deklaration einer Prozedur ist es unzulässig, CHAR- oder VARCHAR2-Parameter in der Länge zu beschränken und NUMBER-Parametern eine Genauigkeit oder Anzahl der Nachkommastellen vorzugeben, weil die Constraints von den konkreten Parametern übernommen werden. So ist beispielsweise die folgende Prozedurdeklaration unzulässig und führt zu einem Kompilierungsfehler: –– Online verfügbar in ParameterLength.sql CREATE OR REPLACE PROCEDURE ParameterLength ( p_Parameter1 IN OUT VARCHAR2(10), p_Parameter2 IN OUT NUMBER(3,1)) AS BEGIN p_Parameter1 := 'abcdefghijklm'; – 15 characters in length p_Parameter2 := 12.3; END ParameterLength; Die korrekte Deklaration für diese Prozedur lautet wie folgt: –– Online verfügbar in ParameterLength.sql CREATE OR REPLACE PROCEDURE ParameterLength ( p_Parameter1 IN OUT VARCHAR2, p_Parameter2 IN OUT NUMBER) AS BEGIN p_Parameter1 := 'abcdefghijklmno'; – 15 characters in length p_Parameter2 := 12.3; END ParameterLength; Welche Constraints bestehen nun für p_Parameter1 und p_Parameter2? Sie rühren von den konkreten Parametern her. Wenn wir ParameterLength wie folgt aufrufen: 8.1 Prozeduren und Funktionen 341 –– Online verfügbar in ParameterLength.sql DECLARE v_Variable1 VARCHAR2(40); v_Variable2 NUMBER(7,3); BEGIN ParameterLength(v_Variable1, v_Variable2); END; hat p_Parameter1 eine maximale Länge von 40 (vom konkreten Parameter v_ Variable1) und p_Parameter2 hat die Genauigkeit 7 und die Anzahl der Nachkommastellen 3 (vom konkreten Parameter v_Variable2). Dies sollte man stets vor Augen haben. Betrachten Sie dazu den folgenden Block, der ebenfalls ParameterLength aufruft: –– Online verfügbar in ParameterLength.sql DECLARE v_Variable1 VARCHAR2(10); v_Variable2 NUMBER(7,3); BEGIN ParameterLength(v_Variable1, v_Variable2); END; Der einzige Unterschied zwischen diesem und dem vorherigen Block ist, dass v_ Variable1 und damit p_Parameter1 eine Länge von 10 anstatt von 40 besitzt. Da ParameterLength an p_Parameter1 (und damit v_Variable1) eine Zeichenfolge der Länge 15 zuweist, ist in der Zeichenfolge nicht genügend Platz vorhanden. Das führt beim Aufruf der Prozedur zu einem Oracle-Fehler: DECLARE * ERROR at line 1: ORA-06502: PL/SQL: numeric or value error character string buffer too small ORA-06512: at "EXAMPLE.PARAMETERLENGTH", line 5 ORA-06512: at line 5 Die Ursache des Fehlers liegt nicht in der Prozedur, sondern im Code, der die Prozedur aufruft. Außerdem ist ORA-6502 ein Laufzeitfehler und kein Kompilierfehler. Damit wird der Block erfolgreich kompiliert. Der Fehler wurde tatsächlich bei der Rückkehr der Prozedur ausgelöst und die PL/SQL-Engine versuchte, den konkreten Wert 'abcdefghijklmno' in den formalen Parameter zu kopieren. 342 8 Prozeduren, Funktionen und Packages erstellen Tipp: Um Fehler wie ORA-6502 zu vermeiden, sollten Sie beim Erstellen der Prozedur alle Constraint-Anforderungen der konkreten Parameter dokumentieren. Diese Dokumentation kann aus Kommentaren bestehen und sollte neben den Parameterdefinitionen angeben, was die Prozedur macht. Alternativ können Sie den formalen Parameter mit %TYPE deklarieren. %TYPE und Prozedurparameter Obwohl formale Parameter nicht mit Constraints deklariert werden können, können ihnen mit %TYPE Constraints zugewiesen werden. Wird ein formaler Parameter mit Hilfe von %TYPE deklariert und der zugrunde liegende Typ ist beschränkt, existiert das Constraint nicht für den konkreten, sondern für den formalen Parameter. Wenn wir ParameterLength wie folgt deklarieren: –– Online verfügbar in ParameterLength.sql CREATE OR REPLACE PROCEDURE ParameterLength ( p_Parameter1 IN OUT VARCHAR2, p_Parameter2 IN OUT books.copyright%TYPE) AS BEGIN p_Parameter2 := 12345; END ParameterLength; wird p_Parameter2 mit der Genauigkeit von 3 beschränkt, weil das die Genauigkeit der Spalte copyright ist. Selbst wenn wir ParameterLength mit einem konkreten Parameter aufrufen, dessen Genauigkeit ausreicht, wird die formale Genauigkeit übernommen. Damit generiert das folgende Beispiel den Fehler ORA-6502: –– Online verfügbar in ParameterLength.sql SQL> DECLARE 2 v_Variable1 VARCHAR2(1); 3 –– Declare v_Variable2 with no constraints 4 v_Variable2 NUMBER; 5 BEGIN 6 –– Even though the actual parameter has room for 12345, the 7 –– constraint on the formal parameter is taken and we get 8 –– ORA-6502 on this procedure call. 9 ParameterLength(v_Variable1, v_Variable2); 10 END; 11 / DECLARE * ERROR at line 1: ORA-06502: PL/SQL: numeric or value error number precision too large 8.1 Prozeduren und Funktionen 343 ORA-06512: at "EXAMPLE.PARAMETERLENGTH", line 5 ORA-06512: at line 9 Hinweis: Der Fehler ORA-6502 wurde für Oracle8i erweitert. Für Oracle8i meldete der Fehler lediglich „ORA-6502: PL/SQL numeric or value error“, ungeachtet der tatsächlichen Fehlerursache. In Unterprogrammen ausgelöste Exceptions Tritt in einem Unterprogramm ein Fehler auf, wird eine Exception ausgelöst. Diese Exception kann benutzer- oder vordefiniert sein. Besitzt die Prozedur für diesen Fehler keinen Exception Handler (oder die Exception wird in einem Exception Handler ausgelöst), wird die Steuerung unmittelbar aus der Prozedur an die aufrufende Umgebung übergeben. Das geschieht in Übereinstimmung mit den Regeln zur Weitergabe von Exceptions (siehe Kapitel 9). In diesem Fall werden die Werte der formalen Parameter OUT und INOUT nicht an die konkreten Parameter zurückgegeben. Die konkreten Parameter haben die gleichen Werte, als wäre die Prozedur nicht aufgerufen worden. Angenommen, wir erstellen folgende Prozedur: –– Online verfügbar in RaiseError.sql /* Illustrates the behavior of unhandled exceptions and * OUT variables. If p_Raise is TRUE, then an unhandled * error is raised. If p_Raise is FALSE, the procedure * completes successfully. */ CREATE OR REPLACE PROCEDURE RaiseError ( p_Raise IN BOOLEAN, p_ParameterA OUT NUMBER) AS BEGIN p_ParameterA := 7; IF p_Raise THEN /* Even though we have assigned 7 to p_ParameterA, this * unhandled exception causes control to return immediately * without returning 7 to the actual parameter associated * with p_ParameterA. */ RAISE DUP_VAL_ON_INDEX; ELSE –– Simply return with no error. This will return 7 to the –– actual parameter. RETURN; END IF; END RaiseError; Wenn wir RaiseError mit folgendem Block aufrufen: 344 8 Prozeduren, Funktionen und Packages erstellen –– Online verfügbar in RaiseError.sql DECLARE v_Num NUMBER := 1; BEGIN DBMS_OUTPUT.PUT_LINE('Value before first call: ' || v_Num); RaiseError(FALSE, v_Num); DBMS_OUTPUT.PUT_LINE('Value after successful call: ' || v_Num); DBMS_OUTPUT.PUT_LINE(''); v_Num := 2; DBMS_OUTPUT.PUT_LINE('Value before second call: ' || v_Num); RaiseError(TRUE, v_Num); EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Value after unsuccessful call: ' || v_Num); END; erhalten wir die folgende Ausgabe: Value Value Value Value before first call: 1 after successful call: 7 before second call: 2 after unsuccessful call: 2 Vor dem ersten Aufruf von RaiseError enthielt v_Num 1. Der erste Aufruf war erfolgreich, und v_Num wurde der Wert 7 zugewiesen. Dann änderte der Block v_Num vor dem zweiten Aufruf von RaiseError in 2. Dieser zweite Aufruf war nicht erfolgreich, und v_Num bleibt unverändert 2 (anstatt ein zweites Mal in 7 geändert zu werden). Hinweis: Die Semantik des Exception-Handlings ändert sich, wenn ein OUT- oder IN OUT-Parameter mit dem NOCOPY-Hint deklariert wird. Übergabe von Parametern nach Referenz und nach Wert Zur Übergabe eines Unterprogrammparameters bieten sich zwei Möglichkeiten – nach Referenz oder nach Wert. Übergibt man einen Parameter nach Referenz, wird dem dazugehörigen formalen Parameter ein Zeiger auf den konkreten Parameter übergeben. Erfolgt die Übergabe eines Parameters nach Wert, wird er aus dem konkreten in den formalen Parameter kopiert. Die Übergabe nach Referenz ist generell schneller, weil der Kopiervorgang entfällt. Das gilt insbesondere für Collectionparameter (Tabellen und Varrays, siehe Kapitel 6) aufgrund ihrer Größe. Standardmäßig kopiert PL/SQL die IN-Parameter nach Referenz und IN OUT- und OUT-Parameter nach Wert. Das geschieht, um die weiter oben besprochenen Exception-Semantiken 8.1 Prozeduren und Funktionen 345 zu erhalten, und damit die Constraints für die konkreten Parameter verifiziert werden können. Vor Oracle8i konnte man dieses Verhalten nicht ändern. NOCOPY verwenden Oracle8i bietet einen Compiler-Hint namens NOCOPY. Die Syntax zur Deklaration eines Parameters mit diesem Hint lautet: parameter_name [mode] NOCOPY datatype wobei parameter_name der Name des Parameters und mode der Parametermodus (IN, OUT oder IN OUT) ist. datatype ist der Datentyp des Parameters. Ist NOCOPY vorhanden, versucht der PL/SQL-Compiler, den Parameter nach Referenz und nicht nach Wert zu übergeben. NOCOPY ist ein Compiler-Hint und keine Anweisung, so dass es nicht immer übernommen wird. Das folgende Beispiel zeigt die Syntax für NOCOPY: –– Online verfügbar in NoCopyTest.sql CREATE OR REPLACE PROCEDURE NoCopyTest ( p_InParameter IN NUMBER, p_OutParameter OUT NOCOPY VARCHAR2, p_InOutParameter IN OUT NOCOPY CHAR) IS BEGIN NULL; END NoCopyTest; Verwendet man NOCOPY für einen IN-Parameter, erhält man einen Compilerfehler, weil IN-Parameter immer nach Referenz übergeben werden und NOCOPY damit unzulässig ist. Exception-Semantik mit NOCOPY Wird ein Parameter nach Referenz übergeben, wirken sich alle Änderungen des formalen Parameters auch auf den konkreten Parameter aus, da beide auf denselben Speicherort zeigen. Sollte also eine Prozedur mit einer unbehandelten Exception nach Änderung des formalen Parameters scheitern, geht der ursprüngliche Wert des konkreten Parameters verloren. Angenommen, wir ändern RaiseError für den Einsatz von NOCOPY wie folgt: –– Online verfügbar in NoCopyTest.sql CREATE OR REPLACE PROCEDURE RaiseErrorNoCopy ( p_Raise IN BOOLEAN, p_ParameterA OUT NOCOPY NUMBER) AS BEGIN p_ParameterA := 7; IF p_Raise THEN RAISE DUP_VAL_ON_INDEX; ELSE RETURN; END IF; END RaiseErrorCopy; 346 8 Prozeduren, Funktionen und Packages erstellen so ist die einzige Änderung, dass p_ParameterA jetzt nach Referenz und nicht nach Wert übergeben wird. Nun rufen wir RaiseError wie folgt auf: –– Online verfügbar in NoCopyTest.sql v_Num NUMBER := 1; BEGIN DBMS_OUTPUT.PUT_LINE('Value before first call: ' || v_Num); RaiseErrorNoCopy(FALSE, v_Num); DBMS_OUTPUT.PUT_LINE('Value after successful call: ' || v_Num); DBMS_OUTPUT.PUT_LINE(''); v_Num := 2; DBMS_OUTPUT.PUT_LINE('Value before second call: ' || v_Num); RaiseErrorNoCopy(TRUE, v_Num); EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Value after unsuccessful call: ' || v_Num); END; (Dies ist derselbe Block, den wir bereits im obigen Abschnitt "In Unterprogrammen ausgelöste Exceptions" gesehen haben, nur dass hier RaiseErrorNoCopy anstelle von RaiseError aufgerufen wird). Die Ausgabe dieses Blocks sieht allerdings anders aus: Value Value Value Value before first call: 1 after successful call: 7 before second call: 2 after unsuccessful call: 7 Der konkrete Parameter wurde beide Male geändert, auch nach Auslösen der Exception. Restriktionen bei NOCOPY In bestimmten Fällen wird NOCOPY ignoriert und der Parameter wird nach Wert übergeben. Dabei wird kein Fehler generiert. Beachten Sie, dass NOCOPY nur ein Hint ist – der Compiler muss dieser Vorgabe nicht zwingend befolgen. NOCOPY wird in folgenden Situationen ignoriert: ■ Der konkrete Parameter ist ein Element eines assoziativen Arrays. Ist der konkrete Parameter eine vollständige Tabelle, gilt diese Restriktion nicht. ■ Der tatsächliche Parameter ist durch eine Gesamtstellenzahl, Skalierung oder ein NOT NULL-Constraint beschränkt. Diese Restriktion gilt nicht für Zeichenparameter, deren maximale Länge beschränkt ist. Der Grund ist, dass der PL/SQLCompiler die Prüfung auf Constraintverletzungen nur bei Rückkehr aus einem Unterprogramm durchführt, wenn die Werte aus dem formalen in den konkreten Parameter kopiert werden. Liegt eine Constraintverletzung vor, muss der ursprüngliche Wert des tatsächlichen Parameters unverändert bleiben, was mit NOCOPY unmöglich ist. 8.1 347 Prozeduren und Funktionen ■ Die konkreten und formalen Parameter sind jeweils Datensätze, die entweder implizit als Variable zur Schleifensteuerung oder mit %ROWTYPE deklariert werden, wobei die Constraints auf die korrespondierenden Felder variieren. ■ Die Übergabe des konkreten Parameters verlangt eine implizite Datentypkonvertierung. ■ Das Unterprogramm gehört zu einem RPC (Remote Procedure Call). Ein RPC ist ein Prozeduraufruf, der über einen Datenbank-Link zu einem Remote-Server erfolgt. Weil die Parameter über das Netzwerk übertragen sind, lassen sie sich nicht nach Referenz übergeben. Tipp: NOCOPY wird in den Fällen ignoriert, in denen das Unterprogramm Teil eines RPCs ist. Ändern Sie eine Anwendung so, dass einige der Aufrufe RPCs und keine lokalen Aufrufe sind, kann sich die Exception-Semantik ändern. Vorteile von NOCOPY Der wichtigste Vorteil von NOCOPY ist die verbesserte Performance. Wie das folgende Beispiel zeigt, bewährt sich das vor allem bei der Übergabe großer PL/SQL-Arrays: –– Online verfügbar als CopyFast.sql CREATE OR REPLACE PACKAGE CopyFast AS –– Associative array of books. TYPE BookArray IS TABLE OF books%ROWTYPE; –– Three procedures which take a –– different ways. They each do PROCEDURE PassBooks1(p_Parameter PROCEDURE PassBooks2(p_Parameter PROCEDURE PassBooks3(p_Parameter parameter of BookArray, in nothing. IN BookArray); IN OUT BookArray); IN OUT NOCOPY BookArray); –– Test procedure. PROCEDURE Go; END CopyFast; CREATE OR REPLACE PACKAGE BODY CopyFast AS PROCEDURE PassBooks1(p_Parameter IN BookArray) IS BEGIN NULL; END PassBooks1; PROCEDURE PassBooks2(p_Parameter IN OUT BookArray) IS BEGIN NULL; 348 8 Prozeduren, Funktionen und Packages erstellen END PassBooks2; PROCEDURE PassBooks3(p_Parameter IN OUT NOCOPY BookArray) IS BEGIN NULL; END PassBooks3; PROCEDURE Go IS v_BookArray BookArray := BookArray(NULL); v_Time1 NUMBER; v_Time2 NUMBER; v_Time3 NUMBER; v_Time4 NUMBER; BEGIN –– Fill up the array with 50,001 copies of a record. SELECT * INTO v_BookArray(1) FROM books WHERE ISBN = '72230665'; v_BookArray.EXTEND(50000, 1); –– Call each version of PassBooks, and time them. –– DBMS_UTILITY.GET_TIME will return the current time, in –– hundredths of a second. v_Time1 := DBMS_UTILITY.GET_TIME; PassBooks1(v_BookArray); v_Time2 := DBMS_UTILITY.GET_TIME; PassBooks2(v_BookArray); v_Time3 := DBMS_UTILITY.GET_TIME; PassBooks3(v_BookArray); v_Time4 := DBMS_UTILITY.GET_TIME; –– Output the results. DBMS_OUTPUT.PUT_LINE('Time to pass IN: ' || TO_CHAR((v_Time2 - v_Time1) / 100)); DBMS_OUTPUT.PUT_LINE('Time to pass IN OUT: ' || TO_CHAR((v_Time3 v_Time2) / 100)); DBMS_OUTPUT.PUT_LINE('Time to pass IN OUT NOCOPY: ' || TO_CHAR((v_Time4 - v_Time3) / 100)); END Go; END CopyFast; Hinweis: Dieses Beispiel verwendet zur Gruppierung zusammengehöriger Prozeduren ein Package. Die Packages sind weiter unten beschrieben. In Kapitel 6 finden Sie Informationen zu den Collections und zum Einsatz der EXTENTS-Methode. AnhangB enthält Informationen zu DBMS_UTILITY. 8.1 Prozeduren und Funktionen 349 Die PassBooks-Prozeduren tun nichts – die Prozedur nehmen nur einen Parameter auf, bei dem es sich um einen Array von Büchern handelt. Der Parameter umfasst 50.001 Datensätze, ist also relativ groß. Der Unterschied zwischen den Prozeduren ist, dass PassBooks1 den Parameter als IN, PassBooks2 als IN OUT und PassBooks3 als IN OUT NOCOPY übernimmt. Damit sollte PassBooks2 den Parameter nach Wert und die beiden anderen sollten ihn nach Referenz übernehmen. Dies ist in den Ergebnissen des Aufrufs von CopyFast.Go zu sehen: SQL> BEGIN 2 CopyFast.Go; 3 END; 4 / Time to pass IN: 0 Time to pass IN OUT: 1.27 Time to pass IN OUT NOCOPY: 0 PL/SQL procedure successfully completed. Obwohl das konkrete Ergebnis auf Ihrem System anders aussehen kann, ist der Zeitaufwand bei der Übergabe nach Wert wesentlich höher als bei der Übergabe der INund IN OUT NOCOPY-Parameter nach Referenz. Hinweis: In Oracle10g wurden Änderungen am PL/SQL-Optimizer vorgenommen, so dass nun leere Prozeduren optimiert werden. Der Zeitunterschied bei Prozeduren kann nun in Oracle10g und höher geringer sein, da Prozeduraufrufe entfernt werden. Es empfiehlt sich, die Performance-Auswirkungen von NOCOPY auf dem eigenen System und mit den eigenen Daten zu testen, um die realistischen Zeiteinsparungen zu bestimmen. Unterprogramme ohne Parameter Gibt es für eine Prozedur keine Parameter, finden sich weder in der Prozedurdeklaration noch im Prozeduraufruf irgendwelche Klammern. Das gilt auch für Funktionen. Dazu das folgende Beispiel: –– Online verfügbar als noparams.sql CREATE OR REPLACE PROCEDURE NoParamsP AS BEGIN DBMS_OUTPUT.PUT_LINE('No Parameters!'); END NoParamsP; CREATE OR REPLACE FUNCTION NoParamsF RETURN DATE AS BEGIN RETURN SYSDATE; END NoParamsF; 350 8 Prozeduren, Funktionen und Packages erstellen BEGIN NoParamsP; DBMS_OUTPUT.PUT_LINE('Calling NoParamsF on ' || TO_CHAR(NoParamsF, 'DD-MON-YYYY')); END; Hinweis: Bei der CALL-Syntax in Oracle8i sind die Klammern optional. Mehr dazu weiter unten. Positionale Notation und Namensnotation In allen bisherigen Beispielen sind die konkreten und formalen Argumente anhand der Position zugeordnet. Nimmt man eine Deklaration wie die folgende: –– Online verfügbar in CallMe.sql CREATE OR REPLACE PROCEDURE CallMe( p_ParameterA VARCHAR2, p_ParameterB NUMBER, p_ParameterC BOOLEAN, p_ParameterD DATE) AS BEGIN NULL; END CallMe; und einen aufrufenden Block wie den folgenden: –– Online verfügbar in CallMe.sql DECLARE v_Variable1 VARCHAR2(10); v_Variable2 NUMBER(7,6); v_Variable3 BOOLEAN; v_Variable4 DATE; BEGIN CallMe(v_Variable1, v_Variable2, v_Variable3, v_Variable4); END; sind die konkreten mit den formalen Parametern anhand ihrer Position verknüpft: v_ Variable1 ist mit p_ParameterA, v_Variable2 ist mit p_ParameterB usw. verknüpft. Dies bezeichnet man als positionale Notation. Die positionale Notation wird öfter eingesetzt und entspricht der Notation in anderen 3GLs, wie C und Java. Alternativ können wir die Prozedur auch mit Hilfe der Namensnotation aufrufen: –– Online verfügbar in CallMe.sql DECLARE v_Variable1 VARCHAR2(10); 8.1 Prozeduren und Funktionen 351 v_Variable2 NUMBER(7,6); v_Variable3 BOOLEAN; v_Variable4 DATE; BEGIN CallMe(p_ParameterA => v_Variable1, p_ParameterB => v_Variable2, p_ParameterC => v_Variable3, p_ParameterD => v_Variable4); END; Bei der Namensnotation sind bei jedem Argument der formale und konkrete Parameter eingebunden. Das erlaubt uns, gegebenenfalls die Reihenfolge der Argumente zu verändern. Der folgende Block ruft beispielsweise mit denselben Argumenten ebenfalls CallMe auf: –– Online verfügbar in CallMe.sql DECLARE v_Variable1 VARCHAR2(10); v_Variable2 NUMBER(7,6); v_Variable3 BOOLEAN; v_Variable4 DATE; BEGIN CallMe(p_ParameterB => v_Variable2, p_ParameterC => v_Variable3, p_ParameterD => v_Variable4, p_ParameterA => v_Variable1); END; Falls gewünscht, können positionale Notation und Namensnotationin einem Aufruf kombiniert werden. Das erste Argument muss anhand der Position angegeben werden, die restlichen Argumente kann man über ihren Namen angeben. Der folgende Block illustriert diese Methode: –– Online verfügbar in CallMe.sql DECLARE v_Variable1 v_Variable2 v_Variable3 v_Variable4 BEGIN VARCHAR2(10); NUMBER(7,6); BOOLEAN; DATE; –– First 2 parameters passed by position, the second 2 are –– passed by name. CallMe(v_Variable1, v_Variable2, p_ParameterC => v_Variable3, p_ParameterD => v_Variable4); END; 352 8 Prozeduren, Funktionen und Packages erstellen Die Namensnotation ist ein weiteres Leistungsmerkmal in PL/SQL, das von Ada stammt. Wann soll man mit der positionalen Notation und wann mit der Namensnotation arbeiten? Keine ist effizienter als die andere, so dass es letztlich eine Stilfrage bleibt. Einige Unterschiede bezüglich der Stile sind in Tabelle 8-2 aufgeführt. Tabelle 8-2: Positionale Notation und Namensnotation im Vergleich. Positionale Notation Namensnotation Steht und fällt mit einer guten Benennung der konkreten Parameter, die zeigt, wofür die einzelnen Parameter gedacht sind. Zeigt eindeutig die Verbindung zwischen konkreten und formalen Parametern. Die Namen für die formalen und konkreten Parameter sind unabhängig: Sie lassen sich unabhängig voneinander ändern. Kann schwieriger zu warten sein, weil alle Aufrufe der Prozedur mit Hilfe der Namensnotation geändert werden müssen, wenn sich die Namen der formalen Parameter ändern. Kann schwieriger zu warten sein, weil alle Aufrufe der Prozedur mit Hilfe der positionalen Notation geändert werden müssen, wenn sich die Reifenfolge der formalen Parameter ändert. Die Reihenfolge für die formalen und konkreten Parameter ist unabhängig: Beide lassen sich unabhängig voneinander ändern. Knapper als die Namensnotation Benötigt mehr Code, weil im Prozeduraufruf sowohl die formalen als auch die konkreten Parameter eingebunden sind. Dieses zusätzliche Kodieren dient dazu, den Zweck jedes konkreten Parameters durch explizite Aufnahme des zugehörigen formalen Parameters zu dokumentieren. Parameter mit Standardwerten müssen am Ende der Argumentliste stehen. Erlaubt die Verwendung von Standardwerten für formale Parameter, ungeachtet dessen, welcher Parameter den Standardwert besitzt. Ich persönlich arbeite mit der positionalen Notation, weil der Code knapper und kompakter ist. Wichtig ist jedoch, für den konkreten Parameter sinnvolle Namen zu wählen. Übernimmt die Prozedur viele Argumente (z. B. mehr als zehn), ist die Namensnotation vorzuziehen, weil sich formale und konkrete Parameter besser zuordnen lassen. Prozeduren mit vielen Argumenten sind allerdings selten. Die Namensnotation bewährt sich auch für Prozeduren mit Standardwerten (siehe nächster Abschnitt). 8.1 Prozeduren und Funktionen 353 Tipp: Je mehr Parameter eine Prozedur besitzt, desto schwieriger ist der Aufruf – prüfen Sie deshalb, ob alle benötigten Parameter vorhanden sind. In solchen Fällen sollten Sie die Definition eines Datensatztyps erwägen, in dem die Parameter als Felder im Datensatz definiert sind. Dann können Sie für diesen Datensatztyp einen einzigen Parameter verwenden. (Sollte die aufrufende Umgebung nicht PL/SQL sein, können Sie vielleicht keinen Datensatztyp einbinden). PL/SQL kennt bei der Anzahl der Parameter keine Beschränkungen. Standardwerte für Parameter Ähnlich wie bei einer Variablendeklaration können die formalen Parameter für eine Prozedur oder Funktion Standardwerte aufweisen. Hat ein Parameter einen Standardwert, muss er von der aufrufenden Umgebung nicht übergeben werden. Wird er übergeben, wird anstelle des Standardwerts der Wert des tatsächlichen Parameters verwendet. Ein Standardwert für einen Parameter bindet man mit folgender Syntax ein: parameter_name [mode] [NOCOPY] parameter_type {:= | DEFAULT} initial_value wobei parameter_name der Name des formalen Parameters, mode der Parametermodus (IN, OUT oder IN OUT), parameter_type der Parametertyp (entweder vor- oder benutzerdefiniert) und initial_value der Wert ist, der dem formalen Parameter standardmäßig zugewiesen wird. Dazu verwendet man entweder := oder das Schlüsselwort DEFAULT. Betrachten wir beispielsweise die Prozedur AddNewBook: –– Online verfügbar in AddNewBook.sql CREATE OR REPLACE PROCEDURE AddNewBook( p_ISBN IN books.ISBN%TYPE, p_Category IN books.category%TYPE := 'Oracle Server', p_Title IN books.title%TYPE, p_NumPages IN books.num_pages%TYPE, p_Price IN books.price%TYPE, p_Copyright IN books.copyright%TYPE DEFAULT TO_NUMBER(TO_CHAR(SYSDATE, 'YYYY')), p_Author1 IN books.author1%TYPE, p_Author2 IN books.author2%TYPE := NULL, p_Author3 IN books.author3%TYPE := NULL) AS BEGIN –– Insert a new row into the table using the supplied –– parameters. INSERT INTO books (isbn, category, title, num_pages, price, copyright, author1, author2, author3) VALUES (p_ISBN, p_Category, p_Title, p_NumPages, p_Price, p_Copyright, p_Author1, p_Author2, p_Author3); END AddNewBook; 354 8 Prozeduren, Funktionen und Packages erstellen AddNewBook verfügt über vier Standardparameter: p_Category, p_Copyright, p_ Author2 und p_Author3. Die Standardwerte werden verwendet, wenn dem forma- len Parameter im Prozeduraufruf kein konkreter Parameter zugeordnet ist. Wir können beispielsweise mit dem folgenden Block vermeiden, p_Author2 und p_Author3 zu übergeben: –– Online verfügbar in AddNewBook.sql BEGIN AddNewBook('0000000000', 'Oracle Basics', 'A Really Nifty Book', 500, 34.99, 2004, 1); END; In diesem Fall wird für p_Author2 und p_Author3 NULL verwendet. Wir können AddNewBook mit der benannten Notation aufrufen: –– Online verfügbar in AddNewBook.sql BEGIN AddNewBook(p_ISBN => '0000000000', p_Category => 'Oracle Basics', p_Title => 'A Really Nifty Book', p_NumPages => 500, p_Price => 34.99, p_Copyright => 2004, p_Author1 => 1); END; Wird die positionale Notation verwendet, müssen alle Parameter mit Standardwerten, die über keine zugeordneten konkreten Parameter verfügen, an das Ende der Parameterliste gesetzt werden, wie wir im obigen Beispiel im ersten Aufruf von AddNewBook gesehen haben. Wenn wir für p_Category oder p_Copyright Standardwerte verwenden möchten, muss die Namensnotation wie folgt verwendet werden: –– Online verfügbar in AddNewBook.sql BEGIN AddNewBook(p_ISBN => '0000000000', p_Title => 'A Really Nifty Book', p_NumPages => 500, p_Price => 34.99, p_Author1 => 1); END; Tipp: Bei der Verwendung von Standardwerten sollten diese möglichst die letzten Parameter in der Argumentliste sein. Damit kann man entweder die positionale Notation oder die Namensnotation verwenden. 8.1 Prozeduren und Funktionen 8.1.3 355 CALL-Anweisungen Seit Oracle8i gibt es für den Aufruf von gespeicherten Unterprogrammen eine neue SQL-Anweisung: die CALL-Anweisung, bei der mit einem PL/SQL-Wrapper sowohl PL/SQL- als auch Java-Unterprogramme aufgerufen werden können. Die Syntax finden Sie im folgenden Diagramm: subprogram_name ist ein eigenständiges oder in ein Package integriertes Unterprogramm. Es kann auch eine Methode des Objekttyps oder eine Remote-Datenbank sein. Die argument_list ist eine durch Komma getrennte Argumentliste, und host_variable ist eine Hostvariable zum Abruf der Rückgabewerte von Funktionen. Die folgende SQL*Plus-Session zeigt zulässige und unzulässige Beispiele für die CALL-Anweisung. Dieses Beispiel verwendet den SQL*Plus-Befehl VARIABLE zur Deklaration einer Hostvariablen. Weitere Informationen zu diesem Befehl und zu anderen Features von SQL*Plus finden Sie in der Oracle-Dokumentation. –– Online verfügbar als calls.sql SQL> CREATE OR REPLACE PROCEDURE CallProc1(p1 IN VARCHAR2 := NULL) AS 2 BEGIN 3 DBMS_OUTPUT.PUT_LINE('CallProc1 called with ' || p1); 4 END CallProc1; 5 / Procedure created. SQL> CREATE OR REPLACE PROCEDURE CallProc2(p1 IN OUT VARCHAR2) AS 2 BEGIN 3 DBMS_OUTPUT.PUT_LINE('CallProc2 called with ' || p1); 4 p1 := p1 || ' returned!'; 5 END CallProc2; 6 / Procedure created. SQL> CREATE OR REPLACE FUNCTION CallFunc(p1 IN VARCHAR2) 2 RETURN VARCHAR2 AS 3 BEGIN 4 DBMS_OUTPUT.PUT_LINE('CallFunc called with ' || p1); 5 RETURN p1; 6 END CallFunc; 7 / Function created. SQL> – Some valid calls direct from SQL. SQL> CALL CallProc1('Hello!'); CallProc1 called with Hello! Call completed. SQL> CALL CallProc1(); CallProc1 called with Call completed. 356 8 Prozeduren, Funktionen und Packages erstellen SQL> VARIABLE v_Output VARCHAR2(50); SQL> CALL CallFunc('Hello!') INTO :v_Output; CallFunc called with Hello! Call completed. SQL> PRINT v_Output V_OUTPUT ––––––––––––––––––––––––––––––––––– Hello! SQL> CALL CallProc2(:v_Output); CallProc2 called with Hello! Call completed. SQL> PRINT v_Output V_OUTPUT –––––––––––––––––––––––––––––––––Hello! returned! SQL> – This is illegal SQL> BEGIN 2 CALL CallProc1(); 3 END; 4 / CALL CallProc1(); * ERROR at line 2: ORA-06502: line 2, column 8: PLS-00103: Encountered the symbol "CALLPROC1" when expecting one of the following: := . ( @ % ; The symbol ":=" was substituted for "CALLPROC1" to continue. SQL> – But these are legal SQL> DECLARE 2 v_Result VARCHAR2(50); 3 BEGIN 4 EXECUTE IMMEDIATE 'CALL CallProc1(''Hello from PL/SQL'')'; 5 EXECUTE IMMEDIATE 6 'CALL CallFunc(''Hello from PL/SQL'') INTO :v_Result' 7 USING OUT v_Result; 8 END; 9 / CallProc1 called with Hello from PL/SQL CallFunc called with Hello from PL/SQL PL/SQL procedure successfully completed. Dieses Beispiel veranschaulicht die folgenden Punkte: ■ CALL ist eine SQL-Anweisung. Sie ist in einem PL/SQL-Block nicht gültig, jedoch bei der Ausführung von dynamischem SQL, in diesem Fall der Anweisung EXECUTE IMMEDIATE. (Innerhalb eines PL/SQL-Blocks können Sie das Unterprogramm mit der PL/SQL-Syntax aufrufen). Mehr über dynamisches SQL finden in Sie Kapitel 13. 8.2 357 Packages ■ Die Klammern werden immer benötigt, selbst wenn das Unterprogramm keine Parameter übernimmt (oder für alle Argumente Standardwerte vorgibt). ■ Die INTO-Klausel wird nur für die Ausgabevariablen von Funktionen verwendet. IN OUT- oder OUT-Parameter werden als Teil der argument_list angegeben. 8.1.4 Prozeduren und Funktionen im Vergleich Prozeduren und Funktionen haben viele gemeinsame Leistungsmerkmale: ■ Beide können über den OUT-Parameter mehr als einen Wert zurückliefern. ■ Beide können einen deklarativen, ausführbaren und einen Exception HandlingBereich enthalten. ■ Beide können Standardwerte akzeptieren. ■ Beide lassen sich mit der positionalen oder der Namenskonvention aufrufen. ■ Beide können NOCOPY-Parameter akzeptieren. Wann werden Funktionen und wann Prozeduren verwendet? Das hängt in erste Linie davon ab, wie viele Werte das Unterprogramm zurückgeben soll und wie diese Werte verwendet werden. Als Faustregel gilt: Bei mehr als einem Rückgabewert verwendet man eine Prozedur. Gibt es nur einen Rückgabewert, kann eine Funktion verwendet werden. Obwohl es zulässig ist, dass eine Funktion mehrere OUT-Parameter hat (und damit mehr als einen Wert zurückliefert), ist das kein besonders guter Programmierstil. Funktionen können auch in einer SQL-Anweisung aufgerufen werden. Weitere Informationen hierzu finden Sie in Kapitel 9. 8.2 Packages Packages sind ein weiteres Ada-Konstrukt, das in das Design von PL/SQL eingeflossen ist. Ein Package ist ein PL/SQL-Konstrukt, mit dessen Hilfe sich zusammengehörige Objekte gemeinsam speichern lassen. Ein Package besteht aus zwei separaten Teilen: der Spezifikation und dem Body. Jedes Element wird im Data Dictionary separat gespeichert. Im Gegensatz zu Prozeduren und Funktionen, die lokal in einem Block enthalten oder in der Datenbank gespeichert sind, kann man ein Package nur speichern; es kann nicht lokal sein. Neben der Möglichkeit, zusammengehörige Objekte zu gruppieren, sind Packages bei Abhängigkeiten weniger restriktiv als gespeicherte Unterprogramme. Zudem können sie die Performance verbessern, was im nächsten Kapitel erörtert wird. Ein Package ist im Wesentlichen ein benannter deklarativer Bereich. Alles, was sich im deklarativen Teil eines Blocks befindet, kann auch in ein Package eingehen. Dazu ge- 358 8 Prozeduren, Funktionen und Packages erstellen hören Prozeduren, Funktionen, Cursor, Typen und Variablen. Diese Objekte in ein Package zu packen hat den Vorteil, dass man sie in anderen PL/SQL-Blöcken referenzieren kann, dadurch stellen Packages für PL/SQL auch globale Variablen (innerhalb einer Datenbanksitzung) zur Verfügung. 8.2.1 Package-Spezifikation Die Package-Spezifikation (auch als Package-Header bezeichnet) enthält Informationen zum Packageinhalt. Sie enthält jedoch keinen Code für Unterprogramme. Betrachten wir folgendes Beispiel: –– Online verfügbar in InventoryOps.sql CREATE OR REPLACE PACKAGE InventoryOps AS –– Modifies the inventory data for the specified book. PROCEDURE UpdateISBN(p_ISBN IN inventory.isbn%TYPE, p_Status IN inventory.status%TYPE, p_StatusDate IN inventory.status_date%TYPE, p_Amount IN inventory.amount%TYPE); –– Deletes the inventory data for the specified book. PROCEDURE DeleteISBN(p_ISBN IN inventory.isbn%TYPE); –– Exception raised by UpdateISBN or DeleteISBN when the specified –– ISBN is not in the inventory table. e_ISBNNotFound EXCEPTION; TYPE t_ISBNTable IS TABLE OF inventory.isbn%TYPE INDEX BY BINARY_INTEGER; –– Returns an array containing the books with the specified status. PROCEDURE StatusList(p_Status IN inventory.status%TYPE, p_Books OUT t_ISBNTable, p_NumBooks OUT BINARY_INTEGER); END InventoryOps; InventoryOps enthält drei Prozeduren, einen Typ und eine Exception. Die allgemei- ne Syntax zum Erstellen eines Package-Headers wird im folgenden Diagramm dargestellt: package_name ist der Name des Package. Die Elemente im Package (Prozedur- und Funktionsspezifikationen, Variablen usw.) sind die gleichen wie im deklarativen Bereich eines anonymen Blocks. Für den Package-Header gelten zudem die gleichen Syntaxregeln wie für den deklarativen Bereich, außer für die Deklarationen von Funktionen und Prozeduren. Diese Regeln lauten wie folgt: 8.2 359 Packages ■ Package-Elemente können in beliebiger Reihenfolge erscheinen. In einem deklarativen Abschnitt muss ein Objekt vor seiner Referenzierung deklariert werden. Enthält ein Cursor beispielsweise eine Variable als Teil der WHEREKlausel, muss die Variable vor der Cursordeklaration deklariert werden. ■ Es müssen nicht alle Elementtypen vorhanden sein. Ein Package kann z. B. nur Prozedur- und Funktionsspezifikationen enthalten, ohne dass Exceptions oder Typen deklariert werden. ■ Alle Deklarationen für Prozeduren und Funktionen müssen Vorwärtsdeklarationen sein. Eine Vorwärtsdeklaration beschreibt das Unterprogramm und dessen Argumente (falls vorhanden), enthält aber nicht den Code. Diese Regel unterscheidet sich vom deklarativen Bereich eines Blocks, in dem Vorwärtsdeklarationen und der konkrete Code für Prozeduren oder Funktionen enthalten sein können. Der Code, der die Prozeduren und Funktionen des Package implementiert, befindet sich im Package-Body. 8.2.2 Package-Body Der Package-Body ist ein eigenes Data-Dictionary-Objekt. Es kann erst kompiliert werden, wenn der Package-Header erfolgreich kompiliert wurde. Der Body enthält den Code für die Vorwärtsdeklaration des Unterprogramms im Package-Header. Weiterhin kann er zusätzliche Deklarationen enthalten, die für den Package-Body global, nicht aber in der Spezifikation sichtbar sind. Das folgende Beispiel zeigt den Package-Body für InventoryOps: 360 8 Prozeduren, Funktionen und Packages erstellen –– Online verfügbar in InventoryOps.sql CREATE OR REPLACE PACKAGE BODY InventoryOps AS –– Modifies the inventory data for the specified book. PROCEDURE UpdateISBN(p_ISBN IN inventory.isbn%TYPE, p_Status IN inventory.status%TYPE, p_StatusDate IN inventory.status_date%TYPE, p_Amount IN inventory.amount%TYPE) IS BEGIN UPDATE inventory SET status = p_Status, status_date = p_StatusDate, amount = p_Amount WHERE isbn = p_ISBN; –– Check for no books updated, and raise the exception. IF SQL%ROWCOUNT = 0 THEN RAISE e_ISBNNotFound; END IF; END UpdateISBN; –– Deletes the inventory data for the specified book. PROCEDURE DeleteISBN(p_ISBN IN inventory.isbn%TYPE) IS BEGIN DELETE FROM inventory WHERE isbn = p_ISBN; –– Check for no books deleted, and raise the exception. IF SQL%ROWCOUNT = 0 THEN RAISE e_ISBNNotFound; END IF; END DeleteISBN; –– Returns an array containing the books with the specified status. PROCEDURE StatusList(p_Status IN inventory.status%TYPE, p_Books OUT t_ISBNTable, p_NumBooks OUT BINARY_INTEGER) IS v_ISBN inventory.isbn%TYPE; CURSOR c_Books IS SELECT isbn FROM inventory WHERE status = p_Status; BEGIN /* p_NumBooks will be the array index. It will start at * 0, and be incremented each time through the fetch loop. * At the end of the loop, it will have the number of rows * fetched, and therefore the number of rows returned in * p_Books. */ p_NumBooks := 0; OPEN c_Books; LOOP 8.2 Packages 361 FETCH c_Books INTO v_ISBN; EXIT WHEN c_Books%NOTFOUND; p_NumBooks := p_NumBooks + 1; p_Books(p_NumBooks) := v_ISBN; END LOOP; CLOSE c_Books; END StatusList; END InventoryOps; Der Package-Body enthält den Code für die Vorwärtsdeklarationen im Package-Header und kann zusätzliche Variablen, Cursor, Typen oder Unterprogramme enthalten. Objekte im Header, bei denen es sich nicht um Vorwärtsdeklarationen handelt (wie die Exception e_ISBNNotFound) können direkt im Package-Body referenziert werden. Der Package-Body ist optional. Enthält der Package-Header keine Prozeduren oder Funktionen (nur Variablendeklarationen, Cursor, Typen usw.), muss kein Body vorhanden sein. Diese Technik ist sehr nützlich für die Deklaration globaler Variablen, da alle Objekte in einem Package außerhalb des Package sichtbar sind. Geltungsbereich und Sichtbarkeit der in einem Package integrierten Elemente werden im nächsten Abschnitt erörtert. Jede Vorwärtsdeklaration im Package-Header muss im Package-Body ausgearbeitet sein. Die Spezifikation für die Prozedur oder Funktion muss in beiden Fällen gleich sein. Das umfasst den Namen des Unterprogramms, die Namen der Parameter und die Parametermodi. Der folgende Package-Header passt beispielsweise nicht zum Package-Body, weil der Body für FunctionA eine abweichende Parameterliste verwendet: –– Online verfügbar als packageError.sql CREATE OR REPLACE PACKAGE PackageA AS FUNCTION FunctionA(p_Parameter1 IN NUMBER, p_Parameter2 IN DATE) RETURN VARCHAR2; END PackageA; CREATE OR REPLACE PACKAGE BODY PackageA AS FUNCTION FunctionA(p_Parameter1 IN CHAR) RETURN VARCHAR2; END PackageA; Wenn wir versuchen, PackageA wie oben anzulegen, erhalten wir für den PackageBody folgende Fehlermeldungen: PLS-00328: A subprogram body must be defined for the forward declaration of FUNCTIONA. PLS-00323: subprogram or cursor 'FUNCTIONA' is declared in a package specification and must be defined in the package body. 362 8 8.2.3 Prozeduren, Funktionen und Packages erstellen Packages und Geltungsbereich Jedes im Package-Header deklarierte Objekt befindet sich im Geltungsbereich und ist außerhalb des Package sichtbar. Dazu qualifiziert man das Objekt mit dem Packagenamen. Wir können beispielsweise InventoryOps.DeleteISBN aus dem folgenden PL/SQL-Block aufrufen: BEGIN InventoryOps.DeleteISBN('78824389'); END; Der Prozeduraufruf ist der gleiche wie für eine eigenständige Prozedur. Der einzige Unterschied ist, dass der Packagename vorangestellt wird. In einem Package integrierte Prozeduren können Standardparameter enthalten und mit der positionalen Notation oder der Namensnotation aufgerufen werden. Das gilt auch für benutzerdefinierte Typen, die im Package definiert sind. Um beispielsweise StatusList aufzurufen, müssen wir eine Variable vom Typ InventoryOps.t _ISBNTable deklarieren. –– Online verfügbar als callSL.sql DECLARE v_BooksInStock InventoryOps.t_ISBNTable; v_NumBooks BINARY_INTEGER; BEGIN –– Fill the PL/SQL table with the ISBNs of the books which –– are in stock. InventoryOps.StatusList('IN STOCK', v_BooksInStock, v_NumBooks); –– And print them out. FOR v_LoopCounter IN 1..v_NumBooks LOOP DBMS_OUTPUT.PUT_LINE('ISBN ' || v_BooksInStock(v_LoopCounter) || ' is in stock'); END LOOP; END; Im Package-Body können die Objekte im Header ohne Packagenamen referenziert werden. So können beispielsweise die Prozeduren UpdateISBN und DeleteISBN die Exception einfach durch e_ISBNNotFound und nicht durch InventoryOps.e_ISBNNotFound referenzieren. Es kann auch der vollständig angegebene Name verwendet werden. 8.2 Packages 363 Geltungsbereich von Objekten im Package-Body InventoryOps.UpdateISBN und InventoryOps.StatusList validieren den übergebenen Status nicht. Wir können dies einbauen, indem wir in den Package-Body eine Prozedur einfügen, wie im Folgenden gezeigt wird: –– Online verfügbar in InventoryOps2.sql CREATE OR REPLACE PACKAGE BODY InventoryOps AS –– Validates the supplied status and raises an error if it is –– not IN STOCK, BACKORDERED, or FUTURE. PROCEDURE ValidateStatus(p_Status IN inventory.status%TYPE) IS BEGIN IF p_Status = 'IN STOCK' OR p_Status = 'BACKORDERED' OR p_Status = 'FUTURE' THEN RETURN; – No error ELSE RAISE_APPLICATION_ERROR(20000, 'Supplied status ' || p_Status || ' is not valid'); END IF; END ValidateStatus; –– Modifies the inventory data for the specified book. PROCEDURE UpdateISBN(p_ISBN IN inventory.isbn%TYPE, p_Status IN inventory.status%TYPE, p_StatusDate IN inventory.status_date%TYPE, p_Amount IN inventory.amount%TYPE) IS BEGIN ValidateStatus(p_Status); UPDATE inventory SET status = p_Status, status_date = p_StatusDate, amount = p_ Amount WHERE isbn = p_ISBN; –– Check for no books updated, and raise the exception. IF SQL%ROWCOUNT = 0 THEN RAISE e_ISBNNotFound; END IF; END UpdateISBN; ... –– Returns a PL/SQL table containing the books with the specified –– status. PROCEDURE StatusList(p_Status IN inventory.status%TYPE, p_Books OUT t_ISBNTable, p_NumBooks OUT BINARY_INTEGER) IS 364 8 Prozeduren, Funktionen und Packages erstellen v_ISBN inventory.isbn%TYPE; CURSOR c_Books IS SELECT isbn FROM inventory WHERE status = p_Status; BEGIN ValidateStatus(p_Status); ... END StatusList; END InventoryOps; ValidateStatus ist lokal zum Package-Body deklariert. Der Geltungsbereich ist da- her der Package-Body. Damit kann diese Prozedur auch von anderen Prozeduren im Body aufgerufen werden (nämlich UpdateISBN und StatusList), ist aber außerhalb des Bodys nicht sichtbar. 8.2.4 In Packages integrierte Unterprogramme überladen Innerhalb eines Package lassen sich Prozeduren und Funktionen überladen. Das heißt, dass es dort mehr als eine gleichnamige Prozedur oder Funktion gibt, die aber verschiedene Parameter enthalten. Diese Funktionalität ist äußerst hilfreich, da hiermit dieselbe Operation auf Objekte verschiedenen Typs angewendet werden kann. Angenommen StatusList soll beispielsweise einen Array von Blöcken zurückgeben oder ein geöffneter Cursor soll die Bücher mit dem angegebenen Status auswählen. Hier können wir InventoryOps wie folgt modifizieren: –– Online verfügbar in overload.sql CREATE OR REPLACE PACKAGE InventoryOps AS ... –– Returns an array containing the books with the specified status. PROCEDURE StatusList(p_Status IN inventory.status%TYPE, p_Books OUT t_ISBNTable, p_NumBooks OUT BINARY_INTEGER); TYPE c_ISBNCur IS REF CURSOR; –– Returns an opened cursor containing the books with the specified –– status. PROCEDURE StatusList(p_Status IN inventory.status%TYPE, p_BookCur OUT c_ISBNCur); END InventoryOps; CREATE OR REPLACE PACKAGE BODY InventoryOps AS ... 8.2 Packages 365 –– Returns an array containing the books with the specified status. PROCEDURE StatusList(p_Status IN inventory.status%TYPE, p_Books OUT t_ISBNTable, p_NumBooks OUT BINARY_INTEGER) IS v_ISBN inventory.isbn%TYPE; CURSOR c_Books IS SELECT isbn FROM inventory WHERE status = p_Status; BEGIN ValidateStatus(p_Status); /* p_NumBooks will be the array index. It will start at * 0, and be incremented each time through the fetch loop. * At the end of the loop, it will have the number of rows * fetched, and therefore the number of rows returned in * p_Books. */ p_NumBooks := 0; OPEN c_Books; LOOP FETCH c_Books INTO v_ISBN; EXIT WHEN c_Books%NOTFOUND; p_NumBooks := p_NumBooks + 1; p_Books(p_NumBooks) := v_ISBN; END LOOP; CLOSE c_Books; END StatusList; –– Returns an opened cursor containing the books with the specified –– status. PROCEDURE StatusList(p_Status IN inventory.status%TYPE, p_BookCur OUT c_ISBNCur) IS BEGIN ValidateStatus(p_Status); OPEN p_BookCur FOR SELECT isbn FROM inventory WHERE status = p_Status; END StatusList; END InventoryOps; Die folgende SQL*Plus-Sitzung zeigt die beiden Aufrufe von InventoryOps.StatusList: –– Online verfügbar in overload.sql SQL> DECLARE 2 v_BooksInStock InventoryOps.t_ISBNTable; 3 v_NumBooks BINARY_INTEGER; 4 v_BookCur InventoryOps.c_ISBNCur; 5 v_ISBN inventory.isbn%TYPE; 366 8 Prozeduren, Funktionen und Packages erstellen 6 BEGIN 7 DBMS_OUTPUT.PUT_LINE('First version of StatusList:'); 8 –– Fill the PL/SQL table with the ISBNs of the books which 9 –– are backordered. 10 InventoryOps.StatusList('BACKORDERED', v_BooksInStock, v_NumBooks); 11 12 –– And print them out. 13 FOR v_LoopCounter IN 1..v_NumBooks LOOP 14 DBMS_OUTPUT.PUT_LINE(' ISBN ' || v_BooksInStock(v_LoopCounter) || 15 ' is backordered'); 16 END LOOP; 17 18 DBMS_OUTPUT.PUT_LINE('Second version of StatusList:'); 19 –– Get an opened cursor with the ISBNs of the books which are 20 –– backordered. 21 InventoryOps.StatusList('BACKORDERED', v_BookCur); 22 23 –– And print them out. 24 LOOP 25 FETCH v_BookCur INTO v_ISBN; 26 EXIT WHEN v_BookCur%NOTFOUND; 27 DBMS_OUTPUT.PUT_LINE(' ISBN ' || v_ISBN || ' is backordered'); 28 END LOOP; 29 CLOSE v_BookCur; 30 END; 31 / First version of StatusList: ISBN 72121203 is backordered ISBN 78824389 is backordered Second version of StatusList: ISBN 72121203 is backordered ISBN 78824389 is backordered PL/SQL procedure successfully completed. Das Überladen kann eine hilfreiche Technik sein, wenn die gleiche Operation mit unterschiedlichen Argumenttypen ausgeführt werden kann. Das Überladen unterliegt allerdings auch verschiedenen Restriktionen: ■ Sie können nicht zwei Unterprogramme überladen, wenn sich deren Parameter nur durch den Namen oder Modus unterscheiden. So lassen sich beispielsweise die beiden folgenden Prozeduren nicht überladen: PROCEDURE OverloadMe(p_TheParameter IN NUMBER); PROCEDURE OverloadMe(p_TheParameter OUT NUMBER); ■ Sie können nicht zwei Funktionen überladen, die sich nur im Rückgabewert unterscheiden. So lassen sich beispielsweise die beiden folgenden Funktionen nicht überladen: FUNCTION OverloadMeToo RETURN DATE; FUNCTION OverloadMeToo RETURN NUMBER; 8.2 367 Packages ■ Die Parameter von überladenen Funktionen müssen aus verschiedenen Typfamilien stammen – die gleiche Familie kann man nicht überladen. Da im folgenden Beispiel sowohl CHAR als auch VARCHAR2 aus der gleichen Familie stammen, können die folgenden Prozeduren nicht überladen werden: PROCEDURE OverloadChar(p_TheParameter IN CHAR); PROCEDURE OverloadChar(p_TheParameter IN VARCHAR2); ■ In Oracle10gR1 können Sie aber zwei Unterprogramme überladen, wenn deren Parameter sich nur im numerischen Datentyp unterscheiden, wie BINARY_ FLOAT und BINARY_DOUBLE. Dies ist vor allem bei mathematischen Funktionen nützlich. Hinweis: Der PL/SQL-Compiler erlaubt derzeit das Erstellen von Packages mit Unterprogrammen, die diese Regeln verletzen. Aber die Runtime Engine kann die Referenzen nicht auflösen und meldet immer den Fehler „PLS-307: Too many declarations of 'subprogram' match this call“. Objekttypen und Überladung In Packages integrierte Unterprogramme lassen sich auch basierend auf benutzerdefinierten Objekttypen überladen. Angenommen, wir erstellen die beiden folgenden Objekttypen: –– Online verfügbar in objectOverload.sql CREATE OR REPLACE TYPE t1 AS OBJECT ( f NUMBER ); CREATE OR REPLACE TYPE t2 AS OBJECT ( f NUMBER ); Jetzt können wir ein Package und einen Package-Body mit Prozeduren anlegen, die aufgrund des Objekttyps ihrer Parameter überladen werden: –– Online verfügbar in objectOverload.sql CREATE OR REPLACE PACKAGE Overload AS PROCEDURE Proc(p_Parameter1 IN t1); PROCEDURE Proc(p_Parameter1 IN t2); END Overload; CREATE OR REPLACE PACKAGE BODY Overload AS PROCEDURE Proc(p_Parameter1 IN t1) IS BEGIN DBMS_OUTPUT.PUT_LINE('Proc(t1): ' || p_Parameter1.f); END Proc; 368 8 Prozeduren, Funktionen und Packages erstellen PROCEDURE Proc(p_Parameter1 IN t2) IS BEGIN DBMS_OUTPUT.PUT_LINE('Proc(t2): ' || p_Parameter1.f); END Proc; END Overload; Wie das folgende Beispiel zeigt, wird die korrekte Prozedur entsprechend dem Argumenttyp aufgerufen: –– Online verfügbar in objectOverload.sql SQL> DECLARE 2 v_Obj1 t1 := t1(1); 3 v_OBj2 t2 := t2(2); 4 BEGIN 5 Overload.Proc(v_Obj1); 6 Overload.proc(v_Obj2); 7 END; 8 / Proc(t1): 1 Proc(t2): 2 PL/SQL procedure successfully completed. Weitere Informationen zu Packages finden Sie in den Kapiteln 14 und 15. 8.2.5 Initialisierung von Packages Wird ein in ein Package integriertes Unterprogramm erstmals aufgerufen oder eine Referenz auf eine im Package integrierte Variable oder einen Typ vorgenommen, instanziert man das Package. Damit wird das Package von der Platte in den Speicher gelesen und der kompilierte Code des aufgerufenen Unterprogramms ausgeführt. An diesem Punkt ist für alle im Package definierten Variablen Speicher reserviert. Jede Sitzung besitzt eine eigene Kopie der im Package integrierten Variablen, was sicherstellt, dass zwei Sessions, die das gleiche Unterprogramm ausführen, unterschiedliche Speicherorte verwenden können. In vielen Fällen muss vor der ersten Instanzierung eines Package in einer Sitzung ein Initialisierungscode ausgeführt werden. Dazu fügen Sie in den Package-Body nach allen anderen Objekten einen Initialisierungsbereich ein. Die dazugehörige Syntax lautet: CREATE OR REPLACE PACKAGE BODY package_name {IS | AS} ... BEGIN initialization_code; END [package_name]; 8.2 Packages 369 package_name ist der Name des Package und initialization_code ist der auszuführende Code. Das folgende Package implementiert beispielsweise eine Funktion für Zufallszahlen: –– Online verfügbar als Random.sql CREATE OR REPLACE PACKAGE Random AS –– Random number generator. Uses the same algorithm as the –– rand() function in C. –– Used to change the seed. From a given seed, the same –– sequence of random numbers will be generated. PROCEDURE ChangeSeed(p_NewSeed IN NUMBER); –– Returns a random integer between 1 and 32767. FUNCTION Rand RETURN NUMBER; –– Same as Rand, but with a procedural interface. PROCEDURE GetRand(p_RandomNumber OUT NUMBER); –– Returns a random integer between 1 and p_MaxVal. FUNCTION RandMax(p_MaxVal IN NUMBER) RETURN NUMBER; –– Same as RandMax, but with a procedural interface. PROCEDURE GetRandMax(p_RandomNumber OUT NUMBER, p_MaxVal IN NUMBER); END Random; CREATE OR REPLACE PACKAGE BODY Random AS /* Used for calculating the next number. */ v_Multiplier CONSTANT NUMBER := 22695477; v_Increment CONSTANT NUMBER := 1; /* Seed used to generate random sequence. */ v_Seed number := 1; PROCEDURE ChangeSeed(p_NewSeed IN NUMBER) IS BEGIN v_Seed := p_NewSeed; END ChangeSeed; FUNCTION Rand RETURN NUMBER IS BEGIN v_Seed := MOD(v_Multiplier * v_Seed + v_Increment, (2 ** 32)); RETURN BITAND(v_Seed/(2 ** 16), 32767); END Rand; PROCEDURE GetRand(p_RandomNumber OUT NUMBER) IS BEGIN –– Simply call Rand and return the value. p_RandomNumber := Rand; END GetRand; 370 8 Prozeduren, Funktionen und Packages erstellen FUNCTION RandMax(p_MaxVal IN NUMBER) RETURN NUMBER IS BEGIN RETURN MOD(Rand, p_MaxVal) + 1; END RandMax; PROCEDURE GetRandMax(p_RandomNumber OUT NUMBER, p_MaxVal IN NUMBER) IS BEGIN –– Simply call RandMax and return the value. p_RandomNumber := RandMax(p_MaxVal); END GetRandMax; BEGIN /* Package initialization. Initialize the seed to the current time in seconds. */ ChangeSeed(TO_NUMBER(TO_CHAR(SYSDATE, 'SSSSS'))); END Random; Zum Abruf einer Zufallszahl rufen Sie einfach Random.Rand auf. Die Zufallszahlenfolge wird von der Ausgangszahl gesteuert – für eine gegebene Ausgangszahl wird stets die gleiche Folge generiert. Um mehr Zufallszahlen zu erhalten, müssen wir die Ausgangszahl bei jeder Instanzierung des Package auf einen anderen Wert initialisieren. Hierzu wird die ChangeSeed-Prozedur aus dem Initialisierungsbereich des Package aufgerufen. Hinweis: Oracle8i enthält ein vordefiniertes Package namens DBMS_ RANDOM, das sich gleichfalls für Zufallszahlen einsetzen lässt. Weitere Informationen zu Built-In Packages finden Sie in Anhang B. 8.3 Zusammenfassung In diesem Kapitel untersuchten wir drei Typen von benannten PL/SQL-Blöcken: Prozeduren, Funktionen und Packages. Wir besprachen die Syntax zum Erstellen dieser Konstrukte und stellten die verschiedenen Arten der Parameterübergabe vor. Im folgenden Kapitel erfahren Sie mehr über die Verwendung von Prozeduren, Funktionen und Packages. Es werden die verschiedenen Unterprogrammtypen erörtert, wie sie im Data Dictionary gespeichert werden und wie man gespeicherte Unterprogramme aus SQL-Anweisungen ruft. In Kapitel 10 beschäftigen wir uns mit dem vierten Typ von benannten Blöcken: den Datenbank-Triggern.