9. Einführung in PL/SQL • • • • Motivation für PL/SQL Aufbau von PL/SQL-Programmen Alternativen Steuerung des Programmablaufs • Records • Ausnahmebehandlung Datenbanken Prof. Dr. Stephan Kleuker 215 Einwurf • Oracle nutzt Transaktionssteuerung (wie alle echten Datenbanken) • Nutzer haben Gefühl, dass sie individuell arbeiten • Vorstellung: Nutzer arbeiten auf lokalen Kopien • erst mit Befehl COMMIT; werden Änderungen endgültig übernommen • kommen mehrere Nutzer in Konflikt (gleiches Datum bearbeiten), findet DB Lösung (z. B. ein Nutzer muss warten) • Fazit: Nach INSERT-Befehlen soll COMMIT; stehen Datenbanken Prof. Dr. Stephan Kleuker 216 Erweiterung relationaler Datenbanken • Einbettung von SQL in prozedurale oder objektorientierte Wirtssprachen (embedded SQL); meistens C, C++, oder Java (JDBC) • Erweiterung von SQL um prozedurale Elemente innerhalb der SQL-Umgebung, PL/SQL (Procedural language extensions to SQL) • Vorteile von PL/SQL: Bessere Integration der prozeduralen Elemente in die Datenbank; Nutzung in Prozeduren, Funktionen und Triggern Datenbanken Prof. Dr. Stephan Kleuker 217 Warum Erweiterung von SQL sinnvoll ist • keine prozeduralen Konzepte in SQL (Schleifen, Verzweigungen, Variablendeklarationen) • viele Aufgaben nur umständlich über Zwischentabellen oder überhaupt nicht in SQL zu realisieren. – Transitive Hülle • Programme repräsentieren anwendungsspezifisches Wissen, das nicht in der Datenbank enthalten ist Datenbanken Prof. Dr. Stephan Kleuker 218 Warum PL/SQL generell interessant ist • PL/SQL erhöht die Funktionalität und Mächtigkeit von SQLAnfragen • Obwohl PL/SQL Oracle-spezifisch ist, können viele zentralen Ideen (z.B. Cursor) auch genutzt werden, wenn sie SQL in andere Programmiersprachen einbetten (z. B. Transact SQL für MS Server) • Für Informatiker ist es uninteressant, welche Programmiersprache sie können, sie müssen aber die Fähigkeit haben, sich schnell in eine beliebige Sprache einzuarbeiten; PL/SQL ist ein gutes Beispiel, diese Fähigkeit zu prüfen Datenbanken Prof. Dr. Stephan Kleuker 219 Anmerkungen zu PL/SQL in dieser Veranstaltung • PL/SQL wird schrittweise erweitert, wir betrachten nur Basiskonzepte (z.B. zum Erstellen von Triggern) • PL/SQL ist eine prozedurale Sprache, die sich an Ada anlehnt (z.B. strenge Typisierung), C-Kenntnisse helfen aber auch • PL/SQL hat einen „objekt-basierten“-Ansatz, erlaubt die Aufteilung der Software in Packages • In Oracle existieren einige Built-In-Packages, die hier nur am Rand betrachtet werden • Achtung: PL/SQL wird kontinuierlich erweitert, ab und zu klappt nicht das, was einem logisch erscheint Datenbanken Prof. Dr. Stephan Kleuker 220 Einschub: SEQUENCE und DUAL • In Oracle gibt es keine Auto-Inkrement-Funktion, man kann aber spezielle Zähler (Sequenzen) definieren CREATE SEQUENCE mi INCREMENT BY 1; definiert eine Sequenz mi, die den Startwert und Inkrement 1 hat, • durch den Aufruf mi.NEXTVAL, wird der Wert von mi zurück gegeben und um eins erhöht • Nutzungsbeispiel: INSERT INTO Angestellte VALUES(mi.NEXTVAL,‘Meier‘,30); INSERT INTO Angestellte VALUES(mi.NEXTVAL,‘Mücke‘,25); • Will man Informationen über verschiedene Werte von Oracle erfahren, kann man dazu die Dummy-Tabelle DUAL nutzen • Beispiel: Anfrage einer Sequenz mit Erhöhung (ohne mi.currval) SELECT mi.NEXTVAL FROM DUAL; Datenbanken Prof. Dr. Stephan Kleuker 221 Einstieg ohne DB (Hello World) CREATE OR REPLACE PROCEDURE Hello IS BEGIN DBMS_OUTPUT.PUT_LINE('Hello World'); END Hello; 1. Zeile: Definition einer Prozedur (Parameter im nächsten Schritt) 2.-4. Zeile: Rumpf der Prozedur mit Ausgabebefehl Ausführung: EXECUTE Hello; Datenbanken Prof. Dr. Stephan Kleuker 222 Anmerkung zur SW-Entwicklung DBMS_OUTPUT.PUT_LINE('...') ist Prozedur eines Zusatzpakets DBMS_OUTPUT und kann bei inhaltlicher Fehlersuche hilfreich sein beachten: http://home.edvsz.hsosnabrueck.de/skleuker/querschnittlich/Datenbankwerkzeuge.pdf Datenbanken Prof. Dr. Stephan Kleuker 223 Aufbau einer PL/SQL-Prozedur oder Funktion CREATE OR REPLACE PROCEDURE <PName> (<Parameter>) IS <Variablenname> <VariablenTyp> BEGIN <PL/SQL-Programmteile und SQL-Anfragen> EXCEPTION <Behandlung von Ausnahmen (optionaler Anteil)> END; CREATE OR REPLACE FUNCTION <FName> (<Parameter>) RETURN <Ergebnistyp> IS ... (wie oben, aber mindestens ein Befehl RETURN <Ergebnis>) Datenbanken Prof. Dr. Stephan Kleuker 224 Beispiel-Datenbank -- einfache Verwaltungstabelle CREATE TABLE ANGESTELLTE( Minr NUMBER(5), Name VARCHAR(10), Gehalt NUMBER(4), PRIMARY KEY (MiNr) ); -- einfache Kontrolltabelle CREATE TABLE PROTOKOLL( WER VARCHAR(10), WAS VARCHAR(10), WO VARCHAR(15), WEN VARCHAR(12), WANN DATE ); -- Zähler für Minr (ohne Test auf Obergrenze) CREATE SEQUENCE mi INCREMENT BY 1; Datenbanken Prof. Dr. Stephan Kleuker 225 Einfache Einfüge-Prozedur CREATE OR REPLACE PROCEDURE EIN0 (N IN VARCHAR, G IN NUMBER) IS BEGIN INSERT INTO ANGESTELLTE VALUES(mi.NEXTVAL,N,G); END; • Prozeduren und Funktionen können Parameter haben • für Parameter ist die Bearbeitungsart (IN OUT INOUT) anzugeben, bei Funktionen nur IN (ist default) • bei VARCHAR und NUMBER-Parametern darf keine Formatangabe (Länge) übergeben werden • In SQL-Statements können (einfache, d.h. keine Tabellen) Variablen referenziert werden Datenbanken Prof. Dr. Stephan Kleuker 226 Einfüge-Prozedur mit Protokoll CREATE OR REPLACE PROCEDURE EIN1 (N IN VARCHAR, G IN NUMBER) IS mi_Nummer NUMBER; /* USER_USERS ist Oracle-Tabelle mit Informationen über den aktuellen Nutzer (Name im Attribut USERNAME) */ NUTZER USER_USERS.USERNAME%TYPE; BEGIN SELECT mi.NEXTVAL INTO mi_Nummer FROM DUAL; INSERT INTO ANGESTELLTE VALUES(mi_Nummer,N,G); SELECT USER_USERS.USERNAME INTO NUTZER FROM USER_USERS; INSERT INTO PROTOKOLL VALUES(NUTZER,'EINFUEGEN', 'ANGESTELLTE',mi_Nummer,SYSDATE); END; Datenbanken Prof. Dr. Stephan Kleuker 227 Ausführung SELECT * FROM Angestellte; SELECT * FROM Protokoll; EXECUTE EIN1('ich',42); SELECT * FROM Angestellte; SELECT * FROM Protokoll; Keine Zeilen gewählt Keine Zeilen gewählt anonymer Block abgeschlossen MINR NAME GEHALT ----- ---------- -----1 ich 42 WER WO WAS WEN WANN ---------- ---------- ------------ ---- -------SKLEUKER EINFUEGEN ANGESTELLTE 1 19.11.12 Datenbanken Prof. Dr. Stephan Kleuker 228 SQL in PL/SQL • In PL/SQL sind grundsätzlich alle SQL DML-Befehle (SELECT, INSERT, UPDATE, DELETE) erlaubt • für DDL-Befehle (CREATE, DROP) muss Zusatzpaket benutzt werden (hier nicht betrachtet) • SELECT-Befehle müssen zusätzlich die INTO-Zeile haben und müssen genau ein Ergebnis liefern • Mit Tabelle.Spaltenname%TYPE kann direkt auf den Typen einer Tabellenspalte zugegriffen werden • Abfragen von Funktionswerten oder Sequenz-Werten werden immer auf die Dummy-Tabelle DUAL bezogen • USER_USERS ist eine der Systemtabellen Datenbanken Prof. Dr. Stephan Kleuker 229 Alternativen CREATE TABLE Gehaltsklassen( INSERT INTO Gehaltsklassen VALUES('A',0); klasse VARCHAR(1), INSERT INTO Gehaltsklassen VALUES('B',0); anzahl NUMBER(3) INSERT INTO Gehaltsklassen VALUES('C',0); ); CREATE OR REPLACE PROCEDURE EIN (N IN VARCHAR, G IN NUMBER) IS BEGIN INSERT INTO ANGESTELLTE VALUES(mi.NEXTVAL VALUES(mi.NEXTVAL,N,G); mi.NEXTVAL,N,G); IF G>90 THEN UPDATE Gehaltsklassen SET anzahl=anzahl+1 anzahl=anzahl+1 WHERE klasse='A'; DBMS_OUTPUT.PUT_LINE('In Klasse A eingefuegt'); eingefuegt'); ELSIF G>60 THEN BEGIN UPDATE Gehaltsklassen SET anzahl=anzahl+1 anzahl=anzahl+1 WHERE klasse='B'; DBMS_OUTPUT.PUT_LINE('In Klasse B eingefuegt'); eingefuegt'); END; ELSE UPDATE Gehaltsklassen SET anzahl=anzahl+1 anzahl=anzahl+1 WHERE klasse='C'; END IF; END; Datenbanken Prof. Dr. Stephan Kleuker 230 Strukturierung des Ablaufs • Alternativen werden durch die IF-THEN-ELSIFELSIF-...-ELSE-END IF Konstruktion beschrieben • Wird eine Bedingung nach „undefiniert“ ausgewertet, wird in den ELSE-Zweig gesprungen • Grundsätzlich können in PL/SQL-Rümpfen weitere PL/SQLBlöcke definiert werden • weitere Blöcke dienen zur Strukturierung der Programme und sind hilfreich bei der Reaktion auf Ausnahmen (Fehlerfälle) • Es gibt die Anweisung NULL; (steht für den leeren Befehl) • Die „üblichen“ Regeln für Sichtbarkeiten von Variablen werden übernommen (sollten bei guter Programmierung keine Rolle spielen) Datenbanken Prof. Dr. Stephan Kleuker 231 Schleifen (Beispiele) CREATE OR REPLACE PROCEDURE FREMD0 (Anzahl NUMBER, Firma VARCHAR, Gehalt NUMBER) IS BEGIN FOR i IN 1 .. Anzahl LOOP EIN(Firma || i, Gehalt); END LOOP; END; CREATE OR REPLACE PROCEDURE FREMD (Anzahl NUMBER, Firma VARCHAR, Gehalt NUMBER) IS i INTEGER DEFAULT 1; BEGIN WHILE i<=Anzahl LOOP EIN(Firma || i, Gehalt); i:=i+1; END LOOP; END; Datenbanken Prof. Dr. Stephan Kleuker 232 Übersicht - Schleifen • Simple LOOP: LOOP ... END LOOP; • WHILE LOOP: WHILE <bedingung> LOOP ... END LOOP; • Numeric FOR LOOP: FOR <loop_index> IN [REVERSE] <Anfang> .. <Ende> LOOP … END LOOP; • Die Variable <loop_index> wird dabei automatisch als INTEGER deklariert. • EXIT [WHEN <bedingung>]: LOOP Verlassen (ist aber schlechter Programmierstil) Datenbanken Prof. Dr. Stephan Kleuker 233 Zugriffsrechte bei Prozeduren / Funktionen • Benutzungsrechte vergeben: GRANT EXECUTE ON <procedure/function> TO <user>; • Prozeduren und Funktionen werden jeweils mit den Zugriffsrechten des Besitzers ausgeführt • d.h. der Nutzer kann die Prozedur/Funktion auch dann aufrufen, wenn er kein Zugriffsrecht auf die dabei benutzten Tabellen hat • Rechtesteuerung in späteren VL • Anmerkung: Sieht man PL/SQL als serverseitige Programmierung, ist dies ein zentrales Argument für PL/SQL Datenbanken Prof. Dr. Stephan Kleuker 234 Records • Ein RECORD enthält mehrere Felder, entspricht einem Tupel in der Datenbasis: TYPE Citytype IS RECORD( Name City.Name%TYPE, Country VARCHAR(4), Province VARCHAR(32), Population NUMBER, Longitude NUMBER, Latitude NUMBER ); • Nutzung (Deklaration einer Variablen diesen Typs): theCity Citytype; • Semantisch verhalten sich RECORD wie struct in C (kein direkter Vergleich, direkte Zuweisung möglich, bei Zuweisung werden Werte kopiert) Datenbanken Prof. Dr. Stephan Kleuker 235 Etwas Semantikanalyse (1/2) CREATE OR REPLACE PROCEDURE RecordTest IS TYPE T1 IS RECORD( X NUMBER, Y NUMBER ); TYPE T2 IS RECORD( X NUMBER, Y NUMBER ); A T1; B T1 DEFAULT A; C T2; BEGIN A.x:=1; Ausgabe: A.y:=2; A.x= 1 -- DBMS_OUTPUT.PUT_LINE(A); geht nicht DBMS_OUTPUT.PUT_LINE('A.x= '||A.x); A.y= 2 DBMS_OUTPUT.PUT_LINE('A.y= '||A.y); B.x= DBMS_OUTPUT.PUT_LINE('B.x= '||B.x); B.y= DBMS_OUTPUT.PUT_LINE('B.y= '||B.y); -- DBMS_OUTPUT.PUT_LINE(B.y); liefert leere Zeile! Datenbanken Prof. Dr. Stephan Kleuker 236 Etwas Semantikanalyse (2/2) B.x:=1; B.y:=2; -- IF A=B ist verboten IF A.x=B.x AND A.y=B.y THEN DBMS_OUTPUT.PUT_LINE('A gleich B'); ELSE DBMS_OUTPUT.PUT_LINE('A ungleich B'); END IF; A:=B; B.x:=2; IF A.x=B.x AND A.y=B.y THEN DBMS_OUTPUT.PUT_LINE('A gleich B'); ELSE DBMS_OUTPUT.PUT_LINE('A ungleich B'); END IF; Ausgabe: A gleich B -- nicht erlaubt C:=A; A ungleich B END; Datenbanken Prof. Dr. Stephan Kleuker 237 Ausnahmen (1/5) • Declaration Section: Deklaration (der Namen) benutzerdefinierter Exceptions. [DECLARE] <exception_name> EXCEPTION; • Exceptions können dann an beliebigen Stellen des PL/SQLBlocks durch RAISE ausgelöst werden. IF <condition> THEN RAISE <exception_name>; • Exception Section: Definition der beim Auftreten einer Exception auszuführenden Aktionen. WHEN <exception_name> THEN <PL/SQL-Statement>; WHEN OTHERS THEN <PL/SQL-Statement>; Datenbanken Prof. Dr. Stephan Kleuker 238 Ausnahmen (2/5) •auslösen einer Exception •entsprechende Aktion der WHEN-Klausel ausführen •innersten Block verlassen (oft Anwendung von anonymen Blöcken sinnvoll) ohne Exception mit gefangener Exception ohne gefangene Exception PROCEDURE PROCEDURE PROCEDURE BEGIN BEGIN BEGIN EXCEPTION EXCEPTION EXCEPTION END END END Datenbanken Prof. Dr. Stephan Kleuker 239 Ausnahmen (3/5) CREATE OR REPLACE FUNCTION noHeinz(name VARCHAR) RETURN VARCHAR IS 1 heinz EXCEPTION; BEGIN CREATE OR REPLACE PROCEDURE heinzTest IS IF name='Heinz' heinz EXCEPTION; THEN BEGIN 2 RAISE heinz; DBMS_OUTPUT.PUT_LINE(noHeinz('Egon')); ELSE DBMS_OUTPUT.PUT_LINE(noHeinz('Heinz')); RETURN name; DBMS_OUTPUT.PUT_LINE(noHeinz('Udo')); END IF; EXCEPTION END; WHEN heinz THEN / DBMS_OUTPUT.PUT_LINE('Ein Heinz'); WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Wat nu?'); END; / Egon 4! 3 EXECUTE heinzTest; Wat nu? SELECT noHeinz('Heinz') FROM DUAL; SELECT noHeinz('Heinz') noHeinz('Heinz') FROM DUAL FEHLER in Zeile 1: ORAORA-06510: PL/SQL: Unbehandelte benutzerdefinierte Ausnahmebedingung (exception) exception) ORAORA-06512: 06512: in „SKLEUKER.NOHEINZ", Zeile 7 Datenbanken Prof. Dr. Stephan Kleuker 240 Ausnahmen (4/5) • Es gibt viele vordefinierte Ausnahmen, die alle abgeprüft und bearbeitet werden können, Beispiele: – NO_DATA_FOUND – TOO_MANY_ROWS – INVALID_CURSOR – ZERO_DIVIDE – DUP_VAL_ON_INDEX • Ausnahmen können auch direkt ausgelöst werden: RAISE_APPLICATION_ERROR(-20101, 'Kunde nicht kreditwürdig'); • Die Werte dieser Ausnahmen müssen zwischen -21000 und -20000 liegen, Rest ist für Oracle Datenbanken Prof. Dr. Stephan Kleuker 241 Ausnahmen (5/5) CREATE OR REPLACE PROCEDURE exTest IS I INTEGER DEFAULT 0; BEGIN BEGIN I:=I/I; DBMS_OUTPUT.PUT_LINE('Nicht Erreicht'); EXCEPTION WHEN ZERO_DIVIDE THEN DBMS_OUTPUT.PUT_LINE(''||SQLCODE||'::'||SQLERRM); END; DBMS_OUTPUT.PUT_LINE(''||SQLCODE||'::'||SQLERRM); RAISE_APPLICATION_ERROR(-20101,'keine Lust mehr'); EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE(''||SQLCODE||'::'||SQLERRM); IF SQLCODE=-20101 THEN DBMS_OUTPUT.PUT_LINE('stimmt nicht'); END IF; -1476::ORA1476::ORA-01476: Divisor ist Null END; 0::ORA0::ORA-0000: normal, successful completion / -20101::ORA20101::ORA-20101: keine Lust mehr EXECUTE exTest; stimmt nicht Datenbanken Prof. Dr. Stephan Kleuker 242 10. Cursor und Trigger in PL/SQL • Definition eines Cursor • Einsatzmöglichkeiten von Cursorn • Verschiedene Arten von Triggern Hinweis: Oracle-Bibliothek (als eine Sammlung herunterladbar) Datenbanken Prof. Dr. Stephan Kleuker 243 RECORD und %ROWTYPE CREATE OR REPLACE PROCEDURE gehaltAnpassen ( mi Angestellte.MiNr%TYPE) IS person Angestellte%ROWTYPE; BEGIN SELECT * CREATE TABLE ANGESTELLTE( INTO person Minr NUMBER(5), FROM Angestellte Name VARCHAR(10), WHERE Angestellte.MiNr=mi; Gehalt NUMBER(4), UPDATE Angestellte PRIMARY KEY (MiNr) SET Gehalt=person.Gehalt ); WHERE Name=person.Name; END; Mit %ROWTYPE erhält man einen RECORD-Typen, der sich genau aus den Attributen der genannten Tabelle zusammensetzt. Variablen dieses Typs können dann eine Zeile der Tabelle aufnehmen Datenbanken Prof. Dr. Stephan Kleuker 244 Cursorbasierter DB-Zugriff • Zeilenweiser Zugriff auf eine Relation aus einem PL/SQLProgramm • Cursordeklaration in der Declaration Section: CURSOR <cursor-name> [(<parameter-list>)] IS <select-statement>; • (<parameter-list>): Parameter-Liste, nur IN als Übergaberichtung erlaubt • Zwischen SELECT und FROM auch PL/SQL-Variablen und PL/SQL-Funktionen. PL/SQL-Variablen können ebenfalls in den WHERE-, GROUP- und HAVING-Klauseln verwendet werden Datenbanken Prof. Dr. Stephan Kleuker 245 Beispiel für Cursor-Definition Alle Städte in dem in der Variablen theCountry angegebenen Land: CURSOR cities_in(theCountry Country.Code%TYPE) IS SELECT City.Name FROM City WHERE City.Country=theCountry; Erinnerung: Tabelle City (vereinfacht): Datenbanken Country Name D Cologne D Hamburg Prof. Dr. Stephan Kleuker 246 OPEN OPEN <cursor-name> [(<argument-list>)]; • • • • das SELECT-Statement des CURSORS wird jetzt ausgeführt das Ergebnis ist eine virtuelle Tabelle im Speicher auf diese Tabelle kann nur zeilenweise zugegriffen werden die aktuelle Zeile wird durch ein FETCH-Kommando eingelesen, wobei der CURSOR automatisch ein Feld weiter gesetzt wird Name OPEN cities_in('D'); = virtuell im Speicher = zugreifbar Datenbanken Cologne FETCH Hamburg Munich Prof. Dr. Stephan Kleuker 247 FETCH FETCH <cursor-name> INTO <record-variable>; oder FETCH <cursor-name> INTO <variable-list>; • bewegt Cursor auf nächste Zeile des Ergebnisses der Anfrage und kopiert diese in die angegebene Record-Variable oder Variablenliste • mit <cursor-name>%ROWTYPE kann auf Record-Typ zugegriffen werden: <variable> <cursor-name>%ROWTYPE; • CLOSE <cursor-name>; schließt einen Cursor (wichtig !!!) • nicht möglich: OPEN cities_in ('D'); OPEN cities_in ('CH'); FETCH cities_in INTO <variable>; ein parametrisierter Cursor, nicht eine Familie von Cursoren Datenbanken Prof. Dr. Stephan Kleuker 248 Attribute von Cursorn • <cursor-name>%ISOPEN : Cursor offen? • <cursor-name>%FOUND : Solange ein Cursor bei der letzten FETCH-Operation ein neues Tupel gefunden hat, ist <cursor-name>%FOUND = TRUE • <cursor-name>%NOTFOUND: TRUE wenn man alle Zeilen eines Cursors geFETCHt hat. • <cursor-name>%ROWCOUNT : Anzahl der von einem Cursor bereits gelesenen Tupel. • Attribute nicht innerhalb eines SQL-Ausdrucks verwendbar (gehören zu Ablaufsteuerungsmöglichkeiten in PL/SQL) Datenbanken Prof. Dr. Stephan Kleuker 249 Cursor – Beispiel (1/2) -- Nutzung von WHILE CREATE OR REPLACE PROCEDURE teureLeute (betr Number) IS CURSOR viel (betrag Number) IS SELECT * FROM Angestellte WHERE Angestellte.Gehalt>=betrag; zeile viel%ROWTYPE; BEGIN OPEN viel(betr); FETCH viel INTO zeile; WHILE viel%FOUND LOOP DBMS_OUTPUT.PUT_LINE(zeile.Name||' bekommt zu viel! (mindestens '||betr||')'); FETCH viel INTO zeile; END LOOP; CLOSE viel; END; Datenbanken Prof. Dr. Stephan Kleuker 250 Cursor FOR LOOP FOR <record_index> IN <cursor-name> LOOP ... END LOOP; • <record_index> wird dabei automatisch als Variable vom Typ <cursor-name>%ROWTYPE deklariert, • <record_index> immer von einem Record-Type – ggf. einspaltig (bei Zugriff beachten). • es wird automatisch ein OPEN ausgeführt • bei jeder Ausführung des Schleifenkörpers wird automatisch ein FETCH ausgeführt, • Schleifenkörper enthält i.a. keinen FETCH-Befehl • am Ende wird automatisch ein CLOSE ausgeführt • Spalten müssen explizit adressiert werden Datenbanken Prof. Dr. Stephan Kleuker 251 Cursor – Beispiel (2/2) -- Nutzung der CURSOR-FOR-Schleife CREATE OR REPLACE PROCEDURE teureLeute0(betr Number) IS CURSOR viel (betrag Number) IS SELECT * FROM Angestellte WHERE Angestellte.Gehalt>=betrag; BEGIN FOR person IN viel(betr) LOOP DBMS_OUTPUT.PUT_LINE(person.Name||' verdient zu viel! (mindestens '||betr||')'); END LOOP; END; Datenbanken Prof. Dr. Stephan Kleuker 252 Aufdatieren an CURSOR-Position CREATE OR REPLACE Procedure mehrFuerAlteLeute IS CURSOR alt IS • Um Veränderungen an der SELECT * aktuellen CURSOR-Position FROM Angestellte WHERE MiNr<100 vornehmen zu können, kann FOR UPDATE; die Bedingung WHERE BEGIN CURRENT OF <CURSORFOR person IN alt Name> genutzt werden, LOOP CURSOR muss zum ändern UPDATE Angestellte SET Gehalt=Gehalt*1.1 markiert sein WHERE CURRENT OF alt; • Beispiel: Gehaltserhöhung END LOOP; um 10% bei allen Leuten END; deren MiNr kleiner als 100 ist Datenbanken Prof. Dr. Stephan Kleuker 253 Trigger (1/2) • spezielle Form von PL/SQL-Prozeduren • werden beim Eintreten eines bestimmten Ereignisses ausgeführt • Spezialfall aktiver Regeln nach dem Event-Condition-ActionParadigma • Werden einer Tabelle (oft auch noch einer bestimmten Spalte) zugeordnet • Bearbeitung wird durch das Eintreten eines Ereignisses (Einfügen, Ändern oder Löschen von Zeilen der Tabelle) ausgelöst (Event) Datenbanken Prof. Dr. Stephan Kleuker 254 Trigger (2/2) • Ausführung von Bedingungen an den Datenbankzustand abhängig (Condition) • Action: vor oder nach der Ausführung der entsprechenden aktivierenden Anweisung ausgeführt • einmal pro auslösender Anweisung (Statement-Trigger) oder einmal für jede betroffene Zeile (Row-Trigger) ausgeführt • Trigger-Aktion kann auf den alten und neuen Wert der gerade behandelten Zeile zugreifen Datenbanken Prof. Dr. Stephan Kleuker 255 Syntax von Triggern (1/2) CREATE [OR REPLACE] TRIGGER <trigger-name> BEFORE | AFTER {INSERT | DELETE | UPDATE} [OF <column-list>] [ OR {INSERT | DELETE | UPDATE} [OF <column-list>]] ... [ OR {INSERT | DELETE | UPDATE} [OF <column-list>]] ON <table> [FOR EACH ROW] [WHEN (<condition>)] <pl/sql-block>; Datenbanken Prof. Dr. Stephan Kleuker 256 Syntax von Triggern (2/2) • BEFORE, AFTER: Trigger wird vor/nach der auslösenden • • • • • Operation ausgeführt OF <column> (nur für UPDATE) schränkt Aktivierung auf angegebene Spalte ein Zugriff auf Zeileninhalte vor und nach der Ausführung der aktivierenden Aktion mittels :OLD bzw. :NEW (Aliasing durch REFERENCING OLD AS ... NEW AS ... ). Schreiben in :NEW-Werte nur mit BEFORE-Trigger. FOR EACH ROW: Row-Trigger, sonst Statement-Trigger. WHEN (<condition>) : zusätzliche Bedingung :OLD und :NEW sind in <condition> erlaubt. Datenbanken Prof. Dr. Stephan Kleuker 257 Beispiel für Trigger (1/5) • Wenn ein Landes-Code geändert wird, pflanzt sich diese Änderung auf die Relation Province fort: CREATE OR REPLACE TRIGGER change_Code BEFORE UPDATE OF Code ON Country FOR EACH ROW BEGIN UPDATE Province SET Country = :NEW.Code WHERE Country = :OLD.Code; END; Datenbanken Prof. Dr. Stephan Kleuker 258 Beispiel für Trigger (2/5) • Wenn ein Land neu angelegt wird, wird ein Eintrag in Politics mit dem aktuellen Jahr erzeugt: CREATE TRIGGER new_Country AFTER INSERT ON Country FOR EACH ROW BEGIN INSERT INTO Politics (Country,Independence) VALUES (:NEW.Code,SYSDATE); END; • Hinweis: Eventuell benötigte Variablen werden zwischen „FOR EACH ROW“ und BEGIN nach dem Schlüsselwort DECLARE definiert Datenbanken Prof. Dr. Stephan Kleuker 259 Beispiel für Trigger (3/5) • Nachmittags dürfen keine Städte gelöscht werden: CREATE OR REPLACE TRIGGER nachm_nicht_loeschen BEFORE DELETE ON City BEGIN IF TO_CHAR(SYSDATE,'HH24:MI') BETWEEN '12:00' AND '18:00' THEN RAISE_APPLICATION_ERROR (-20101,'Unerlaubte Aktion'); END IF; END; Mit RAISE_APPLICATION_ERROR wird ein Fehler erzeugt, der zum Abbruch führt. Der erste Parameter muss zwischen -21000 und -20000 liegen, der zweite ist der Fehlertext Datenbanken Prof. Dr. Stephan Kleuker 260 Beispiel für Trigger (4/5) Niemand darf anfänglich mehr als die „Meiers“ in der Firma verdienen CREATE OR REPLACE TRIGGER nichtMehrAlsMeiers BEFORE INSERT ON Angestellte FOR EACH ROW DECLARE maxMeier NUMBER; BEGIN SELECT MAX(Gehalt) INTO maxMeier FROM Angestellte WHERE Name='Meier'; IF :NEW.Gehalt>maxMeier THEN RAISE_APPLICATION_ERROR(-20111, 'Nicht mehr als die Meiers'); END IF; END; Datenbanken Prof. Dr. Stephan Kleuker 261 Beispiel für Trigger (5/5) Niemand darf anfänglich mehr als die „Meiers“ in der Firma verdienen CREATE OR REPLACE TRIGGER nichtMehrAlsMeiers AFTER INSERT nicht äquivalent zu vorher: ON Angestellte DECLARE - was wenn neuer verboten INTEGER; Mitarbeiter Meier heißt BEGIN - was bei update dann insert SELECT COUNT(*) INTO verboten FROM Angestellte WHERE Angestellte.Gehalt > (SELECT MAX(Gehalt) FROM Angestellte WHERE Name='Meier'); IF verboten > 0 THEN RAISE_APPLICATION_ERROR(-20111, 'Nicht mehr als die Meiers'); END IF; END; Datenbanken Prof. Dr. Stephan Kleuker 262 FOR EACH ROW oder nicht CREATE TABLE Tr( X NUMBER, Y NUMBER ); INSERT INTO Tr VALUES (1,3); INSERT INTO Tr VALUES (1,4); INSERT INTO Tr VALUES (1,5); SELECT * FROM Tr; CREATE TRIGGER TrOhneEach BEFORE UPDATE ON Tr BEGIN DBMS_OUTPUT.PUT_LINE('TrOhneEach'); END; / CREATE TRIGGER TrMitEach BEFORE UPDATE ON Tr FOR EACH ROW BEGIN DBMS_OUTPUT.PUT_LINE('TrMitEach'); END; / UPDATE TR SET Y=Y+1 WHERE X=1; SELECT * FROM Tr; Datenbanken Prof. Dr. Stephan Kleuker X Y ---------- ---------1 3 1 4 1 5 3 Zeilen ausgewählt. Trigger wurde erstellt. Trigger wurde erstellt. TrOhneEach TrMitEach TrMitEach TrMitEach 3 Zeilen wurden aktualisiert. X Y ---------- ---------1 4 1 5 1 6 3 Zeilen ausgewählt. 263 Problem der Mutating Tables (1/2) • • • • Trigger können die Inhalte von Tabellen ändern (der Tabelle, auf der sie definiert sind und andere), d.h. jede Ausführung des Triggers sieht eventuell einen anderen Datenbestand der Tabelle, auf der er definiert ist, sowie der Tabellen, die er evtl. ändert d.h. Ergebnis abhängig von der Reihenfolge der veränderten Tupel ORACLE: Betroffene Tabellen werden während der gesamten Aktion als „mutating“ gekennzeichnet, können nicht erneut von Triggern gelesen oder geschrieben werden Nachteil: Oft ein zu strenges Kriterium Datenbanken Prof. Dr. Stephan Kleuker 264 Problem der Mutating Tables (2/2) • Trigger soll auf Tabelle zugreifen, auf der er selber definiert ist – nur das auslösende Tupel soll von dem Trigger gelesen/geschrieben werden: Verwendung eines BEFORE-Triggers und der :NEW- und :OLD-Variablen – es sollen neben dem auslösenden Tupel auch weitere Tupel verändert werden: Verwendung eines Statementorientierten Triggers • Trigger soll auf andere Tabellen zugreifen: Verwendung von Statement-Triggern und ggf. Hilfstabellen Datenbanken Prof. Dr. Stephan Kleuker 265 Beispiel (1/4): Aufgabe • Tabelle speichert Gebote eines Mitglieds (mnr) für eine Ware (ware) als Preis (gebot) • Forderung: bei neuen Geboten (insert oder update erlaubt) für die gleiche Ware muss das Gebot erhöht werden CREATE TABLE Gebot( mnr INTEGER, ware INTEGER, gebot NUMBER(8,2), PRIMARY KEY(mnr,ware,gebot) ); Datenbanken Prof. Dr. Stephan Kleuker 266 Beispiel (2/4) : Trigger CREATE OR REPLACE TRIGGER GEBOTERHOEHEN BEFORE INSERT OR UPDATE ON GEBOT FOR EACH ROW DECLARE maxi Gebot.gebot%TYPE; BEGIN SELECT MAX (Gebot.gebot) INTO maxi FROM Gebot WHERE Gebot.mnr = :NEW.mnr AND Gebot.ware = :NEW.ware; IF maxi IS NOT NULL AND maxi >= :NEW.Gebot THEN RAISE_APPLICATION_ERROR(-20900 ,'Gebot muss erhoeht werden'); END IF; END; Datenbanken Prof. Dr. Stephan Kleuker 267 Beispiel (3/4): Versuch der Anwendung INSERT INTO Gebot VALUES(42,99,1.00); 1 Zeilen eingefügt. COMMIT; festgeschrieben. UPDATE Gebot SET gebot = 1.01 WHERE mnr = 42 AND ware = 99; SQL-Fehler: ORA-04091: table ICH.GEBOT is mutating, trigger/function may not see it ORA-06512: at "ICH.GEBOTERHOEHEN", line 4 ORA-04088: error during execution of trigger 'ICH.GEBOTERHOEHEN' 04091. 00000 - "table %s.%s is mutating, trigger/function may not see it" Datenbanken Prof. Dr. Stephan Kleuker 268 Beispiel (4/4): Korrektur CREATE OR REPLACE TRIGGER GEBOTERHOEHEN BEFORE INSERT OR UPDATE ON GEBOT FOR EACH ROW DECLARE PRAGMA AUTONOMOUS_TRANSACTION; maxi Gebot.gebot%TYPE; • Korrektur sinnvoll, da nur ursprüngliche Daten gelesen und sonst nichts verändert wird • auch PRAGMA kann zur Laufzeit scheitern • Erinnerung: Transaktionen mit COMMIT abschließen Datenbanken Prof. Dr. Stephan Kleuker 269