Oracle-Programmierung
EDITION Oracle Oracle 8i und Java - Oracle JDeveloper 2.0 als Entwicklungswerkzeug Steven Ponndorf / Wolf-Gert Matthäus Oracle 8 für den DBA - Verwalten, optimieren, vernetzen Uwe Herrmann / Dierk Lenz / Günter Unbescheid Oracle 7.3 - Verwalten, optimieren, vernetzen Uwe Herrmann / Dierk Lenz / Günter Unbescheid Oracle8 effizient einsetzen - Verteilung und Betrieb leistungsfähiger Oracle8-Anwendungen Andreas Christiansen / Michael Höding / Gunter Saake / Claus Rautenstrauch Oracle-Programmierung - Datenbankprogrammierung und -administration Heinz-Gerd Raymans Oracle Designer R 6i und Developer 6i - Professionelle Modulentwicklung Werner Hasselberg (Erscheinungstermin: Januar 2001) Oracle Developer 6i - Softwareprojekte für das Internet-Computing Steven Ponndorf / Wolf-Gert Matthäus (Erscheinungstermin: März 2001)
Heinz-Gerd Raymans
Oracle-Programmierung Datenbankprogrammierung und -administration
Bitte beachten Sie: Der originalen Printversion liegt eine CD-ROM bei. In der vorliegenden elektronischen Version ist die Lieferung einer CD-ROM nicht enthalten. Alle Hinweise und alle Verweise auf die CD-ROM sind ungültig.
ADDISON-WESLEY An imprint of Pearson Education München Boston San Francisco Harlow, England Don Mills, Ontario Sydney Mexico City Madrid Amsterdam
Die Deutsche Bibliothek CIP-Einheitsaufnahme Ein Titeldatensatz für diese Publikation ist bei Der Deutschen Bibliothek erhältlich Die Informationen in diesem Produkt werden ohne Rücksicht auf einen eventuellen Patentschutz veröffentlicht. Warennamen werden ohne Gewährleistung der freien Verwendbarkeit benutzt. Bei der Zusammenstellung von Texten und Abbildungen wurde mit größter Sorgfalt vorgegangen. Trotzdem können Fehler nicht vollständig ausgeschlossen werden. Verlag, Herausgeber und Autoren können für fehlerhafte Angaben und deren Folgen weder eine juristische Verantwortung noch irgendeine Haftung übernehmen. Für Verbesserungsvorschläge und Hinweise auf Fehler sind Verlag und Herausgeber dankbar. Alle Rechte vorbehalten, auch die der fotomechanischen Wiedergabe und der Speicherung in elektronischen Medien. Die gewerbliche Nutzung der in diesem Produkt gezeigten Modelle und Arbeiten ist nicht zulässig. Fast alle Hardware- und Softwarebezeichnungen, die in diesem Buch erwähnt werden, sind gleichzeitig auch eingetragene Warenzeichen oder sollten als solche betrachtet werden. Umwelthinweis: Dieses Produkt wurde auf chlorfrei gebleichtem Papier gedruckt. Die Einschrumpffolie zum Schutz vor Verschmutzung ist aus umweltverträglichem und recyclingfähigem PE-Material.
10 9 8 7 6 5 4 3 2 1 04 03 02 01 ISBN 3-8273-1733-9 © 2001 by Addison-Wesley Verlag Ein Imprint der Pearson Education Deutschland GmbH Martin-Kollar-Straße 1012, D-81829 München/Germany Alle Rechte vorbehalten Einbandgestaltung: Lektorat: Herstellung: Korrektorat: Satz: Druck und Verarbeitung: Printed in Germany
Hommer Design, Haar bei München Martin Asbach,
[email protected] Elisabeth Egger,
[email protected] Christine Depta, Freising mediaService, Siegen Bercker, Kevelaer
Inhaltsverzeichnis Vorwort .................................................................................................... 9 V.1 Zum Inhalt ...............................................................................................10 1
Oracle – erste Schritte ............................................................................ 13 1.1 Grundlagen..............................................................................................13 1.1.1 Klienten und Diener..................................................................14 1.1.2 Aufbau des Oracle-DBMS..........................................................18 1.1.3 Aufbau einer Oracle-Datenbank ................................................20 1.2 Installation ...............................................................................................24 1.2.1 Der Installationsvorgang ...........................................................25 1.2.2 Installation der Version 8 ..........................................................26 1.2.3 Installation von 8i .....................................................................32 1.3 Dienste unter Windows-NT ......................................................................35 1.4 Verbindung zur Datenbank herstellen ......................................................37 1.4.1 Einführung in SQL*Net bzw. Net8 ............................................38 1.4.2 Verbindung zur „Starterdatenbank“ herstellen..........................40 1.4.3 Konfiguration mit Hilfe von Net8 Easy Config ...........................48 1.4.4 Oracle-Networking mit Hilfe des Net8 Assistant ........................53 1.4.5 Ausblick ....................................................................................60 1.5 Erstellen einer Datenbank .........................................................................62 1.5.1 Struktur einer Oracle-Datenbank...............................................62 1.5.2 Das manuelle Verfahren ............................................................65 1.5.3 Der Oracle Database Assistant ..................................................72 1.5.4 Automatische Generierung unter NT ........................................86 1.6 Administrierung der Datenbanken............................................................87 1.6.1 Instanz starten und stoppen .....................................................88 1.6.2 Datenbank öffnen und schließen ..............................................89 1.6.3 Löschen einer Datenbank .........................................................91 1.7 Der Oracle Storage Manager ....................................................................92 1.8 SQL*Plus ..................................................................................................95 1.8.1 Abfragen eingeben und ausführen............................................97 1.8.2 SQL*Plus als Skriptinterpreter..................................................100 1.9 Das SQL-Worksheet................................................................................105
2
Datenbankobjekte in einer Oracle-DB ...................................................109 2.1 Der Oracle-Schema-Manager .................................................................109 2.2 Beschreibung der Objekte ......................................................................113 2.2.1 Array-Typ (Array Types) ..........................................................114 2.2.2 Cluster (Clusters) ....................................................................119
6
Inhaltsverzeichnis
2.3
2.4
3
2.2.3 Datenbank-Link (Database Links) ............................................120 2.2.4 Funktion (Functions)...............................................................123 2.2.5 Index (Indexes).......................................................................127 2.2.6 Objekttyp (Object Types) .......................................................132 2.2.7 Paketrumpf (Package bodies)..................................................137 2.2.8 Paket (Packages) .....................................................................138 2.2.9 Prozedur (Procedures) ............................................................149 2.2.10 Warteschlangentabelle (Queue Tables) ...................................151 2.2.11 Abgleichengruppe (Refresh groups)........................................156 2.2.12 Sequenz (Sequences)..............................................................156 2.2.13 Log Materialisierte View (Snapshot logs).................................160 2.2.14 Materialisierte View (Snapshots) .............................................160 2.2.15 Synonym (Synonyms).............................................................170 2.2.16 Tabellentyp (Table Types).......................................................172 2.2.17 Tabelle (Tables) ......................................................................185 2.2.18 Trigger (Triggers) ...................................................................203 2.2.19 Ansicht (Views) .......................................................................212 2.2.20 Zusammenfassung..................................................................216 Die Beispieldatenbank anlegen ...............................................................218 2.3.1 Anlage des Schema-Eigners ....................................................219 2.3.2 Verwalten der Tablespaces......................................................222 2.3.3 Anlegen des Schemas .............................................................223 Datenmigration......................................................................................230 2.4.1 Variationsmöglichkeiten..........................................................230 2.4.2 Laden unserer Beispiel-DB.......................................................233 2.4.3 Der Data Manager..................................................................236 2.4.4 Laden der Stammdaten per SQL*Loader .................................239 2.4.5 Wie geht es weiter? ................................................................247
Abfragen ...............................................................................................249 3.1 Einfache Auswahlabfragen......................................................................249 3.1.1 Struktur einer Auswahlabfrage ................................................250 3.1.2 Where-Bedingungen...............................................................254 3.1.3 Ergebnisse sortieren ................................................................259 3.1.4 Gruppierungen.......................................................................261 3.1.5 Spalten vertexten ...................................................................265 3.2 Verknüpfungen ......................................................................................266 3.2.1 Inner-Joins ..............................................................................268 3.2.2 Unterabfragen ........................................................................271 3.2.3 Outer-Joins .............................................................................278 3.2.4 Mengenoperationen ...............................................................281 3.2.5 Hierarchische Abfragen...........................................................287
Inhaltsverzeichnis
3.3
3.4
3.5
7
Änderungsabfragen................................................................................298 3.3.1 Ändern von Daten ..................................................................298 3.3.2 Löschen von Daten .................................................................301 3.3.3 Einfügen von Daten ................................................................303 3.3.4 Sperren von Datensätzen ........................................................306 3.3.5 Erstellen eines Änderungscursors ............................................309 3.3.6 Transaktionen .........................................................................319 Tuning von Abfragen .............................................................................324 3.4.1 Abfrageoptimierung ...............................................................325 3.4.2 Ausführungspläne...................................................................329 3.4.3 Ein paar Grundregeln für die Erstellung von Abfragen.............336 3.4.4 Verwenden von Hints .............................................................341 Data Dictionary Views ............................................................................346 3.5.1 Analyse der Datenbankobjekte................................................347 3.5.2 Aktivitäten in der Datenbank abfragen....................................352 3.5.3 Zugriffsrechte entschlüsseln ....................................................355
4
Benutzer und Rollen ..............................................................................357 4.1 Einführung in die Benutzerverwaltung....................................................357 4.1.1 Das Rollenkonzept ..................................................................359 4.1.2 Der Oracle Security Manager ..................................................359 4.2 Benutzerverwaltung mit SQL..................................................................361 4.2.1 Einen Benutzer bearbeiten ......................................................361 4.2.2 Rollen anlegen und bearbeiten ...............................................363 4.2.3 Profile anlegen und bearbeiten ...............................................363 4.3 Rechtevergabe mit SQL ..........................................................................366 4.3.1 Vergabe von Einzelrechten und Rollen ....................................366 4.3.2 Zugriffsschutz auf Datensatzebene..........................................370 4.3.3 Benutzerverwaltung in der Praxis ............................................375 4.4 Auswertung der Benutzerprofile .............................................................375
5
PL/SQL-Programmierung ......................................................................381 5.1 Einführung in PL/SQL .............................................................................381 5.1.1 Allgemeines ............................................................................381 5.1.2 Blockstruktur ..........................................................................384 5.1.3 Datentypen ............................................................................386 5.1.4 Funktionen .............................................................................398 5.1.5 Ausgabe von Meldungen........................................................410 5.1.6 Konditionalbedingungen ........................................................412 5.1.7 Schleifen.................................................................................415 5.1.8 Datenbankabfragen ................................................................418 5.1.9 Fehlerbehandlung ..................................................................429 5.1.10 Dateiverarbeitung...................................................................441 5.1.11 Pipes.......................................................................................446 5.1.12 Verwenden von PL/SQL ..........................................................451
8
Inhaltsverzeichnis
5.2
6
Anwendungsbeispiele.............................................................................454 5.2.1 Benutzerverwaltung................................................................454 5.2.2 Änderungsprotokollierung ......................................................460 5.2.3 Komplexe Integritätsbedingungen..........................................465 5.2.4 Exporthilfe ..............................................................................475
Anwendungsentwicklung ......................................................................481 6.1 Grundlagen............................................................................................481 6.1.1 ODBC-Verbindung .................................................................482 6.1.2 Verwenden der Oracle-Objekte...............................................491 Stichwortverzeichnis .............................................................................495
Vorwort Noch ein Buch über Oracle?! Nun, in einem haben Sie sicher Recht: Bücher über Oracle gibt es sicherlich eine ganze Menge. Aber als ich im Sommer 1998 intensiveren Kontakt zu diesem Datenbanksystem bekam, hätte ich mir ein solches Buch gewünscht. Ich habe es gesucht, aber nicht gefunden – das ist einer der Gründe, warum ich es hier schreibe. Als ich als freiberuflicher Berater im Rahmen eines PeopleSoft-Projekts engen Kontakt zu Oracle bekam, da hatte ich neben umfangreicher Programmiererfahrung und detaillierten SQL-Kenntnissen auch weitreichende Erfahrungen mit verschiedenen Datenbanksystemen, beispielsweise Sybase oder dem Microsoft SQL-Server. Wenn Sie auf mein oben genanntes Datum geachtet haben und sich ein wenig auskennen, dann werden Sie jetzt sagen „damals waren die beiden Server doch nahezu gleich“. Stimmt genau, und damit sind wir der eingangs genannten Fragestellung wieder ein Stückchen näher gekommen. Beim Umgang mit Oracle war nämlich vieles anders, als bei den bisherigen DBMSSystemen. Die Werkzeuge sind andere, die Struktur der Datenbank (Schema, Tablespace) ist anders, Abfragen müssen zum Teil anders gestaltet werden. Anders ausgedrückt: Es gibt andere Restriktionen, aber auch andere Möglichkeiten. Diese auf einem Blick zu erkennen und umzusetzen ist nicht ganz einfach, und genau hier soll dieses Buch helfen. Was hat man denn für Möglichkeiten, wenn man im Rahmen eines Projekts auf eine Datenbank stößt, mit der man sich bisher noch gar nicht oder zumindest nicht regelmäßig bzw. nicht so oft beschäftigt hat? Jedes Mal einen Kurs besuchen? Wohl dem, der diese Möglichkeiten hat. Das Handbuch lesen? Ich wünsche Ihnen bei Oracle viel Spaß dabei und glauben Sie mir: Die Bibel können Sie leichter lesen; außerdem ist sie auch noch weniger umfangreich. Die einzige Variante ist, sich mal wieder möglichst schnell per „Try and Error“ einzuarbeiten. Ein bisschen Hilfe kann hierbei aber nicht schaden, und genau diese halten Sie gerade in Ihren Händen. Ich betrachte eine Datenbank immer als Werkzeug, als Mittel zum Zweck, mehr oder weniger Datensätze irgendwo zu speichern und bei Bedarf auch wiederzufinden. Genau wie das komplexe Thema Netzwerk, gehe ich in meinen Projekten immer davon aus, dass diese Dinge funktionieren und sich kompetente Kollegen um die Details kümmern. Ich bin eher für die Lösung irgendwelcher Anwenderprobleme zuständig. Ich weiß, was man mit einer Bohrmaschine alles machen kann, aber muss ich deshalb auch wissen, aus wie vielen Teilen meine Bohrmaschine besteht, wie man sie in alle Einzelteile zerlegen und wieder zusammensetzen kann? Ich denke nicht, Stecker rein muss reichen; danach kann ich ein schönes Loch bohren.
10
Vorwort
Genau aus diesem Blickwinkel ist dieses Buch aufgebaut. Bestimmte Themen, für die es in der Praxis Datenbankadministratoren und Netzwerkbetreuer gibt, werden ganz bewusst nur grob skizziert oder sogar ganz weggelassen. Gleiches gilt für Funktionen, die in der Praxis nur selten vorkommen und für die Sie, wenn Sie die Grundzüge von Oracle erst einmal beherrschen, schnell Informationen in den Handbüchern finden. Dieser Workshop kooperiert also mit der Dokumentation des Herstellers, er versteht sich also nicht als Ersatz, sondern lediglich als Ergänzung der ziemlich umfangreichen Online-Handbücher. Er soll Ihnen helfen, sich in die wichtigsten Themen schnell und mit Hilfe pragmatischer Beispiele einzuarbeiten. Abschließend möchte ich in diesem Vorwort natürlich alle erwähnen, die mich zu diesem Buch inspiriert oder bei der Erstellung unterstützt haben. Da sind zunächst einmal die netten Kollegen des letzten Deutschen Bank-Projekts: warum soll ich nicht mal all das aufschreiben, was ich in den letzten dreißig Monaten erzählt habe. Dem letzten Projekt, der Einführung von PeopleSoft, kommt natürlich insgesamt eine tragende Rolle zu, denn es lieferte einige der in diesem Buch aufgearbeiteten Anregungen und Beispiele. Erwähnen möchte ich auch meine beiden Kinder: euch traf es mal wieder am meisten, als Papa in den letzten Wochen noch weniger Zeit als sonst hatte. Ich wünsche Ihnen nun viel Spaß und Erfolg beim Lesen und Durcharbeiten dieses Buches. Ihr Heinz-Gerd Raymans
V.1
Zum Inhalt
Bevor ich etwas zum Inhalt sage, möchte ich noch vorwegnehmen, dass alle Beispiele und Erklärungen zunächst mit einer 8er-Version und zum Abschluss noch einmal mit der Version Oracle8i Release 2 (Version 8.1.6) überprüft wurden. Aus diesem Grund wurden im Nachhinein noch verschiedene Passagen erweitert, so dass Sie mit diesem Buch auch ein Vergleichswerk für die beiden zur Zeit noch auf dem Markt befindlichen Versionen besitzen. Auf der anderen Seite sind viele der hier angesprochenen Themen relativ versionsstabil und gelten vielleicht abgesehen von den Bildern und einigen Programmnamen für alle 8er-Versionen bzw. in Teilen sogar für eine Datenbank der Version 7. Das Buch hat also vornehmlich das klassische Datenbankgeschäft in Form von SQL, Views, Triggern usw. im Auge und überlässt die neuen 8i-Erweiterungen wie Internet-Integration, JDeveloper und Java anderen Werken, die sich schwerpunktmäßig mit diesen Themen beschäftigen (z.B. Oracle 8i und Java, ADDISON-WESLEY, ISBN 3-8273-1485-2). Im ersten Teil des Workshops „Oracle – erste Schritte “ geht es neben einigen allgemeinen Grundlagen vor allem um die Installation von Oracle, beispielsweise auf einem NT-Client (z.B. Notebook) oder Server mit anschließender Erstellung einer eigenen Datenbank und deren Einbindung in das Oracle-Netzwerk (Net8). Sowohl
Zum Inhalt
11
bei der Erstellung der Datenbank, als auch bei deren Einbindung in das Netzwerk werden Sie verschiedene Methoden kennen lernen, um diese Aufgaben manuell oder mit Hilfe des einen oder anderen Werkzeugs zu erledigen. Mit Hilfe des zweiten Hauptkapitels erhalten Sie einen Überblick über die in Oracle verfügbaren Datenbankobjekte. Zunächst werden die einzelnen Objekte aufgezählt bzw. kurz beschrieben und im weiteren Verlauf wird die im Kapitel 1 erstellte Datenbank mit Leben gefüllt, d.h. die üblicherweise benötigten Tabellen und Indices werden angelegt. Abschließend wird die Datenbank mit Hilfe der auf der CD befindlichen Daten gefüllt. Auch hierzu lernen Sie verschiedene Methoden, vor allem auch mehrere Varianten zur Verwendung des Oracle-Import-Managers kennen. Der nächste bzw. dritte Teil des Buches widmet sich dem komplexen Thema Abfragen. Ausgehend von einfachen Auswahlabfragen geht der Streifzug weiter durch die Welt der Verknüpfungen (Joins) und mündet danach in den Komplex der Änderungsabfragen inklusive dem Erstellen von Cursorn zur Durchführung von Datenbankänderungen. Abgerundet wird der ganze Themenkomplex mit einigen Ausführungen und Tipps zum Thema Abfragetuning und verschiedenen Beispielen zur Anwendung der verfügbaren Data-Dictionary-Views, mit deren Hilfe Sie das Oracle-System bzw. Ihre Datenbank analysieren können. Dem Thema Benutzter- und Rollenprofile widmet sich der vierte Teil des Buches. Hier lernen Sie die wichtigsten Ausprägungen dieser Profile mitsamt verschiedener Verfahrensweisen zu deren Verwaltung kennen. Im fünften Buchteil finden Sie eine Einführung in die PL/SQL-Programmierung. Gleichgültig ob es immer gleich um die Programmierung von Prozeduren oder Funktionen oder nur um die Erstellung größerer Skripts oder komplexerer Änderungscursors geht: mit PL/SQL haben Sie eine schier unerschöpflichen Menge von Möglichkeiten. Abgeschlossen wird das Kapitel mit verschiedenen Anwendungsbeispielen, beispielsweise einer universellen Exportfunktion. Der letzte Teil des Buches nähert sich noch einmal der Anwendungsentwicklung. Neben ein paar allgemeinen Hinweisen finden Sie hier auch Beispiele wie Sie mit Hilfe von ODBC oder den Oracle Objects im Handumdrehen leistungsfähige FrontEnd-Anwendungen erstellen können. Als Demonstrationsobjekt finden Sie unter anderem eine Funktion, mit deren Hilfe Sie von Excel aus Abfragen in der OracleDatenbank starten können. Nun folgt zum Abschluss noch ein Hinweis in eigener Sache. Die meisten Dinge im Leben sind nicht perfekt, das gilt vielleicht auch für den einen oder anderen Satz oder Erklärung in diesem Buch. Bedenken Sie, dass wenn man solche Bücher neben seiner normalen Arbeit schreibt, der letzte Eintrag im Windows-Ereignisprotokoll häufig knapp vor oder weit hinter 0:00 liegt. Vielleicht gibt es aber auch ein Thema, das eigentlich gut in dieses Buchkonzept passt, zur Zeit aber überhaupt nicht behandelt wird. Was ich eigentlich sagen will ist, dass gute Ideen, konstruktive Kritik, interessante Beispiele aber auch Lob immer willkommen sind und wenn Sie wollen, dann können Sie mir Ihre Anregungen und Bemerkungen direkt an die Adresse
[email protected] zusenden, doch nun wünsche ich Ihnen erst einmal viel Spaß bei der Arbeit.
1
Oracle – erste Schritte
Ich finde, dass auch ein technisches Buch einen Spannungsbogen haben sollte. In diesem Buch beginnt er im Kapitel „Grundlagen“ mit der Erläuterung verschiedener Konzepte und Grundbegriffe. Sofern Sie schon einmal mit ähnlichen Datenbanksystemen (z.B. Sybase, Informix oder dem MS SQL-Server) gearbeitet haben, dann können Sie das Grundlagen-Kapitel natürlich auch getrost übergehen; Sie werden dort nichts Neues mehr erfahren. Danach beschäftigen wir uns ein wenig mit der Installation einer Oracle-Datenbank, inklusive dem Einrichten des Zugriffs über SQL*Net. Hiernach folgen die Erstellung verschiedener Tabellen und das Laden von Musterdaten, die im weiteren Verlauf des Buches für Beispiele und Übungen immer wieder benötigt werden. Im Rahmen dieser Einführung werden Sie also auch erste Erfahrungen mit Oracle sammeln können und wichtige Werkzeuge (z.B. SQL-Worksheet, Loader oder SQL*Plus) kennen lernen.
1.1
Grundlagen
Was ist eine Datenbank, was ein Datenbank-Management-System? Wie ist der schematische Aufbau einer Oracle-Datenbank? Was ist EDV? Nein, die letzte Frage werde ich hier nicht behandeln, aber zu den anderen beiden Fragen will ich schon das eine oder andere anmerken. Die Antwort auf die erste Frage scheint komplex, jedoch ist die Antwort darauf kurz, prägnant und ziemlich einfach. Eine Datenbank ist eine strukturierte Sammlung von Daten – das war schon alles! Um mit diesen Daten bzw. der Datenbank arbeiten zu können, benötigen wir üblicherweise noch geeignete Software, wobei man hierbei von einem Datenbank-Management-System (oft und auch hier mit DBMS abgekürzt) spricht, wenn sie vor allem das anwendungsunabhängige Arbeiten mit einer oder mehreren Datenbanken ermöglicht. Strukturierte Datensammlungen gab es eigentlich schon immer. Aber, und das wissen vor allem diejenigen, die schon länger im EDV-Geschäft tätig sind, der Umgang mit gespeicherten Daten war nicht immer so komfortabel wie heute, denn oftmals musste man sich unter anderem auch um Verfahrensweisen zum Einfügen oder Löschen von Datensätzen oder dem schnellen Wiederfinden von Informationen kümmern. Ein Ergebnis dieser Verfahrensweisen war meistens eine sehr enge Verzahnung von Anwendungsprogramm und Datenbasis. Bei einem DatenbankManagement-System werden solche Detailarbeiten wie das Einfügen, Löschen oder Finden von Daten von ihm selbst erledigt. Hierzu erhält es vom Anwendungsprogramm lediglich bestimmte Kommandos in Form einer speziellen Sprache. Durch das vorhandene Regelwerk dieser Sprache und der Möglichkeit, das viele verschiedene Anwendungsprogramme die zugehörigen Kommandos absetzen können, entsteht die gewünschte Anwendungsunabhängigkeit.
14
Oracle – erste Schritte
1.1.1
Klienten und Diener
Entsprechend der eben erläuterten Definition handelt es sich auch bei dem Produkt Access von Microsoft also wirklich um ein Datenbank-Management-System. Zum einen ermöglicht es die strukturierte Speicherung von Datensammlungen und zum anderen besteht für viele Anwendungen (nicht nur Access selbst) die Möglichkeit, mit den Daten der Access-Datenbank zu arbeiten. Dennoch gibt es aber einen gravierenden Unterschied zwischen beispielsweise Access und einem Oracle-DBMS. Technisch aber trotzdem vereinfachend betrachtet besteht das DBMS von Access aus einer „unendlichen“ Anzahl von DLLs (Dynamic Link Libraries = Dynamische Laufzeitbibliothek), die bei Bedarf in den Speicher Ihres Rechners geladen werden. Diese DLLs dienen dazu, die benötigten Datenbankkommandos entgegenzunehmen, zu interpretieren und in der Datenbank entsprechend auszuführen. Bei der Datenbank handelt es sich meistens um eine (MDB-) Datei, die sofern man nicht alleiniger Nutzer ist, auf einem zentralen Dateiserver abgelegt ist. Betrachten Sie hierzu die zugehörige Abbildung (vgl. Abb. 1.1).
Arbeitsplatz Win NT, 95, 98, 2000 Excel
Server
Access
Access-DBMS Netzwerk
Datenbank
Abbildung 1.1: Struktur eines „arbeitsplatzorientierten“ DBMS-Systems
Gemäß der Abbildung 1.1 wird eine auf einem Dateiserver gespeicherte Datenbank von Ihrem Arbeitsplatz aus mit Hilfe des Access-DBMS über das Netzwerk abgefragt. Dabei liegen alle an diesem Prozess wesentlich beteiligten Komponenten, also die beteiligten Anwendungen (Excel, Access oder andere) und das DBMS (in unserem Beispiel die entsprechenden DLLs), im Speicher des Arbeitsplatzrechners. Das DBMS ruft die benötigten Daten aus der Datenbank ab bzw. führt dort Änderungen durch. Mit ein wenig Phantasie sollte klar werden, was bei einer solchen Architektur alles passieren kann:
X
Die Anzahl der Anwender entspricht der Anzahl der im Einsatz befindlichen DBMS-Systeme. Ein solches DBMS-System ist eigentlich immer ein höchst komplexes Gebilde mit einer Unzahl verschiedenster Funktionalitäten. Es sollte somit plausibel sein, dass seine Arbeitsgeschwindigkeit nicht unwesentlich von
Grundlagen
X
X
15
den Ressourcen des ausführenden Rechners abhängt. Muss also die Leistungsfähigkeit des DBMS-Systems gesteigert werden, so müssen unter Umständen alle Rechner auf denen es läuft aufgerüstet werden. Bestimmte Sachverhalte werden besonders deutlich, wenn man ein wenig schwarz malt bzw. drastisch übertreibt. Stellen wir uns einfach mal vor, unsere Datenbank hätte eine Größe von mehreren hundert Megabyte. Mit Hilfe einer Abfrage suchen wir eine winzige Information, z.B. einen Namen, wobei die Form der gewählten Abfrage vielleicht dazu führt, dass sie vom DBMS nicht optimal ausgeführt werden kann und somit zur Bereitstellung unseres gesuchten Namens alle Datensätze der Datenbank abgerufen werden müssen. In dem Fall müssten alle Datensätze der Datenbank über das Netzwerk zu unserem Arbeitsplatzrechner transportiert werden, damit das DBMS uns anschließend als Ergebnis einen – oder noch besser keinen – Namen präsentiert. Ein Phänomen, dass es in der Praxis wirklich gibt. Eine Abfrage führt zu einer erheblichen Belastung des Netzwerks und unseres Rechners und am Ende kommt wenig oder sogar nichts heraus. Sie wollen ein Feld einer in der Datenbank gespeicherten Tabelle ändern, und zwar für jede vorhandene Reihe. Auch in diesem Fall muss das auf Ihrem Rechner laufende DBMS jeden Satz über das Netzwerk anfordern, die Änderung durchführen und anschließend für die Rückübertragung und die Speicherung des neuen Wertes sorgen.
Damit keine Missverständnisse entstehen: ich will das Produkt Access hier nicht kritisieren oder schlecht machen. MS-Access ist lediglich ein - wenn auch ziemlich bekannter - Vertreter für diese Gruppe von DBMS-Systemen. An dem Beispiel sollte lediglich verdeutlicht werden, wie diese Klasse von Datenbank-Management-Systemen (z.B. auch FoxPro, Paradox, Approach u.a.) funktionieren. In der englischsprachigen Literatur wird ein solches DBMS oftmals auch als File Sharing Database System bezeichnet. Die Bezeichnung kommt daher, dass sich hierbei in der Tat alle Anwendungen die auf dem Server befindliche Datenbank direkt teilen; jede Anwendung veranlasst beispielsweise indirekt über das DBMS das Öffnen der zugehörigen Datenbankdateien. Was muss man nun ändern, um die eben genannten Eigenschaften zu verändern bzw. zu verbessern? Zunächst einmal wäre wünschenswert, das Datenbanksystem näher an seine Datenbank zu bringen, um Lese- oder Änderungsanforderungen einfacher und schneller bearbeiten zu können. Mit anderen Worten: das DBMS muss auf den Server umziehen, wo auch die Datenbank gespeichert ist. Betrachten Sie einmal die Abbildung 1.2. Durch den Umzug des DBMS ist ein System entstanden, wie Sie es in der heutigen Praxis oftmals vorfinden. Zum einen haben Sie den Arbeitsplatzrechner, den Client, auf dem die eigentlichen Anwendungen laufen. Diese fordern mit Hilfe von Nachrichten Daten aus der Datenbank ab oder veranlassen dortige Änderungen. Die Nachrichten werden über das Netzwerk an das auf dem Server laufende DBMS übermittelt. Das sorgt für die Bereitstellung der gewünschten Daten und veranlasst die Übermittlung der Ergebnisse an den Client und im Falle von Änderungen werden überhaupt keine Daten mehr über das Netzwerk transportiert.
16
Oracle – erste Schritte
Arbeitsplatz
Server
Win NT, 95, 98, 2000 Excel
DBMS
Access Nachrichtendienst
Nachrichtendienst Netzwerk
Datenbank
Abbildung 1.2: DBMS mit einer Client/Server-Struktur
Oftmals kommunizieren die Clients nicht einmal direkt mit dem DBMS, sondern das leistet sich für diese besondere Aufgabe einen vorgelagerten Prozess, quasi so eine Art Sekretariat. Solche Prozesse werden üblicherweise als Listener bezeichnet, und wie man der englischen Bezeichnung ableiten kann, besteht seine Hauptaufgabe darin, das Netzwerk permanent nach Nachrichten für sein zugehöriges DBMS abzuhören. Übrigens, genau wie im richtigen Leben, wo ein Sekretariat durchaus für mehrere Chefs arbeiten kann, kann auch solch ein Listener Nachrichten für mehrere installierte DBMS-Instanzen abhören bzw. weiterleiten. Ein weiterer Unterschied zu den File Sharing Database Systemen ist, dass das auf dem Server befindliche DBMS die zugehörigen Datenbankdateien üblicherweise exklusiv nutzt. Die einzelnen Anwendungen verbinden sich lediglich direkt oder über den Listener mit dem DBMS; auch bei tausend Anwendern sind die Datenbankdateien nur einmal geöffnet. Sie haben nun auf ziemlich pragmatische Weise die Struktur eines Client/ServerSystems (CS-System) kennen gelernt. Ich möchte die wesentlichen Eigenschaften in der folgenden Tabelle noch einmal zusammenfassen: File Sharing System
Client/Server System
Das DBMS befindet sich auf dem Client.
Das DBMS befindet sich auf dem Server.
Soll in einer Datenbank jeder Datensatz aktualisiert werden, so müssen alle Datensätze zum Client transportiert werden. Dort werden die Änderungen durchgeführt und anschließend müssen alle Datensätze wieder in die Datenbank auf den Server zurückgeschrieben werden.
Der Client schickt eine entsprechende Aktualisierungsnachricht an das DBMS. Dieses führt anschließend direkt die notwendigen Aktualisierungen in der Datenbank durch.
Grundlagen
17
File Sharing System
Client/Server System
In vielen Anwendungen hängt die Arbeitsgeschwindigkeit direkt von der Geschwindigkeit des DBMS ab. Daher muss jeder Arbeitsplatzrechner entsprechend leistungsfähig sein, damit das auf ihm laufende DBMS über genügend Ressourcen verfügen kann.
Der Server muss entsprechend leistungsfähig ausgelegt werden, damit das dort laufende DBMS die Anforderungen der Clients schnell genug abarbeiten kann. Die auf den Arbeitsstationen verfügbaren Ressourcen spielen (gäbe es nicht Windows) eigentlich keine Rolle mehr.
Wird die konkrete Anwendung zu langsam und liegt dies zum Beispiel an der Größe der Datenbank, so muss das DBMS und damit alle Arbeitsplatzrechner aufgerüstet werden.
In dem Fall muss lediglich der Server entsprechend aufgerüstet werden.
Tabelle 1.1: Gegenüberstellung File Sharing und Client/Server System
Vielleicht ist bei den bisherigen Erklärungen der Eindruck entstanden, dass die Client/Server-Technologie von Datenbanksystemen gepachtet ist. Dem ist natürlich nicht so, d.h. ein modernes leistungsfähiges DBMS ist nur ein Vertreter vieler anderer CS-Anwendungen. Ganz allgemein gilt bei dieser Technik eigentlich nur, dass bestimmte Teile der Anwendung auf dem Server und andere Teile auf den Clients verfügbar sind und das beide Teile in Form von Nachrichten miteinander kommunizieren und der Client die zentral auf dem Server bereitgestellten Dienste bzw. Anwendungsfunktionen nutzt. Zurück zu den Datenbanksystemen. Ohne vernünftiges Netzwerk funktionieren natürlich beide System nicht richtig. Da nützt auch der „dickste“ Server nichts, wenn die Nachrichten bzw. Ergebnisse zum bzw. vom DBMS nicht vernünftig transportiert werden und auch der Client mit neuestem Prozessor und hunderten Megabyte RAM dreht eine Warteschleife nach der anderen, wenn die aus der Datenbank angeforderten Daten aufgrund des langsamen Netzwerks nur „tröpfchenweise“ ankommen. Bei soviel offenkundiger Werbung für Client/Server möchte ich hier dennoch einmal kurz auf die Bremse treten. Ich werde in meinem weiteren Berufsleben keine CS-Systeme mehr in Unternehmen einführen, in denen die für einen reibungslosen Betrieb notwendigen Strukturen fehlen. Hiermit meine ich vor allem vorhandenes KnowHow über das vorhandene Netzwerk und die eingesetzten Server oder vor Ort verfügbares Personal, das zumindest bestimmte Grundkenntnisse (z.B. Datensicherung, Erweitern der Datenbank, Hoch- und Runterfahren des DBMS) über das neue Datenbanksystem besitzt. Ich habe schon häufiger schlechte Erfahrungen damit gemacht, wenn im Rahmen irgendwelcher Softwareprojekte ein DBMS auf C/S-Basis eingeführt bzw. durchgesetzt wurde, dieses aber in der Softwarelandschaft des Kunden einen Fremdkörper darstellte. Solche Projekte laufen am Anfang meistens sogar ziemlich gut. Die eigene EDV mischt sich kaum ein (weil keine Ahnung) und alle auftretenden Probleme können, weil man im Rahmen des Projekts sowieso oft oder permanent vor Ort ist, rasch erledigt werden. Treten dann im laufenden Betrieb die üblichen Problemchen auf (Server macht Mucken – und damit auch das DBMS; Sessions hängen usw.), dann kann die Stimmung schnell drehen, entweder weil man jetzt aufgrund eines anderen Projekts nicht bzw.
18
Oracle – erste Schritte
schlecht verfügbar ist oder weil der Kunde irgendwann die Lust verliert, immer neue Wartungsrechnungen zu bezahlen. Ein solcher Kunde wäre mit einer Anwendung, die ihre benötigten Daten mit Hilfe einer File Sharing Datenbank verwaltet, besser bedient, da diese im laufenden Betrieb meistens ohne spezielle Kenntnisse betreut werden kann (DBMS kaputt = Anwendung auf dem Client neu installieren; Datenbank kaputt = Files von der normalen Datensicherung zurückspielen); klingt einfach und ist es meistens auch. Wie im Leben, so gibt es auch in der EDV mittlerweile für jede Aufgabe das richtige Handwerkszeug und um so merkwürdiger ist es allerdings, dass die Auswahlentscheidung noch immer so häufig aus einer Bierlaune heraus oder auf dem Tennisbzw. Golfplatz getroffen wird. Ich würde diesen Entscheidungsträgern gerne mal im Privatleben zusehen, wie sie die Heftzwecke für ein neues Kindergartenbild mit dem Vorschlaghammer in der Wand versenken, anstatt hierfür den kleinen und handlichen Bastelhammer zu benutzen. Aber schließlich machte der Vorschlaghammer im Geschäft den stattlicheren Eindruck, hier bekommt man wirklich was für sein Geld und daher muss er einfach das richtige Tool für alle denkbaren Aufgabenstellungen sein.
1.1.2
Aufbau des Oracle-DBMS
Aufgrund des letzten Kapitels wissen Sie nun, dass Oracle zur Klasse der Client/Server-Datenbank-Management-Systeme (CS-DBMS) gehört und obwohl sich dieses Buch primär nicht an Techniker richtet, soll im Folgenden der konkrete Aufbau des Oracle-DBMS ein wenig genauer betrachtet werden. Sollten Ihnen die hier bzw. auch im nächsten Kapitel gelieferten Informationen zu oberflächlich sein, dann empfehle ich Ihnen den Genuss eines speziellen Buches für Oracle-Datenbankadministratoren oder Sie schmökern einfach mal ein wenig in den per HTML gelieferten Handbüchern. Im Buch „Oracle8 Server“ finden Sie beispielsweise allein schon in den Abschnitten „Server Concepts“ oder „Oracle Server Administrator's Guide“ eine schier unerschöpfliche Informationsquelle. Beim 8i-Server finden Sie diese beiden Kapitel auch wieder, wenn Sie auf der Startseite den Link „Oracle8i Server and Data Warehousing“ folgen. In allen Versionen finden Sie die Online-Dokumentation übrigens im \DOC-Verzeichnis, das direkt unter dem Oracle-Homeverzeichnis angeordnet ist. Schaut man etwas genauer auf die Funktionsweise eines Oracle-DBMS so stellt man fest, dass es sich in der Architektur ein wenig von unserer bisherigen allgemeingültigen Definition und in diesem Punkt auch in der Tat von andern DBMS-Systemen unterscheidet. Wir, nein ich, hatte gesagt, dass es sich bei einem DBMS um ein Stück Software handelt, das ein anwendungsunabhängiges Arbeiten mit einer oder mehreren Datenbanken ermöglicht. Microsofts SQL-Server arbeitet beispielsweise nach diesem Prinzip. Bei dieser Datenbank finden Sie auf Ihrem Rechner genau einen Prozess, repräsentiert durch das Programm SQLSERVR.EXE, können aber trotzdem mit allen definierten Datenbanken arbeiten.
Grundlagen
19
Im Unterschied hierzu arbeitet bei Oracle ein DBMS (z.B. ORACLE80.EXE unter NT) immer genau mit einer Datenbank zusammen, was natürlich nicht heißt, dass Sie nicht mehrere Datenbanken auf einem Rechner betreiben können – Sie müssen in dem Fall auch einfach nur entsprechend viele Kopien des DBMS im Speicher laufen haben. Das ist also genau so, als würden Sie Excel mehrfach starten und mit jeder Programmkopie genau eine Tabelle laden, wohingegen die definitionskonforme Arbeitsweise des SQL-Servers dem einmaligen Starten von Excel entspricht, wobei anschließend für jede Tabelle ein Arbeitsfenster geöffnet wird. Im Oracle-Sprachgebrauch wird eine solche DBMS-Kopie üblicherweise mit dem Begriff einer Instanz belegt. Technisch betrachtet verbirgt sich hinter einer solchen Instanz die Menge aller benötigten Prozesse zuzüglich einem Stückchen reserviertem Hauptspeicher, der für alle diese Prozesse erreichbar ist. Dieser Hauptspeicherbereich wird als System Global Area (SGA) bezeichnet, die Instanz selbst wird durch einen System-Identifer (SID) gekennzeichnet (vgl. Abb 1.3).
SID=orcl SGA = System Global Area
Oracle-Systemprozesse DBWR
SMON
PMON
LGWR
CKPT
Abbildung 1.3: Schematischer Aufbau einer Oracle-Instanz mit den wichtigsten Hintergrundprozessen
Sofern es Sie interessiert, finden Sie in der folgenden Aufstellung eine grobe Beschreibung, welche Funktionen die einzelnen Systemprozesse erfüllen:
X
X X
DBWR (Database Writer) schreibt in der SGA modifizierte Datenblöcke zurück in die Datenbank. SMON (System Monitor) Überwacht die Wiederherstellung der Datenbank bei einem Neustart. Ferner registriert dieser Prozess freiwerdende Bereiche in der Datenbank und vereinfacht somit deren Wiederbelegung. LGWR (Log Writer) Schreibt die im Rahmen von Transaktionen anfallenden Protokollinformationen in die zugehörigen Plattenbereiche (Redo-Log-Einträge).
20
X X
Oracle – erste Schritte
CKPT (Checkpoint) Generiert die sogenannten Checkpoints, zu denen modifizierte Datenblöcke aus der SGA in die Datenbank zurückgeschrieben werden. PMON (Process Monitor) Überwachung der Benutzerprozesse. Freigabe der Ressource von abgebrochenen Benutzerprozessen.
Die SGA selbst ist auch in mehrere unterschiedliche Speicherbereiche aufgeteilt. Diese Speicherbereiche werden während der Initialisierung der Instanz angelegt und können in der Größe zur Laufzeit nicht geändert werden. In der folgenden Aufstellung finden Sie eine kurze Beschreibung der wichtigsten Speicherbereiche:
X
X X
Database Buffer Cache In diesem Speicherbereich werden die gerade benötigten Datenblöcke vorgehalten. Da üblicherweise nicht alle Daten gleichzeitig in diesen Puffer passen, findet ein permanenter Ein- und Auslagerungsprozess zwischen aktuell benötigten und länger nicht mehr gebrauchten Daten statt. Hierbei werden die am längsten ungenutzten Puffer aus diesem SGA-Bereich ausgelagert (LRU-Algorithmus, LRU = Least Recently Used). Zur Erinnerung: zum Wegschreiben von Änderungen aus diesem Puffer war der Prozess DBWR zuständig. Redo Log Buffer Die während einer Transaktion anfallenden Protokollinformationen werden in diesem Puffer zwischengespeichert. Shared Pool SQL-Befehle jeglicher Art, Funktionen oder Prozeduren werden in diesem Pool zwischengelagert, wobei diese hier gelagerten Abfragen direkt ausführungsfähig sind, d.h. sie werden hier mitsamt ihren Ausführungsplänen gespeichert. Ähnlich wie beim Database Buffer Cache wird auch dieser Bereich nach dem LRUAlgorithmus verwaltet, d.h. häufig benutzte Abfragen oder Prozeduren stehen direkt zur Ausführung zur Verfügung.
Mehr Informationen zu diesen und weiteren Prozessen einer Oracle-Instanz sowie weitergehende Informationen über den Aufbau der SGA finden Sie in der OracleDokumentation im Buch „Server Concepts“ im Kapitel „Memory Structure and Processes“. Um selbst Datenbanken anzulegen bzw. die dazu notwendigen Schritte besser zu verstehen, reicht allerdings ein grobes Gefühl über den Aufbau einer Instanz völlig aus.
1.1.3
Aufbau einer Oracle-Datenbank
Nachdem Sie nun ein paar Grundkenntnisse über den Aufbau des Oracle-DBMS besitzen, möchte ich Ihnen auch noch ein paar Grundlagen über den Aufbau der Datenbank vermitteln, d.h. es geht jetzt darum mal nachzuschauen, wie sich die Datenbank auf der Festplatte ausbreitet.
Grundlagen
21
Jedes Datenbanksystem speichert seine Daten in ganz gewöhnlichen Dateien (wie auch sonst), d.h. spätestens hier finden Sie eine wichtige Nahtstelle zum vorhanden Betriebssytem, denn beim Anlegen und Verwalten dieser Dateien muss sich natürlich auch das DBMS an die vorgegebenen Spielregeln des Betriebssystems halten. Konkret geht es hierbei beispielsweise um Namenskonventionen (Länge von Pfad- und Dateinamen, Sonderzeichen) oder eventuell vorhandene Größenbeschränkungen von einzelnen Dateien oder des definierten Filesystems (UNIX). Für uns als Anwender bildet die gesamte Datenbank eine logische Einheit. Physikalisch kann das ganz anders aussehen und hängt konkret vom vorhandenen DBMS ab. Es gibt sowohl Datenbanksysteme, wo die gesamte Datenbank auch immer genau einer Datei entspricht, als auch Systeme, wo jede Tabelle automatisch in einer eigenen Datei gespeichert wird. Oracle liegt irgendwo zwischen diesen beiden Extremem, wobei der genaue Standort sehr stark von Ihrer Datenbankdefinition abhängt. Oracle speichert Ihre Anwendungsdaten in Form von Tabellen. Eine solche Tabelle benötigt hierzu natürlich Platz, der ihr vom Datenbanksystem in Form eines sogenannten Tablespace bereitgestellt wird. Dieser Tablespace wird physikalisch – und jetzt wird es interessant – durch eine oder auch mehrere Dateien gebildet (vgl. Abb. 1.4).
Logisches Modell
Datenbank
Tablespaces
Tablespace 1
Physische Dateien
Datei 1
Tablespace 2
Datei 2
Datei 3
Festplatten Abbildung 1.4: Schematischer Aufbau einer Oracle-Datenbank
Betrachten Sie den schematischen Aufbau mit Hilfe der Abbildung 1.4. In dem Beispiel wird der Tablespace 2 durch zwei Dateien gebildet, die sogar auf unterschiedlichen Platten angelegt wurden. In diesen Tablespaces werden nun die benötigten Tabellen und anderen Datenbankobjekte (z.B. Indices) angelegt, d.h. es besteht beispielsweise die Möglichkeit, den für eine Tabelle zuständigen Tablespace und damit vielleicht auch die Tabelle auf mehrere Platten zu verteilen, um hierdurch unter Umständen bessere Zugriffsgeschwindigkeiten zu erreichen.
22
Oracle – erste Schritte
Früher wurde viel mit diesen Optionen experimentiert, um das Antwortszeitverhalten des Systems und damit natürlich auch der Anwendung zu verbessern. Heute spielt bei diesen Möglichkeiten der Tuninggedanke eine immer geringere Rolle. Das liegt aus meiner Sicht vor allem an folgenden drei Gründen. Erstens hat man es heutzutage oftmals nicht mehr mit einzelnen direkt addressierbaren Platten, sondern ganzen Plattenstapeln (Disk-Arrays) zu tun. Hierdurch geht die direkte Kontrolle was genau wo gespeichert wird sowieso verloren bzw. das Speichermedium des Plattenstapels stellt schon von ganz alleine sicher, dass der Zugriff auf große Dateien optimiert erfolgt. Zum zweiten bieten manche Betriebssysteme (z.B. UNIX) die Möglichkeit, die physikalische Plattenstruktur durch ein darüber liegendes sogenanntes Filesystem zu verschleiern. Die zum Tablespace gehörenden Dateien werden dann in diesem Filesystem angelegt, also geht auch hierbei die direkte Kontrolle über die Plattenzuordnung verloren. Der dritte und wesentlichste Grund ist aber, dass sich die Aufwände dieser physikalischen Tuningmaßnahmen meistens nicht mehr rechnen. Zum einen ist die Hebelwirkung dieser Maßnahmen kleiner geworden. Die Rechner und Platten sind heutzutage so schnell, dass viele Anwendungen auch bei miserabelstem Speicherkonzept noch ordentlich laufen dürften. Zum anderen zeigt sich der Engpassfaktor oft gar nicht mehr in der Form der durchgeführten Datenzugriffe, sondern übertrieben verspielte grafische Oberflächen, hüpfende Büroklammern und ähnliche Gimmiks. Die soeben aufgeführten Gründe sollen Sie allerdings auch nicht zum anderen Extrem, nämlich Datenbanken mit nur einem Tablespace, verleiten. Die folgenden Regeln können Ihnen vielleicht bei der Strukturierung Ihrer Datenbank helfen, denn ein allgemeingültiges Patentrezept gibt es auch hier natürlich mal wieder nicht:
X
X
X
Viele Anwendungen definieren für die benötigten Indexdateien einen separaten Tablespace. Wenn Sie nun noch versuchen, wenigstens diese zugehörigen Dateien in ein separates Filesystem bzw. auf anderen Platten zu verlagern, dann kann Ihr System getrennt auf Index- und Datenbereiche zugreifen. Selbst wenn dieser Eingriff Ihre Abfragen nur um 0,00x Sec beschleunigt; diese Maßnahme kostet nichts und es spricht auch ansonsten überhaupt nichts dagegen. Logische Gesichtspunkte können für die Gestaltung der Tablespaces ausschlaggebend sein. Beispielsweise besteht die Anwendung aus mehreren verschiedenen Produkten (z.B. Finanzbuchhaltung, Personalabrechnung und Reisekosten), dann ist es sicherlich sinnvoll, die produktspezifischen Tabellen in eigens hierfür angelegte Tablespaces zu speichern. Dies erhöht nicht nur die Übersicht, sondern kann auch spezielle Wartungsarbeiten vereinfachen, denn die einzelnen Tablespaces können im laufenden Betrieb ein- oder ausgeschaltet werden. Auch wäre die Entzerrung der Daten recht einfach, sollte eines der Produkte mal in eine eigene Datenbank umziehen müssen. Verteilen von Tabellen mit unterschiedlichem Wachstumsverhalten. Es kann durchaus sinnvoll sein, stark wachsende Tabellen von mehr oder weniger statischen Tabellen zu trennen. Es liegt irgendwie auf der Hand, dass der Verwal-
Grundlagen
X
23
tungsoverhead geringer sein muss, wenn eine stark wachsende Tabelle alleine in einem Tablespace liegt, als wenn sie zusammen mit anderen Tabellen um den noch freien Platz „kämpfen“ muss. Anforderungen eines besonderen Betriebsablaufs können natürlich auch eine Rolle spielen. Wie ich schon erwähnte, besteht die Möglichkeit einen einzelnen Tablespace im laufenden Betrieb ein- bzw. auszuschalten. Solche Praktiken könnte man im Zusammenhang mit speziellen Programmen nutzen, die eine Tabelle im Batch komplett verändern, um diese Tabelle vor dem Programmlauf auf einfache und schnelle Weise zu sichern.
Innerhalb eines Tablespaces werden die Daten in sogenannten Segmenten gespeichert. Dabei bildet beispielsweise jede Tabelle oder jeder Index ein eigenes Segment. Die Segmente selbst bestehen aus einem oder mehreren zusammenhängenden Datenblöcken (data block), wobei diese zusammenhängenden Blöcke mit dem Begriff Extend bezeichnet werden. Ein solcher Datenblock ist die kleinste Speichereinheit, die Oracle beim Schreiben oder Lesen der Festplatte bewegt. Die Größe eines solchen Datenblocks können Sie beim Anlegen der Datenbank festlegen, indem Sie den Parameter DB_BLOCK_SIZE der Konfigurationsdatei entsprechend anpassen. Der Standardwert für diese Größe ist betriebssystemabhängig und beträgt für Windows-NT beispielsweise 2048 Byte.
Festplatte Segment
1. Extend 2Kb 2Kb 2Kb 2Kb 2Kb 2Kb
2. Extend
2Kb 2Kb 2Kb 2Kb 2Kb 2Kb 2Kb 2Kb 2Kb 2Kb 2Kb 2Kb
Tablespace
2Kb 2Kb 2Kb 2Kb 2Kb 2Kb
2Kb 2Kb 2Kb 2Kb 2Kb 2Kb
Abbildung 1.5: Segmente, Extends und Datenblöcke
Beim Anlegen eines Tablespace können Sie Regeln definieren, wie die einzelnen Segmente gebildet werden sollen, d.h. welche Größe beispielsweise die erste und jede weitere Ausdehnung (Extend) besitzen soll. Allerdings können Sie beim Anlegen der einzelnen Objekte diese Regel überschreiben und dem Objekt damit eine individuelle Speicherbelegungsregel mitgeben. Das bisher Gesagte soll an dieser Stelle über den Aufbau einer Oracle-Datenbank zunächst einmal reichen. Mehr Informationen über dieses spezielle Thema finden Sie weiter unten in diesem Buch im Kapitel „1.5 Erstellen einer Datenbank“. Ansonsten möchte ich natürlich auch wieder auf die Oracle-Dokumentation verweisen. Hier finden Sie weiterführende Informationen zum Beispiel im Buch
24
Oracle – erste Schritte
„Oracle8“. Folgen Sie dort dem Querverweis „Server Concepts“. Die Informationen finden Sie im Kapitel „Database Structure and Space Management“. In der 8i-Dokumentation finden Sie das Kapitel im Buch „Oracle8i Concepts“, das sich hinter dem Querverweis „Oracle8i Server and Data Warehousing“ verbirgt.
1.2
Installation
Nun soll sich dieses Buch nicht an DBA’s richten, und trotzdem finden Sie so ziemlich am Anfang ein Kapitel über die Installation. Der Grund dafür ist ziemlich einfach: vielleicht haben Sie ja auch als klassischer Anwendungsentwickler oder Berater einmal Lust, ein Oracle-DBMS zu installieren, beispielsweise weil es auch auf Ihrem NT-Notebook prima läuft. Sie haben richtig gelesen. Trotz der komplexen Struktur des Oracle-DBMS funktioniert es auch in primitiven Wohnstuben höchst zuverlässig. Ein gewöhnlicher Rechner mit Windows-NT (Workstation) reicht völlig aus, um darauf eine lauffähige Installation durchzuführen. Vielleicht besteht ja im Rahmen des aktuellen Projekts die Möglichkeit, eine Version auf Ihrem Rechner zu installieren oder – und das gilt natürlich vor allem für alle selbständigen Berufskollegen – Sie kaufen sich gleich eine eigene Version. Die gibt es mittlerweile nämlich in einer Einplatzlizenz für etwa 600_ zu kaufen, wofür Sie einen ca. zehn Zentimeter dicken CD-Stapel erhalten. Im Übrigen gibt es auch immer wieder mal spezielle Partnerprogramme, bei deren Teilnahme Sie ebenfalls die Möglichkeit haben, besonders günstige Test- und Entwicklungslizenzen zu beziehen. Was den letzten Satz angeht, da haben sich die Zeiten wirklich sehr zum positiven geändert. Es ist noch nicht so lange her, da war es für kleine Firmen, Einzelunternehmen oder Freiberufler schon ein bisschen schwieriger, ein Oracle-DBMS zu erwerben. Dabei war das gar nicht mal eine Frage des Preises, sondern eher so wie bei Ferrari. Da soll auch nicht jeder ein Auto kriegen, selbst wenn er es bezahlen kann. Heute ist das anders. Zum einen ist das Produkt in speziellen Konfigurationen sehr günstig und zum anderen bemüht man sich in den letzten Jahren seitens Oracle sehr, das Produkt durch spezielle Angebote auch kleinen Softwarehäusern oder Freiberuflern schmackhaft zu machen. Immer noch geblieben ist der eine oder andere „AHA“-Effekt während oder nach der Installation; frei nach dem Motto „Installation gelungen, Patient tot“. Damit das bei Ihnen nicht passiert, will ich im Folgenden eine kleine Installationshilfe geben. Mit dem Begriff „Installation“ habe ich eigentlich eine Reihe von Aktivitäten gemeint. Zunächst einmal müssen Sie in der Tat die benötigte Software auf Ihren Rechner kopieren. Der nächste Schritt wäre die Erstellung einer Datenbank und zum Schluss müssen Sie auch noch Kontakt zu Ihrer Datenbank herstellen.
Installation
1.2.1
25
Der Installationsvorgang
Die primäre Installation der Software ist natürlich stark abhängig vom konkret eingesetzten Betriebssystem und der eingesetzten Oracle-Version. Im Prinzip geht es natürlich immer um das Gleiche: Sie müssen die benötigten Dateien von der Oracle-Produkt-CD auf Ihren Rechner kopieren. Prinzipiell muss man auch zwischen der Installation des Oracle-Servers und der benötigten Software für die einzelnen Arbeitsstationen unterscheiden. Manchmal liegen diese beiden Produktpakete auch auf verschiedenen CDs vor (z.B. Oracle8 Server für Sun SPARC und Oracle8 Client für Windows 95 und NT). In den folgenden Abschnitten erhalten Sie sowohl Informationen zur Installation einer „normalen“ 8er-Version als auch zum Installationsvorgang der 8i-Variante. Wie schon eingangs erwähnt, soll die Installation auf einem NT-Notebook erfolgen, d.h. Server und Client laufen auf dem gleichen Rechner. Die Installation erfolgt unter NT 4.0 (Workstation) mit Service Pack 5 auf einem DELL Latitude CPINotebook mit Pentium II-Prozessor (366 Mhz) und 256 MB Hauptspeicher. Keine Angst, Sie müssen jetzt nicht sofort aufrüsten. Auf einem Rechner mit nur 64 MB Hauptspeicher und einem 166 Mhz-Prozessor läuft der Oracle-Server auch ordentlich. Selbst mit noch weniger Ressourcen ist eine Installation möglich. Vor ein paar Jahren habe ich mal einen Oracle-Server (Version 7.3) auf einem Notebook mit nur 32 MB Hauptspeicher installiert, allerdings ging der Betrieb des Servers jetzt schon stark zu Lasten der Performance anderer Anwendungen. Das einzigste was einem beim ersten Hinschauen den Atem verschlägt ist der veranlagte Platzbedarf. Die aktuelle 8i-Version beansprucht in der Standardinstallation immerhin ca. 1 GB Plattenplatz (ohne Starter-Datenbank), wohingegen sich die normaler 8er-Version inklusive Starterdatenbank mit ein paar hundert MB freiem Platz installieren lässt. Ebenfalls phänomenal ist der temporäre Platzbedarf des 8i-Installationsprogramms. Bei meinen Installationsversuchen lief das Programm erst dann bis zum Ende durch, nachdem auf meinem Systemlaufwerk ca. 100 MB für Temporärdateien zur Verfügung standen. Unter Windows erfolgt die Installation von Oracle-Produkten mit Hilfe eines speziellen Setup-Programms, dem Oracle-Installer. Dieses Programm wird im Übrigen üblicherweise auch selbst installiert, da Sie mit ihm jederzeit weitere Komponenten nachinstallieren oder auch überflüssige Programmteile entfernen können. Sollte sich der Installer nach dem Einlegen der CD nicht automatisch melden, so starten Sie es einfach selbst, indem Sie das im Hauptverzeichnis der CD befindliche Programm SETUP.EXE ausführen. Das Aussehen und die Funktionsweise des Installers war in den 7er und ersten 8er Versionen nahezu gleich. Erst mit einen der letzten Versionen bekam das Installationsprogramm eine neue Garderobe verpasst, wobei die eigentlichen Funktionen aber immer noch die gleichen sind. Seit einiger Zeit schleicht sich auch in der Oracle-Produktpalette die typische „Look and Feel“ der unter Windows berühmt gewordenen Assistenten ein und auch das Installationsprogramm liegt bei der 8iVersion im Assistentengewand vor.
26
1.2.2
Oracle – erste Schritte
Installation der Version 8
Nach kurzer Ladezeit des Installationsprogramms erhalten Sie eine kleine Dialogbox, mit deren Hilfe Sie für die Installation das Zielverzeichnis, die gewünschte Sprache und einen symbolischen Namen für das Installationsziel auswählen können (vgl. Abb. 1.6).
Abbildung 1.6: Auswahl der Sprachoption für die Oracle-Installation
An dieser Stelle können Sie schon den ersten schmerzvollen Fehler machen. Wer würde an nichts Böses denkend, nicht intuitiv „German“ auswählen, weil das – wenn es schon angeboten wird – irgendwie am naheliegendsten ist. Vor ein paar Jahren auf einem Oracle-Seminar war die Aussage des Trainers zu dieser Option ziemlich eindeutig: „Sie dürfen jede Sprache auswählen, so lange es Englisch ist“. Nach meinem Kenntnisstand gilt dies auch heute noch. Bei der Installation eines Servers sowieso und auch beim Client ist die Sprachoption „English“ meistens die bessere Wahl. Zwar funktioniert der Oracle-Client mit anderen Sprachen einwandfrei, jedoch kann es bei anderen Anwendungen (z.B. PeopleSoft oder ODBC-Datenbankanwendungen) zu seltsamen Anomalien kommen, falls eine andere Sprache gewählt wurde. Dies äußert sich beispielsweise darin, dass Datums- oder Zahlenwerte in der Anwendung nicht korrekt formatiert dargestellt werden. Die hier gewählte Spracheinstellung führt bei Windows-Systemen übrigens zum Registrierungseintragung NLS_LANG mit dem Wert „AMERICAN_AMERICA. WE8ISO8859P1“, den Sie in der Registrierungsdatenbank im Verzeichnis \HKEY_ LOCAL_MACHINE\SOFTWARE\ORACLE finden. Neben der Sprache haben Sie im letzten Dialog das sogenannte Oracle-Homeverzeichnis f (engl. Home-Directory, z.B. E:\ORANT) festgelegt. Die unter diesem Homeverzeichnis liegende Struktur ist nur von der eingesetzten Oracle-Version abhängig, d.h. sie ist ansonsten konstant und allen Systemanwendungen bekannt. Im Übrigen ist es auch möglich, auf einem Rechner mehrere parallele Installationen zu erstellen. In dem Fall existieren üblicherweise auch entsprechend viele Oracle-Homeverzeichnisse. Werden nun auf dem Server irgendwelche Anwendungen gestartet, so holen sich diese zunächst einmal das Homeverzeichnis mit Hilfe einer Umgebungsvariablen oder eines Registrierungseintrags ab, weshalb beispiels-
Installation
27
weise unter Unix in vielen Skripten immer mal wieder die oracle_home-Umgebung exportiert wird. Wenn Sie unter NT mehrere Installationen durchführen und dabei andere Home-Verzeichnisse wählen, dann müssen Sie darauf achten, dass vor dem Start der Anwendungen das richtige Home-Verzeichnis ausgewählt ist, und falls nicht, dann müssen Sie es vor dem Programmstart entsprechend einstellen, wofür Sie nach der Installation in der Oracle-Programmgruppe ein kleines Utility finden. Was folgt, ist eine weitere Dialogbox (vgl. Abb. 1.7) mit einem Hinweis, den Sie ebenfalls durchaus ernst nehmen sollten.
Abbildung 1.7: Eintragung des richtigen Suchpfades für DLLs und Programme
Unter Windows werden Informationen über Speicherorte (Home- und andere Verzeichnisse) in der Windows-Registrierungsdatenbank gespeichert. Hier kann also jede Anwendung Informationen abrufen, die zur Ausführung von bestimmten Programmen oder zum Laden von DLLs gebraucht werden. Allerdings arbeitet nicht (oder noch nicht ?) jedes Programm auf diese Weise. Es gibt noch viele Anwendungen, die zum Laden solcher Bibliotheken Hilfestellung in Form von voreingestellten Suchpfaden benötigen. Wenn Sie den Dialog mit der Yes-Schaltfläche verlassen, dann trägt Oracle den richtigen Suchpfad für Programme und DLLs in die Windows-Systemvariable path ein. Beenden Sie den Dialog mit No sofern Sie sicher sind, dass Sie keinen solchen Pfad-Eintrag brauchen bzw. Sie die notwendige Einstellung später selbst vornehmen wollen. Kleine Ursache – große Wirkung. Das gilt unter Umständen auch für diesen PfadEintrag. Ich habe in meiner Laufbahn schon Trouble-Tickets zu diesem Thema bearbeitet. Die Oracle-Installation war erfolgreich, die Datenbank konnte ohne erkennbare Fehler generiert und geladen werden, der Zugriff auf die Datenbank mit Hilfe von Oracle-Tools (z.B. SQL-Worksheet oder SQL*Plus) funktioniert ebenfalls und auch die Anwendung wurde ohne Fehler installiert. Trotzdem läuft sie nicht, d.h. der Zugriff auf die Datenbank klappt nicht. Manchmal war dieser Pfad-Eintrag die Ursache des Übels. Denken Sie vor allem auch daran: selbst wenn Sie den Client mit Hilfe des Oracle-Installers installieren und dabei den Pfad setzen lassen - in machen Firmen existieren eigenwillige Login-Scripts, die diese Pfad-Angabe beim Anmelden des Benutzers selbst setzen bzw. überschreiben.
28
Oracle – erste Schritte
Zurück zur Installation. Mit Hilfe des nächsten Dialogs müssen Sie die gewünschte Art der Installation auswählen. In meiner vorliegenden Version habe ich hierbei drei verschiedene Varianten zur Auswahl (vgl. Abb. 1.8).
Abbildung 1.8: Was soll installiert werden?
Die voreingestellte Option Oracle8 installiert vorgegebene Komponenten des Servers und des Clients. Die nächste Option Oracle8 Client wird benötigt, wenn beispielsweise der Oracle-Client auf weiteren Arbeitsstationen installiert werden soll und die letzte Option ermöglicht Ihnen die manuelle Auswahl aller gewünschten Komponenten. Da später jederzeit beliebige Komponenten nachinstalliert werden können, ist gegen eine Standardinstallation eigentlich nichts einzuwenden. Also klicke ich auf OK und setze den Installationsvorgang damit fort. Für alle möglichen Installationsvarianten gilt im Übrigen, dass bestimmte ausgewählte Komponenten während des Installationsvorgangs zu spezifischen kleinen Dialogen führen können, in denen dann irgendwelche Einstellungen vorgenommen werden können. Welche Optionen in an dieser Stelle genau angeboten werden, hängt natürlich vom Inhalt der eingelegten CD ab. So existieren beispielsweise auch CDs, auf denen nur der Oracle Client verfügbar ist. In meinem Beispiel, also der Installation des Oracle-Servers auf einem NT-Laptop, werden sowohl die Programme des Servers also auch des Clients in das gemeinsame Oracle-Home-Verzeichnis (z.B. E:\ORANT) installiert. Das wäre bei einem NT-Server im Übrigen zunächst einmal nicht anders, denn auch auf einem Server kann sich ein Anwender theoretisch anmelden und anschließend gewöhnliche Anwendungsprogramme starten. „ Starterdatenbank““ generieren Aufgrund der gewählten Installationsmethode erscheint als Nächstes ein Dialog (Abbildung 1.9), der es mir ermöglicht zusammen mit der Installation auch gleich eine Datenbank anzulegen. Es gibt viele Wege nach Rom – dies ist sicherlich der einfachste, eine Oracle-Datenbank zu erhalten. Im Kapitel „Erstellen einer Datenbank“ werden Sie einen anderen, weitestgehend manuellen, Weg zur Erstellung einer Datenbank kennen lernen.
Installation
29
Abbildung 1.9: „ Starterdatenbank“ installieren
Die Option Typical führt zur Installation einer Datenbank mit dem Namen ORACLE und der Instanzbezeichnung ORCL (=SID). Diese Datenbank ist von ihrem Aufbau bzw. Konfiguration zum Spielen und Ausprobieren (z.B. für die Übungen in diesem Buch) gar nicht so schlecht geeignet und im Übrigen kann man ja im Nachhinein auch alle fehlenden Parameter ergänzen oder vorhanden Einstellungen nach Belieben ändern. Mit der Option None wird nur das Oracle-Softwarepaket installiert. Die mittlere Option ruft nach der Installation der Software den Oracle-Datenbank Assistenten auf. Mit Hilfe dieses Assistenten können Sie die benötigte Datenbank direkt erstellen oder Sie lassen sich die zur späteren Erstellung benötigten Skripte generieren. Im Kapitel „Erstellen einer Datenbank“ werden Sie mehr über dieses Werkzeug erfahren. Auch nach diesem Dialog ist die Installationsfragestunde noch nicht beendet. Der nächste Dialog klärt die Frage, ob die Oracle-Dokumentation auf die Festplatte kopiert oder von der CD aus gestartet werden soll. Eine Kopie beansprucht zur Zeit zwar immerhin ca. 70MB, aber die lohnen sich allemal. Beenden Sie nun auch diesen letzten Dialog, wodurch der Oracle-Installer mit der eigentlichen Installationsarbeit beginnt. In unserem Fall werden hierbei nicht nur die benötigten Programme und Dateien in das vorgegebene Verzeichnis kopiert, sondern während der Installation wird auch unsere erste Datenbank angelegt (Mehr Informationen zum Aufbau der Starterdatenbank finden sie übrigens im Kapitel „3 Starter Database Contents“ im Buch „Oracle8 Getting Started for Windows NT“). Nach ein paar Minuten – das hängt natürlich vom Rechner ab – erscheint dann (hoffentlich) die folgende Meldung (vgl. Abb. 1.10), die uns die erfolgreich abgeschlossene Installation quittiert.
Abbildung 1.10: Das war’s, die Installation wurde erfolgreich beendet
30
Oracle – erste Schritte
Sollten Sie übrigens bei einer Erst- bzw. Vollinstallation Probleme haben, dann ist neben zu wenig Plattenplatz auch immer ein vorhergehender misslungener Installationsversuch bzw. eine nicht vollständige Deinstallation ein möglicher Grund dafür. Etwas weiter unten im Kapitel „Installation entfernen“ finden Sie ein paar Hinweise, was Sie alles kontrollieren sollten, um dies als Grund auszuschließen. Die Installation begutachten Wenn man Oracle zum ersten Mal installiert, dann lohnt es sich schon ein wenig genauer hinzuschauen, was alles Neues im System und auf der Festplatte verteilt wurde. Mit Hilfe der Abbildung 1.11 erhalten Sie einen Überblick, was während der Installation alles auf Ihrem Rechner erstellt wurde.
Abbildung 1.11: Optischer Überblick des Installationsergebnisses
Zunächst einmal liefert die Standardinstallation zwei neue Programmgruppen. Beide enthalten eine Fülle von Werkzeugen, die Sie zum Teil im weiteren Verlauf dieses Buches noch kennen lernen werden. Wie Sie in dem Ausschnitt rechts oben der Abbildung sehen, entsteht in dem vorgegebenen Oracle-Zielverzeichnis eine ziemlich komplexe Verzeichnisstruktur. Ebenfalls umfangreich sind die in der Windows-Registrierung erstellten Einträge. Interessant ist auch der Ausschnitt im mittleren Teil der Abbildung. Dort sehen Sie, dass auch verschiedene neue Dienste erstellt wurden. Besonders wichtig hierbei sind vor allem diejenigen, die zum Betrieb unserer Starterdatenbank benötigt werden (OracleServiceORCL, OracleStartORCL und OracleTNSListener80).
Installation
31
Nachinstallieren Ein weiteren guten Überblick über soeben installierte Komponenten erhalten Sie auch, wenn Sie jetzt einfach mal den ebenfalls installierten Oracle-Installer, den Sie in der Programmgruppe „Oracle for Windows NT“ finden, aufrufen (Abbildung 1.12).
Abbildung 1.12: Hauptfenster des Oracle-Installers
Im Prinzip besteht das Hauptfenster des Installationsprogramms aus zwei Listen. In der rechten Liste finden Sie eine Aufstellung der bereits installierten Komponenten und in der linken haben Sie eine Auswahl der installierbaren Komponenten. Der Rest dürfte eigentlich intuitiv klar sein. Markieren Sie beliebige Einträge in der rechten Liste, um sie anschließend durch Drücken der Schaltfläche Remove zu entfernen. Oder Sie wählen die benötigten Einträge in der linken Liste aus, um sie mit Hilfe der Install-Schaltfläche zu installieren. Alle verfügbaren Komponenten sind in Oracle zu Paketen zusammengefasst, die technisch durch sogenannte „Product Files“ (*.PRD) repräsentiert werden. Mit Hilfe der From-Schaltfläche haben Sie die Möglichkeit diese Produktpakete auszuwählen bzw. zu öffnen.
32
Oracle – erste Schritte
Installation entfernen Wie einzelne Komponenten installiert oder entfernt werden können, haben Sie im vorhergehenden Kapitel gesehen. Eigentlich nicht erwähnenswert ist, dass Sie mit Hilfe des Oracle-Installers die ganze Installation löschen können, indem Sie alle in der rechten Liste angezeigten Komponenten markieren und danach die Schaltfläche Remove betätigen. Allerdings klappt dieser Vorgang nicht immer so reibungslos, wie die vorhergehende Installation. Manchmal ist ein manueller Clean-Up notwendig; eine Kontrolle ist in jedem Falle empfehlenswert. Im Folgenden finden Sie einen möglichen Putzplan, um Ihre Oracle-Installation wieder zu entfernen:
X X X X X X X
Beenden und Deaktivieren Sie mit Hilfe der Systemsteuerung alle eventuell noch vorhandenen Oraclexxxx-Dienste. Löschen Sie alle installierten Komponenten mit Hilfe des Oracle-Installers. Löschen Sie, sofern noch vorhanden, das Oracle-Verzeichnis (z.B. C:\ORANT). Löschen Sie ggf. die noch vorhandenen Programmgruppen. Kontrollieren Sie die Regestrierungsdatenbank. Löschen Sie ggf. den Eintrag ORACLE im Verzeichnis HKEY_LOCAL_MACHINE\SOFTWARE. Löschen Sie diesen Eintrag ebenfalls im Verzeichnis HKEY_CURRENT_USER\SOFTWARE. Auch die Einträge der Dienste müssen aus der Windows-Registrierung verschwunden sein oder ansonsten manuell von Ihnen gelöscht werden. Sie finden diese Einträge im Verzeichnis HKEY_LOCAL_MACHINE\SYSTEM\Services. Löschen Sie hier alle Einträge, die mit „Oracle“ beginnen. Starten Sie Ihren Rechner neu.
Bei Klementine war es nicht nur sauber, sondern immer rein. Bei der Registrierungsdatenbank von Windows schafft auch Ariel das nicht immer, aber sofern Sie die eben genannten Schritte ausführen, dann ist Ihr System so aufgeräumt, dass eine erneute Installation problemlos möglich sein sollte.
1.2.3
Installation von 8i
Die Installation eines 8i-Servers ist zwar nicht grundsätzlich anders, sieht aber zumindest völlig anders aus. Das gilt allerdings nicht nur für die Installation, denn mit dieser Version haben nahezu alle Tools und Werkzeuge neue „Beinkleider“ bekommen. Schon mit dem ersten Bild des Installationsprogramms werden Ihnen verschiedene Möglichkeiten angeboten. Beispielsweise können Sie von hier aus die bereits installierten Produkte betrachten oder eine Deinstallation vorhandener Softwarekomponenten einleiten. Uns interessiert im Augenblick natürlich der Installationsvorgang, weshalb wir mit Hilfe der Weiter-Schaltfläche den nächsten Installationsschritt einleiten. Der zweite Arbeitsschritt des Installationsvorgangs entspricht im Prinzip der Abbildung 1.6 und dient im Wesentlichen wieder zur Festlegung des Installationsziels und zur Vorgabe des Standardverzeichnisses, das wieder mit der Umgebungsvariable oracle_home verknüpft ist. Legen Sie also hier das gewünschte Installationsziel
Installation
33
fest und wechseln Sie wieder mit Hilfe der Weiter-Schaltfläche zum nächsten Arbeitsschritt. Dieser entspricht im Vergleich mit der klassischen Variante der Abbildung 1.8, d.h. Sie haben hier die Wahl, verschiedene Produktkategorien auszuwählen.
Abbildung 1.13: Auswählen der Produktkategorie bei der 8i-Installation
Ich wähle hier die erste Kategorie „Oracle8i Enterprise Edition“, um einen Datenbankserver mit allen benötigten Komponenten zu installieren. Danach geht es mit der Weiter-Schaltfläche zum nächsten Schritt, in dem die Installationsmethode ausgewählt werden muss. Dabei haben Sie wieder die Auswahl zwischen einer Standardmethode, einem etwas kleiner geschnürten Paketes und der individuellen Auswahl der zu installierenden Komponenten. Ich habe diesmal die individuelle Variante gewählt (vgl. Abb. 1.14) und mit Hilfe der dargestellten hierarchischen Liste alle Komponenten ausgewählt, die in etwa einer klassischen 8er-Installation entsprechen. Natürlich können Sie fehlende Komponenten wieder jederzeit nachinstallieren. Falls Sie sich überhaupt nicht sicher sind oder Ihre erste Installation ausführen und genügend Plattenplatz zur Verfügung haben, dann spricht auch nichts dagegen, die Standardinstallationsmethode zu verwenden, so dass auch automatisch wieder eine Starterdatenbank angelegt wird. Bei der manuellen Installationsvariante erhalten Sie im nächsten Arbeitsschritt einen Dialog, in dem abgefragt wird, ob Sie zusammen mit der Installation eine Starterdatenbank generieren möchten. Ich lehne diesmal dankend ab und wechsele zum nächsten und letzten Dialog des Installationsassistenten, in dem Ihnen noch einmal das geplante Installationsszenario (vgl. Abb. 1.15) gezeigt wird.
34
Abbildung 1.14: Manuelle Auswahl der installierbaren Komponenten
Abbildung 1.15: Anzeige der geplanten 8i-Installation
Oracle – erste Schritte
Dienste unter Windows-NT
35
Nach Auswahl der Schaltfläche Installieren beginnt das Programm damit, die benötigten Dateien von der CD zu kopieren. Dabei muss man eindeutig feststellen, dass die Angelegenheit im Vergleich zu früher eindeutig länger dauert. Außerdem muss ich hier noch einmal meinen Schrecken über den benötigten Platzbedarf kundtun, von dem ich mich bis heute noch nicht ganz erholt habe, denn trotz meiner zurückhaltenden Auswahl hat die Installation ca. 700 MB auf meiner Festplatte gefressen. Das wäre Stoff für folgende Schlagzeile „der sich vom ersten Schock leicht erholte Patient verstarb vollends, nachdem er feststellte, dass in dieser Menge noch keine Datenbank und keine Dokumentation enthalten war“.
1.3 Dienste unter Windows-NT X Mit Hilfe des eben beschriebenen Installationsprogramms haben Sie nun Oracle
oder Oracle 8i auf Ihrem Rechner installiert und eventuell auch Ihre erste Oracle-Instanz (SID=orcl) mitsamt zugehöriger Datenbank generiert. Durch die standardmäßige Installation und die Generierung der Starterdatenbank wurden in Ihrem Windows-Betriebssystem eine Reihe von Diensten eingetragen, die den Betrieb der Datenbankinstanz vereinfachen (vgl. Abb. 1.16).
Abbildung 1.16: Wichtige Dienste zum Betrieb der Oracle-Instanz
Von den insgesamt installierten Diensten sind für den Betrieb der Starterdatenbank aber nur folgende drei notwendig, wobei die vom System konkret vergebenen Dienstnamen zum einen von der installierten Oracle-Version und zum anderen von dem von Ihnen vorgegebenen Namen für das Homeverzeichnis abhängt:
X X
OracleServiceORCL Dieser Dienst startet die für die Datenbank benötigten Instanz. Entsprechend den letzten Buchstaben der Dienstbezeichnung wird hierbei die Instanz ORCL (=SID) gestartet. OracleStartORCL Dieser Dienst veranlasst die Instanz zum Öffnen der zugehörigen Datenbank. Einmal gestartet, verrichtet dieser Dienst die beschriebene Aufgabe und „frisst anschließend kein Brot mehr“. Sie können ihn also laufen lassen; Sie könnten
36
X
Oracle – erste Schritte
ihn auch beenden, für die Betriebsbereitschaft der Oracle-Datenbank spielt das keine Rolle. OracleTNSListener80 Wenn Sie das Grundlagen-Kapitel vollständig gelesen haben, dann erinnern Sie sich vielleicht an den schematischen Aufbau eines Client/Server-Systems (Abbildung 1.2). Dieser Dienst repräsentiert genau den auf dem Server beschriebenen Nachrichtendienst. Er muss also laufen, damit eine Kontaktaufnahme mit der Datenbank möglich ist.
Wenn Sie eine Installation auf einem NT-Server durchführen, dann ist es sicherlich sinnvoll alle diese Dienste mit dem Start von NT auch automatisch zu starten, d.h. als „Startart“ für diese drei Dienste wäre „Automatisch“ – so wie in der Abbildung 1.12 - die richtige Einstellung. Auf meinem Notebook habe ich aber keine Lust, dass mir im Hintergrund permanent Ressourcen geklaut werden, obwohl ich gerade gar keine Datenbank brauche, weil ich vielleicht nur einen Brief schreibe. Oder von allen installierten Datenbanken braucht man je nach Aufgabenstellung gerade nur eine spezielle Auswahl. In all diesen Fällen können Sie die nicht benötigten Datenbankinstanzen beenden, indem Sie die zugehörigen Dienste mit Hilfe der Systemsteuerung beenden. Die Reihenfolge, in der Sie die einzelnen Dienste beenden ist dabei gleichgültig. Beachten Sie allerdings, dass der Dienst OracleTNSListener80 laufen muss, wenn Sie mit wenigstens einer Datenbankinstanz arbeiten wollen. Wenn die Nichtbenutzung der Datenbankdienste eher die Regel als die Ausnahme ist, dann sollten Sie die Startart dieser Dienste auf „Manuell“ abändern, damit mit dem Starten Ihres Rechners keine Oracle-Instanzen hochgefahren werden. Bei Bedarf können Sie die benötigten Datenbankdienste jederzeit mit Hilfe der Windows-Systemsteuerung aktivieren; die Reihenfolge in der Sie die einzelnen Dienste aktivieren ist dabei gleichgültig. Wenn Sie beispielsweise zuerst den Dienst OracleStartORCL starten, dann werden Sie feststellen, dass diese Aktion im Hintergrund auch den Start des Dienstes OracleServiceORCL und damit das Hochfahren der zugehörigen Datenbankinstanz bewirkt. Bei so viel Arbeit macht es vielleicht Sinn, sich für dieses Hoch- und Runterfahren einen „Liftboy“ anzuschaffen, der einem die Arbeit abnimmt und nur noch auf die Kommandos „hoch“ bzw. „runter bitte“ hört. Windows-NT bietet Ihnen standardmäßig die Möglichkeit, sich einen solchen Diener zu basteln. Mit Hilfe des NETKommandos können Sie jeglichen installierten Dienst hoch- oder runterfahren. Konkret werden die hier benötigten NET-Befehle in einer Stapelverarbeitungsdatei (ich finde BATCH klingt besser) zusammengefasst und für diese BATCH-Datei erstellen Sie einfach eine Verknüpfung in einer Programmgruppe oder sogar direkt auf Ihrem Desktop. Die Batchdatei zum Starten der Dienst (CD: \SCRIPT\START_ORCL.BAT) könnte etwa folgendermaßen aussehen:
Verbindung zur Datenbank herstellen
rem rem net net net
37
Starten des Listeners, Starten der Oracle-Instanz ORCLÖffnen der Datenbank start OracleTNSListener80 start OracleStartORCL stop OracleStartORCL
Listing 1.1: Starten der Datenbank mit Hilfe von Net-Kommandos
Ganz ähnlich sieht das benötigte Skript zum Beenden der ORCL-Datenbankinstanz (CD: \SCRIPT\STOP_ORCL.BAT) aus: rem net net net
Stoppen des Listeners, Stoppen der Datenbank ORCL stop OracleTNSListener80 stop OracleStartORcL stop OracleServiceORCL
Listing 1.2: Stoppen einer Datenbank mit Hilfe von Net-Kommandos
Auf ähnliche Weise können Sie sich für jede denkbare Kombination von Datenbankdiensten entsprechende Skripte erstellen. Wenn Sie bei deren Ausführung versuchen einen schon laufenden Dienst (z.B. OracleTNSListener80) noch einmal zu starten, dann ist das nicht weiter schlimm; Sie erhalten in dem Fall lediglich eine entsprechende Warnmeldung.
1.4
Verbindung zur Datenbank herstellen
Die Installation einer Oracle-Datenbank läuft dank der mitgelieferten Installationsprogramme zwischenzeitlich unproblematisch und zeitgemäß. Dennoch gibt es im Rahmen der ersten Experimente und Gehversuche in der Regel zwei zunächst unsichtbare Hürden mit erheblichem Frustpotential, die es zu überwinden gilt. Zum einen haben Sie im Rahmen der Installation jetzt eine Datenbank - nebst einer Menge Werkzeuge zur Bearbeitung derselben - auf Ihrem Rechner laufen, jedoch ist die Verfahrensweise zur Kontaktaufnahme noch völlig ungeklärt und ohne weitere Eingriffe auch nicht möglich. Zum anderen hegen Sie in Ihrem inneren vielleicht den Wunsch, selbständig eine eigene Datenbank zu erstellen. Aufgrund der Architektur von Oracle (z.B. eine Instanz pro Datenbank) ist das nicht ganz so einfach wie in manch anderem DBMS-Systemen. Ein Befehl der Form create database reicht hier alleine jedenfalls nicht aus. Falls Sie an einem dieser beiden Probleme schon einmal verzweifelt sind, so kann ich Sie trösten: „Willkommen im Club“, Sie sind nicht der erste, einzigste und sicherlich auch nicht der letzte, der sich bei diesen Aufgaben irgendwann einmal mit scheinbar unüberwindbaren Problemen konfrontiert sah. Doof ist eigentlich nur, dass das Ganze eigentlich ganz einfach ist, aber im Nachhinein ist man ja immer schlauer. In diesem Kapitel will ich Ihnen im Folgenden die Kontaktaufnahme mit Ihrer Datenbank etwas näher bringen und im nächsten Kapitel beschäftigen wir uns dann mit dem Erstellen eigener Datenbanken. Allerdings werde ich Ihnen hierbei nur soviel technische und theoretische Hintergrundinformationen liefern wie unbedingt
38
Oracle – erste Schritte
notwendig ist, damit Sie zum einen wissen worum es überhaupt geht und zum anderen in der Lage sind, Ihre eigene Installation oder die Installation in einem kleinen Netzwerk erfolgreich durchführen zu können. Zum Thema „Oracle Networking“ könnte man mittlerweile ein eigenes umfangreiches Buch erstellen. Ich will daher gar nicht erst den Versuch unternehmen, dieses komplexe Thema im Rahmen des Workshops irgendwie umfassend oder zusammenhängend abzuhandeln.
1.4.1
Einführung in SQL*Net bzw. Net8
Das Zauberwort für die Verbindungsherstellung zu einer Oracle-Datenbank heißt SQL*Net., wobei man entsprechend der aktuellen Version mittlerweile auch nur noch von Net8 spricht. Dieser Begriff hat aber überhaupt nichts mit SQL zu tun und die Entscheidung, ob das Ganze „nett“ ist, überlasse ich getrost Ihnen. Vielmehr handelt es sich hierbei um eine Art „Überprotokoll“, das zwischen der Anwendung und dem eigentlichen Netzwerk gelagert ist (vgl. Abb. 1.17).
Client
Server
Anwendung
Oracle-DBMS
Net8
Net8
Oracle Protokoll-Adapter
Oracle Protokoll-Adapter
Netzwerkprotokoll
Netzwerkprotokoll
Physisches Netzwerk Abbildung 1.17: Schema der Net8-Kommunikation
Will eine Anwendung Verbindung mit einer Datenbank aufnehmen, dann fordert sie diese quasi ganz abstrakt, d.h. ohne Berücksichtigung irgendwelcher physischen Gegebenheiten, über Net8 an. Das Gleiche gilt auch, wenn das Anwendungsprogramm Daten versenden will oder auf den Empfang von Daten vom Server wartet. Das Anwendungsprogramm muss sich in keinem Fall selbst um die durch das Netzwerkprotokoll vorgegebenen Regeln bei der Kommunikation kümmern. Diese Art der Kommunikation bezeichnet Oracle übrigens mit dem Begriff Transparent Network Substrate, was an vielen Stellen einfach mit TNS abgekürzt wird. Die von der Anwendung gestellten Verbindungsanforderungen werden von Net8 mit Hilfe des installierten Oracle-Protokoll-Adapters für das konkret vorhandene Netzwerk übersetzt bzw. an dieses übergeben. Das Verfahren wird sowohl auf dem Client als auf dem Server angewendet und funktioniert in beide Richtungen.
Verbindung zur Datenbank herstellen
39
Für uns Nichtnetzwerker hat Net8 den Nachteil, dass wir selbst bei der Installation einer kleinen Spiel- oder Übungsdatenbank bestimmte Konfigurationseinstellungen durchführen müssen, damit das in Abbildung 1.17 dargestellte „Sandwich“ entsprechend durchlässig ist. Aber hören wir auf zu jammern, die Vorteile dieser Architektur überwiegen mehr als eindeutig:
X
X
X
X
X
Stellen Sie sich einmal vor, jemand erfindet ein neues Netzwerkprotokoll. In dem Fall müssen Sie nur auf die Verfügbarkeit des passenden Oracle-ProtokollAdapters warten. Weder Ihre Anwendung noch der Oracle-Server selbst müssen in irgendeiner Form angepasst werden. Was bei einfachen Strukturen vielleicht ein wenig kopflastig erscheint, zeigt seine wahren Stärken erst bei komplexeren Netzwerken. Stellen wir uns hierzu zwei Datenbanken, eine auf einem NT- und die andere auf einem NOVELL-Server vor. Jetzt haben wir es üblicherweise mit zwei Netzwerkprotokollen (TCP/IP und SPX), d.h. wir installieren einfach diese beiden Oracle-Protokoll-Adapter. Grundsätzlich wird durch Net8 die Verwendung verschiedener Netzwerkprotokolle oder einer Server/Server-Verbindung wesentlich vereinfacht. Die genaue Arbeitsweise der Verbindungsschicht zwischen Anwendung und DBMS wird mit Hilfe von Konfigurationsdateien gesteuert. Bei einem Versionswechsel müssen Sie in der Regel daher lediglich die entsprechenden Programme und Bibliotheken austauschen. Die alten Konfigurationsdateien bilden anschließend zusammen mit der neuen SQL*Net-Version wieder genau das gleiche logische Oracle-Netzwerk wie vorher ab. Dieser Vorteil gilt im Übrigen auch, wenn Sie mit verschiedenen SQL*Net bzw. Net8-Versionen parallel arbeiten müssen. Immerhin gibt es noch reichlich klassische (16-Bit) Windows-Anwendungen und diese benötigen für Ihren Betrieb die Installation des Oracle-Clients für Windows. Gerade der 16-Bit-Client für Windows und der NT-Client laufen auf einem Rechner problemlos nebeneinander, wobei Sie die Net-Konfigurationsdateien für beide Installationen verwenden können. Die Net8-Architektur vereinfacht die Bereitstellung zentraler Hilfsmittel, beispielsweise zur Fehleranalyse oder zur Verschlüsselung der übermittelten Daten. Ein entsprechend benötigter Service wird einfach in der zughörigen Konfigurationsdatei ein- bzw. ausgeschaltet. Bleiben wir mal bei der Verschlüsselung der übermittelten Daten: Wird eine solche Option gewählt, dann gilt sie automatisch für alle eingesetzten Anwendungsprogramme, ohne das in diesen irgendetwas geändert oder angepasst werden muss.
Bei der Verbindungsanforderung von einem Anwendungsprogramm zum OracleDBMS spielt allerdings noch eine weitere Komponente eine Rolle, die Sie bisher zwar schon gesehen haben, deren genaue Funktion bis jetzt aber noch nicht besprochen wurde. Es handelt sich hierbei um den auf dem Server laufenden Listener-Prozess, den Sie bei einer standardmäßigen NT-Installation als Dienst OracleTNSListener80 kennen gelernt haben. Dieser Dienst hört permanent das Netzwerk ab und dient quasi als zentrale Empfangsstation aller eingehenden Verbindungsanfragen.
40
Oracle – erste Schritte
Wie schon gesagt, kann der genaue Name dieses Dienstes variieren, d.h. der standardmäßig vergebene Name hängt von der verwendeten Version und Ihren Installationsvorgaben ab. Die Wörtchen TNS und Listener tauchen aber im Regelfall immer auf, so dass der von mir gemeinte Dienst eigentlich immer zu identifizieren ist. Dadurch wird das eigentliche DBMS von dieser Horchaufgabe entlastet. Stellen Sie sich mal einen Busfahrer (DBMS) vor, der neben seiner Fahrtätigkeit auch noch permanent auf alle Fahrgäste achten muss, ob an der nächsten Haltestelle jemand aussteigen will. Während der Fahrt reden sowieso alle Gäste (Clients) durcheinander und trotzdem müsste der Fahrer permanent auf signifikante Begriffe (z.B. „Stop“) achten. Da ist es doch einfacher, man installiert einen Listener in Form eines roten Stopknöpfchens und wenn ein Client (Fahrgast) diesen drückt, dann erhält der Fahrer (DBMS) diese Aufforderung ganz automatisch (weil es bei ihm vorne klingelt) ohne sich um den Erhalt der Information kümmern zu müssen. Solche Listener können das Netzwerk für mehr als ein DBMS abhören. Für welche sie es konkret tun, das muss ebenfalls im Rahmen der Net8-Konfiguration festgelegt werden. Für mich sind im Rahmen meiner Beratertätigkeit solche Dinge, genau wie Netzwerke, Rechnerarchitekturen u.ä., immer Mittel zum Zweck. In der Regel sie genau wie eine Net8-Installation beim Kunden sowieso von entsprechenden Spezialisten geplant und konfiguriert. Trotzdem ist es natürlich interessant zu wissen, worum es eigentlich geht; schließlich will man ja mitreden können und hierzu haben Sie, so denke ich, jetzt genug Informationen bekommen. Das ganze Spektrum der Net8Konfiguration und Möglichkeiten finden Sie natürlich wieder in der Oracle-Dokumentation, und zwar im Buch „Oracle8 Networking“ bzw. „Oracle8i Server Networking“.
1.4.2
Verbindung zur „ Starterdatenbank““ herstellen
Nun geht es endlich los, wir kümmern uns um die Verbindung zu unserer Starterdatenbank. Standardmäßig befinden sich die Net8-Konfigurationsdateien im \NET80\ADMIN-Verzeichnis bzw. bei der 8i-Version im \NETWORK\ADMIN, dass selbst wiederum unterhalb des Oracle-Homveverzeichnisses (z.B. E:\ORANT) angelegt ist. Beim alten 16-Bit-Client heißt der zugehörige Pfad übrigens auch \NETWORK\ADMIN (also „Back to the Roots“) und befindet sich im entsprechenden Home-Verzeichnis der 16-Bit-Installation (z.B. C:\ORAWIN). Keine Regel ohne Ausnahmen, das gilt natürlich auch hier. Gerade in größeren Unternehmen werden Sie in der Windows-Registrierung öfters den Schlüssel TNS_ADMIN im Verzeichnis \HKEY_LOCAL_MACHINE\SOFTWARE\ORACLE finden. Mit Hilfe dieses Eintrags können Sie den Ort für die Konfigurationsdateien des Net8-Zugriffs festlegen, beispielsweise um die entsprechenden Dateien zentral bereitzustellen. Bedenken Sie: Je nach Softwarearchitektur wird der Oracle-Client ggf. auf jeden Arbeitsplatzrechner installiert. Da sich der Client nicht täglich ändert und wegen den heutzutage möglichen Verfahrensweisen der automatischen Softwareverteilung ist das eigentlich auch nicht weiter dramatisch. Aber schnell
Verbindung zur Datenbank herstellen
41
reagieren kann man auf diese Weise sicherlich nicht. Bei einer solchen Installation müssten aber auch alle Konfigurationsdateien auf die lokalen Arbeitsplatzrechner kopiert werden und wie Sie gleich sehen werden, ändern sich diese, wenn beispielsweise eine neue Datenbank angelegt wird oder sich irgendwelche Servernamen oder Adressen ändern. Die neue Net8-Konfiguration müsste jetzt ebenfalls auf alle Rechner übertragen werden. Da ist es schon sinnvoll, die Steuerungsdateien des Net8-Netzwerks auf einem zentralen Dateiserver bereitzustellen; mit Hilfe von TNS_ADMIN ist das kein Problem. Den „Listener“ konfigurieren Beginnen möchte ich mit der Konfiguration des Listeners. Hierzu finden Sie auf dem Server, ebenfalls im \NET80\ADMIN- bzw. \NETWORK\ADMIN Verzeichnis die Datei LISTENER.ORA. Der Aufbau der Datei ist meisten relativ einfach bzw. sie enthält nur wenige Parameter, weshalb eine Anpassung auch per Hand möglich ist. Im Rahmen der Installation wird übrigens meistens schon eine Beispieldatei mit verschiedenen Einträgen erstellt und in jedem Fall finden Sie auf der Begleit-CD ein Muster unter dem Namen \NET80\LISTENER.ORA. Ähnlich wie eine Windows-Konfigurationsdatei enthält auch die Datei LISTENER.ORA neben Kommentaren verschiedene Einstellungen, die mit Hilfe spezieller Schlüsselwörter in der Form „Schlüssel = Wert“ festgelegt werden. # Ich bin ein Kommentar STARTUP_WAIT_TIME_LISTENER = 0
Die vom Listener abzuhörenden Protokolle und Adressen werden mit Hilfe des Schlüsselwortes LISTENER in Form einer Adress-Parameter-Liste angegeben. Das nachfolgende Beispiel zeigt die Konfiguration für einen Listener, der ebenfalls den Namen „LISTENER“ erhält und auf dem Rechner ray_dell (hier könnte auch eine IPAdresse stehen) die Ports 1521 und 1526 überwacht: LISTENER = (ADDRESS_LIST = (ADDRESS= (PROTOCOL= TCP) (Host= ray_dell) (Port= 1521) ) (ADDRESS= (PROTOCOL= TCP) (Host= ray_dell) (Port= 1526) ) ) Listing 1.3: Konfiguration der Ports für den Listener-Prozess
In unserem Beispiel müssen Sie den Rechnernahmen natürlich gegen den Namen bzw. die Adresse Ihres Datenbankservers austauschen. Wie schon erwähnt, kann so ein Listener für mehrere Datenbanksysteme zuständig sein. Läuft auf dem Rechner mehr als eine Datenbankinstanz, so müssen Sie die zum Listener gehhörenden Instanzen mit Hilfe des Parameters SID_LIST_LISTENER festlegen. Dabei schadet es natürlich nicht, wenn Sie den Parameter auch bei nur einer Datenbankinstanz eintragen:
42
Oracle – erste Schritte
SID_LIST_LISTENER = (SID_LIST = (SID_DESC = (GLOBAL_DBNAME = oracle) (SID_NAME = ORCL) ) (SID_DESC = (GLOBAL_DBNAME = datab01) (SID_NAME = db01) ) ) Listing 1.4: Festlegen der „Horchliste“ für den Listener
Wie Sie an dem Beispiel erkennen können, besteht die eben beschriebene Instanzenaufzählung im Wesentlichen aus der Benennung des Datenbanknamens nebst zugehöriger Instanzbezeichnung (SID). Für die Starterdatenbank war der Datenbankname oracle und die Instanz wurde mit orcl bezeichnet. Später im Kapitel 1.5 werden Sie erfahren, wie diese Begriffe zustande kommen. Außerdem habe ich dem Listener schon einmal angewiesen, den Abhördienst auf für die noch zu erstellende Datenbank (datab01, SID=db01) aufzunehmen. Wenn Sie Änderungen in der Konfigurationsdatei vornehmen, dann werden die erst wirksam, wenn Sie den Listener runter und anschließend wieder hochfahren. Außerdem können Sie mit Hilfe verschiedener Parameter (vgl. nachfolgenden Verweis auf die Dokumentation) die Protokollfunktion des Listeners ein- bzw. ausschalten oder den Namen bzw. den Ort des Protokolls festlegen. Standardmäßig ist die Protokollfunktion eingeschaltet und Sie finden die Ausgabe LISTENER.LOG in dem \NET80\LOG- bzw. \NETWORK\LOG-Verzeichnis. Weitere Informationen über die Parametrierung des Listeners bzw. zu den Einstellungen in der Datei LISTENER.ORA finden Sie wie immer in der Oracle-Dokumentation, vor allem in den Kapiteln des Buches „Net8 Administrator’s Guide“. Eine vollständige Übersicht aller vorhandenen Parameter finden Sie bei der Version 8 dort im Anhang B „Configuration Parameters“, d.h. genauer im dortigen Kapitel „B.4 Listener Parameters (LISTENER.ORA)“. Bei der 8i-Version ist aus diesem „B“ ein „C“ geworden, der eigentlich Titel des Anhangs ist aber gleich geblieben. Listener-Verwaltung Wie schon gesagt, handelt es sich bei dem Listener um einen Hintergrundprozess, der unter Windows-NT mit Hilfe eines entsprechenden Dienstes verwaltet wird. Damit stellt sich die Frage, wie dieser Dienst überhaupt zustande kam, was prinzipiell zu tun ist, wenn Sie einen weiteren Listenerprozess installieren möchten oder wenn aus irgendwelchen Gründen während der Installation kein passender Windows-Dienst generiert wurde. Zu diesem Zwecke existiert ein Programm, das auf den Namen LSNRCTL80.EXE bzw. LSNRCTL.EXE bei 8i hört und eine Art Listener-Konsole repräsentiert. Das Programm können Sie in einem MS-DOS-Fenster starten, wobei sich nach dem Start (vgl. Abb. 1.18) zunächst einmal nur der angezeigte Eingabeprompt ändert.
Verbindung zur Datenbank herstellen
43
Abbildung 1.18: Starten der Listener-Konsole bei einer 8i-Installation
Das Programm bietet Ihnen eine ganze Reihe von Möglichkeiten, wobei Sie vorhandene Kommandos angezeigt bekommen, wenn Sie am Prompt den Befehl help eingeben. Die Bedeutung weiterer wichtiger Kommandos können Sie der Tabelle 1.2 entnehmen. Kommando
Beschreibung
start <listener>
startet den spezifizierten Listener. Da die Konfigurationsdatei für den Listener heißt LISTENER.ORA, und dort wurde durch Eintrag LISTENER= der Name des Listeners festgelegt. Bei mehreren Listener existieren in dieser Datei auch entsprechend viele Einträge (z.B. LISTENER01= usw.). Durch die Eingabe des Befehls „start listener“ können Sie also den in der Datei definierten Listener starten. Hierdurch erhalten Sie beispielsweise auch einen verlorengegangenen Windows-Dienst zurück.
stop <listener>
stoppt den angegebenen Listener-Prozess.
status <listener>
zeigt den aktuellen Status des vorgegebenen Listeners am Bildschirm an.
reload <listener>
aktiviert eine geänderte Listenerkonfiguration.
exit
beendet das Programm.
Tabelle 1.2: Wichtige Kommandos der Listener-Konsole
Den Client konfigurieren Der Net8-Client wird im Minimum mit Hilfe der Dateien SQLNET.ORA und TNSNAMES.ORA konfiguriert. Im Unterschied zur Datei LISTENER.ORA befinden sich diese Dateien meistens nicht nur auf jedem Client, sondern ebenfalls auch auf dem Server, und zwar in beiden Fällen wieder im \NET80\ADMIN- bzw. \NETWORK\ADMIN-Unterverzeichnis des Oracle-Homeverzeichnisses. Sofern ein TNS_ADMIN-Eintrag in der Windows-Registrierung vorliegt befinden sich die Dateien natürlich an dem dort spezifizierten Ort. Ähnliches gilt auch, wenn wei-
44
Oracle – erste Schritte
tere Net8-Clients (z.B. 16-Bit Windows) installiert wurden, d.h. in den Fällen müssen die Konfigurationsdateien zusätzlich in alle \NET80\ADMIN- bzw. \NETWORK\ADMIN-Unterverzeichnisse kopiert werden. Wenn Sie sich nun fragen, warum diese beiden Dateien, deren Funktion zur Zeit noch völlig offen ist, auch auf dem Server kopiert werden sollen, dann will ich Sie in Bezug auf diesen Punkt nicht mehr länger auf die Folter spannen. Innerhalb einer Oracle-Datenbank besteht die Möglichkeit, beispielsweise mit Hilfe einer Abfrage Daten aus einer anderen Datenbank abzurufen. Diese Datenbank könnte mit seinem DBMS auf demselben oder einen völlig anderen Rechner liegen. Wie auch immer: Wenn eine Oracle-Datenbank Informationen aus einer zweiten Datenbank anfordert, dann verhält sie sich aus Sicht dieser zweiten Datenbank wie ein gewöhnlicher Client. Aus dem Grund sollte also auch auf dem Server eine funktionsfähige Client-Konfiguration vorliegen. Grundeinstellungen via SQLNET.ORA Mit Hilfe der Konfigurationsdatei SQLNET.ORA können Sie ein sogenanntes Profil erstellen, mit dem Sie verschiedene Präferenzen für die Nutzung von Net8 vorgeben können. Das lässt den Schluss zu, dass diese Datei im Ausnahmefall sogar fehlen könnte, wenn es überhaupt keine solcher Voreinstellungen gibt. Meistens werden Sie bei der Begutachtung vorhandener Installationen allerdings ein definiertes Profil in Form der Datei SQLNET.ORA finden.
Im Einzelnen können Sie mit Hilfe des Profils
X X X X
Parameter zur Namensgebung, Verschlüsselungseinstellungen, Sicherheitseinstellungen, Trace- und oder Log-Einstellungen
und viele weitere Parameter festlegen. Für kleine Installation können Sie die während der Installation als Muster kopierte Version ohne Änderungen verwenden und selbst bei riesigen Netzwerken gibt es keinen zwingenden Grund für irgendwelche Eingriffe. Beachten Sie allerdings die fett markierte Einstellung des Parameters names.default_domain, denn der Wert taucht ein paar Absätze tiefer bei der Behandlung der Dienst- bzw. Servicenamen wieder auf. Der hier eingestellte Wert sorgt für eine automatische Qualifizierung der definierten Dienstnamen, indem er ihm automatisch zusammen mit einem Punkt angehängt wird. Möchte ein Anwender beispielsweise mit dem Dienst „otto“ arbeiten, dann führt dies aufgrund des abgebildeten Parameters intern zur Anforderung „otto.world“. TRACE_LEVEL_CLIENT = OFF sqlnet.authentication_services = (NTS) names.directory_path = (TNSNAMES, HOSTNAME) names.default_domain = world name.default_zone = world automatic_ipc = off Listing 1.5: Typisches Net8-Profil
Verbindung zur Datenbank herstellen
45
Alle verfügbaren Parameter finden Sie mit einer ausführlichen Beschreibung wieder im „Net8 Administrator’s Guide“ der Oracle-Dokumentation und dort je nach Version im Anhang B oder C. In diesem Buch finden Sie im Übrigen an vielen Stellen Informationen zu den Profilen mit seinen möglichen Einstellungen. Ansonsten kann ich Ihnen auch die Seiten „Oracle Net8 Getting Started for Windows NT/95” empfehlen. Dort finden Sie eine zusammenhängende Beschreibung der Datei SQLNET.ORA im Anhang B „Configuration File Content“ im Kapitel „Understanding the SQLNET.ORA File”. Konfiguration der Servicenamen Alle guten Dinge sind drei, und so kommen wir abschließend zur dritten benötigten Konfigurationsdatei. Immer wenn Sie sich mit Hilfe einer Anwendung, beispielsweise einem der reichlich mitinstallierten Werkzeuge, an eine Oracle-Datenbank anmelden möchten, dann erhalten Sie zunächst einen kleinen Dialog, in dem Sie neben einer Benutzer-Id und einem Passwort auch einen Servicenamen bzw. Dienstnamen eintragen müssen (vgl. Abb. 1.19).
Abbildung 1.19: Abfrage des Servicenamens bei einer Datenbankanmeldung
Der Service- oder auch Dienstname repräsentiert eine DBMS-Instanz nebst Datenbank, die auf irgendeinem Rechner verfügbar ist. Auf dem Client wird die Verbindung zur Datenbank also noch einmal abstrahiert, in dem Sie hier einen nahezu völlig frei wählbaren Servicenamen zur Anmeldung eingeben müssen. So wäre es denkbar, dass Sie sich über den Service DonaldDuck konkret an eine Datenbankinstanz TT23 mit der Datenbank AUSKUNFT auf einem Rechner mit der IP-Adresse 1.33.133.8 anmelden. Diese Verbindung zwischen den Dienstnamen und irgendwelchen auf irgendwelchen Rechnern laufenden Datenbankinstanzen findet in der Datei TNSNAMES.ORA statt. GutenMorgen.world = (DESCRIPTION = (ADDRESS = (PROTOCOL = TCP) (HOST = ray_dell) (PORT = 1521) ) (CONNECT_DATA = (SID = ORCL) ) ) Listing 1.6: Beispiel der Definition eines Net8-Dienstnamens
46
Oracle – erste Schritte
In dem Beispiel wird der Dienstname GutenMorgen definiert und mit der DBMSInstanz orcl auf dem Rechner ray_dell verknüpft. Die Kommunikation erfolgt über TCP/IP mit Hilfe des Ports 1521. Kommt Ihnen hierbei einiges bekannt vor? Ich habe die Zusammenhänge der drei Konfigurationsdateien LISTENER.ORA, SQLNET.ORA und TNSNAMES.ORA in der Abbildung 1.20 noch einmal zusammengefasst. TNSNAMES.ORA GutenMorgen.world = (DESCRIPTION = (ADDRESS = (PROTOCOL = TCP) (HOST = ray_dell) (PORT = 1521) ) (CONNECT_DATA = (SID = ORCL) ) )
names.default_domain = world
SQLNET.ORA
LISTENER.ORA LISTENER = (ADDRESS_LIST = (ADDRESS= (PROTOCOL= TCP) (Host= ray_dell) (Port= 1521) ) ) SID_LIST_LISTENER = (SID_LIST = (SID_DESC = (GLOBAL_DBNAME = oracle) (SID_NAME = ORCL) ) )
Abbildung 1.20: Verbindungen innerhalb der Konfigurationsdateien
Bei den Dienst- oder Servicenamen handelt es sich also wirklich um willkürliche Bezeichnungen für konkret existierende Datenbankinstanzen. Demzufolge können Sie für ein und dieselbe Datenbank auch mehrere verschiedene Dienstnamen in der Datei TNSNAMES.ORA definieren. Ein Beispiel: In der Datei TNSNAMES.ORA sind die Dienstnamen Montag, Dienstag ... Freitag definiert. Entsprechend dem Wochentag meldet sich der Benutzer mit Hilfe des zugehörigen Dienstes in der Datenbank an. Die konkreten Definitionen der Dienste in der Datei TNSNAMES.ORA legen nun fest, mit welcher Datenbank der Anwender wirklich arbeitet; vielleicht sogar immer mit derselben. Verbindung zur „ Starterdatenbank““ herstellen Wenn Sie möchten und mit einem TCP/IP-Netzwerk arbeiten, dann können Sie nun die Verbindung zur „Starterdatenbank“ manuell herstellen. Hierzu finden Sie auf Ihrer CD im \NET80-Unterverzeichnis die drei soeben beschriebenen Konfigurationsdateien. Ansonsten finden Sie in den nächsten beiden Kapiteln Hinweise, wie Sie die Konfiguration mit Hilfe verschiedener Oracle-Werkzeuge vornehmen können. Gehen Sie zur manuellen Installation folgendermaßen vor:
Verbindung zur Datenbank herstellen
X X X
X
X
X X X X
47
Kopieren Sie die drei Dateien in das \NET80\ADMIN- bzw. \NETWORK\ ADMIN-Verzeichnis auf dem Datenbankserver. In der Datei SQLNET.ORA müssen Sie keine Anpassungen vornehmen. Beenden Sie alle laufenden Listenerdienste. Ermitteln Sie den Rechnernamen und ggf. die IP-Adresse des Datenbankservers. Hierzu können Sie auf dem Server in der Netzwerkkonfiguration nachschauen oder auch folgendermaßen vorgehen: –
Öffnen Sie auf dem Server ein MS-DOS-Fenster. Geben Sie dort den Befehl ipconfig /all (verwenden Sie ipconfig /all | more, wenn Sie nicht alles lesen können) ein.
–
Notieren bzw. merken Sie sich den Rechnernamen und die IP-Adresse.
Öffnen Sie auf dem Client ein MS-DOS-Fenster und überprüfen Sie die Netzwerkkonfiguration. Geben Sie den Befehl ping gefolgt von dem eben ermittelten Rechnernamen (z.B. ping ray_dell) ein. Erhalten Sie über den Rechnernamen keinen Kontakt zum Server, dann müssen Sie in den folgenden Arbeitsschritten statt des Namens die zugehörige IP-Adresse verwenden. Editieren Sie die Datei LISTENER.ORA mit Hilfe irgendeines Texteditors (z.B. NOTEPAD.EXE). Die Datei enthält Einträge für die Instanz der Starterdatenbank (orcl) und die noch zu erstellende Datenbank db01. Tauschen Sie in der Datei den Namen ray_dell gegen Ihren Servernamen bzw. die eben ermittelte IP-Adresse aus. Starten Sie die Listener, indem Sie den zugehörigen Dienst wieder aktivieren. Editieren Sie die Datei TNSNAMES.ORA. Vorausschauend auf die noch zu erstellende Datenbank finden Sie auch hier schon zwei Diensteinträge (orcl.world und db01.world). Tauschen Sie jetzt den Namen ray_dell wieder gegen Ihren Servernamen bzw. dessen IP-Adresse aus. Kopieren Sie die Dateien SQLNET.ORA und TNSNAMES.ORA auf einen Client in das entsprechende \NExx\ADMIN-Unterverzeichnis. Testen Sie Ihre Konfiguration. Öffnen Sie hierzu auf dem Client ein MS-DOSFenster Starten Sie das Programm TNSPING80 bzw. TNSPING (bei korrekter Installation sollte ein Pfad-Eintrag auf das \BIN-Unterverzeichnis existieren) gefolgt von dem zu überprüfenden Servicenamen (z.B. TNSPING80 orcl; vgl. Abb. 1.21). Aufgrund unserer Einstellungen in der Datei SQLNET.ORA führt die Eingabe des Dienstnamens orcl intern immer zur Verwendung des Dienstes orcl.world.
48
Oracle – erste Schritte
Abbildung 1.21: Erfolgreicher Ping zu unserer ORCL- Instanz
Verläuft der Test nicht erfolgreich, so überprüfen Sie noch einmal alle durchgeführten Arbeitsschritte. Haben Sie die Dateien an die richtigen Stellen kopiert? Überprüfen Sie Ihre Änderungen auf Tippfehler. Ist der Datenbank- und Listenerdienst aktiv? Ein nicht erfolgreicher Test äußert sich in der Anzeige einer Fehlermeldung, zum Beispiel „TNS-03505: Failed to resolve name“. In der Dokumentation finden Sie unter „Oracle8 Error Messages“ eine Übersicht aller vorhandenen Fehlermeldungen. Neben den Beschreibungen befinden sich hier größtenteils auch brauchbare Kochrezepte zu deren Behebung. Die TNS-Fehlermeldungen stehen dort konkret im Kapitel 25. Natürlich ist es nicht unbedingt notwendig, die Anpassungen an der Net8-Konfiguration manuell durchzuführen. Aber wie Sie an den letzten Beispielen gesehen haben, ist das bei kleinen TCP/IP-Netzwerken durchaus möglich bzw. nicht schwierig. Ansonsten haben Sie die Möglichkeit, zur Anpassung des Net8-Netzwerkes auf verschiedene Werkzeuge zurückzugreifen, die im Rahmen der Client-Installation auf Ihren Rechner kopiert werden. In den nächsten zwei Kapiteln möchte ich die Verwendung dieser beiden Werkzeuge kurz darstellen.
1.4.3
Konfiguration mit Hilfe von Net8 Easy Config
Ein Hilfsmittel zur Konfiguration des Net8-Netzwerkes heißt Net8 Easy Config (Version 8: N8SW.EXE) und befindet sich üblicherweise in der Programmgruppe „Oracle for Windows NT“. Anwendung bei einer 8er-Version Mit Hilfe dieses Programms können Sie auf besonders einfache Weise die Liste der Dienstnamen bearbeiten, d.h. hiermit verändern Sie lediglich die Datei TNSNAMES.ORA, die anderen beiden Konfigurationsdateien (SQLNET.ORA bzw. LISTENER.ORA) müssen Sie bei Bedarf immer noch manuell anpassen.
Verbindung zur Datenbank herstellen
49
Mit der Version 8i haben sich die Programmnamen dieser Werkzeuge alle geändert bzw. sind gar nicht mehr als eigenständiges Programm vorhanden. Das liegt ganz einfach daran, dass die Assistenten und Hilfsmittel als JavaApplikation geliefert werden und somit besondere Klassen zur Ausführung des benötigten Programms existieren. Damit ist das Programm zum einen eigentlich nur geeignet, einen neuen Datenbankdienst schnell auf einem Arbeitsplatzrechner verfügbar zu machen. Zum anderen hilft das Programm natürlich auch, Tip- oder Formatfehler zu vermeiden, die sich beim manuellen Bearbeiten der Datei TNSNAMES.ORA sicherlich leicht einschleichen können. Im Rahmen der Installation erhalten Sie in Ihrem \NETxx\ADMIN-Unterverzeichnis eine TNSNAMES-Musterdatei mit verschiedenen Dienstnamenseinträgen. Mit Hilfe des Net&Easy-Programms, so will ich dieses Tool fortan mal nennen, wollen wir diese Datei nun so anpassen, dass wir Zugriff auf die Starterdatenbank erhalten. Hierzu müssen Sie zunächst die Listener-Konfiguration wie im vorherigen Kapitel beschrieben manuell anpassen. Anschließend müssen Sie den Listener und die ORCL-Datenbankinstanz hochfahren. Nach dem Start des Programms (vgl. Abb. 1.22) erhalten Sie einen Dialog, mit dem Sie die Liste der Dienstnamen entsprechend Ihren Anforderungen verändern können. Rechts unten im Bild finden Sie eine Liste aller aktuell definierten Dienstnamen.
Abbildung 1.22: Bearbeiten der TNSNAMES.ORA mit Hilfe von Net8 Easy Config
Im mittleren Teil des Fensters können Sie die durchzuführende Aktion auswählen. Zunächst wollen wir alle nicht benötigten Dienste aus der Konfiguration entfernen. Also müssen wir die Aktion Delete auswählen und können anschließend den zu löschenden Dienst in der Liste markieren. Weiter geht es mit der Next-Schaltfläche. Jetzt müssen Sie den Löschvorgang nochmals bestätigen und danach erhalten Sie einen weiteren Dialog (vgl. Abb. 1.23).
50
Oracle – erste Schritte
Abbildung 1.23: Änderung speichern, verwerfen oder weitere Anpassungen durchführen
An dieser Stelle können Sie Ihre Änderungen speichern, indem Sie das Programm mit Hilfe der Schaltfläche Finish beenden. Möchten Sie so wie wir allerdings noch weitere Änderungen durchführen, dann können Sie über die Previous-Schaltfläche wieder in das Ausgangsbild (Abb. 1.22) zurückkehren. Löschen Sie auf diese Weise alle Einträge bis auf den Dienst mit dem Namen „TcpExample“. Den verbleibenden Dienst „TcpExample“ wollen wir entsprechend unseren Bedürfnissen anpassen. Wählen Sie hierzu die Aktion Modify und markieren Sie den Dienst anschließend in der Liste. Danach gelangen Sie nach Betätigen der NextSchaltfläche zu weiteren Dialogen zum Bearbeiten der verschiedenen Diensteinstellungen. Im ersten Seite dieser Dialogfolge müssen Sie das zu verwendende Netzwerkprotokoll auswählen. Danach gelangen Sie wieder mit Hilfe der Next-Schaltfläche zur nächsten Seite, deren Aussehen sich natürlich in Abhängigkeit des gewählten Netzwerkprotokolls unterscheidet. Wenn Sie mit TCP/IP arbeiten, dann müssen Sie hier (vgl. Abb. 1.24) den Namen bzw. die Adresse des Datenbankservers und den zu verwendenden Port eintragen. Die nächste Seite (Next-Schaltfläche drücken) Dient zur Festlegung der zugehörigen Datenbankinstanz, d.h. Sie müssen hier die entsprechend SID-Bezeichnung eingeben (vgl. Abb. 1.25). Mit Hilfe der nächsten und letzten Seite können Sie Ihren neuen Diensteintrag testen. Im Unterschied zum Test via TNSPING80 versucht das Programm Sie jetzt allerdings mit Hilfe eines vorhandenen Benutzers an der Datenbank anzumelden, d.h. Sie werden beim Test aufgefordert einen gültigen Benutzernamen nebst Passwort einzugeben. Wenn Sie den Test für die ORCL-Instanz durchführen möchten, dann können Sie sich als Benutzer „system“ (Passwort „manager“) anmelden.
Verbindung zur Datenbank herstellen
51
Abbildung 1.24: Festlegen des Datenbankservers und der Port-Nummer
Abbildung 1.25: Verknüpfen des Dienstnamens mit der Datenbankinstanz
Wie Sie jetzt sicherlich schon gemerkt haben, ist die Änderung des eigentlichen Dienstnamens mit Hilfe des Programms nicht möglich. Den Dienstnamen müssen Sie entweder manuell in der Datei TNSNAMES.ORA ändern oder Sie müssen den alten Dienst löschen und anschließend mit neuen Namen wieder anlegen. Die Anlage eines neuen Dienstnamens entspricht übrigens weitestgehend der soeben beim Ändern beschriebenen Vorgehensweise. Neu ist, dass Sie nach Auswahl der Aktion Add New Service zunächst den neuen Dienstnamen mit Hilfe des entsprechenden Eingabefeldes festlegen müssen.
52
Oracle – erste Schritte
Besonderheiten der 8i-Version In Bezug auf die Dienstnamen beinhaltet das Programm auch in der 8i-Version die gleichen Funktionalitäten, sieht in einigen Details nur ein wenig anders aus. Allerdings besteht dennoch ein wesentlicher Unterschied zur Vorgängerversion, denn jetzt können Sie mit diesem Assistenten (vgl. Abb. 1.26) auch den Listener und wichtige Profileinstellungen konfigurieren, d.h. dieser Assistent ist nun geeignet, kleinere Netze vollständig zu administrieren.
Abbildung 1.26: Startbild des Net8-Assistenten in der 8i-Version
Im Startbild des Assistenten können Sie auswählen, welchen Dienst des Net8-Protokolls Sie konfigurieren möchten. Die dritte Auswahl führt zur Konfiguration der Dienstnamen und entspricht weitestgehend der Verfahrensweise der vorhing gezeigten 8er-Version. Mit Hilfe der zweiten Auswahlmöglichkeit können Sie Profileinstellungen vornehmen und die erste Auswahl ermöglicht die Konfiguration des Listeners, wobei ich die Arbeitsweise des Assistenten an diesem Beispiel kurz demonstrieren möchte. Mit Hilfe der Weiter-Schaltfläche gelangen Sie auf die zweite Seite, in der Sie wählen können, ob Sie einen neuen Dienst erstellen oder einen vorhandenen Dienst bearbeiten, löschen oder umbenennen möchten. Sofern Sie die Musterdatei LISTENER. ORA in das \NETWORK\ADMIN-Verzeichnis kopiert haben, können Sie sich hier einmal für das Bearbeiten des Dienstes entscheiden, und sich anschließend die Darstellung der verschiedenen Einstellungen einmal anschauen. In dieser Datei ist lediglich ein Listener definiert, der genau wie dieser Prozess nämlich „LISTENER“ heißt, so dass Sie in der auf der dritten Seite angezeigten Auswahlbox auch nur diesen Namen wählen können. Auf der nun folgenden Seite können Sie die gewünschten Netzwerkprotokolle auswählen (vgl. Abb. 1.27) und in den darauffolgenden Seiten müssen Sie je nach ausgewähltem Protokoll weitere Einstellungen, beispielsweise die Port-Nummer bei TCP, vornehmen.
Verbindung zur Datenbank herstellen
53
Abbildung 1.27: Auswahl der Protokolle für die Listenerkonfiguration
1.4.4
Oracle-Networking mit Hilfe des Net8 Assistant
Bis einschließlich der Version 7 des Oracle-Servers gab es bisher auch bei NTArbeitsplatzrechnern immer einen Grund, sich neben dem NT-Client auch den alten 16-Bit Windows-Client zu installieren, da im Rahmen dieser Installation ein Werkzeug, der Oracle Network Manager, zur komfortablen Konfiguration des SQL*Nets installiert wurde. Mit Hilfe des Oracle Network Managers konnte man nicht nur die Dienstnamen verwalten, sondern das Programm konnte weiterhin alle benötigten Parameterdateien erzeugen. Heute ist die Installation des Windows-Clients zumindest aus diesem Grund nicht mehr notwendig, denn mittlerweile wird im Rahmen des NT-Clients das Programm Oracle Net8 Assistant installiert, mit dem ebenfalls alle benötigten Konfigurationsdateien erstellt werden können, wobei hierbei vor allem auch alle neuen Features des Net8-Netzwerkes unterstützt werden. Standardmäßig finden Sie den Oracle Net8 Assistant in der Programmgruppe „Oracle for Windows NT“. Nach dem Start des Programms erhalten Sie links im Bild (vgl. Abb. 1.28) eine Übersicht aller vorhandenen Konfigurationseinstellungen. Wenn Sie mit der Maus auf einen dieser dargestellten Konfigurationsparameter klicken, dann erhalten Sie im rechten Arbeitsfenster einen hierzu passenden Dialog. Die Abbildung 1.28 zeigt den Dialog den Sie erhalten, wenn Sie in der Liste die Konfiguration „Profile“ markieren. Dieses Programm ist bei den Versionen 8 bzw. 8i im Übrigen nahezu funktionskompatibel und sieht in der 8i-Varianten wie Sie der Abbildung 1.29 entnehmen können nur anders aus. Aus diesem Grund ist die Behandlung dieses Werkzeugs diesmal zusammengefasst, und nur die Abbildungen kommen zum Teil doppelt vor.
54
Abbildung 1.28: Profilerstellung mit Hilfe des Oracle Net8 Assistant
Abbildung 1.29: Oracle Net8 Assistant in der 8i-Version
Oracle – erste Schritte
Verbindung zur Datenbank herstellen
55
Wie Sie schon wissen, entspricht das Profile den Einstellungen aus der Datei SQLNET.ORA. Als Symbol wird kein Ordner angezeigt, da es immer nur ein Profil gibt. Die anderen Konfigurationen können mehrfach auftreten (z.B. mehrere Service- bzw. Dienstnamen oder mehrere Listener), weshalb Sie hierfür entsprechende Ordnereinträge finden. Zur grundsätzlichen Bedienung des Programms möchte ich an dieser Stelle auch nicht viele weitere Worte verlieren. Ich denke, dass man das Programm weitestgehend intuitiv bedienen kann. Ein weiterer Unterschied zu dem im vorherigen Kapitel beschriebenen Net&EasyProgramm ist auch, dass Sie mit Hilfe des Oracle Net8 Assistant Konfigurationsdateien in jedem erreichbaren Verzeichnis bearbeiten können. Standardmäßig öffnet das Programm die im Verzeichnis \NETxx\ADMIN gespeicherten Konfigurationen, allerdings können Sie mit Hilfe der File- bzw. Datei-Menüeintrage das aktuelle Verzeichnis wechseln oder geladene Konfigurationen in einem anderen Verzeichnis speichern bzw. von dort laden. Im weiteren Verlauf dieses Kapitels soll nun zum wiederholten Male eine Verbindung zu unserer Starterdatenbank hergestellt werden, wobei diesmal logischerweise der Oracle Net8 Assistant verwendet wird. Beginnen wir mit dem Profil, also mit den Einstellungen in der Datei SQLNET.ORA. Obwohl wir hier nichts ändern wollen bzw. müssen, können Sie sich die verschiedenen Profileinstellungen ja einmal anschauen, indem Sie mit der Maus auf den entsprechenden Eintrag in der Liste klicken (vgl. Abb. 1.29 oder Abb. 1.28). Standardmäßig zeigt der Net8-Assistent für das Profil den Naming-Dialog bzw. die Namensgebung an, der insgesamt drei Registerkärtchen enthält. Mit Hilfe des oberhalb dargestellten Kombinationsfeldes können Sie weitere Konfigurationsdialoge für das Profil einblenden. Anpassen müssen Sie natürlich wieder die Dienstnamen. Klicken Sie hierzu zunächst auf das zugehörige Ordnersymbol in der Liste, so dass anschließend alle vordefinierten Servicenamen angezeigt werden (vgl. Abb. 1.30 und Abb.1.31). Ähnlich der Vorgehensweise bei der Konfiguration mit Hilfe von Net&Easy können Sie zunächst alle nicht benötigten Dienstnamen löschen. Markieren Sie hierzu Sie den entsprechenden Dienst in der Liste und löschen Sie Ihn entweder mit Hilfe der roten X-Schaltfläche oder Sie verwenden den entsprechenden Eintrag aus dem Edit- bzw. Bearbeiten-Menü. Das Anlegen eines neuen Dienstes geht genauso schnell. Mit Hilfe des Edit- bzw. Bearbeiten-Menüs oder durch Klicken auf der grünen +-Schaltfläche können Sie einen neuen Dienstnamen erstellen. Anschließend erhalten Sie die schon bekannten Seiten des Net&Easy Assistenten zum Erstellen neuer Dienstseiten. Für Anwender der 8er-Version ist eigentlich nur die Möglichkeit zur Konfiguration der Listenersteuerung wirklich neu. Klicken Sie auf das Ordnersymbol des Listeners und fügen Sie mit Hilfe der entsprechenden Schaltfläche oder des zugehörigen Filebzw. Bearbeiten-Menüeintrags eine neue Listenerkonfiguration in die Liste ein. Zunächst erhalten Sie einen Dialog, in dem Sie den Namen des Listeners eintragen müssen. Standardmäßig heißt der erste und oftmals einzige Horchdienst schlicht und einfach „LISTENER“ (vgl. Abb. 1.32).
56
Oracle – erste Schritte
Abbildung 1.30: Bearbeiten der Dienstnamen mit dem Net8-Assistenten
Abbildung 1.31: Bearbeiten der Dienstnamen mit dem Net8-Assistenten unter 8i
Verbindung zur Datenbank herstellen
57
Abbildung 1.32: Eingabe des neuen Listenernamens
Wenn Sie den Dialog mit der OK-Schaltfläche beenden, dann erscheint der neue Eintrag in der Liste und Sie haben die Möglichkeit, die verschiedenen Konfigurationseinstellungen vorzunehmen. Wählen Sie mit Hilfe des Kombinationsfeldes zunächst den Dialog „Listening Locations“ bzw. „Listener Adressen“ (vgl. Abb. 1.33 und Abb. 1.34) aus. Anschließend erhalten Sie einen Dialog, mit dem Sie die abzuhörenden Protokolle nebst weiteren protokollabhängigen Einstellungen vornehmen können. Die hier spezifizierten Protokolle und Einstellungen finden Sie später in der Konfigurationsfdatei als Adress-Parameter-Liste hinter dem Schlüsselwort LISTENER. Mit Hilfe der Schaltfläche Add Address können Sie weitere Protokolle hinzufügen, wobei Sie für jedes neue Protokoll ein weiteres Registerkärtchen erhalten. Bei unserer manuellen Konfiguration hatten wir den Listener an zwei Ports lauschen lassen. Für diesen zweiten Port müssen Sie ebenfalls ein zweites Registerkärtchen anlegen, indem Sie den zweiten Port 1526 eintragen.
Abbildung 1.33: Abhördienste des neuen Listeners konfigurieren
58
Oracle – erste Schritte
Abbildung 1.34: Konfiguration der Listener-Adressen beim 8i-Assistenten
Neben den Protokollen müssen Sie auch noch festlegen, für welche Datenbanken der Listener das Netzwerk abhorchen soll. Wählen Sie hierzu in dem oberen Kombinationsfeld den Eintrag „Database Services“ bzw. „Datenbankdienste“, wodurch Sie den zugehörigen Konfigurationsschirm erhalten (vgl. Abb. 1.35 bzw. Abb. 1.36).
Verbindung zur Datenbank herstellen
Abbildung 1.35: Eintragen der Datenbanken, für die das Netz abgehorcht werden
Abbildung 1.36: Einstellung der Datenbankdienste beim 8i-Assistenten
59
60
Oracle – erste Schritte
Tragen Sie in dem Bild den System-Identifer (SID) der Datenbankintanz (z.B. orcl) und den Datenbanknamen (z.B. oracle) ein. Erstellen Sie anschließend mit Hilfe der Schaltfläche Add Database bzw. Datenbank hinzufügen eine zweite Registerkarte für die noch zu erstellende Datenbank (SID = db01, Name = datab01). Zum Schluss müssen Sie Ihre Konfiguration noch mit Hilfe des File- bzw. DateiMenüs speichern, wodurch die Konfigurationsdateien in dem gewählten Verzeichnis erstellt bzw. aktualisiert werden.
1.4.5
Ausblick
Mit Hilfe der letzten Kapitel haben Sie (hoffentlich) gesehen, dass es zumindest auf den zweiten Blick gar nicht so schwierig ist, eine Datenbankverbindung mit Hilfe von Net8 zu konfigurieren. Ich denke, dass wenn man die Zusammenhänge in etwa kennt, dann ist es mit Hilfe der Assistenten gar nicht so schwierig, die Konfiguration auf die benötigten Belage zuzuschneiden. In diesem letzten Kapitel zu dem Thema Oracle-Networking möchte ich nun noch einige weitere Konfigurationsmöglichkeiten, die Ihnen die aktuelle Version von Net8 bietet, kurz ansprechen und damit diesen Themenkomplex abrunden. Verbindung via Host-Naming Hierbei handelt es sich um ein neues Verfahren, das allerdings ausschließlich für TCP/IP-Netzwerke verfügbar ist. Konkret bietet das Verfahren die Möglichkeit, die Verbindung zur Datenbank über das installierte Verfahren zur Namensauflösung herzustellen, indem als Servicename der Name des Datenbankrechners verwendet wird. Hierbei ist es gleichgültig, ob die Namensauflösung im IP-Netzwerk mit Hilfe irgendwelcher Namensserver oder mit Hilfe simpler Hostnamendateien durchgeführt wird. Wie schon gesagt, muss bei dieser Methode kein spezieller Dienstname definiert werden, d.h. die Erstellung bzw. Anpassung der Datei TNSNAMES.ORA ist nicht notwendig. Allerdings muss das Verfahren im verwendeten Profil aktiviert sein, indem der Parameter names.directory_path den Wert HOSTNAME erhält: names.directory_path = (TNSNAMES, HOSTNAME)
Weiterhin benötigten Sie in der Konfiguration des Listeners eine entsprechende Einstellung, die den Datenbanknamen mit der zugehörigen Datenbankinstanz verbindet, was allerdings, wie Sie schon wissen, quasi „Listener-Standard“ ist: (SID_DESC = (GLOBAL_DBNAME = oracle) (SID_NAME = ORCL))
Was jetzt noch fehlt, ist ein Hostname des Datenbankservers, der dem Datenbanknamen entspricht. Auch wenn Sie Ihren Server nicht „ORACLE“ taufen möchten, so ist dieser benötigte Name zumindestens als sogenannter Aliasname schnell erstellt, indem Sie einen entsprechenden Eintrag in Ihrem Netzwerknamensdienst vornehmen. Sollten Sie keinen speziellen Namensdienst haben, dann suchen Sie einfach mal nach der Datei HOSTS, denn diese Datei stellt einen Minimalnamensdienst zur Verfügung. Tragen Sie in der Datei die IP-Adresse des Datenbankservers
Verbindung zur Datenbank herstellen
61
gefolgt von dem zugehörigen Datenbanknamen ein und schon können Sie sich über den Hostnamen an die Datenbank anmelden. Das Verfahren funktioniert übrigens auch, wenn auf einem Datenbankserver mehrere Instanzen bzw. Datenbanken aktiv sind. In dem Fall müssen Sie einfach für alle Datenbanken entsprechende Aliasnamen vergeben. Oracle Names Hierbei handelt es sich im Prinzip um eine Erweiterung des eben beschriebenen einfachen Verfahrens des Host-Naming. Konkret wird bei dieser Methode ein zentraler Dienst im Netzwerk bereitgestellt, der die Dienstnamen den entsprechenden Rechneradressen, wodurch sich der Client mit der Datenbank verbinden kann. Ein wichtige Funktionalität dieser Methode ist beispielsweise die Möglichkeit der dynamischen Registrierung von Servern während des laufenden Betriebes. Die bei dieser Methode notwendige Konfiguration können Sie mit Hilfe des Oracle Net8 Assistant durchführen. Genaueres zu der Methode und deren Konfiguration finden Sie in der Dokumentation im Buch „Net8 Administrator’s Guide“. Oracle Connection Manager Dieses Feature wurde ebenfalls neu mit der Version 8 bereitgestellt und bietet im Wesentlichen folgende Funktionalitäten:
X
X
Verbindungsbündelung Der Connection Manager ermöglicht die logische Zusammenfassung der Verbindungen mehrerer Client-Sitzungen, die mit demselben Server kommunizieren. In dem Fall kommuniziert der Server nur noch mit diesem zentralen Knoten, der die einzelnen Anforderungen sammelt bzw. die erhalten Ergebnisse verteilt. Hierdurch können die benötigten Netzwerkressourcen reduziert bzw. die Performance gesteigert werden. Zugriffskontrolle Durch die Definition verschiedener Regeln besteht die Möglichkeit zu definieren, unter welchen Bedingungen ein Client auf einen Server bzw. eine Datenbank zugreifen darf. Wie Sie im Verlauf dieses Kapitels sicherlich schon gemerkt haben, reicht normalerweise die Kenntnis der IP-Adresse des Datenbankservers aus, um in der Lage zu sein, zumindest eine Verbindung zum DBMS herzustellen. Zwar benötigt man immer noch eine Benutzer-ID und ein Passwort, aber die Verbindung ist schon einmal hergestellt. Der Connection-Manager kann hier eine Art Firewall-Funktion wahrnehmen, indem er verhindert, dass nicht explizit zugelassene Clients eine Verbindung aufnehmen können.
62
1.5
Oracle – erste Schritte
Erstellen einer Datenbank
In diesem Kapitel möchte ich Ihnen zeigen, wie man in Oracle eigene Datenbanken erstellen kann. Allein an der Tatsache, dass es für dieses Vorgang ein eigenes Kapitel gibt, können Sie vielleicht erahnen, dass dieser Prozess im Vergleich zu anderen Datenbank-Managementsystemen ein wenig aufwendiger ist. Es gibt Datenbanksysteme (z.B. SYBASE oder MS SQL-Server), da handelt es sich bei der Anweisung zur Anlage einer Datenbank um einen Befehl wie jeden anderen auch, d.h. man baut eine Verbindung zum DBMS auf und gibt anschließend einen Befehl der Art create database mit irgendwelchen Parametern ein. Eine solche Vorgehensweise funktioniert in der Regel beim einem DBMS, das nicht nur für eine, sondern für alle auf dem Rechner laufenden Datenbank zuständig ist. Nun haben Sie in den einführenden Kapiteln aber schon gesehen, dass sich Oracle gerade in dieser Architektur von dem einen oder anderen Konkurrenzprodukt unterscheidet. Für jede Datenbank existiert auch ein eigenes DBMS, also steht vor der Anlage der Datenbank die Erzeugung einer neuer DBMS-Instanz auf dem Aufgabenzettel. Konkret gibt es für die Anlage einer DBMS-Instanz und der Erstellung der Datenbank wie immer mehrere verschiedene Verfahrensweisen; die Bandbreite der möglichen Wege reicht je nach verwendetem Betriebssystem von „do it yourself“ bis hin zur völlig automatischen Datenbankgenerierung. Dabei werde ich das vollautomatische Verfahren, das beispielsweise unter Windows-NT möglich ist, nur kurz beschreiben. Das manuelle Verfahren ist wesentlich interessanter. Zum einen ist es leicht auf alle verfügbaren Betriebsplattformen übertragbar, bietet viele nützliche Informationen über Oracle und entspricht zum anderen der zur Zeit gängigen Verfahrenspraxis. Zumindest ist mir bisher noch kein Anwender begegnet, bei dem Datenbanksysteme von irgendwelchen Programmen abstrakt und vollautomatisch nach manueller Parametereingabe generiert wurden. Stattdessen wurden zunächst alle zur Generierung benötigten Skripte und Parameterdateien erstellt, mit deren Hilfe die eigentliche Datenbankerstellung in einem zweiten Schritt erfolgte. Diese vielleicht altmodisch wirkende Vorgehensweise hat jedoch den Vorteil, dass die Datenbankerstellung (z.B. auf einem anderen Rechner) jederzeit leicht wiederholbar ist. Im Übrigen ist das manuelle Verfahren in der aktuellen Oracle-Version gar nicht mehr so ganz manuell, denn die benötigte Skripte können mit Hilfe eines Assistenten erstellt werden. Für welches Verfahren Sie sich auch entscheiden: in jedem Fall benötigen Sie bestimmte Informationen über die Struktur einer Oracle-Datenbank, d.h. an dem folgenden Kapitel führt so oder so kein Weg vorbei.
1.5.1
Struktur einer Oracle-Datenbank
Wie Sie wissen, wird in Oracle eine Datenbank als logische Einheit einer DBMSInstanz und den eigentlichen Datenbankdateien gebildet. Diese Instanz vereint alle für den Betrieb der Datenbank benötigten Prozesse nebst Hauptspeicherbereiche und holt seine Konfiguration, z.B. die Größe der SGA oder den Namen der Datenbank beim Starten aus einer speziellen Parameterdatei (vgl. Abb. 1.37) mit
Erstellen einer Datenbank
63
dem Namen INITSID.ORA, wobei Sie den Platzhalter SID durch den konkreten System-Identifer des DBMS ersetzen müssen. Wenn Sie die Starterdatenbank (SID=orcl) installiert haben, dann finden Sie in Ihrem \DATABASE-Unterverzeichnis beispielsweise die zugehörige Konfigurationsdatei INITORCL.ORA. Die Datenbank selbst besteht aus mindestens einer physikalischen Datei, in der die zu speichernden Daten mit Hilfe von Tabellen oder andere Datenbankobjekte abgelegt werden. Wie Sie schon wissen, liegt zwischen der physikalischen Datei und der Tabelle das logische Tablespace-Konstrukt und somit folgt aus dem bisher Gesagten, dass jede Datenbank zumindest auch einen Tablespace besitzen muss. Dieser trägt den Namen SYSTEM und wird beim Anlegen der Datenbank automatisch erzeugt. Zusätzlich benötigt die Datenbank mindestens zwei Protokolldateien. Oftmals werden Sie in dem Zusammenhang die Bezeichnungen „redo log“ oder „redo log Dateien“ finden. Diese Log-Dateien werden benötigt, um die Datenbank beispielsweise nach einem Systemzusammenbruch (Server wird ausgeschaltet) wiederherzustellen. Konkret werden in den redo log Dateien alle Änderungen mit Hilfe des LGWR-Prozesses (vgl. Kapitel 1.1.2) protokolliert. Genauer betrachtet werden in diesen Dateien alle fertiggestellten Änderungstransaktionen in der Form von Low Level-Änderungsoperationen festgehalten. Erst wenn die Änderungen im redo log protokolliert wurden, wird eine Transaktion als endgültig erledigt markiert und das Zurückschreiben der geänderten Daten aus der SGA in die Datendatei erfolgt vielleicht sogar noch etwas später. Wird die Datenbank nach einem versehentlichen Ausschalten oder einem Stromausfall wieder hochgefahren, dann wird die Datenkonsistenz mit Hilfe dieser Log-Dateien wieder hergestellt. Der Prozess des Hochfahrens kann somit schon einmal eine Weile dauern, wenn das notwendige Recovery der Daten entsprechend aufwendig ist. Während des Datenbankbetriebs wachsen die einzelnen Log-Dateien allerdings nicht ins Unendliche, sondern sie werden zyklisch genutzt und somit in regelmäßigen Abständen immer wieder überschrieben. Damit schützen die Log-Dateien zunächst auch nur vor einem Systemcrash, beispielsweise als Folge unbezahlter Stromrechnungen. Bei anderen Fehlern, beispielsweise dem Datenbankausfall wegen defekter Festplatten, müssen Sie zunächst auf die letzte Datensicherung zurückgreifen. Hätte man nun noch alle Log-Einträge, die seit der letzten Vollsicherung erstellt wurden, dann könnte man sich einen Wiederherstellungsprozess bis zu jedem beliebigen nachfolgenden Zeitpunkt vorstellen, denn wie gesagt, die LogDateien repräsentieren jede fertiggestellte Transaktion durch die zugehörigen Änderungsdaten. Damit dies nicht nur ein Wunsch bleibt, besteht die Möglichkeit, die einzelnen Log-Dateien vor dem Überschreiben zu kopieren. Hierzu müssen Sie für Ihre Datenbankinstanz den ARCH-Prozess (engl. Archiver) aktivieren, der für die Archivierung voller Log-Dateien zuständig ist. So, eigentlich wollte ich nur anmerken, dass Sie bei einer Oracle-Datenbank mindestens immer zwei Log-Dateien finden, aber Sie sehen ja selbst, wie schnell man bei diesem komplexen aber auch interessanten Thema vom Wege abkommt. Wenn Sie weitergehende Informationen zu diesem Themenkomplex suchen, empfehle
64
Oracle – erste Schritte
ich Ihnen neben den originalen Handbüchern ein Buch, das sich hauptsächlich mit Themen aus dem Alltag eines Datenbankadministrators beschäftigt. Dort sollten weiterführende Dinge wie Konzepte zur Sicherung und Wiederherstellung von Datenbanken, Online-Sicherungen oder den 24 Stundenbetrieb einer Datenbank hinreichend behandelt werden. Zurück zu unserer Datenbankstruktur. Neben der Konfigurationsdatei für das DBMS, der eigentlichen Datendatei und den beiden Log-Dateien besitzt jede Oracle-Datenbank noch mindestens eine sogenannte Kontrolldatei (engl.. Control File). Diese Kontrolldatei stellt die Achillesverse Ihres Datenbanksystems dar, denn in ihr werden Informationen über die zur Datenbank gehörenden Dateien gespeichert. Ist diese Kontrolldatei zerstört bzw. weg, dann kann die Datenbank, wenn überhaupt, nur noch mit viel Glück wiederhergestellt werden. Das ist auch der Grund, warum Oracle grundsätzlich die Anlage von mehreren (wenigstens zwei) Kontrolldateien empfiehlt, die möglichst auch auf unterschiedlichen Festplatten gespeichert werden sollen. Mehr zum Umgang mit diesen Kontrolldateien finden Sie in der Oracle-Dokumentation, beispielsweise im Buch „Server Administrator’s Guide” im Kapitel „Managing Control Files”.
DBMS SID = DB01
TABLESPACE SYSTEM
Konfguration (INITDB01.ORA)
ROLLBACK SYSTEM
Datei (datafile)
Log-Datei (redo log)
Kontrolldatei (controlfile)
Abbildung 1.37: Physische Struktur einer Oracle-Datenbank
Wie Sie in der Abbildung 1.37 erkennen können, wird in dem Datenfile neben dem SYSTEM-Tablespace auch noch ein sogenanntes Rollback-Segment, ebenfalls mit dem Namen SYSTEM, angelegt. Rollback-Segmente Jede Datenbank enthält mindestens ein sogenanntes Rollback-Segment. In einem solchen Rollback-Segment werden die während einer Transaktion geänderten Daten protokolliert. Diese Protokolle werden für das Zurücksetzen von Transaktion, dem Recovery beim Öffnen der Datenbank oder auch für die Bereitstellung lesekonsistenter Abfragen benötigt. Oracle zeigt geänderte Daten erst nach dem
Erstellen einer Datenbank
65
Beenden und Freigeben (commit) einer Transaktion. Während der Transaktion erhalten andere Abfragen die Daten in dem Zustand vor der Änderung, d.h. die Werte werden bei Bedarf mit Hilfe der Rollback-Segmente geliefert. Ohne Rollback-Segment wäre eine Oracle-Datenbank eine reine Leseveranstaltung, deshalb wird im Rahmen der Datenbankanlage automatisch ein Rollback-Segment mit dem Namen SYSTEM im gleichnamigen Tablespace angelegt. Das Rollback-Segment muß seinerseits aus mindestens zwei Erweiterungen (Extends) bestehen, die im Rahmen der Transaktionen sequentiell beschrieben bzw. im Rahmen neuer Transaktionen immer wieder überschrieben werden. Die Größe des Rollback-Segments muss hierbei so dimensioniert werden, dass die Änderungsdaten der größten benötigten Transaktion dort hineinpassen, wobei es prinzipiell möglich ist, die Änderungen einer Transaktion gezielt einem speziellen (besonders großen) Rollback-Segment zuzuordnen. Standardmäßig werden die freien Rollback-Segemente automatisch zugeordnet, so dass vor allem im Mehrbenutzerbetrieb viele Transaktionen um die Gunst bzw. den Platz der Rollback-Segmente ringen. Die Segmente selbst ringen ggf. konkurrierend mit anderen Objekten um Platz im Tablespace. Aus diesen Gründen empfiehlt Oracle zum einen die Anlage eigener Rollback-Segmente, möglichst ein einem Tablespace, der auf einer eigenen Datei basiert. Nachdem Sie nun die minimale physische Struktur einer Oracle-Datenbank kennen, können wir im nächsten Abschnitt damit beginnen, endlich unsere eigene Datenbank anzulegen. Hierbei werden sie die eben kennen gelernten Dateien erstellen oder deren Namen nebst Zugriffspfad festlegen müssen.
1.5.2
Das manuelle Verfahren
In Oracle erfolgt die Anlage einer neuen Datenbank im Prinzip in folgenden Schritten:
X
X X X
Erstellen der Konfigurationsdatei INITxxxx.ORA entsprechend dem gewünschten System Identifer. Erstellen der neuen Datenbankinstanz. Anlegen der neuen Datenbank. Abspielen verschiedener Standardskripte, die in der neuen (noch leeren) Datenbank verschiedene Systemobjekte anlegen.
Nach Durchführung dieser einzelnen Arbeitsschritte sind Sie im Besitz einer neuen Datenbank. Anschließend sind in der Regel noch weitere Aktivitäten, z.B. die Erweiterung des Net8-Netzwerkes oder die Anlage weiterer Tablespaces oder Datenbankdateien, notwendig, was aber genau genommen mit der eigentlichen Datenbankerzeugung gar nichts zu tun hat. Im Folgenden wollen wir die Datenbankinstanz „db01“ und die zugehörige Datenbank „datenb01“ erstellen. Alle hierzu benötigten Dateien und Skripte finden Sie auf der Begleit-CD im \DB01-Unterverzeichnis.
66
Oracle – erste Schritte
Erstellen der Konfigurationsdatei Da es sich bei dieser Konfigurationsdatei um eine gewöhnliche Textdatei handelt, kann sie folglich mit jedem beliebigen Texteditor erstellt werden. Selten werden Sie dabei in der Praxis die Arbeit mit einer leeren Datei beginnen, sondern die neue Konfiguration entsteht aus einer Kopie einer schon vorhandenen Datenbank. Selbst wenn Sie gerade die erste Datenbank erstellen, dann finden Sie in Ihrem \DATABASE-Unterverzeichnis die Datei INITORCL.ORA, so dass Sie die Konfigurationsdatei der Starterdatenbank als Muster verwenden können. Diese Musterdatei besteht hauptsächlich aus Kommentaren, denn neben den wirklichen Anmerkungen in der Datei sind auch die meisten Parameter mit Hilfe des Gatterzeichens (#) auskommentiert. Trotzdem erhalten Sie hierdurch eine Vorstellung über die wichtigsten Konfigurationsparameter, deren genaue Bedeutung Sie in der Oracle-Dokumentation finden. Folgen Sie im Buch „Server Reference“ bzw. „Oracle8i Reference“ den Eintrag „1 Initialization Parameters” und dort weiter zum Abschnitt „Parameter Description“. Hier finden Sie eine alphabetische Liste aller vorhandenen Konfigurationsparameter zusammen mit der zugehörigen Beschreibung. Im „Server Administrator’s Guide“ finden Sie im Abschnitt „Chapter 2 Creating an Oracle Database“ im Kapitel „Parameters“ ebenfalls eine Beschreibung der wichtigsten Konfigurationsmöglichkeiten. Falls Sie die Musterdatei INITORCL.ORA nicht besitzen, so finden Sie die Datei übrigens auf der Begleit-CD im \ORCL-Unterverzeichnis. In unserem Beispiel hat die Konfigurationsdatei den Namen INITDB01.ORA und lediglich folgenden Inhalt: db_name=datenb01 db_files = 100 control_files=(E:\ORANT\DATABASE\db01\ctl1db01.ora,E:\ORANT\DATABASE\db01\ctl2db01.ora) remote_login_passwordfile = shared #rollback_segments = (RB0, RB1) Listing 1.7: Minimum einer Instanz-Konfiguration
X
X
db_name Mit Hilfe dieses Parameters bestimmen Sie den maximal achtstelligen Namen der Datenbank. Gültige Zeichen sind vor allem Buchstaben, Ziffern und der Unterstrich (_). Zwischen Groß- und Kleinschreibung wird nicht unterschieden. Der hier spezifizierte Name muss auch beim späteren Anlegen der Datenbank verwendet werden. db_files Maximale Anzahl der möglichen Datenbankdateien. Sowohl der Standard- als auch der Maximalwert für dieses Parameter ist abhängig vom vorhandenen Betriebssystem. Wir wählen den Wert von 100 (das sollte erst einmal reichen) und müssen diesen Wert später beim Anlegen der Datenbank noch einmal vorgeben.
Erstellen einer Datenbank
X
X
X
67
control_files Legen Sie hier den Pfad und Dateinamen der Kontrolldatei(en) fest. Wenn Sie, wie empfohlen, wenigstens zwei oder mehr Kontrolldateien verwenden, dann müssen Sie die einzelnen Dateinamen durch Komma trennen und das Ganze wie in meinem Beispiel in Klammern setzen. Die genaue Spezifizierung der Dateinamen hängt natürlich vom jeweiligen Betriebssystem ab. remote_login_passwordfile = shared Dieser Parameter legt fest, ob zur Überprüfung der Anmeldeberechtigung eines Datenbankadministrators eine spezielle Passwortdatei angelegt werden soll. Die Passwortdatei wird standardmäßig im \DATABASE-Unterverzeichnis mit dem Namen PWDSID.ORA erstellt, wobei SID wieder ein Platzhalter der entsprechenden Instanz darstellt. Mehr zum Thema der Identifizierung von Administratoren finden Sie in den Oracle-Unterlagen im Buch „Server Administrator's Guide”, beispielsweise im Kapitel „The Oracle Database Administrator“. rollback_segments Mit dem Parameter legen Sie die zu verwendenden Rollback-Segmente fest. Dies ist wichtig, wenn Sie neben dem SYSTEM-Tablespace weitere Bereiche definieren und verwenden möchten. Da diese Rollback-Segmente beim Anlegen der Datenbank jedoch noch nicht definiert sind, ist es wichtig den Parameter zunächst einmal auszukommentieren. Schalten Sie ihn erst ein, nachdem die gesamte Datenbank erstellt wurde. Damit er aktiv wird, müssen Sie die Instanz runter, und anschließend wieder hochfahren.
Warum hat diese Konfiguration im Vergleich zur Starterdatenbank oder im Vergleich zu real existierenden Versionen so wenige Parameter? Zum einen stellen Sie beim Lesen der Dokumentation fest, dass für die allermeisten Parameter ein fester oder betriebssystemabhängiger Standardwert existiert, der beim Starten der Datenbankinstanz verwendet wird, sofern in der Konfigurationsdatei nichts anderes spezifiziert wurde. Zum anderen finden Sie in der Musterdatei INITORCL.ORA die wirklich interessanten und wichtigsten Parameter, deren einzelne Bedeutung Sie in einer ruhigen Stunde durchaus mal nachschlagen sollten, sofern Sie zukünftig häufiger Datenbanken erstellen müssen. Auf der anderen Seite möchte ich hier natürlich aufzeigen, worauf es wirklich ankommt und das erkennt man meistens am besten, wenn mal alles Überflüssige bzw. Unnötige einfach mal weglässt. Erstellen der neuen Datenbankinstanz Nachdem wir nun unsere neue Konfigurationsdatei für das System DB01 besitzen, können wir die zugehörige Instanz auf unserem Rechner starten. Wie das genau geht, hängt allerdings stark vom jeweiligen Betriebssystem ab. Für Windows NT erhalten Sie mit der Installation das Programm ORADIM80.EXE (Instanzen-Manager), mit dem eine neue Datenbankinstanz erstellen bzw. starten können. Das Programm können Sie zum Beispiel in einem MS-DOS-Fenster zusammen mit allen benötigten Parametern starten:
68
Oracle – erste Schritte
ORADIM80 -NEW -SID SID [-INTPWD INTERNAL_PWD] [-SRVC SRVCNAME] [-STARTMODE AUTO, MANUAL][-PFILE FILENAME]
Unter 8i heißt das Programm einfach nur ORADIM.EXE, und wird ansonsten allerdings völlig identisch verwendet.
X X
X
X
X
SID System Identifer der neuen Instanz. Entsprechend dem hinteren Teil der Konfigurationsdatei müssen Sie in unserem Beispiel „db01“ als SID vorgeben. INTERNAL_PWD Passwort für die Administrator-ID „internal“. Während der Anlage der neuen Datenbankinstanz wird hierfür eine Passwortdatei angelegt. Diese Datei befindet sich im \DATABASE-Unterverzeichnis und hat den Namen PWDSID.ORA, wobei SID wieder durch den konkreten System Identifer ersetzt wird. SRVCNAME Wie Sie schon wissen, besteht unter Windows-NT die Möglichkeit, die Datenbankinstanz mit Hilfe eines entsprechenden Diensteintrags zu administrieren. Mit Hilfe dieses Parameters können Sie den Namen dieses Dienstes vorgeben. Standardmäßig heißt der neue Dienst „OracleServiceSID“, wobei SID wieder für den System Identifer (z.B. DB01) der neuen Instanz steht. AUTO, MANUAL Legt die Startart des neuen Dienstes fest. Auf einem echten Datenbankserver wäre „AUTO“ wahrscheinlich die richtige Wahl. Auf meinem NT-Notebook gefällt mir „MANUAL“ besser. Außerdem werden wir im weiteren Verlauf wieder eine Batchdatei zum Starten der Datenbank erstellen. Beachten Sie aber, dass der Instanzen-Manager den Service OracleStartSID zum Starten der Datenbank nur dann einrichtet, wenn Sie den Wert „AUTO“ verwenden. FILENAME Pfad und Name der Konfigurationsdatei INITSID.ORA. Standardmäßig erwartet das Programm die Konfigurationsdatei im \DATABASE-Unterverzeichnis unter der eben beschriebenen Namenskonvention, so dass Sie den Parameter in dem Fall auch weglassen können.
In meinem Beispiel soll die Datenbank db01 angelegt werden. Die Konfigurationsdatei INITDB01.ORA befindet sich im Verzeichnis E:\ORANT\DATABASE\DB01, so dass die neue Instanz folgendermaßen erstellt werden kann: oradim80 -new -sid db01 -intpwd oracle -startmode auto -pfile E:\ORANT\database\db01\initdb01.ora
Das Programm arbeitet stillschweigend, legt den neuen Dienst und die Passwortdatei an. Eventuelle Fehler oder Erfolgsmeldungen müssen Sie der zugehörigen LogDatei ORADIMxx.LOG entnehmen, die Sie standardmäßig im \RDBMSxx-Unterverzeichnis finden. Sofern beim Programmaufruf und in der Konfigurationsdatei
Erstellen einer Datenbank
69
keine Tippfehler vorliegen, dann sollten Sie in der Log-Datei lediglich eine Erfolgsmeldung über die Anlage der neuen Instanz vorfinden. Nach dem Einrichten des neuen Dienstes muss dieser, sofern nicht automatisch geschehen, auch gleich gestartet werden. Dies könnten Sie mit Hilfe der Systemsteuerung durchführen, aber da wir gerade im MS-DOS-Fenster sind, erledigen wir es durch einen erneuten Aufruf des Instanzen-Managers mit folgenden Parametern: oradim80 -startup -sid db01 -starttype srvc,inst -usrpwd oracle -pfile E:\ORANT\database\db01\initdb01.ora
Wie gesagt, normalerweise ist das nicht notwendig, da der Dienst wegen des von uns vergebenen Startmodus nach der Anlage auch sofort gestartet wird. Ansonsten haben die diesmal verwendeten Parameter folgende Bedeutung: Mit Hilfe des Parameters starttype legen Sie fest, ob das Programm den Service (srvc), die Datenbankinstanz (inst) oder beides starten soll. Beim Betrachten dieses Programmaufrufs schleicht sich der Verdacht ein, dass man Oracle-Datenbank auf einem NT-Rechner auch ohne Diensteinträge administrieren kann. Dem ist in der Tat so und wir werden uns das im weiteren Verlauf auch noch anschauen. Die Dienste sind unter NT nur ein Hilfsmittel, den Job des Rauf- und Runterfahrens von Instanz und Datenbank zu vereinfachen. Mehr Informationen über ORADIM80 finden Sie im Buch „Oracle8 Getting Started for Windows NT“ im Kapitel „4 Database Tools“ und die 8i-Anwender können die Informationen im Buch „Oracle8i Administrator's Guide for Windows NT“ nachschlagen. Anlegen der neuen Datenbank Die Anlage der neuen Datenbank erfolgt mit Hilfe spezieller Befehle, die über die zugehörige Instanz abgesetzt werden. Zum Absetzen solcher (SQL-) Befehle erhalten Sie im Rahmen der Installation verschiedene Werkzeuge, sogenannte SQL-Editoren wie beispielsweise SQL*Plus oder das SQL-Worksheet. Da wir zur Zeit aber lediglich ein Halbfertigprodukt besitzen, benötigen wir einen besonders systemnahen SQL-Editor, mit dem wir uns an die noch ohne Datenbank laufende Instanz anmelden können. Einen solches Werkzeug finden Sie unter Windows NT für Oracle 8 im Programm SVRMGR30.EXE. Dieser sogenannte Server Manager ist in der Lage, sich auch ohne Datenbank mit der Datenbankinstanz zu verbinden. Für andere Betriebssysteme existieren ähnliche Programme, beispielsweise SQLDBA unter AIX. Auch dieses Programm hat in der 8i-Version einen anderen Namen und heißt diesmal nur SVRMGRL.EXE. Diese Programme haben noch eine weitere gemeinsame Eigenschaft, denn Sie ermöglichen die Verbindung mit dem DBMS ohne spezielle Konfiguration des Oracle-Netzwerkes Net8, weshalb das jeweilige Programm auf dem Datenbankser-
70
Oracle – erste Schritte
ver gestartet werden muss. Die Vorgabe, mit welcher Datenbankinstanz sich der Server-Manager beim Start verbinden soll, erfolgt mit Hilfe einer Umgebungsvariablen bzw. bei NT mit Hilfe eines Eintrags in der Registrierungsdatenbank. Im Verzeichnis \HKEY_LOCAL_MACHINE\SOFTWARE\ORACLE finden Sie den Eintrag ORACLE_SID. Der zugehörige Wert beschreibt den System Identifer, an dem sich der Server-Manager anzumelden versucht. Wenn Sie den Wert temporär ändern möchten, müssen Sie jedoch nicht jedes Mal den Registrierungseintrag ändern, sondern Sie können ihn mit Hilfe des SET-Kommandos überschreiben. SET ORACLE_SID=db01
Im Übrigen entspricht auch dies der prinzipiellen Vorgehensweise für andere Betriebssysteme. Alles, was Sie unter NT in der Registrierung finden oder per SETKommando setzen können, müssen Sie unter UNIX entsprechend exportieren (z.B. EXPORT ORACLE_SID=db01). Starten Sie nun den Server-Manager auf Ihrem Datenbankserver, indem Sie zunächst ein MS-DOS-Fenster öffnen, danach die Umgebungsvariable ORACLE_SID entsprechend setzen und dann das Programm SVRMGR30.EXE bzw. SRVMRGL.EXE aufrufen (vgl. Abb. 1.38).
Abbildung 1.38: Starten des Server-Managers zum Anlegen der DB01-Datenbank
Auch nach dem Start des Server-Managers erinnert das Programm immer noch an ein MS-DOS-Fenster; lediglich der Eingabeprompt hat sich von C:\> auf SVRMGR> geändert. Da wir später noch wesentlich komfortablere SQL-Eingabewerkzeuge kennen lernen werden, möchte mit der Bedienung des Server-Managers nicht viel Zeit verbringen. Am Eingabeprompt können Sie die benötigen Befehle eintippen. Sofern Sie viel Platz brauchen und der konkrete Befehl dies zulässt, können Sie die Zeile mit Hilfe der Eingabetaste wechseln. In dem Fall müssen Sie die gesamte Befehlskette mit einem Semikolon (;) abschließen. Es gibt aber auch Befehle, die sind sofort scharf, d.h. nach dem Drücken der Eingabetaste erhalten Sie hier keine zweite Zeile, sondern der Server-Manager beginnt sofort mit der Verarbeitung Ihrer Eingabe. Am besten verwenden Sie am Eingabeprompt des Server-Managers lediglich folgende zwei Befehle:
Erstellen einer Datenbank
X X
71
Exit Beendet den Server-Manager, d.h. Sie kehren zum MS-DOS-Eingabefenster zurück. @ Schreiben Sie hinter dem Klammeraffen den Pfad und Namen eines Skripts, das die einzelnen auszuführenden Befehle enthält. Dieses Skript können Sie mit jedem beliebigen Texteditor erstellen, so dass Sie die benötigten Befehle wenigstens entsprechend komfortabel erfassen bzw. bearbeiten können. Noch besser ist vielleicht sogar, Sie verwenden den Server-Manager überhaupt nicht interaktiv, sondern starten ihn immer zusammen mit einem Befehlsskript, dessen Namen Sie als Kommandozeilenparameter übergeben: SVRMGR30 @%oracle_home%\database\db01\credb01_1.sql
Zum Anlegen unserer neuen Datenbank können Sie das folgende Skript verwenden, das Sie auch auf der Begleit-CD im \DB01-Verzeichnis unter dem Namen CREDB01_1.SQL finden: # Erstellung der Datenbank DB01 spool c:\temp\credb01_1.log set echo on connect INTERNAL/oracle startup nomount pfile=E:\ORANT\database\db01\initdb01.ora create database datenb01 CONTROLFILE REUSE LOGFILE 'E:\ORANT\database\db01\logdb011.ora' SIZE 200K, 'E:\ORANT\database\db01\logdb012.ora' SIZE 200K MAXLOGFILES 32 MAXLOGMEMBERS 2 MAXLOGHISTORY 1 DATAFILE 'E:\ORANT\database\db01\Sys1db01.ora' SIZE 50M MAXDATAFILES 254 MAXINSTANCES 1 CHARACTER SET WE8ISO8859P1 NATIONAL CHARACTER SET WE8ISO8859P1; spool off Listing 1.8: Beispiel eines Skripts zum Anlegen einer Datenbank
Zunächst wird in dem Skript mit Hilfe des connect-Kommandos eine Verbindung zur Datenbankinstanz hergestellt. Danach wird die Instanz durch Verwendung des startup-Befehls gestartet bzw. initialisiert. Der Parameter nomount sorgt dafür, dass die Datenbank hierbei nicht geöffnet wird und der pfile-Parameter verweist wieder auf unsere INITDB01.ORA-Datei.
72
Oracle – erste Schritte
Wesentlichster Bestandteile der eben abgebildeten Befehlsfolge ist sicherlich die Anweisung create database, mit dem unsere neue Datenbank datenb01 angelegt wird. Entsprechend der Übersicht in Abbildung 1.37 legen wir hierbei das erste Datenfile (SYS1DB01.ORA) und die beiden Logdateien (LOGDB011.ORA bzw. LOGDB012.ORA) an. Ferner wird automatisch der Tablespace SYSTEM und das gleichnamige Rollback-Segment angelegt. Abspielen verschiedener Standardskripte Nun haben wir endlich eine neue Datenbank, allerdings eine im Miniaturformat. Damit meine ich nicht die anfängliche Größe des Datenfiles, sondern in der Datenbank fehlen eine Menge von Systemobjekten, die teilweise auch für die Arbeit mit dem ein oder anderen Werkzeug notwendig sind. Außerdem sollte eine Datenbank mindestens einen weiteren Tablespace für die Anlage und Speicherung von Benutzerdaten und weitere Rollback-Segmente (ebenfalls mit eigenem Tablespace) haben. Zumindest für die erste der beiden Aufgaben, dem Anlegen der benötigten Systemobjekte, liefert Oracle zwei Standardskripte, die Sie im \RDBMSxx-Unterverzeichnis finden. Es handelt sich hierbei um die Dateien CATALOG.SQL und CATPROC.SQL. In der ersten Datei werden mit Hilfe entsprechender SQL-Kommandos eine Reihe von Views und anderen Objekten angelegt. Die zweite Datei ist scheinbar kürzer, allerdings werden von hier weitere unzählige Skripts gestartet. Natürlich könnten Sie nun mit Hilfe des Server-Managers diese beiden Skripte abspielen und anschließend auch mit Hilfe entsprechender SQL-Befehle weitere Datenfiles, Tablespaces und Rollback-Segmente anlegen. Wir halten aber einen Moment inne, denn es gibt eine einfache Möglichkeit, ein fertiges Kochrezept für die notwendige Nachbearbeitung zu erhalten.
1.5.3
Der Oracle Database Assistant
Wie Sie ja schon gemerkt haben, tauchen mit der Version 8 auch bei Oracle mehr und mehr Assistenten für die Erledigung bestimmter Aufgaben auf. Das gilt im Übrigen auch bei der Erstellung einer neuen Datenbank, denn auch hierfür bietet Ihnen das System seine Unterstützung in Form eines Assistenten an. Üblicherweise finden Sie für diesen Assistenten ein entsprechendes Symbol in Ihrer Programmgruppe „Oracle for Windows NT“. Nun muss ich allerdings einschränken, dass in dem Fall das Wörtchen „üblicherweise“ voraussetzt, dass Sie den Assistenten bei der Installation von Oracle ausgewählt haben; falls nicht, dann empfiehlt es sich, das in jedem Fall nachzuholen. Unter 8i gibt es den „Database Configuration Assistant“ natürlich auch, wobei die beiden Programme mal wieder nahezu identisch sind bzw. sich lediglich in Bezug auf die Oberfläche unterscheiden.
Erstellen einer Datenbank
73
Mit Hilfe des „Oracle Database Assistant“ können Sie eigene Oracle-Datenbanken erstellen. Hierbei haben Sie die Wahl, die Datenbank abstrakt im Hintergrund erstellen zu lassen, oder Sie erstellen mit Hilfe des Assistenten lediglich die zur Generierung benötigten Skripte. Wie Sie dem ersten Programmfenster (vbl. Abb. 1.39 bzw. Abb.1.40) übrigens entnehmen können, eignet sich der Assistent auch zum Löschen vorhandener Datenbanken, wobei er dabei auch in der Windows-Systemumgebung aufräumt und beispielsweise die zugehörigen Dienste löscht.
Abbildung 1.39: Der Oracle Database Assistant
Abbildung 1.40: Datenbank-Konfigurationsassistent unter 8i
74
Oracle – erste Schritte
Da wir noch immer dabei sind, unsere neue Datenbank zu vervollständigen, geht es mit der ersten Option „Create a database“ bzw. „Erstellen einer Datenbank“ weiter zur zweiten Seite des Assistenten. Dort haben Sie die Wahl zwischen einer Standardgenerierung und einer benutzerdefinierten Variante (Custom). Beachten Sie, dass nur diese zweite Variante die Möglichkeit bietet, die benötigten Befehle in einem Skript zu speichern, weshalb wir nach Auswahl der Option „Custom“ bzw. „Benutzerdefiniert“ auf die dritte Assistentenseite wechseln. Mit Hilfe dieser dritten Seite können Sie die Installation verschiedener Zusatzpakete veranlassen, allerdings wird die Auswahl nur angeboten, wenn Sie die zugehörigen Features im Rahmen der Softwareinstallation ausgewählt haben. Beim 8i-Assistenten wählen wir hier die Option „Mehrzweck“ und danach geht es in jedem Fall gleich weiter zur vierten Seite (vgl. Abb. 1.41 bzw. Abb. 1.42), in der Sie endlich mal wieder etwas eingeben müssen.
Abbildung 1.41: Festlegen verschiedener Datenbankparameter
Ich hatte noch nicht verraten, dass auch die benötigten Konfigurationsdatei (INITSID.ORA) durch den Assistenten erstellt wird. Mit Hilfe der drei Optionen Small, Medium bzw. Large legen Sie die Größe des Shared Pools (vgl. Kap. 1.1.2) mit Hilfe des shared_pool_size-Eintrags und weiterer Parameter fest. Oracle empfiehlt verschiedene Einstellungen in Abhängigkeit der gleichzeitig arbeitenden Benutzer. Für bis zu 10 Anwender wird „klein“ (Small), zwischen 11 und 100 „mittel“ (Medium) und ab 101 Benutzer die Einstellung „groß“ (Large) empfohlen. In unserer Konfigurationsdatei hatten wir hierfür überhaupt keine Parameter explizit vorgegeben, da ihre Standardwerte der Einstellung „klein“ entsprechen. Entsprechend der Empfehlung, die Größe von den parallel arbeitenden Benutzern abhängig zu machen, ist die vierte Seite des 8i-Assistenten (vgl. Abb. 1.42) geändert worden und fragt ganz einfach diese Benutzerzahl ab.
Erstellen einer Datenbank
75
Abbildung 1.42: Festlegen der Größe mit dem 8i-Assistenten
Beachten Sie allerdings, dass die hier getroffene Einstellung noch nicht direkt zu Einträgen in der Konfigurationsdatei führt. Später auf einer der folgenden Seiten des Assistenten haben Sie noch die Möglichkeit, die verschiedenen SGA-Parameter explizit vorzugeben, wobei die dort voreingestellten Werte allerdings von der hier gemachten Größenvorgabe abhängen. Wichtig ist auch die Kontrolle des eingestellten Zeichensatzes, da sich dieser nach Erstellung der Datenbank nicht mehr ändern lässt. Der gewählte Zeichensatz regelt unter anderem die Darstellung von Tabellennamen und wird ebenfalls beim Speichern von SQL-Programmen herangezogen. Von hier ab laufen die Bilder des 8erund 8i-Assistenten allerdings zunächst einmal auseinander. Die Auswahl des Zeichensatzes erfolgt erst ein paar Seiten später, wobei der 8i-Assistent bis dahin noch weitere zusätzliche Parameter abfragt. Mit Hilfe der Schaltfläche „Change Character Set“ bzw. später „Zeichensatz ändern“ können Sie die Voreinstellung überprüfen bzw. ändern. Falls nötig, sollten Sie den Wert auf „WE8ISO8859P1“ ändern. Das steht für „ISO 8859-1 West European“ und hört sich, so finde ich, ziemlich gut an. Übrigens, wenn Sie sich einmal einer unbekannten Datenbank nähern, dann können Sie diese Einstellung überprüfen, indem Sie folgende Abfrage eingeben: select * from v$nls_parameters
Die nächste Seite des Assistenten bzw. eine der folgenden bei der 8i-Version (vgl. Abb. 1.43 bzw. Abb. 1.44) ermöglicht Ihnen die Eingabe des Datenbanknamens, des System Identifers (SID) und weiterer Parameter, die Sie aufgrund des vorherigen Kapitels alle schon kennen.
76
Oracle – erste Schritte
Abbildung 1.43: Datenbanknamen, SID und weitere Parameter vorgeben
Abbildung 1.44: Datenbanknamen, SID und Zeichensatz beim 8i-Assistenten vorgeben
Eigentlich wollen wir die Datenbank DB01 fertigstellen, allerdings erhalten Sie bei entsprechender Eingabe ein kleines Problem in Form einer Fehlermeldung, nachdem Sie versuchen, auf die nächste Seite des Assistenten zu wechseln. Der Oracle Database Assistent merkt nämlich, das die Datenbankinstanz DB01 schon von uns erstellt wurde und fordert Sie daher auf, eine andere SID einzugeben. Da wir aber eigentlich nur die zur Nachbearbeitung erforderlichen Skripte wollen, müssen wir hier ein wenig schummeln und geben im Feld SID „db02“ oder irgendetwas anderes ein, um anschließend auf die nächste Seite wechseln zu können.
Erstellen einer Datenbank
77
Auf dieser mittlerweile sechsten (bzw. achten) Seite haben Sie die Möglichkeit, die Namen der Kontrolldateien sowie die Anzahl der zulässigen Datenfiles vorzugeben. Das alles hatten wir in unserer manuell erstellten Konfigurationsdatei INITDB01. ORA bzw. beim Absetzen des create database-Befehls schon festgelegt, so dass Sie diesmal ohne irgendwelche Eingabe mit der siebten (neunten) Seite fortfahren können.
Abbildung 1.45: Anlegen zusätzlicher Files, Tablespaces und Rollback-Segmente
Abbildung 1.46: Anlegen zusätzlicher Tablespaces beim 8i-Assistenten
78
Oracle – erste Schritte
Auf der siebten bzw. neunten Seite (vgl. Abb. 1.45 oder Abb. 1.46) des Assistenten wird es endlich so richtig interessant. Mit Hilfe der fünf bzw. sechs verschiedenen Registerkärtchen können Sie sowohl den Systembereich - das ist für uns allerdings nicht mehr so wichtig - genauer definieren, als auch weitere Dateien, Tablespaces und Rollback-Segmente anlegen. Die hier gezeigten Möglichkeiten entsprechen im übrigen auch den von Oracle gemachten Empfehlungen, zumindest noch weitere Dateien bzw. Tablespaces für Benutzerdaten, Rollback-Segmente, Indizes und einen speziellen Bereich für temporäre Aktivitäten anzulegen. Wie Sie beim Vergleich der beiden Abbildungen 1.45 und 1.46 feststellen können, besitzt die 8i-Datenbank standardmäßig einen zusätzlichen Tablespace mit dem Namen TOOLS. Tragen Sie für alle Registerkärtchen die gewünschten Dateinamen im Feld File und die Namen der Tablespaces im Feld Name ein. Achten Sie hierbei darauf, dass alle Verzeichnisse und Namen zu Ihrer neuen DB01-Instanz gehören, damit das später erstellte Skript ohne Änderung verwendbar ist. Rechts neben dem Namensfeld können Sie die anfängliche Größe des Tablespace angeben. Im unteren Bereich des Bildes haben Sie die Möglichkeit, eine allgemeine Speicherbelegungsregel (vgl. Kap. 1.1.3) für den Tablespace vorzugeben. Wie Sie später noch sehen werden, können Sie diese Regel bei Bedarf für jede einzelne Tabelle noch ändern. Besonders interessant ist die Einstellung des Schalters Auto Extend. Ist er so wie in unserem Beispiel eingeschaltet, dann wird der Tablespace bzw. die zugehörige Datei bei Bedarf automatisch vergrößert. Wenn Sie diese Funktion ausschalten, dann müssen Sie die Datei im Bedarfsfall selbst vergrößern oder dem Tablespace ein weiteres Datenfile zuweisen. Genaueres zu diesem Thema erfahren Sie allerdings erst später, wenn wir im weiteren Verlauf noch einmal auf die Administration von Dateien und Tablespaces zurückkommen werden. Hier und jetzt können Sie die vom Assistenten vorgeschlagenen Werte zunächst einmal so übernehmen und in der Parametrierung der benötigten Erstellungsskripte mit der nächsten Seite fortfahren. Die achte (bzw. zehnte) Seite des Assistenten bringt für uns wieder mal nichts Neues. Hier müssen Sie die Namen der benötigten Log-Dateien festlegen, die wir allerdings schon bei der create database-Anweisung zugeordnet haben und die im Rahmen der Ausführung dieses Befehls auch bereits angelegt wurden. Also können Sie gleich mit der nächsten Seite des Assistenten fortfahren. Mit Hilfe der neunten (elften) Assistentenseite (vgl. Abb. 1.47 bzw. Abb. 1.48) können Sie verschiedene Einstellungen vornehmen, die zu entsprechenden Parametern in der Konfigurationsdatei führen. Mit Hilfe des Kontrollkästchens Archive Log veranlassen Sie beispielsweise die Archivierung der redo log-Einträge (vgl. Kap. 1.5.1). Das Einschalten dieser Option führt zum Setzen der Parameter log_archive_ dest, log_archive_format, log_archive_buffers und log_archive_buffer_size, mit denen vor allem das Format und der Zielort der Archivdateien festgelegt werden. Soll die Archivierung automatisch mit dem Initialisieren der Datenbankinstanz gestartet werden, dann müssen Sie in der Konfigurationsdatei zusätzlich noch den Parameter log_archive_start = true vorgeben.
Erstellen einer Datenbank
79
Abbildung 1.47: Festlegen weiterer Parameter der Konfigurationsdatei
Abbildung 1.48: Einstellen der Logging-Parameter im 8i-Assistenten
Die anderen beiden Eingabefelder des Dialogs legen die Werte für die Parameter log_checkpoint_interval bzw. log_checkpoint_timeout fest. Der erste der beiden Werte regelt, wie häufig zusätzliche Checkpoints generiert werden, wobei der zweite Wert den notwendigen Abstand zwischen zwei Checkpoints bestimmt. Übernehmen Sie die beiden vorgeschlagenen Werte ohne Änderung in Ihre neue Konfigurationsdatei und wechseln Sie zur nächsten Seite des Datenbankassistenten.
80
Oracle – erste Schritte
Abbildung 1.49: Festlegen der SGA-Dimensionierung
Mittlerweile sind wir auf der zehnten (zwölften) Seite (vgl. Abb. 1.49 und Abb. 1.50) des Assistenten angelangt. Mit Hilfe der hier veränderbaren Werte können Sie die SGA Ihrer Instanz konfigurieren. Die konkret vorgeschlagenen Werte hängen von der zuvor auf Seite vier vorgegebenen Datenbankgröße ab.
Abbildung 1.50: Festlegen der SGA-Konfiguration beim 8i-Assistenten
Erstellen einer Datenbank
81
Mit Hilfe des ersten Feldes („Shared Pool Size“ bzw. „gemeinsamer Pool“) bestimmen Sie den Wert für den Parameter shared_pool_size und damit die Größe des Shared Pools (vgl. Kap. 1.1.2) in Byte. Mehr ist nicht unbedingt immer besser. Sicherlich gilt im Mehrbenutzerbetrieb, dass der Shared Pool möglichst groß gewählt werden muss, allerdings sollten Sie darauf achten, dass der geforderte Speicher auf dem Rechner auch permanent verfügbar ist. Der Schuss geht nämlich wie man so schön sagt nach hinten los, wenn aufgrund der eingestellten Größe sich bestimmte Teile des Pools häufig im Auslagerungsspeicher befinden bzw. das Betriebssystem dauernd damit beschäftigt ist, SGA-Teile im Hauptspeicher einbzw. auszulagern. Aus diesem Grund habe ich diesen Wert auf 35 MB geändert. Das soeben Gesagte gilt natürlich nicht nur für den Shared Pool, sondern für die gesamte SGA. Auch die anderen Puffer sollten so dimensioniert werden, dass die gesamte SGA permanent im Hauptspeicher Platz findet. Das nächste Feld „Block Buffers“ bzw. „Blockpuffer“ legt über den Parameter db_block_buffers die Anzahl der Blöcke im Database Buffer Cache, das war derjenige Speicher in dem die gerade benötigten Datenblöcke zwischengespeichert werden, fest. Zusammen mit dem Parameter db_block_size, der die Größe eines solchen Blockes festlegt und der mit Hilfe des letzten Eingabefeldes verändert werden kann, ergibt sich somit die gesamte Größe des Buffer Caches. In unserem Beispiel können wir mit der Vorgeschlagenen Anzahl von 200 Datenblöcken gut leben. Die Größe eines solchen Blockes belasse ich auf 2048 Byte, was im Übrigen auch dem Standardwert dieses Parameters entspricht. Das dritte Eingabefeld dimensioniert die Größe des Log-Buffers und führt in der Konfigurationsdatei zum gleichnamigen Parameter (log_buffer). Je größer der zugehörige Wert, um so seltener muss der LGWR-Prozess den Puffer in die aktuelle redo log-Datei kopieren. In unserem Beispiel belassen wir den Wert unverändert auf 8192 Byte. Mit Hilfe des Datenfeldes Processes können Sie die maximale Anzahl von Benutzerprozessen bestimmen, die sich gleichzeitig an der Oracle-Datenbank anmelden können. Der Eintrag führt in der Konfiguration zum Parameter processes, dessen Standardwert 30 beträgt. Der Wert würde für unsere Spiel- und Trainingsdatenbank sicherlich ausreichen, jedoch ist gegen den vorgeschlagenen Wert von 50 auch nichts einzuwenden. Nachdem Sie nun die wesentlichen SGA-Einstellungen der Oracle-Instanz festgelegt haben, können Sie mit dem Assistenten auf die nächste und vorletzte Seite wechseln. Dort finden Sie zwei Eingabefelder, in denen Sie Verzeichnisse für die Erstellung von Tracedateien festlegen können. In der Konfigurationsdatei finden Sie die hier vorgegebenen Einstellungen in den Parametern user_dump_dest bzw. background_dump_dest wieder.
82
Oracle – erste Schritte
Abbildung 1.51: Endlich am Ziel, im nächsten Schritt werden die Skripte erstellt
Mit der nächsten Seite des Assistenten (vgl. Abb. 1.51 bzw. Abb. 1.52) sind wir endlich am Ziel bzw. stehen kurz davor. Mit Hilfe der entsprechend Option erhalten Sie nach Auswahl der Finish- bzw. Fertig-Schaltfläche die Möglichkeit, ein Verzeichnis und den Namen einer Batchdatei festzulegen. Die Batchdatei (z.B. SQL.BAT) enthält alle Befehle, die zum Anlegen der Instanz und Datenbank notwendig sind. Einige der dort enthaltenen Schritte hatten wir im Verlauf des vorhergehenden Kapitels schon ausgeführt. Zusätzlich erhalten Sie zwei SQL-Skripte, deren Namen der vergebenen Datenbank-SID entsprechen (z.B. DB02.SQL und DB021.SQL).
Abbildung 1.52: Beim 8i-Assistenten sind Sie ebenfalls am Ziel angelangt
Erstellen einer Datenbank
83
Das erste Skript enthält den create database-Befehl, den wir ebenfalls schon ausgeführt hatten. Das zweite und die eventuell folgenden Skripte vereinen alle notwendigen Schritte, um die bisherige Rudimentärdatenbank fertigzustellen. Auf Ihrer Begleit-CD finden Sie die vom Assistenten erstellte Konfigurationsdatei im \DB01Unterverzeichnis unter dem Namen INITDB01A.ORA. Sie können die DB01Instanz zukünftig auch mit dieser generierten Konfigurationsdatei hochfahren , d.h. in dem Fall müssen Sie Ihre aktuelle Konfigurationsdatei ersetzen. Das zweite SQL-Skript finden Sie im gleichen Verzeichnis, heißt CREDB01_2.SQL und hat folgenden Inhalt: spool c:\temp\credb01_2.log set echo on connect INTERNAL/oracle
1. ALTER DATABASE DATAFILE 'E:\ORANT\database\db01\Sys1db01.ora' AUTOEXTEND ON; 2. CREATE ROLLBACK SEGMENT SYSROL TABLESPACE "SYSTEM" STORAGE (INITIAL 100K NEXT 100K); ALTER ROLLBACK SEGMENT "SYSROL" ONLINE; @E:\ORANT\Rdbms80\admin\catalog.sql; @E:\ORANT\Rdbms80\admin\catproc.sql 3. REM **************TABLESPACE FOR ROLLBACK***************** CREATE TABLESPACE RBS DATAFILE 'E:\ORANT\database\db01\Rbs1db01.ora' SIZE 10M DEFAULT STORAGE ( INITIAL 1024K NEXT 1024K MINEXTENTS 2 MAXEXTENTS 121 PCTINCREASE 0); ALTER DATABASE DATAFILE 'E:\ORANT\database\db01\Rbs1db01.ora' AUTOEXTEND ON; 4. REM ***********Alter system tablespace ******************** ALTER TABLESPACE SYSTEM DEFAULT STORAGE ( INITIAL 100K NEXT 100K MINEXTENTS 1 MAXEXTENTS 300 PCTINCREASE 1);
5. REM **************TABLESPACE FOR USER********************* CREATE TABLESPACE USR DATAFILE 'E:\ORANT\database\db01\Usr1db01.ora' SIZE 3M DEFAULT STORAGE ( INITIAL 50K NEXT 50K MINEXTENTS 1 MAXEXTENTS 121 PCTINCREASE 1); ALTER DATABASE DATAFILE 'E:\ORANT\database\db01\Usr1db01.ora' AUTOEXTEND ON; REM **************TABLESPACE FOR TEMPORARY***************** CREATE TABLESPACE TEMPORARY DATAFILE 'E:\ORANT\database\db01\Tmp1db01.ora' SIZE 10M DEFAULT STORAGE ( INITIAL 100K NEXT 100K MINEXTENTS 1 MAXEXTENTS 121 PCTINCREASE 0) TEMPORARY; ALTER DATABASE DATAFILE 'E:\ORANT\database\db01\Tmp1db01.ora' AUTOEXTEND ON; REM **************TABLESPACE FOR INDEX*********************
84
Oracle – erste Schritte
CREATE TABLESPACE INDX DATAFILE 'E:\ORANT\database\db01\Indx1db01.ora' SIZE 10M DEFAULT STORAGE ( INITIAL 50K NEXT 50K MINEXTENTS 1 MAXEXTENTS 121 PCTINCREASE 1); ALTER DATABASE DATAFILE 'E:\ORANT\database\db01\Indx1db01.ora' AUTOEXTEND ON; 6. REM **** Creating two rollback segments **************** CREATE ROLLBACK SEGMENT RB0 TABLESPACE "RBS" STORAGE ( INITIAL 50K NEXT 50K MINEXTENTS 2 MAXEXTENTS 121 ); CREATE ROLLBACK SEGMENT RB1 TABLESPACE "RBS" STORAGE ( INITIAL 50K NEXT 50K MINEXTENTS 2 MAXEXTENTS 121 ); ALTER ROLLBACK SEGMENT "RB0" ONLINE; ALTER ROLLBACK SEGMENT "RB1" ONLINE; 7. alter user sys temporary tablespace TEMPORARY; alter user system default tablespace USR; alter rollback segment "SYSROL" offline; spool off Listing 1.9: Typisches „Nachlauf“ Skript zum Fertigstellen einer Datenbank
Bei der Erstellung einer Datenbank für die 8i-Version enthält das Skript natürlich auch die Erstellung des zusätzlichen Tablespaces TOOLS. Auf der CD finden Sie daher auch noch die Datei CREDB01_2I.SQL als Muster für ein solches Skript bei der 8i-Version. Das Skript können Sie wieder mit Hilfe des Server-Managers starten, in dem Sie in einem MS-DOS-Fenster Folgendes eingeben: Set oracle_sid=db01 svrmgr30 @e:\orant\database\db01\credb01_2.sql
Den Pfad und Namen des Skriptes müssen Sie natürlich Ihren individuellen Gegebenheiten anpassen. Die Ausführung des Skripts kann je nach Rechner durchaus 30 bis 60 Minuten dauern; das liegt vor allem an der Datei CATPROC.SQL, aus der endlos viele weitere SQL-Befehlsdateien gestartet werden. Während der Ausführung können Sie die verschiedenen Aktionen am Bildschirm mitverfolgen, zumindest wenn Sie schnell genug lesen können; gut, dass alle Aktivitäten in dem mit der spool-Anweisung vorgegebenen Datei protokolliert werden. In den folgenden Abschnitten finden Sie eine kurze Beschreibung der verschiedenen Passagen des SQL-Skripts. Die im Listing abgedruckten Nummern sind in der Originaldatei natürlich nicht enthalten. Außerdem finden Sie weitergehende Informationen zu den einzelnen Befehlen entweder am Ende des Kapitels „5. Datenbankprogrammierung“ oder in der „SQL-Reference“ der Oracle-Dokumentation. Nun zu den einzelnen Passagen des abgedruckten Skripts: 1. Für das Datenfile des Systembereichs wird die autoextend-Eigenschaft eingeschaltet, damit es anschließend beliebig wachsen kann.
Erstellen einer Datenbank
85
2. Im SYSTEM-Tablespace wird ein weiteres Rollback-Segment angelegt. Dieses Rollback-Segment ist von der Größe her so dimensioniert, dass alle nachfolgenden Statements ausgeführt werden können. Anschließend wird das RollbackSegment aktiviert und danach werden nacheinander die beiden Skripte CATALOG.SQL und CATPROC.SQL aufgerufen. 3. In dem Schritt wird der Tablespace RBS im Datenfile RBS1DB01.ORA angelegt. Hierbei werden die im Assistenten spezifizierten Regeln zur Speicherbelegung (storage-Klausel) verwendet. Anschließend wird der Autoextend-Modus für die Datei eingeschaltet. 4. Das Verfahren zur Speicherbelegung wird für den SYSTEM-Tablespace angepasst. 5. Ähnlich wie im Punkt 3 werden die Tablespaces USR, TEMPORY und INDX mitsamt den Dateien USRLDB01.ORA, TMP1DB01.ORA und INDX1DB01.ORA entsprechend den im Assistenten gemachten Vorgaben angelegt. 6. Es werden zwei neue Rollback-Segmente RB0 und RB1 im RBS-Tablespace angelegt und anschließend aktiviert. Beachten Sie nochmals, dass Sie die Verwendung der beiden Rollback-Segmente in der Konfigurationsdatei der Instanz noch einschalten müssen: rollback_segments = (RB0, RB1)
7. Für die angelegten Benutzer sys und system wird die jeweilige Standard-Tablespace-Zuordnungen geändert. Anschließend wird das am Anfang angelegte Rollback-Segment SYSROL deaktiviert. Endlich fertig; Sie sind im Besitz einer eigenen voll funktionsfähigen Datenbank. Neben der Benutzer-Id INTERNAL mit dem während der Installation vorgegebenen Passwort legt Oracle während der Datenbankanlage weitere Benutzer-Id’s an. Wir werden später im Workshop bei der Behandlung von Benutzer- und Rollenprofilen sehen, welche ID’s alle schon in der Datenbank vorhanden sind. Vorab schon mal eine kleine Übersicht der vorhandenen Benutzer mit Administratorrechten: Benutzer-ID
Passwort
INTERNAL SYS
CHANGE_ON_INSTALL
SYSTEM
MANAGER
Tabelle 1.3: Übersicht der standardmäßig vorhandenen DBA-Accounts
Die insgesamt generierten oder aufgerufenen Skripte hängen konkret von der verwendeten Datenbankversion und von den installierten Zusatzoptionen ab. Von daher ist es natürlich nicht besonders empfehlenswert, bei der Anlage einer Datenbank die auf der CD befindlichen Muster zu verwenden, sondern sich mit Hilfe der Assistenten einen individuellen Ablauf zu generieren und unter NT vielleicht auch automatisch abzuspielen.
86
1.5.4
Oracle – erste Schritte
Automatische Generierung unter NT
Wie Sie im bisherigen Verlauf des Buches vielleicht schon gemerkt haben, führen in Oracle meistens nicht nur mehrere, sondern immer gleich viele Wege zum Ziel. Dabei geht es gar nicht unbedingt immer nur um die Abwägung zwischen einem manuellen oder abstraktem vollautomatischen Verfahren, sondern viele vorhandene Werkzeuge haben einen sich überschneidenden Aktionsbereich, so dass es manchmal reine Geschmacksache ist, ob man eine Aufgabe mit diesem oder jenem Hilfsmittel bewerkstelligt. Ein Beispiel für diese Aufgabenüberlappung stellt auch die Möglichkeit dar, eine neue Datenbankinstanz nebst Datenbank mit Hilfe des schon bekannten Programms ORADIM80.EXE zu generieren, was allerdings in der hier beschriebenen Form unter 8i nicht mehr bzw. in anderer Form mit Hilfe des neuen Assistenten funktioniert. Dieses Programm, das es in der entsprechenden Vorgängerversion auch schon unter Version 7 gab, habe ich vor allem früher immer ganz gerne genutzt, um einfach und schnell einen neue Datenbank zu erzeugen. Starten Sie hierzu einfach das Programm ORADIM80.EXE ohne weitere Parameter. Als Ergebnis erhalten Sie ein kleines Programmfenster, in dem alle auf dem Rechner vorhandenen Datenbankinstanzen angezeigt werden. Wählen Sie hier die Schaltfläche New, um die Erstellung einer neuen Datenbankinstanz einzuleiten.
Abbildung 1.53: Anlegen einer neuen Datenbankinstanz
Auf der ersten Seite (vgl. Abb. 1.53) zur neuen Datenbankinstanz müssen Sie als Erstes den System-Identifer (SID) der neuen Datenbank (z.B. db01) angeben. Anschließend trägt das Programm den Namen der zugehörigen Initialisierungdatei (INTIDB01.ORA) automatisch rechts unten in das zugehörige Eingabefeld ein. Legen Sie als Nächstes das Passwort für den Benutzer INTERNAL fest, das Sie im folgenden Feld noch einmal bestätigen müssen. Danach wechseln Sie mit Hilfe der Schaltfläche Advanced auf die zweite Seite, um weitere Einstellungen für die Generierung vorzunehmen.
Administrierung der Datenbanken
87
Abbildung 1.54: Anlegen weiterer Generierungsparameter für die neue Datenbank
Auf der zweiten Seite (vgl. Abb. 1.54) müssen Sie den Datenbanknamen vorgeben, der dem Namen aus der Initialisierungsdatei entsprechen muss. Anschließend müssen Sie mindestens noch die Namen und Verzeichnisse der Logdateien und des Systembereichs festlegen. Danach können Sie den Dialog mit Hilfe der OK-Schaltfläche beenden, wodurch Sie zum vorhergehenden Dialog zurückkehren. Beenden Sie auch diesen jetzt mittels OK, wonach das Programm mit der Erstellung der Datenbankinstanz und anschließend mit der Generierung der Datenbank beginnt. Nach ein paar Minuten, die genaue Zeit hängt natürlich mal wieder vom Rechner ab, erscheint ein Hinweis, der es Ihnen ermöglicht, ein spezielles Skript abzuspielen. Dieses Skript wurde während der Generierung erstellt und enthält im Wesentlichen wieder den Aufruf der schon bekannten Dateien CATALOG.SQL und CATPROC.SQL. Das war schon alles; auch jetzt sind Sie wieder im Besitz einer neuen Datenbank. Alles was Sie zusätzlich benötigen (Benutzer, Tablespaces usw.), können bzw. müssen Sie in weiteren Arbeitsschritten anlegen.
1.6
Administrierung der Datenbanken
Wie Sie im Kapitel „1.3 Dienste unter Windows-NT“ schon lesen konnten, werden die Komponenten einer Oracledatenbank unter Windows am einfachsten mit Hilfe sogenannter Dienste administriert. So einfach dieses Verfahren auch ist, es lässt auf den ersten Blick keinen direkten Vergleich zur Vorgehensweise auf andere Systeme (z.B. UNIX) zu. Dabei gibt es, zumindest was die prinzipielle Vorgehensweise angeht, eigentlich kaum Unterschiede. Auf einem Server läuft die Oracle-Datenbankinstanz üblicherweise als Hintergrundprozess und damit schlagen wir auch schon die Brücke zu den Windows-Diensten, denn diese Dienste sind nur ein Hilfsmittel, um solche Hintergrundprozesse zu verwalten.
88
Oracle – erste Schritte
Eigentlich geht es nur darum, auf Ihrem Rechner das Programm ORACLE80.EXE (unter vielen anderen Systemen wie auch 8i einfach nur ORACLE) zu aktivieren. Öffnen Sie doch einmal ein MS-DOS-Fenster (vgl. Abb. 1.55) und geben Sie am Prompt folgenden Befehl ein:
Abbildung 1.55: Starten des Oracle-Servers im Vordergrund
Hierdurch starten Sie das Programm ORACLE80.EXE, dem Sie die zugehörige SID als Kommandozeilenparameter übergeben und – boom – nach wenigen Sekunden erhalten Sie die abgebildete Nachricht und auf Ihrem Rechner läuft die DB01Datenbankinstanz. Einen Nachteil hat das Ganze natürlich: sobald Sie das MSDOS-Fenster schließen oder dort irgendeine Taste drücken, ist die Datenbankinstanz wieder weg, da das zugehörige ORACLE80-Programm beendet wird.
1.6.1
Instanz starten und stoppen
Um nun das ORACLE-Programm im Hintergrund zu starten, können Sie unter NT das schon bekannte Programm ORADIM80.EXE folgendermaßen verwenden: oradim80 -startup -sid db01 -starttype srvc oradim -startup -sid db01 -starttype srvc
Die verschiedenen Schalter und Parameter haben dabei folgende Bedeutung:
X X X
-startup Der Schalter gibt an, dass ORADIM eine Datenbankinstanz auf Ihrem Rechner (im Hintergrund) starten soll. -sid Verwenden Sie den Schalter zusammen mit dem System Identifer derjenigen Instanz, die Sie auf dem Rechner starten wollen. -starttype srvc Mit Hilfe dieses Schalters müssen Sie festlegen, wie die Datenbankinstanz gestartet werden soll. Wie Sie noch sehen werden, stehen Ihnen hierbei verschiedene Varianten zu Verfügung. Der Wert srvc führt zum Starten der Datenbankinstanz.
Administrierung der Datenbanken
89
Nach Eingabe des eben beschriebenen Befehls sollte die Prozess-Liste Ihres Windows Task-Managers einen weiteren Eintrag mit dem Namen ORACLE80.EXE (bzw. ORACEL80.EXE) aufweisen. In jedem Fall finden Sie Hinweise oder Fehler zum Start der Instanz in der Datei ORADIMxx.LOG, die Sie im \RDBMSxx-Unterverzeichnis Ihrer Oracle-Installation finden. Übrigens eignet sich ORADIM auch zum Beenden einer laufenden Instanz. In dem Fall müssen Sie das Programm folgendermaßen aufrufen: oradim80 -shutdown -sid db01 -shuttype srvc -shutmode N oradim -shutdown -sid db01 -shuttype srvc -shutmode N
X X X X
-shutdown legt fest, dass Sie eine Datenbankinstanz beenden möchten. -sid identifiziert die zu schließende Instanz, d.h. geben Sie hier den zugehörigen System Identifer ein. -shuttype Verwenden Sie srvc, um die Datenbankinstanz zu beenden. -shutmode Mit diesem Schlüssel können Sie die Modus für das Herunterfahren vorgeben. Das übliche Verfahren ist das sogenannte „normale“ Beenden der Instanz, weshalb Sie den Schalter zusammen mit dem Buchstaben „N“ verwenden.
Beim Beenden der Datenbankinstanz wird eine eventuell geöffnete Datenbank ebenfalls geschlossen. Wird die Instanz normal beendet (-shutmode N), dann wird mit dem Heruntergefahren gewartet, bis aktuell laufende Prozesse (z.B. eine Datenänderung) beendet sind. Die anderen Beendigungsmethoden z.B. „I“ (für IMMEDIATE, engl. immediately) erzwingen ein sofortiges Beendigen der Instanz, was in der Regel zu entsprechenden Recovery-Aktivitäten beim nächsten Hochfahren der Datenbank zur Folge hat. Eine vollständige Übersicht aller vorhandenen Parameter und Schalter des Programms ORADIM80.EXE erhalten Sie, wenn Sie das Programm zusammen mit dem Schalter „-?“ oder „/H“ aufrufen. Weitere Verwendungsformen finden Sie auch im nächsten Kapitel, denn mit dem Programm können Sie auch die zur Instanz gehörende Datenbank öffnen oder schließen.
1.6.2
Datenbank öffnen und schließen
Nach dem Hochfahren der Datenbankinstanz ist ein Arbeiten mit der zugehörigen Datenbank noch nicht möglich. Hierzu muss sie nämlich zunächst einmal noch geöffnet werden. Unter NT haben Sie auch hierfür einen entsprechenden Diensteintrag, doch wie funktioniert das Verfahren ohne Dienste bzw. unter anderen Betriebssytemen?
90
Oracle – erste Schritte
Beginnen wir auch diesmal zunächst mit einem leicht übertragbaren Verfahren, das ich in ähnlicher Form schon in so manchen Start- bzw. Bootskripts gefunden habe. Konkret geht es darum, die Datenbank mit Hilfe des Server-Managers zu starten, indem Sie mit seiner Hilfe beispielsweise folgendes Skript abspielen: connect internal/oracle startup pfile=e:\orant\database\db01\initdb01.ora
Nach dem Anmelden and die noch untätige Datenbankinstanz, wird die Datenbank mit Hilfe des startup-Befehls und der Vorgabe der zugehörigen Konfigurationsdatei geöffnet. Ähnlich einfach lässt sich auch das Herunterfahren bzw. Schließen der Datenbank bewerkstelligen: connect internal/oracle shutdown normal
Der Zusatz „normal“ ist übrigens die standardmäßige Variante des shutdownBefehls, d.h. Sie können diesen Teil der Anweisung auch weglassen. Falls Sie wissen, dass gleich der Strom ausfällt, dann sollten Sie besser den Befehl shutdown immediate verwenden, damit alle aktiven Benutzer sofort abgehängt, alle laufenden Transaktionen zurückgerollt und die Datenbank unverzüglich geschlossen wird. Diese shutdown-Variante wird im Übrigen auch angewendet, wenn Sie mit ORADIM80 die Instanz in der Form „–shutmode I“ herunterfahren. Öffnen und Schließen mit ORADIM Wie ich schon angedeutet hatte, besteht auch die Möglichkeit, eine Datenbank mit Hilfe des Programms ORADIMxx.EXE zu öffnen oder zu schließen. Sie erleben also gerade mal wieder ein Beispiel für die Vielfalt der möglichen Wege, um zum Ziel zu kommen. Das Öffnen der Datenbank erfolgt bei schon laufender Instanz beispielsweise folgendermaßen: oradim80 -startup -sid db01 -starttype inst -usrpwd oracle -pfile e:\orant\database\db01\initdb01.ora
Bei Verwendung der Version 8i müssen Sie jetzt anstelle von oradim80 natürlich nur wieder oradim eingeben, da das Programm entsprechend umbenannt wurde. Neu bei dem Aufruf ist zunächst einmal der verwendete Starttyp „inst“, der das Programm zum Öffnen der zugehörigen Datenbank veranlasst. Die Starttypen inst und svrc können im Übrigen auch gemeinsam verwendet werden („svrc, inst“), um das Laden der Instanz mit dem Öffnen der Datenbank zu verbinden. Dabei muss das Programm natürlich dieselben Aktivitäten durchführen, die Sie vorhin zusammen mit dem Server-Manager manuell durchgeführt haben. Aus dem Grund benötigen Sie zwei weitere Parameter „-usrpwd“ und „-pfile“, um mit deren Hilfe das Kennwort für den Internal-Benutzer und den Pfad und Namen der Konfigurationsdatei vorzugeben.
Administrierung der Datenbanken
91
Ebenfalls möglich ist auch das Schließen der Datenbank via ORADIM, indem Sie das Programm beispielsweise folgendermaßen aufrufen: oradim80 -shutdown -sid db01 -usrpwd oracle -shuttype inst, -shutmode N
1.6.3
Löschen einer Datenbank
Aufgrund der Oracle-Architektur sind für das Löschen einer Datenbank keine speziellen Befehle (z.B. drop database) notwenig, da die zu löschende Datenbank ja nicht in irgendeiner Systemdatenbank des DBMS eingetragen ist, sondern zusammen mit seinem DBMS eine logische Einheit bildet. Außerdem kann man ja auch kaputtmachen, was man gleich sowieso löschen möchte. Aus dem Grund wäre folgende Holzhammermethode zum Entfernen einer Oracle-Datenbank denkbar:
X X X
Beenden Sie den zugehörigen ORACLE-Hintergrundprozess, beispielsweise durch ein entsprechendes KILL-Kommando (z.B. UNIX) oder indem Sie das Programm unter Windows mit Hilfe des Task-Managers aus der Prozessliste schmeißen. Entfernen Sie den Aufruf der Datenbank aus allen Startup- bzw. Boot-Skripten. Löschen Sie unter NT in der Registrierung alle eventuell vorhandenen Diensteinträge. Löschen Sie alle zugehörigen Dateien (Daten-, Log-, Kontroll- und Konfigurationsdateien).
Der Nachteil dieses Verfahrens ist natürlich, dass man hierbei genau wissen muss, was man im Einzelnen abschießen bzw. löschen darf. Schnell hat man mal daneben gegriffen und beispielsweise den falschen Prozess abgewürgt oder in der Registrierung leider den darüber liegenden Dienst markiert und entfernt. Aus diesem Grund gibt es auch für das Entfernen einer Datenbank die eine oder andere Hilfestellung. Zunächst einmal sollten Sie sich einen Überblick über die vorhandene Dateistruktur der Datenbank machen. Identifizieren Sie zunächst die verwendete Konfigurationsdatei, denn in ihr finden Sie einen Verweis auf die zur Datenbank gehörenden Kontrolldateien: control_files = (E:\ORANT\DATABASE\db01\ctl1db01.ora,E:\ORANT\DATABASE\db01\ctl2db01.ora)
Führen Sie mit Hilfe eines SQL-Editors, beispeilsweise dem Server-Manager, folgende Abfragen aus, um eine Übersicht der zugehörigen Datendateien (vgl. Listing 1.10) und Log-Dateien (vgl. Listing 1.11) zu erhalten. SVRMGR> select name from v$datafile; NAME ------------------------------------------E:\ORANT\DATABASE\DB01\SYS1DB01.ORA
92
Oracle – erste Schritte
E:\ORANT\DATABASE\DB01\RBS1DB01.ORA E:\ORANT\DATABASE\DB01\USR1DB01.ORA E:\ORANT\DATABASE\DB01\TMP1DB01.ORA E:\ORANT\DATABASE\DB01\INDX1DB01.ORA 5 rows selected. SVRMGR> Listing 1.10: Abfrage der Datenfiles mit Hilfe der View v$datafile
SVRMGR> connect internal/oracle Connected. SVRMGR> select member from v$logfile; MEMBER -----------------------------------------------E:\ORANT\DATABASE\DB01\LOGDB011.ORA E:\ORANT\DATABASE\DB01\LOGDB012.ORA 2 rows selected. SVRMGR> Listing 1.11: Ermittlung der Log-Dateien durch Abfrage der View v$logfile
Nachdem Sie nun alle Dateien kennen, können Sie die Datenbank zusammen mit der Instanz herunterfahren; hierzu haben Sie in den beiden vorhergehenden Kapiteln genügend Beispiele kennen gelernt. Als Nächstes müssen Sie dafür sorgen, dass die Instanz nebst Datenbank beim nächsten Durchstarten des Rechners nicht wieder hochgefahren wird, indem Sie eventuell vorhandene Bootskripts kontrollieren. Unter NT müssen Sie im nächsten Schritt die zugehörigen Diensteinträge aus der Registrierungsdatenbank entfernen. Hierzu können Sie übrigens auch das Programm ORADIMxx.EXE verwenden: oradim80 -delete -sid db01 oradim80 -delete -srvc OracleStartDB01
Der erste Aufruf des Programms entfernt den zur Instanz gehörenden Diensteintrag (OracleServiceDB01) und der zweite Aufruf entfernt den Dienst, mit dem Sie die Datenbank automatisch öffnen konnten. Nun ist nur noch Datenmüll übrig, d.h. Sie können im letzten Schritt alle zugehörigen Dateien löschen.
1.7
Der Oracle Storage Manager
Während der Erstellung der DB01-Datenbank (vgl. Kap. 1.5.3) haben Sie gesehen, wie man mit Hilfe spezieller SQL-Befehle Tablespaces, Datenfiles oder Rollback-Segmente anlegt. Dieses Verfahren, eventuell notwendige Strukturänderungen mit Hilfe geeigneter Skripts durchzuführen, entspricht übrigens oftmals der gängigen Praxis für produktive Datenbanksysteme. Die einzelnen Skripte werden hierbei archiviert, so dass man bei Bedarf, z.B. nach einem Rechnercrash oder für den Auf-
Der Oracle Storage Manager
93
bau eines Backup-Systems, eine exakt gleich konfigurierte Datenbank nahezu automatisch generieren kann, indem nach dem Erstellskript einfach auch alle Änderungsskripts nacheinander eingespielt werden. Wie Sie im letzten Kapitel erfahren haben, ist aber nicht nur die Erstellung der Speicherobjekte via SQL möglich, sondern auch die Abfrage der vorhandenen Objekte mitsamt ihren Eigenschaften ist mit Hilfe spezieller Views möglich. Sofern Sie allerdings unter Windows arbeiten, dann gibt es aber mittlerweile für nahezu jegliche Aufgabenstellung auch ein entsprechendes Tool mit grafischer Oberfläche. Oracle8 Storage-Manager Zwecks Analyse oder Bearbeitung der Datenbankstruktur finden Sie hierzu in der klassischen 8er-Version den Oracle Storage Manager (VAG.EXE, vlg. Abb. 1.56), der im Rahmen des Oracle Enterprise Managers auf Ihrem Rechner installiert wird.
Abbildung 1.56: Der Oracle Storage Manager
Im weiteren Verlauf des Workshops werden Sie noch weitere ähnliche Werkzeuge kennenlernen, d.h. die verschiedenen Tools haben einen ähnlichen Aufbau, funktionieren sehr ähnlich, unterscheiden sich aber durch ein jeweils spezielles Einsatzgebiet. Im linken Teil des Fensters finden Sie immer eine Übersicht der vorhandenen Objekte. Wenn Sie eines dieser Objekte markieren, dann erhalten Sie im rechten Teil ein Fenster mit einem oder mehreren Registerkärtchen, in denen Sie die vorhandenen Eigenschaften des markierten Objekts betrachten oder ändern können.
94
Oracle – erste Schritte
Im letzteren Fall können Sie die neuen Einstellungen mit Hilfe der Apply-Schaltfläche speichern bzw. aktivieren oder Sie verwenden die Schaltfläche Show SQL, um ein Fenster mit den zugehörigen SQL-Befehlen zu erhalten. Ein Programm mit Trainerqualitäten also. Ansonsten denke ich, dass uns die Bedienung des Programms als mittlerweile Explorer und Windows gewöhnten Anwender, keine größeren Schwierigkeiten bescheren sollte. In der links dargestellten Übersichtsliste können Sie übrigens auch mit der rechten Maustaste agieren. Das ist hilfreich, wenn Sie neue Objekte anlegen oder vorhandene kopieren bzw. löschen wollen. Damit bin ich mit meinen Ausführungen zu diesem Werkzeug auch schon fast am Ende. Bemerkenswert ist lediglich noch eine besondere Funktionalität, die dieses und alle weiteren Manager-Werkzeuge besitzen. Mit Hilfe des Eintrags Record aus dem Log-Menü haben Sie die Möglichkeit, alle Ihre Aktivitäten aufzuzeichen, d.h. hierbei werden die zugehörigen SQL-Kommandos protokolliert. Damit ist die Brücke geschlossen, zum einen die notwendigen Arbeiten mit Hilfe eines grafischen Tools komfortabel erledigen zu können und zum anderen alle Änderungen in Form eines SQL-Befehlsskripts aufzuheben bzw. zu archivieren. Wählen Sie den Eintrag Stop aus dem Log-Menü, um die Aufzeichnungsaktiviäten zu beenden, wobei Sie jetzt die Möglichkeit erhalten, das Protokoll unter einem beliebigen Namen zu speichern. Probieren Sie das Ganze doch einfach mal aus. Wie Sie sich vielleicht erinnern, haben wir beim Anlagen der DB01-Datenbank im Rahmen der Nachbearbeitung das Rollback-Segment SYSROL angelegt. Dabei wurde dieses Segment am Ende der Verarbeitung inaktiviert, so dass es jetzt eigentlich gelöscht werden könnte. Führen Sie diese Aktion mit Hilfe des Storage Managers durch. Da wir aber wissen wollen, welche SQL-Anweisung sich dahinter verbirgt, schalten wir vorher die Protokoll-Funktion ein. Danach markieren Sie das Rollback-Segment SYSROL, drücken anschließend die Taste (Entf), oder wählen im Kontext-Menü (rechte Maustaste) den Eintrag Remove oder Sie verwenden den gleichen Eintrag im Rollback-Menü. Bestätigen Sie jetzt alle erscheinenden Sicherheitsabfragen, so dass das Segment aus der Datenbank entfernt wird. Anschließend beenden Sie die Protokollierung mit Hilfe des Log-Menüs und Speichern das Protokoll unter irgendeinem Namen ab. Öffnen Sie jetzt das Protokoll mit einem beliebigen Texteditor, um den zugehörigen SQL-Befehl abzulesen: DROP ROLLBACK SEGMENT "SYSROL"
Storage-Manager für Oracle8i Mit der Version 8i wurden verschiedene Administrationsprogramme zum Oracle DBA Studio zusammengefasst, wobei Sie auch die Funktionen des Storage-Managers im DBA Studio (vgl. Abb. 1.57) wiederfinden. Mann kann sich das im Prinzip so vorstellen, dass der jeweils links im Bild angezeigte Baum um weitere Blätter erweitert wurde, die für die jeweils unterschiedlichen Administratorwerkzeuge stehen. Konkret verbergen sich die Storage-Funktionalitäten unter dem Eintrag „Speicher“ und was Sie von dort aus aufklappen können, ist völlig identisch mit der in Abbildung 1.56 gezeigten Struktur.
SQL*Plus
95
Abbildung 1.57: Storage Manager im neuen Gewand des DBA Studios
Ich denke, dass man auch hier eigentlich kein Wort über die prinzipielle Bedingung des Programms verlieren muss, wobei ich einmal anmerken möchte, dass mir persönlich die neuen 8i-Oberflächen sogar besser gefallen. Sie finden im DBA Studio in Bezug auf das Speichermanagement alle Funktionen wieder, die Ihnen bisher vom Storage Manager geboten wurden. So richtig neu ist eigentlich nur die Artund Weise, Ihre Arbeit gegebenenfalls in einem Protokoll zu speichern. Im Unterschied zum Storage Manager verfügt das DBA Studio über ein permanentes Protokoll aller ausgeführten SQL-Anweisungen, d.h. Sie finden hier auch eine Aufzeichnungen aller Abfragen, die das Programm zur Anzeige der einzelnen Objekte und deren Eigenschaften absenden muss. Sie erhalten dieses Protokoll, indem Sie im Ansicht-Menü den Eintrag „SQL-Historie der Anwendung“ auswählen. Hierdurch erhalten Sie ein Fenster, indem alle vom Programm abgesetzten SQL-Anweisungen angezeigt werden. Ich finde das ist wirklich eine tolle Sache und ermöglicht Ihnen den einfachen Zugriff auf viele technische Informationen Ihrer Oracle-Datenbank.
1.8
SQL*Plus
Wie Sie schon gesehen haben, benötigt man zum Arbeiten mit einer Datenbank, neben den eigentlichen Anwendungsprogrammen (z.B. PeopleSoft oder SAP), oftmals ein Werkzeug, mit dem man SQL-Befehle abschicken und die entsprechenden
96
Oracle – erste Schritte
Ergebnisse empfangen kann. Am Beispiel des Server-Managers haben Sie bereits ein solches Werkzeug, einen SQL-Editor, kennengelernt. Allerdings macht es meistens wenig Spaß, die Erstellung von Abfragen oder die Entwicklung von SQL-Programmen im Serverraum durchzuführen. Zwar hindert die Pelzmütze nicht unbedingt bei der Arbeit, aber mit dem Fäustlingen aus dem letzten Skiurlaub kann man halt doch nur schlecht tippen. Wir benötigen also ein ähnliches Werkzeug, das nicht auf die Ausführung auf dem Datenbankserver beschränkt ist, sondern das wir an unseren normalen Arbeitsplatz benutzen können. Wer die Wahl hat, hat die Qual, denn Oracle bietet hierfür mal wieder gleich mehrere verschiedene Möglichkeiten. Beginnen wir zunächst mit dem Klassiker, den Sie im Rahmen der gewöhnlichen Installation des Oracle-Clients erhalten. Wie Sie der Überschrift entnehmen können, meine ich hiermit das Programm SQL*Plus, das je nach Betriebssystem und Installation als PLUS80.EXE, PLUS80W.EXE, SQLPLUSW.EXE oder lediglich SQLPLUS vorliegt. Über Geschmack kann man bekanntlich streiten und in Bezug auf dieses Tool gibt es, zumindest nach meiner Erkenntnis, wirklich genau zwei Lager: entweder man liebt oder hasst es (ich gehöre eher zur zweiten Gruppe). Aber lassen wir den Geschmack mal außen vor. Für das etwas angestaubt wirkende Tool gibt es in jedem Fall eine Daseinsberechtigung, denn im Unterschied zum später beschriebenen SQL-Worksheet, finden Sie SQL*Plus in jeder Betriebsumgebung. Benötigen Sie beispielsweise auf einem AIX-Server einen SQL-Editor zum Abspielen eines Skripts, so ist das dank SQL*Plus überhaupt kein Problem. Tippen Sie innerhalb einer Telnet-Session das Kommando sqlplus ein, um den SQL-Editor zu starten (vgl. Abb. 1.58).
Abbildung 1.58: UNIX-Variante des SQL*Plus-Programms
Unter Windows heißt das Programm der aktuellen 8er-Version PLUS80W.EXE (vgl. Abb. 1.59) und befindet sich üblicherweise im /BIN-Unterverzeichnis der ClientInstallation. Das zugehörige Programm-Icon finden Sie in der Programmgruppe „Oracle for Windows NT“. Auch bei der Installation des 8i-Clients erhalten Sie
SQL*Plus
97
natürlich wieder ein SQL*Plus, wobei das Programm diesmal SQLPLUSW.EXE heißt, sich diesmal aber überhaupt nicht von den bisherigen Windows-Varianten unterscheidet.
Abbildung 1.59: SQL*Plus unter Windows
Sie müssen zugeben, dass sich die beiden „SQL*Plusse“ ziemlich ähnlich sehen. In echt funktionieren Sie auch gleich, d.h. wer sich irgendwo mit SQL*Plus auskennt, kommt unter jeder Betriebsumgebung mit diesem Produkt zurecht; das ist sicherlich ein nicht zu vernachlässigender Vorteil. Falls Sie es auch unter Windows noch proprietärer mögen, dann können Sie SQL*Plus auch in einem MS-DOS-Fenster starten, in dem Sie dort das Programm PLUS80.EXE bzw. SQLPLUS.EXE beim 8iClient starten. Sie sehen also, warum so viele Installationsbeschreibungen immer noch auf SQL*Plus abstellen. Dieses Tool gibt es zum einen überall und funktioniert zum anderen immer gleich. Ansonsten gilt nicht nur bei Ikea: „Entdecke die Möglichkeiten“. Das Thema SQL*Plus ist so umfangreich, dass es hierzu in der OracleDokumentation sogar ein eigenes Buch „SQL*Plus User’s Guide and Reference“ gibt. Dort finden Sie in dem Kapitel „Command Reference” eine vollständige Übersicht der vorhandenen Befehle mit vielen Beispielen. Ich möchte im Rahmen dieses Buches nur einen Überblick über die vorhandenen Möglichkeiten geben und habe hierzu die nachfolgenden Häppchen ausgewählt.
1.8.1
Abfragen eingeben und ausführen
Ähnlich wie der Server-Manager arbeitet auch SQL*Plus zeilenorientiert, d.h. es erwartet Ihre Eingaben am Prompt (SQL>) und mit Hilfe der Eingabezeile erhalten Sie eine neue Zeile. Ihre Eingabe bzw. Zeilen werden innerhalb von SQL*Plus in einem internen Puffer zwischengespeichert und erst nach Abschluss Ihrer Eingaben an die Datenbank geschickt. Um die Eingabe abzuschließen haben Sie verschiedene Möglichkeiten:
98
X X
Oracle – erste Schritte
Sie beenden die Zeile mit einem Semikolon (;) und drücken danach die Eingabetaste. Hierdurch werden die im SQL-Puffer gespeicherten Befehle sofort an die Datenbank gesendet. Sie betätigen die Eingabetaste zweimal hintereinander. SQL*Plus beendet nämlich das Füllen des Puffers, wenn Sie in einer Leerzeile die Eingabetaste verwenden. Allerdings wird in dem Fall der Pufferinhalt nicht an den Server geschickt. Dies müssen Sie anschließend noch durch Eingabe des Befehls run bzw. exec, wenn sich im Puffer ein PL/SQL-Programmblock befindet, veranlassen.
Der SQL-Puffer Erinnern Sie sich noch an Edlin oder arbeiten Sie regelmäßig mit UNIX-Systemen und kennen sich mit Befehlen wie ed oder vi bestens aus? Falls ja, dann willkommen Zuhause. Auch unter SQL*Plus stehen Ihnen ähnliche zeilenorientierte Befehle zur Verfügung, um den im SQL-Puffer gespeicherten Text nachträglich zu bearbeiten. So können Sie beispielsweise durch die Eingabe des Buchstabens „l“, den kompletten Inhalt des Puffers anlisten. Durch Eingabe eines „i“ können Sie so lange weitere Zeilen am Puffer anfügen, bis Sie wieder zweimal Eingabe drücken und damit die Editierung beenden. Die wichtigsten Befehl zum Bearbeiten des SQL-Puffers können Sie der Tabelle 1.3 entnehmen: Befehl
Beschreibung
a
fügt den spezifizierten Text am Ende der aktuellen Zeile an, vgl. auch Hinweise zu l .
c //
ändert in der aktuellen Zeile den Text in den Text , z.B. c / fromm/from, um „fromm“ in „from“ zu ändern.
c /
löscht den Text aus der aktuellen Zeile.
cl buff
löscht den SQL-Puffer.
del
löscht die aktuelle Zeile, vgl. zu l .
del
löscht die Zeile aus dem SQL-Puffer.
del *
löscht den Puffer von der Zeile bis zur aktuellen Zeile.
del <m>
löscht die Zeilen bis <m> aus dem Puffer.
edit
ruft den per define_editor definierten Texteditor auf (vgl. nächsten Abschnitt).
exec
startet den im Puffer befindlichen PL/SQL-Block.
i
fügt weitere Zeilen hinter der aktuellen Zeile des SQL-Puffers ein.
i
fügt eine Zeile hinter der aktuellen Zeile eine neue mit dem Inhalt ein.
l
führt zur Anzeige des SQL-Puffers
l
zeigt die Zeile des SQL-Puffers an und macht diese zur aktuellen Zeile.
l *
zeigt den Puffer von der Zeile bis zu aktuellen Zeile.
l <m>
listet die Zeilen bis <m> aus dem Puffer.
run
startet die im Puffer befindliche SQL-Anweisung.
Tabelle 1.4: SQL*Plus-Befehle zum Bearbeiten des SQL-Puffers
SQL*Plus
99
Verwenden eines Texteditors Dieses zeilenorientierte Editieren ist ehrlich gesagt nicht so nach meinem Geschmack. Da ist es schon gut, dass SQL*Plus den Inhalt des SQL-Puffers mit Hilfe einer temporären Datei, die standardmäßig den Namen AFIEDT.BUF besitzt, zwischenspeichern und zur weiteren Bearbeitung an andere Programme, vor allem Editoren, übergeben kann. Welchen Editor Sie hierbei verwenden möchten, können Sie durch Ändern der Einstellung define_editor selbst festlegen: define_editor=C:\Programme\pfe\PFE32.EXE
Wenn Sie anschließend am SQL-Prompt den Befehl edit eingeben, dann setzt SQL*Plus das Kommando „C:\Programme\pfe\PFE32.EXE AFIEDT.BUF“ in Form eines Kindprozesses ab, so dass in dem Beispiel der PFE-Editor zusammen mit der Pufferdatei wie ein modales Kindfenster von SQL*Plus erscheint. Nun, was will Ihnen der Autor hiermit sagen? So lange Sie das Fenster des Editors geöffnet lassen, wird die SQL*Plus-Anwendung gesperrt, d.h. Sie können in SQL*Plus erst weiterarbeiten, nachdem Sie den Texteditor wieder geschlossen haben. Laden und Speichern von Dateien Unter Windows finden Sie im File-Menü die Einträge Open, Save und Save as, um eine als Datei gespeicherte Abfrage in den Puffer zu laden bzw. die dort zwischengespeicherten Anweisungen in einer Datei abzulegen. Je nach Betriebssystem besitzt die zugehörige SQL*Plus-Version ein solches Menü aber gar nicht. Dennoch ist das Laden oder Speichern von Dateien in bzw. aus dem SQL-Puffer möglich, indem Sie am SQL-Prompt die Anweisungen get bzw. save verwenden: get file_name [lis[t]|nol[ist]]
X X
file_name Geben Sie hier den Pfad und Namen der zu ladenden Datei ein. lis[t]|nol[ist] Mit dieser Zusatzoption können Sie vorgeben, ob die Datei nach dem Laden angezeigt wird (list = Standard) oder nicht (nolist).
sav[e] file_name [rep[lace]|app[end]]
X X
file_name Geben Sie hier den Pfad und Namen der zu speichernden Datei ein. rep[lace]|app[end] Standardmäßig versucht SQL*Plus die angegebene Datei zu erstellen. Ist die schon vorhanden, dann erhalten Sie eine Fehlermeldung und müssen dann die Option replace verwenden, um die Datei zu überschreiben. Mit der Option append wird der aktuelle SQL-Puffer an die vorgegebene Datei angehängt.
100
1.8.2
Oracle – erste Schritte
SQL*Plus als Skriptinterpreter
Genau wie beim Server-Manager können Sie auch mit Hilfe von SQL*Plus sogenannte SQL-Skripte ablaufen lassen. In einem solchen Skript werden vielleicht verschiedene Abfragen oder spezielle SQL-Befehle ausgeführt oder sogar komplexe PL/ SQL-Programme aufgerufen. Vielleicht verwenden Sie auf Ihrem Rechner ja auch gelegentlich oder sogar regelmäßig Batch-Dateien, um mit deren Hilfe regelmäßige Aufgaben, wie z.B.
X X X X
die Durchführung von Sicherungen, einen Dateiversand via FTP, das automatische Löschen bestimmter Dateien oder Verzeichnisse, oder sogar das sequentielle Starten verschiedener Programme
zu automatisieren. Die Befehle solcher Batch-Dateien werden eigentlich immer nicht direkt vom Recher bzw. dessen Betriebssystem, sondern immer mit Hilfe eines speziellen Kommandointerpreters, z.B. einem MS-DOS-Fenster unter NT, ausgeführt. In Analogie hierzu können Sie SQL*Plus als Kommandointerpreter zur Oracle-Datenbank einsetzen. Hierzu können Sie SQL*Plus zusammen mit einem Skript starten, das dann automatisch ausgeführt wird. Dieses Verfahren findet in der Praxis häufig Verwendung. Zum einen kann man hierfür spezielle Verknüpfungen auf dem Desktop oder in einer Programm-Gruppe anlegen, um dann per Mausklick immer wiederkehrende Abläufe (z.B. Reorganisationsarbeiten, Füllen von Arbeitstabellen etc.) zu starten. Zum anderen finden Sie diese Methode aber in den Batchabläufen von Produktionsumgebungen. Da werden im Rahmen der Nachtverarbeitung, gesteuert durch UNIX-Shell-Skripte, immer wieder verschiedene Abfragen oder PL/SQL-Programme gestartet. Zwischendurch laufen andere Programme wie zum Beispiel Datensicherungen oder andere Cobol- oder SQR-Anwendungen. Klar ist, dass in dem zuletzt genannten Beispiel die gesamte Verarbeitung auf dem UNIX-Server bleibt, was aufgrund der auch dort vorhandenen SQL*Plus-Version kein Problem ist. Kommanodzeilenparameter Um SQL*Plus in der eben beschriebenen Weise zu verwenden, müssen Sie das Programm zusammen mit verschiedenen Kommoandozeilenparametern verwenden. Diese Kommondozeilenparameter dienen vor allem zum Anmelden an die Datenbank bzw. zur Übergabe des zu startenden SQL-Skriptes. Im Einzelnen können Sie hinter dem Programmnamen folgende Parameter verwenden: [-s] [Logon] [Skript] [Argumente]
X
-s Dieser „ruhige Modus“ sorgt dafür, dass zum einen das Fenster von SQL*Plus im Hintergrund bleibt und zum anderen in dem Fenster keine Meldungen ausgegeben werden. Dieser Modus ist damit vor allem geeignet, wenn Sie SQL*Plus aus
SQL*Plus
X
X
X
101
einem anderen Programm heraus starten und alle benötigten Anmeldeinformationen vorhanden sind. Logon Mit diesem Parameter müssen Sie die benötigten Anmeldeinformationen übergeben. Diese Anmeldeinformationen bestehen aus dem Benutzernamen, gefolgt von einem Schrägstrich (/) und dem zugehörigen Passwort und wird mit einem Klammeraffen (@) gefolgt von der Datenbankinstanz abgeschlossen (z.B. system/manager@db01). Dabei besteht auch die Möglichkeit, das Passwort wegzulassen, damit es nach dem Start von SQL*Plus abgefragt wird (z.B. system@db01). Skript Mit diesem Parameter können Sie das zu startende SQL-Skript vorgeben. Beginnen Sie Ihre Eingabe hierbei mit einem Klammeraffen (@) und spezifizieren Sie anschließend das Skript mit geeignetem Zugriffspfad und Namen (z.B. @c:\temp\test.sql). Argumente Sofern Ihr Skript selbst weitere Argumente benötigt, dann können Sie diese jetzt vorgeben. Die im Skript verwendeten Platzhalter werden durch die übergebenen Argumente ersetzt. Dabei müssen Sie für das erste Argument den Platzhalter &1 und für alle weiteren Argumente die Platzhalter &2, &3 usw. verwenden.
Vervollständigen wir diesen Abschnitt durch ein kleines Beispiel. Betrachten Sie hierzu zunächst das kleine nachfolgende Listing (1.12), das Sie auf der Begleit-CD im \SQLPLUS-Unterverzeichnis unter dem Namen PLUS01.SQL finden. set termout off; spool &1; select name from v$datafile; spool off; set termout on; exit; Listing 1.12: Beispiel für ein kleines SQL*Plus-Skript
In dem Skript wird zunächst einmal mit Hilfe des Befehls set termout off die Ausgabe von Meldungen im SQL*Plus-Arbeitsfenster ausgeschaltet. Danach werden alle Ausgaben in die Datei &1 umgeleitet (spool). Da es sich hierbei um einen Platzhalter handelt, muss der konkrete Dateiname beim Start des Skripts als Argument übergeben werden. Danach erfolgt mit Hilfe der schon bekannten Abfrage die Ausgabe der Namen der zur Datenbank gehörenden Datenfiles. Zum Schluss wird die Ausgabeumleitung wieder aus- (spool off) und die Ausgabe im SQL*Plus-Fenster wieder eingeschaltet. Danach wird das Plus-Programm mittels Exit-Befehl beendet. Starten Sie nun, beispielsweise mit Hilfe des Ausführen-Eintrags, aus dem WindowsStart-Menü folgenden Befehl (vgl. Abb. 1.42):
102
Oracle – erste Schritte
plus80w -s system/manager@db01 @c:\temp\plus01.sql c:\temp\ausgabe.dat sqlplusw -s system/manager@db01 @c:\temp\plus01.sql c:\temp\ausgabe.dat
Abbildung 1.60: Starten von SQL*Plus mit Parametern
Als Ergebnis dieses Aufrufs erhalten Sie in dem vorgegebenen Verzeichnis die Datei AUSGABE.DAT, in der Sie die Namen aller Dateibankdateien finden. Experimentieren Sie einfach mal mit den verschiedenen Startmöglichkeiten, indem Sie beispielsweise mal den Schalter –s weglassen. Variieren Sie außerdem die verwendeten Logon-Informationen, indem Sie zum Beispiel nur system@db01 verwenden, um die Passworteingabe nach dem Programmstart zu erzwingen. Benutzereingaben Manchmal kann es sinnvoll sein, während der Ausführung eines Skripts weitere Eingaben vom Anwender abzufragen. Ein prädestiniertes Beispiel hierfür sind Installationsskripte, in denen am Anfang verschiedene Installationsparameter abgefragt werden. Solche Eingaben können Sie zur Laufzeit mit Hilfe des AcceptBefehls vom Benutzer einfordern: acc[ept] variable [num[ber]|char|date] [for[mat] format] [def[ault] default] [prompt text]
X X X
variable legt den Namen der Variable fest, in der die Eingabe gespeichert wird. Innerhalb des Skripts können Sie die Variable test über die Zeichenfolge &test ansprechen. num[ber]|char|date Legen Sie hier bei Bedarf den Datentyp Ihrer Variablen fest. Wenn Sie nichts vorgeben, dann wird der Typ char angenommen. for[mat] format Legen Sie hier bei Bedarf das Format für Ihre Eingabe fest. Bei Zeichenfolgen (char) haben Sie hierbei die Möglichkeit die maximale Länge der Eingabe festzulegen, indem Sie als Format beispielsweise A10 für eine maximal zehnstellige Eingabe verwenden. Bei Datums- oder Zeitwerten (date) können Sie das Eingabeformat mit Hilfe der Buchstaben „DD“ (Tage), „MM“ (Monate) und „YY“ bzw. „YYYY“ für das Jahr (z.B. DD.MM.YYYY) oder „HH24“ (Zeit im 24-Stundenmodus), ‚MI“ (Minuten) und „SS“ für die Sekunden (z.B. HH24:MI:SS) festlegen.
SQL*Plus
X X
103
Beim Eingabeformat numerische Werte legen Sie mit Hilfe der Ziffer 9 und dem Punkt (.) als Dezimaltrennzeichen vor allem wieder die maximale Länge der Zahl bzw. die Anzahl der Nachkommastellen fest (z.B. 999.99). def[ault] default Legen Sie hier einen Standardwert fest, der genommen wird, wenn Sie die Eingabeaufforderung mit Hilfe der Eingabetaste übergehen. prompt text Geben Sie hier den Text ein, der während der Eingabeaufforderung am Bildschirm angezeigt wird.
Wir wollen nun unser kleines Beispiel erweitern, in dem wir die in einem vom Benutzer vorgegebenen Tablespace angelegte Tabellen ausgeben (vgl. Listing 1.13, PLUS02.SQL). acc tname char format 'A20' default 'SYSTEM' prompt 'Name des Tablespace: '; set termout off; spool &1; select name from v$datafile; select table_name from dba_tables where tablespace_name = '&tname'; spool off; set termout on; exit; Listing 1.13: Benutzereingaben während des Skripts abfragen
Am Anfang des Listings wird die Eingabe des Benutzers in der Variablen tname gespeichert, die etwas später in einer Abfrage als &tname Verwendung findet. Mit Hilfe des Objekts dba_tables werden die Namen der Tabellen angezeigt, die in dem vorgegebenen Tablespace gespeichert sind; ansonsten entspricht das Beispiel der Datei PLUS01.SQL. Befehle des Betriebssystems verwenden Wie Sie mittlerweile wissen, können Sie SQL*Plus innerhalb eines Betriebssystemskripts verwenden, um eine Abfrage oder ein SQL-Programm zu starten. Das Ganze geht auch umgekehrt, d.h. Sie können innerhalb eines SQL-Skripts einen Betriebssystembefehl absetzen bzw. ein entsprechendes Skript starten. Testen Sie das doch gleich einmal und geben Sie am SQL-Prompt die folgende Anweisung ein: ho[st] calc.exe
Hierdurch starten Sie aus SQL*Plus heraus den Windows-Taschenrechner. Genau wie beim Start des Editors wird das aufgerufene Programm hierbei wieder synchron aufgerufen, d.h. SQL*Plus wartet so lange mit der weiteren Ausführung der restlichen Zeilen im SQL-Puffer, bis das gestartete Programm wieder beendet wurde.
104
Oracle – erste Schritte
Für dieses Verfahren gibt es in der Praxis eine Menge Einsatzmöglichkeiten. Zum Beispiel können Sie hierdurch das Kopieren oder den Ftp-Versand einer erstellten Ausgabedatei aus Ihrem SQL-Programm heraus veranlassen. Oder Sie verschicken automatisch ein E-Mail, nachdem Sie mit Hilfe verschiedener Abfragen eine spezielle Arbeitstabelle erstellt haben. Eine weitere Möglichkeit besteht in der Erstellung einer Datensicherung, bevor Sie eine komplexe Änderungsabfrage starten, indem Sie zunächst den zugehörigen Tablespace inaktivieren und anschließend die zugehörigen Dateien kopieren. Danach aktivieren Sie den Tablespace wieder und führen Ihre Änderungsabfragen durch. Hier und jetzt möchte ich diese Möglichkeit jedoch nur mit Hilfe eines einfachen Beispiels demonstrieren und hierzu das letzte Beispiel noch einmal ein wenig erweitern (vgl. Listing 1.14, PLUS03.SQL und 1.15, NACHLAUF.BAT). acc tname char format 'A20' default 'SYSTEM' prompt 'Name des Tablespace: '; set termout off; spool &1; select name from v$datafile; select table_name from dba_tables where tablespace_name = '&tname'; spool off; set termout on; ho c:\temp\nachlauf.bat &1; Listing 1.14: Starten der Batchdatei NACHLAUF.BAT
In dem Beispiel neu hinzugekommen ist lediglich die letzte Zeile mit dem ho bzw. host-Befehl. Dieses Kommando erlaubt den Aufruf eines beliebigen Betriebssystembefehls, also auch den Start einer ganzen Batch- bzw. Shellskripts. In unserem Beispiel wird die Batchdatei NACHLAUF.BAT, der beim Aufruf der Name der Ausgabedatei in Form des Parameters &1 übergeben wird. echo jetzt läuft die Batchdatei copy %1 c:\temp\protokoll.txt dir c:\temp rem ... beliebige weitere Kommandos pause Listing 1.15: Ausdruck der Batchdatei NACHLAUF.BAT
In dieser Batchdatei können Sie natürlich alle möglichen Befehle des jeweiligen Betriebssystems verwenden. In unserem Beispiel verwenden wir verschiedenen DOS-Befehle, so dass Sie es ohne große Anpassungen auf jedem Windows-Rechner verwenden können. Während der Ausführung wird die im SQL-Skript erstellte Ausgabedatei, die dem Batch als Parameter übergeben und dort mit Hilfe der Variablen %1 angesprochen werden kann, zur Datei PROTOKOLL.TXT kopiert. Am Ende wird die Batchausführung durch den Befehl pause angehalten, so dass das zugehörige MS-DOS-Fenster erst nach einem Tastendruck geschlossen wird, wonach Sie nach SQL*Plus zurückkehren.
Das SQL-Worksheet
1.9
105
Das SQL-Worksheet
Im Rahmen der Installation des „Oracle Enterprise Managers” erhalten Sie mit dem SQL-Worksheet einen weiteren SQL-Editor (vgl. Abb. 1.61 oder 1.62), der in der 8iVersion wieder in einem neuen Layout vorliegt. Zwar lässt dieser Editor ein komfortableres Bearbeiten von Programmen oder Abfragen zu, aber dennoch ist er kein vollständiger Ersatz für SQL*Plus, weil ihm vor allem die im Kapitel 1.8.2 beschriebenen Fähigkeiten fehlen, als Batchschnittstelle zwischen Datenbank und externen Abläufen zu fungieren.
Abbildung 1.61: Das SQL-Worksheet der 8er-Version
Nach dem Start des Programms erscheint zunächst ein kleines Fenster, mit dessen Hilfe Sie sich an einer Oracle-Datenbank anmelden können. Danach, d.h. nach erfolgreicher Datenbankanmeldung, erhalten Sie mit dem SQL-Worksheet im Prinzip zwei Fenster. Bei der 8er-Version dient das untere Eingabefenster als FullscreenEditor zum Eingeben bzw. Bearbeiten der benötigten Befehle und nach deren Ausführung werden die Ergebnisse im oberen Ausgabefenster dargestellt. In der 8i-Version ist das genau umgekehrt, d.h. im oberen Fenster wird getippt und im unteren geschaut. Die beiden Arbeitsfenster werden in der Mitte des Programmfensters durch eine Linie getrennt und sind standardmäßig gleich groß, wobei Sie deren Aufteilung mit Hilfe dieser Linie und der Maus beliebig verändern können.
106
Oracle – erste Schritte
Abbildung 1.62: SQL-Worksheet im neuen 8i-Look
Der prinzipielle Umgang mit dem SQL-Worksheet sollte eigentliche überhaupt keine Probleme bereiten. Besondere oder verborgene Funktionalitäten gibt es eigentlich nicht, deshalb möchte ich mich an dieser Stelle auch ziemlich kurz fassen. Alle Funktionen lassen sich entweder mit Hilfe des vorhandenen Menüs oder der beispielsweise links unten bzw. oben sichtbaren Icons aufrufen. Besonders praktisch ist, dass das Ausführen- bzw. Execute-Menü mit der Funktionstaste F5 verknüpft wurde, so dass Sie die erfassten SQL-Anweisungen quasi auf Knopfdruck starten können. Mit Hilfe der File- bzw. Datei-Menüeinträge können Sie den Inhalt des Eingabefensters löschen, eine SQL-Datei in dieses Fenster hineinladen oder den aktuellen Inhalt speichern bzw. unter einem neuen Namen abspeichern. Besonders praktisch ist auch, dass die zuletzt ausgeführten SQL-Anweisungen im SQL-Worksheet gepuffert werden und bei Bedarf jederzeit wieder mit Hilfe der Befehlshistorie wieder abgerufen werden können. Hierzu finden Sie im Worksheetbzw. Arbeitsblatt-Menü entsprechende Menüeinträge. Mit Hilfe des Clear All- bzw. Alles löschen-Eintrags aus dem Edit- bzw. BearbeitenMenü können Sie den Inhalt des Eingabe- oder Ausgabefensters löschen. Welches Fenster hierbei gelöscht wird, das hängt davon ab, in welchem der beiden Fenster Ihr Cursor stand bevor Sie den entsprechenden Menüeintrag ausgewählt haben. Klicken Sie also zunächst in das jeweilige Eingabe- oder Ausgabefenster und wählen
Das SQL-Worksheet
107
Sie danach den eben beschriebnen Menüeintrag, um das Fenster bzw. seinen Inhalt zu löschen. Ich glaube, das waren genug Informationen, um mit der Arbeit des SQL-Worksheets problemlos zurecht zu kommen. Falls nicht, dann finden Sie in der Dokumentation des Oracle Enterprise Managers noch ein paar weitere Informationen.
2
Datenbankobjekte in einer Oracle-DB
Ich denke, dass ich kein großes Geheimnis lüfte, wenn ich sage, dass Oracle genau wie zumindest auch alle anderen relationalen Datenbank seine Daten mit Hilfe von Tabellen speichert. Aber genau wie in den anderen Datenbanksystemen auch sind Tabellen natürlich nicht das Einzige, was Sie bei genauerem Hinschauen finden werden. In diesem Teil des Buches werden ich zunächst alle vorhandenen Datenbankobjekte kurz vorstellen bzw. erklären. Im weiteren Verlauf werden die für unsere Beispieldatenbank benötigten Objekte angelegt und danach werden die auf der CD befindlichen Daten in die angelegten Tabellen geladen.
2.1
Der Oracle-Schema-Manager
Im Rahmen der Installation des Oracle Enterprise-Managers erhalten Sie ein Werkzeug, mit dem Sie alle verfügbaren Datenbankobjekte anlegen bzw. bearbeiten können. Es handelt sich bei der Version 8 hierbei um den Oracle Schema Manager (VAS.EXE), dessen Programmsymbol Sie üblicherweise in der Programmgruppe „Oracle Enterprise Manager“ finden (vgl. Abb. 2.1). Sofern Sie eine 8i-Datenbank verwenden, dann finden Sie die gleichen Funktionalitäten jetzt im neuen DBA-Studio (vgl. Abb. 2.2). Nun kann man sich streiten, ob es sinnvoll ist, irgendwelche Datenbankdefinitionen mit Hilfe eines solchen Werkzeugs durchzuführen. Betrachten Sie einmal die Abbildung 2.1, in der das Fenster zum Bearbeiten des Programmcodes einer OracleDatenbankprozedur gezeigt wird. Ich glaube, man braucht nicht viel Phantasie um sich vorstellen zu können, dass der zugehörige Programmeditor in Form des kleinen Textfensters schon bei Programmen mit wenigen Zeilen alles andere als hilfreich ist.
110
Datenbankobjekte in einer Oracle-DB
Abbildung 2.1: Übersicht der Objekte mit Hilfe des Oracle-Schema-Managers
Abbildung 2.2: Darstellung des Schema-Managers im DBA Studio der 8i-Version
Der Oracle-Schema-Manager
111
Des weiteren ist es in vielen Unternehmen nicht üblich, Datenbankdefinitionen auf Basis manueller, nicht nachvollziehbarer und vor allem nicht automatisch wiederherstellbarer Einzelschritte durchzuführen. Entweder ist das konkrete Anwendungsprogramm aufgrund dort gespeicherter Definitionen in der Lage, die Datenbank mit Hilfe entsprechender SQL-Befehle oder eines generierten Skripts zu erzeugen oder die zur Erstellung der einzelnen Objekte benötigten Statements werden manuell in einem oder mehreren SQL-Skripts gesammelt. In jedem Fall hat diese Vorgehensweise gegenüber der manuellen Erstellung über den Schema Manager bzw. dem DBA-Studio den Vorteil, dass die gesamte Datenbank im Bedarfsfall automatisch generiert werden kann, indem beispielsweise einfach alle gesammelten Erstell- bzw. Änderungsskripts nacheinander abgespielt werden. Trotzdem hat das hier kurz beschriebene Programm natürlich seine Daseinsberechtigung: Der Oracle-Schema-Manager bzw. das DBA-Studio bietet eine Visualisierung aller in der Datenbank enthaltenen Objekte. Dies ist unendlich komfortabeler, als sich beispielsweise alle Einzelheiten mit Hilfe diverser Abfragen aus den Systemtabellen herauszufummeln. Sie können das Programm als Hilfstrainer verwenden. Immer wenn Sie ein Objekt erstellen oder bearbeiten, finden Sie unterhalb des zugehörigen Dialogs die Schaltfläche Show SQL bzw. SQL anzeigen, die Sie auch schon vom Oracle Storage Manager her kennen. Genau wie dort, führt zu einem Dialog, in dem Sie die zu Ihrer Erstellung oder Änderung passende SQL-Anweisung finden. Nicht zu verachten sind auch die sich hinter der Help- bzw. Hilfe-Schaltfläche verbergenden Informationen. Die zugehörige Online-Hilfe enthält zeigt nämlich nicht lapidare Hinweise zur Programmbedienung, sondern erklärt die Ausprägungen der verschiedenen Eigenschaften des aktuell geöffneten Objekts. Zur grundsätzlichen Bedienung gibt es eigentlich nicht viel zu sagen, das Programm entspricht im Aussehen und in der Funktionsweise dem schon vorgestellten Storage Manager. Außerdem fühlen wir Windows-Explorer gewöhnten Anwender uns gleich zu Hause: links der Baum, rechts die zum Blatt gehörenden Details. Ein letzter Hinweis zu dem Programm muss allerdings noch sein. Mit Hilfe der links im Bild befindlichen Liste werden alle Objekte katalogisiert, die in der zugehörigen Oracle-Datenbank möglich sind. Wundern Sie sich jedoch nicht, falls Sie im konkreten Fall mal weniger oder vielleicht auch mal mehr sehen. Die zur Verfügung stehenden Objekte hängen zum Teil nämlich von besonderen Zusatzfeatures ab, d.h. sie sind erst verfügbar, wenn Sie die entsprechenden Zusatzpakete installiert haben. Dies gilt vor allem für die neuen objektorientierten Elemente, wie zum Beispiel die Array Types, Object Types oder Table Types. Sollten Sie die zugehörigen Kategorien in der Übersicht Ihres Schema-Managers vermissen, dann müssen Sie nicht nach einem Installationsfehler suchen oder unbedingt nach einer neueren Version fahnden, denn sie sind nur dann verfügbar, wenn auf dem Datenbankserver die OracleObjekte installiert wurden, wobei es sich hierbei früher um ein extra zu beziehen-
112
Datenbankobjekte in einer Oracle-DB
des und damit meistens wohl auch kostenpflichtiges Erweiterungspaket handelte. Nach meinem Kenntnisstand sind die Objekte heute in aktuellen 8er-Versionen immer enthalten aber es gibt auch noch andere Features wie zum Beispiel „Advanced replication“ oder „Parallel Server“, die nicht in jeder Version verfügbar sind. Fragen Sie im Zweifel direkt bei Oracle nach, wenn Sie gerade darüber nachdenken eine eigene Oracle-Lizenz zu erwerben und unbedingt Wert auf die Objekt-Optionen legen. Ich werde dieses Feature im Rahmen der gleich folgenden Schema-Übersicht abschließend abhandeln, d.h. sowohl bei der Anlage unserer Musterdatenbank als auch in den noch folgenden Kapitel werden die vorgestellten Beispiele keine Objekte beinhalten. Auch wenn diese Features zum heutigen Stand zumindest für ein Datenbanksystem beeindruckend sind, so spielen sie im Zusammenhang mit Standardsoftware im Augenblick noch keine Rolle. Solche Standardpakete nutzen meistens sowieso nur wenige spezielle Funktionen, da sie darauf ausgelegt sind, auf möglichst vielen unterschiedlichen Datenbanksystemen zu laufen. Aber zurück zum Thema. Wollen Sie nun ganz sicher gehen, ob Sie diese ObjektOption haben oder nicht, dann können Sie dies mit Hilfe einer Abfrage der View v$option klären: SQLWKS> select * from v$option; PARAMETER ---------------------------------------------------------------Partitioning Objects Parallel Server Advanced replication Bit-mapped indexes Connection multiplexing Connection pooling Database queuing Incremental backup and recovery Instead-of triggers Parallel backup and recovery Parallel execution Parallel load Point-in-time tablespace recovery
VALUE -----TRUE TRUE FALSE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
14 rows selected. Listing 2.1: Abfrage der installierten bzw. verfügbaren Optionen
Was ist ein Schema? Was ist überhaupt ein Schema? Ich habe den Begriff bisher selbst schon ein paar Mal verwendet, aber bisher noch nicht erklärt. Sofern Sie bisher nur mit anderen Datenbanksystemen gearbeitet haben, dann hören Sie diesen Begriff hier vielleicht zum ersten Mal, obwohl Sie das damit gemeinte Prinzip mit Sicherheit kennen. Hinter diesem Begriff verbirgt sich eigentlich nur die logische Zusammenfassung
Beschreibung der Objekte
113
von Datenbankobjekten wie Tabellen, Indizes oder Prozeduren zu einer Einheit, eben diesem sogenannten Schema. Technisch wird das in Oracle, wie übrigens in den meisten anderen Datenbanken auch, durch einen speziellen Benutzer (engl. User) realisiert, dem alle zum Schema gehörenden Objekte gehören, also Eigentümer dieser Objekte ist. Aus technischer Sicht kann man also Schema und Benutzer gleichsetzen, d.h. wenn Sie im Rahmen einer Installationsanleitung lesen, dass Sie zunächst ein bestimmtes Schema anlegen müssen, dann ist damit die Anlage eines sicherlich meistens mit speziellen Rechten ausgestatteten Users gemeint, der Eigentümer der während der Installation anzulegenden Objekte wird. Oftmals handelt es sich bei einem solchen Schema um einen speziellen technischen Benutzer, d.h. mit Hilfe der zugehörigen Benutzer-Id wird nicht wirklich in der Datenbank gearbeitet, sondern diese Id dient nur zum Vorhalten der benötigten Datenbankobjekte oder wird auch zu deren Wartung bzw. Änderung verwendet. Wenn man dieses Konzept durchhält, dann entsteht zwangsläufig eine gewisse Ordnung in der Datenbank. Dies gilt vor allem dann, wenn mehrere verschiedene Produkte in einer Oracle-Datenbank installiert wurden. Nehmen wir einfach mal an, die Tabellen und sonstigen Objekte der HR-Software PeopleSoft werden mit Hilfe des Benutzers psoft, die Objekte des Weiterbildungsprodukts Pisa unter Verwendung des gleichnamigen Benutzers und schließlich auch noch eine SAP-Anwendung, über den User saphr installiert. Jedes dieser Produkte generiert für sich zum Teil mehr als tausend verschiedene Datenbankobjekte, die allerdings wegen der drei unterschiedlichen Schematas jederzeit den einzelnen Anwendungen zugeordnet werden können. Betrachten Sie unter diesem Eindruck noch einmal das Bild des Schema-Managers (vgl. Abb. 2.1 oder 2.2). Die in der Datenbank vorhandenen Prozeduren werden mit Hilfe des links im Bild befindlichen Baums den jeweiligen Eigentümern, sprich dem Schema, zugeordnet, so dass eine sehr gute Übersicht entsteht. Zum Schluss möchte ich noch auf ein weiteres kleines Schmankerl hinweisen, das dieses Schema-Konzept mit sich bringt. Nichts währt für die Ewigkeit, und so kommt irgendwann zwangsläufig die Stunde, in der eine Anwendung gelöscht werden soll, d.h. es müssen alle zugehörigen Objekte aus der Datenbank entfernt werden. Nichts einfacher als das, denn Sie müssen in dem Fall lediglich das entsprechende Schema, also den zugehörigen Benutzer, aus der Datenbank löschen. Hier gibt es nichts zu erben, d.h. mit dem Ableben des Besitzers verschwinden auch alle dessen Besitztümer.
2.2
Beschreibung der Objekte
Ich weiß nicht, wie es Ihnen geht, aber ich bin immer froh, wenn ich beim erstmaligen Kontakt mit einer neuen Datenbank eine Übersicht der vorhandenen Möglichkeiten finde, um rasch den Brückenschlag aus der alten in die neue Datenbankwelt hinzukriegen. Mit Möglichkeiten beschränke ich mich hierbei natürlich auf die Fragestellung, was Sie alles in einer Oracle-Datenbank anlegen können. Auch
114
Datenbankobjekte in einer Oracle-DB
wenn eine solche Übersicht zunächst immer nur einen oberflächlichen Eindruck hinterlässt, so hilft Sie dennoch, die folgenden ersten Gehversuche zu vereinfachen bzw. beschleunigen. Bestimmte Themen klassifiziert man sofort als wichtig und arbeitet sich folglich tiefer in die entsprechende Materie ein. Andere Themen bleiben im Hinterkopf und können bei Bedarf weitergehender erforscht werden. Mit Hilfe der folgenden Abschnitte will ich versuchen eine solche Übersicht zu erstellen, in dem ich alle Objekttypen, die Sie mit Hilfe des Oracle Schema Managers erstellen können kurz beschreibe und auf die SQL-Befehle, die Sie zum Erstellen bzw. Bearbeiten der Objekte benötigen verweise, bzw. diese in den beschriebenen Beispielen verwende. Wie Sie im Verlauf des ersten Kapitels schon bemerkt haben dürften, halte ich mich mit der Darstellung und Erklärung umfangreicher Syntax-Diagramme zu den einzelnen Oracle-Befehlen sehr zurück. Dies wird sich auch hier bzw. im weiteren Verlauf des Workshops nicht ändern. Die genaue Syntax eines Befehls können Sie mit all seinen Varianten am besten in den Unterlagen des Herstellers nachlesen und würde in diesem Buch nur kostbaren Platz verbrauchen. Das größere Problem liegt zumindest nach meiner Erfahrung meistens darin, in Erfahrung zu bringen, welche Befehl einem bei dem aktuellen Vorhaben bzw. Problem weiterhelfen könnte. Kennt man erst einmal den Befehl, dann kann man die genaue Verwendung und Kodierung leicht mit Hilfe der Programmdokumentation nachschlagen. Weitergehende Informationen zur genauen Syntax bzw. Verwendung eines Befehls finden Sie in der Oracle-Dokumentation im Buch „Oracle8 SQL Reference“ und tiefergehende Beschreibungen über die Verwendung der einzelnen Objekte verbergen sich hinter dem Link „Oracle8 Concepts“. Bezüglich der Programmierobjekte (Funktionen, Prozeduren und Trigger) finden Sie auch in diesem Buch, und zwar im fünften Kapitel Kapitel „PL/SQL-Programmierung“, weitergehende Informationen. Dort finden Sie neben weiteren Beispielen auch eine systematische Einführung in PL/SQL. Die nachfolgenden Beispiele (Listings) finden Sie alle auf der zum Buch gehörenden Begleit-CD. Sofern nichts anderes angegeben ist, dann finden Sie die Beispiele dieses Kapitels im \SCHEMA-Unterverzeichnis finden, wobei die Dateinamen den Listing entsprechend, d.h. die Datei für das Listing 2.1 heißt entsprechend LISTING21.SQL. Bei allen Beispielen müssen Sie natürlich beachten, dass Sie vor deren Anwendung spezielle individuelle Einstellungen wie beispielsweise Tablespacenamen überprüfen und ggf. anpassen müssen.
2.2.1
Array-Typ (Array Types)
Wie ich schon eingangs erwähnt habe, handelt es sich hier um eine Kategorie, die Sie nur verwenden können, wenn auf Ihrem Datenbankserver die Objekt-Erweiterungen installiert wurden. Worum geht es aber nun bei diesen Array Types. Wenn Sie aus der Programmierung kommen, dann sagt Ihnen wahrscheinlich alleine schon der Begriff Array ziemlich deutlich, worum es hier geht. In deutschen Übersetzungen spricht man in dem Zusammenhang meistens von Datenfeldern, und
Beschreibung der Objekte
115
damit wir alle über das gleiche Sprechen, habe ich mal eine Definition des Begriffs Datenfeld ausgegraben: Datenfeld: Ein Satz aufeinanderfolgender, indizierter Elemente, die den gleichen Datentyp besitzen. Jedes Element eines solchen Feldes wird durch eine eindeutige Index-Nummer identifiziert. Würde ich ein Buch zu einer Programmiersprache erstellen, dann hätte ich zur Veranschaulichung des Arrays bzw. Datenfeldes vielleicht sogar den Begriff der Tabelle ins Spiel gebracht, aber Tabellen sind in einer relationalen Datenbank wirklich etwas ganz anderes. Mit Hilfe der Array Types haben Sie nun die Möglichkeit neue (eigene) Datentypen in Form von Datenfeldern zu definieren. Ein solcher selbstdefinierter Datentyp kann anschließend innerhalb eines SQL-Programms, das alleine wäre gar nicht so aufregend, und auch bei der Anlage von Tabellen verwendet werden. Beispiele Nehmen wir an, Sie möchten oder müssen eine Mitarbeitertabelle entwerfen, in der die individuelle Wochenarbeitszeit gespeichert werden kann. Da wir ja mittlerweile alle flexibel sind, reicht hierfür ein einfaches numerisches Feld zum Speichern der Wochenstundenzahl meistens nicht mehr aus. Stattdessen benötigen wir eher sieben numerische Felder, um die festgelegte Arbeitszeit pro Wochentag abzuspeichern (vgl. Listing 2.2). drop table ma_arbeitszeit; / create table ma_arbeitszeit ( persnr varchar2(11) not null, sonntag number, montag number, dienstag number, mittwoch number, donnerstag number, freitag number, samstag number ) tablespace usr storage (initial 10000 next 10000 maxextents unlimited pctincrease 0) / commit; Listing 2.2: Anlegen einer Tabelle zum Speichern der Wochenstundenzahl
Mit Hilfe einer solchen Tabelle könnten Sie nun für jeden Mitarbeiter die individuelle Wochenarbeitszeit abspeichern. Betrachtet man nun die sieben Einzelfelder zum Speichern der Tagesstunden, so ist das gemäß der eben beschriebenen Definition ein erstklassiger Kandidat für ein Datenfeld. Statt Sonntag, Montag, Dienstag
116
Datenbankobjekte in einer Oracle-DB
usw. sprechen wir dann nur noch von Tag(0), Tag(1), Tag(2), d.h. der Index des Tag -Datenfeldes legt den genauen Wochentag fest. Das Feature der Array Types ermöglichen Ihnen nun genau ein solches Datenfeld anzulegen, indem Sie folgende Anweisung eingeben: create type tag as varray(7) of number; / commit; Listing 2.3: Anlegen des Datenfeld-Datentyps „tag“
Anschließend können Sie die Arbeitszeittabelle dann mit Hilfe des nun folgenden Skripts definieren: drop table ma_arbeitszeit; / create table ma_arbeitszeit ( persnr varchar2(11) not null, az_tag tag ) tablespace usr storage (initial 10000 next 10000 maxextents unlimited pctincrease 0) / commit; Listing 2.4: Anlegen der Tabelle unter Verwendung des neuen Datentyps
Der wesentliche Unterschied zur vorhergehenden Version dieser Tabelle ist die Verwendung des neuen Datentyps tag anstelle der sieben numerischen Felder. Das hat natürlich Auswirkungen auf die Verwendung der Tabelle beim Einfügen oder Änderung der dort gespeicherten Daten (CD: LISTING24A.SQL). insert into ma_arbeitszeit values ('4711',tag(0, 7.5, 7.5, 7.5, 7.5, 7.5, 0)); insert into ma_arbeitszeit values ('4712',tag(0, 0, 9, 7.5, 7.5, 4.5, 2)); insert into ma_arbeitszeit values ('4713',tag(3, 7.5, 7.5, 0, 0, 0, 0)); commit; update ma_arbeitszeit set az_tag=tag(3, 7.5, 7.5, 4, 0, 0, 0) where persnr='4713'; commit;
Sowohl beim Einfügen neuer Datensätze als auch bei deren Änderung müssen Sie für das neue Feld az_tag das komplette Datenfeld vorgeben. Hierbei müssen Sie den Namen des Datentyps und danach alle Werte des Datenfeldes in Klammern und durch Komma getrennt spezifizieren.
Beschreibung der Objekte
117
Wollen Sie das Datenfeld jedoch im Rahmen eines SQL-Ausdrucks oder für die Ausgabe innerhalb einer Auswahlabfrage verwenden, dann haben Sie allerdings noch ein kleines Problem, denn hierbei können Datenfelder nicht direkt eingesetzt werden, sondern Sie benötigen hierzu PL/SQL-Sprachelemente. In unserem Beispiel könnte wir als Abhilfe also eine kleine Funktion schreiben, mit der die gewünschten Elemente aus dem Datenfeld herauskopiert werden (vgl. Listing 2.5). Im Vorgriff auf das übernächste Kapitel soll die Funktion hier schon einmal erstellt werden. create or replace function get_az(in_arr in tag, in_tag in number) return number is begin return in_arr(in_tag); end; / show errors; commit; Listing 2.5: Erstellen einer Funktion zur Abfrage des Datenfeldes
Zur Funktion selbst möchte ich an dieser Stelle nicht allzu viel sagen. Im weiteren Verlauf dieses Streifzugs durch die Schema-Objekte der Oracle-Datenbank werden Sie weitergehende Hinweise über die Erstellung und Verwendung von Funktionen finden. Das Problem war, dass Oracle beispielsweise im Rahmen des select-Befehls keine Datenfelder zulässt. Allerdings können Sie dort Funktionen verwenden und diese können wiederum Datenfelder als Parameter übernehmen, so dass wir unser Problem über diesen Umweg lösen können. Insgesamt übergeben wir unserer Funktion get_az zwei Parameter. Der erste Parameter erwartet das Datenfeld und mit Hilfe des zweiten Wertes übergeben wir der Funktion den Index, für den die Funktion den Wert aus dem Feld zurückliefern soll. Zur Vereinfachung verzichten wir innerhalb der Funktion auf jeglichen Komfort beispielsweise einer Wertebereichsprüfung für den übergebenen Index. Warum die direkte Verwendung des varray-Feldes im select-Ausdruck zu Problemen führt wird klar, wenn man sich einmal die Tabellenbeschreibung anschaut: SQLWKS> desc ma_arbeitszeit Column Name Null? ------------------------------ -------PERSNR NOT NULL AZ_TAG
Type ---VARCHAR2(11) RAW(192)
Wie Sie sehen, wird das Datenfeld intern mit Hilfe des Datentyps raw gespeichert. Normalerweise werden mit diesem Typ jegliche Binärdaten bzw. Bytekette wie zum Beispiel Grafiken oder Klänge gespeichert, d.h. die dort gespeicherten Daten sind in jedem Fall interpretationsbedürftig und damit nicht ohne weiteres anzeigefähig.
118
Datenbankobjekte in einer Oracle-DB
Schreiten wir also zur Verwendung unsere neuen Funktion und listen alle Mitarbeiter mit ihrer jeweiligen Dienstagsarbeitszeit an: SQLWKS> select persnr, get_az(persnr, 2) from ma_arbeitszeit; PERSNR GET_AZ(PER ----------- ---------4711 7.5 4712 0 4713 7.5 3 rows selected.
Mit Hilfe des nächsten Abfragebeispiels suchen wir in unserer Tabelle Mitarbeiter, die am 12.07.2000 zumindest laut der vereinbarten Arbeitszeit anwesend sind: SQLWKS> select persnr 2> from ma_arbeitszeit 3> where get_az(persnr, to_number(to_char(to_date('2000.07.12','YYYY.DD.MM'),'d'))) > 0; PERSNR ----------4711 4712 2 rows selected.
Diesmal verwenden wir unsere neue Funktion get_az innerhalb der where-Klausel der SQL-Abfrage. Das sieht zugegebener Weise auf den ersten Blick nicht so einfach aus, ist aber in Wirklichkeit gar nicht so schlimm wie es vielleicht scheint. Machen wir uns also die Mühe, und lösen die verwendete Schachtelfunktion einmal auf (vgl. Abb. 2.3). get_az(persnr, 4) to_number(‘4’) to_char(2000.07.12,'d') to_date('2000.07.12','YYYY.DD.MM') Abbildung 2.3: Entschachteln der verwendeten Funktionskette
Der innerste Funktionsaufruf verwendet die Standardfunktion to_date, um eine Zeichenfolge entsprechend der vorgegebenen Formatierung in ein Datum umzuwandeln. Dieses Datum erhält die Standardfunktion to_char als Eingabeparameter und wandelt das Datum wieder in eine Zeichenkette um. Aufgrund des diesmal verwen-
Beschreibung der Objekte
119
deten Formatcodes erhalten wir hierbei den Tag der Woche (Sonntag = ‚0’, Montag = ‚1’ usw.), den wir der Funktion to_number als Eingabeparameter übergeben, und die das übergebene Zeichen als Zahl zurückliefert. Noch ein Hinweis: Bei der Anlage solcher Datenfelder können Sie nicht nur die standardmäßig in Oracle enthaltenen Datentypen (Zeichen, Zahlen, Datumswerte usw.), sondern auch alle selbstdefinierten Typen bzw. Objekte verwenden.
2.2.2
Cluster (Clusters)
Die sogenannten Cluster bieten in Oracle eine Methode zusammengehörige Daten in einem gemeinsamen Bereich, eben diesem Cluster, zu speichern. Die im Cluster gespeicherten Tabellen sollten also gemeinsame Spalten aufweisen, wobei als weiteres Kriterium hinzukommt, dass die Tabellen meistens auch zusammen gelesen werden. Letzteres ist der Fall, wenn die Tabellen immer wieder über die gemeinsamen Felder verknüpft werden oder mit Hilfe dieser Felder referentielle Integritätsbedingungen definiert wurden. Potentielle Kandidaten für das Anlegen solcher Cluster sind damit Tabellen, die als Referenz in vielen andere Tabellen vorkommen. Technisch betrachtet stellt ein solcher Cluster zunächst einmal einen abgegrenzten Speicherbereich innerhalb eines vorhandenen Tablespaces dar und wird mit Hilfe der Anweisung create cluster erstellt bzw. mit alter cluster geändert und via drop cluster gelöscht. Beim Anlegen der einzelnen Tabellen werden dann für die gemeinsamen Daten zusätzlich die zu verwendenden Cluster vorgegeben. Nehmen wir als Beispiel mal ein Abrechnungssystem und betrachten dort das Feld „Kostenstelle“. Dieses Feld ist zum einen der Primärschlüssel der zugehörigen Tabelle „Kostenstellen“, in der die zugehörigen Stammdaten (Bezeichnung, Gültigkeit, Vorgesetzter usw.) gespeichert werden. Weiterhin enthält auch der Mitarbeiterstamm eine Kostenstelle und auch in der Tabelle der Abrechnungsergebnisse ist eine Kostenstelle enthalten. Wird nun die Mitarbeiterkostenstelle ausgewertet, so benötigt man fast immer auch die Kostenstellentabelle selbst, um beispielsweise auch die Kostenstellenbezeichnung anzuzeigen oder die Auswertung nach Vorgesetzten zu sortieren. Zusammen mit der Kostenstelle werden die Tabellen Mitarbeiterdaten und Kostenstellen also meistens gemeinsam benötigt. Das Gleiche gilt auch bei den Abrechnungsergebnissen. Spielt hier bei einer Auswertung die Kostenstelle eine Rolle, dann benötigt man aus den eben genannten Gründen meistens auch noch die Kostenstellentabelle. Folglich könnte man in dem Fall einen Cluster für das Feld Kostenstelle anlegen und anschließend die drei Tabellen diesem Cluster zuordnen. Auch wenn man spontan einen anderen Eindruck bekommen könnte: Das Clustern von Tabellen hat keinen Einfluss auf deren sonstige Verwendbarkeit. Sie müssen also bei einer Abfrage nicht wissen oder berücksichtigen, ob eine Tabelle geclustert wurde oder nicht. Eine Gesamtübersicht über die in der Datenbank vorhandenen Cluster erhalten Sie außer mit dem Schema-Manger oder DBA Studio durch eine Abfrage der View all_clusters.
120
2.2.3
Datenbankobjekte in einer Oracle-DB
Datenbank-Link (Database Links)
Wie Sie mittlerweile wissen, stellen verschiedene Oracle-Datenbanken selbst auf einem Rechnerknoten eigenständige und voneinander abgeschottete Datensammlungen dar. In manch anderen Datenbanksystem ist das anders und entsprechend einfach ist es, beispielsweise innerhalb einer Abfrage auch Daten aus einer anderen, aber in dem DBMS registrierten, Datenbank zu verwenden. Dafür wird es meistens sehr viel schwieriger, wenn sich die benötigte Datenbank nicht auf dem gleichen Rechnerknoten bzw. unter Hoheit des gleichen DBMS befindet. Unter Oracle benötigen Sie für den Zugriff auf eine weitere Datenbank immer eine Art Wegweiser, den Sie mit Hilfe der hier beschriebenen Datenbankverknüpfung (Database Link) erstellen können. Dabei ist es dann allerdings gleichgültig, wo sich die benötigten Datenbank auf dieser Welt befindet. Einzige Voraussetzung ist, dass die benötigte Oracle-Datenbank über das Net8-Netzwerk erreichbar ist (vgl. Abb. 2.4). Zwecks Anlage einer solchen Datenbankverknüpfung benötigen Sie für die Zieldatenbank eine geeignete Benutzer-Id nebst zugehörigem Passwort. Des weiteren muss auf dem Server der primären Datenbank ein Dienstname für die Zieldatenbank existieren, beispielsweise mit Hilfe einer entsprechend konfigurierten Datei TNSNAMES.ORA. Der letzte Satz verdient noch einmal eine Verstärkung: der Client muss nicht in der Lage sein, sich selbst an die zu verknüpfende Datenbank anzumelden, d.h. die Instanz, in der Client gerade arbeitet, stellt die Verbindung zu der verknüpften Datenbank her. Wenn Sie MS-Access kennen, dann wissen Sie, dass Sie dort beispielsweise ähnliche Möglichkeiten haben, indem Sie in der aktuellen Datenbank eine Tabelle aus einer anderen Datenbank einbinden bzw. verknüpfen. So wie das Ganze in MS-Access wirklich den Namen „Table Link“ verdient, beschreibt der hier verwendete Begriff Database Link zutreffend, worum es sich bei Oracle handelt, nämlich um ein universelles Tor zu einer anderen Datenbank. Bleiben wir bei diesen Bildern. Das per Link geöffnete Tor zur fremden Welt (Datenbank) kann zwar in der Tat recht universell sein, aber es ist in jedem Fall auch recht schmal. Damit will ich sagen, dass Sie bei datenbankübergreifenden Abfragen die Ausführungsgeschwindigkeit ein wenig im Auge behalten sollten, doch hierzu erfahren Sie erst am Ende des dritten Kapitel mehr. In dem skizzierten Beispiel der Abbildung 2.4 ist der Client über das Netzwerk mit dem Server „Frankfurt“ verbunden und hat über das Net8-Netzwerk Zugriff auf die dort laufende Datenbank „einkauf“, wobei er hierfür den in seiner Konfigurationsdatei TNSNAMES.ORA definierten Dienstnamen „eink“ verwendet. Mehr Dienstnameneinträge hat der Client nicht, d.h. die Vertriebsdatenbank in München ist für ihn direkt nicht erreichbar.
Beschreibung der Objekte
121
connect hugo@eink select * from ver_mitarb@ein2
Client
Netzwerk
München Datenbank: vertrieb
Frankfurt Datenbank: einkauf Link ein2 = ver0
eink
vert
ver0.world = (DESCRIPTION = (ADDRESS = (PROTOCOL = TCP) (HOST = muenchen)) (CONNECT_DATA = (SID = vert)))
Abbildung 2.4: Verwenden von Datenbankverknüpfungen
Auf dem Frankfurt-Server ist ebenfalls eine TNSNAMES-Konfiguration gespeichert. Mit Hilfe dieser Konfiguration wird der Dienstname „ver0“ definiert, der auf die Datenbankinstanz „vert“ auf dem Server „Muenchen“ verweist. Außerdem existiert in der Datenbank „einkauf“ ein Link mit dem Namen „ein2“, der den Zugriffspfad zu irgendeiner Datenbank mit Hilfe des Dientsnamens „ver0“ beschreibt. Zugegeben, die in dem Beispiel verwendeten Kürzel sind ein wenig abenteuerlich. In der Praxis hätte man sowohl für den Linknamen als auch für den Dienstnamen auf dem Server sicherlich die Zeichenfolge „vert“ verwendet, um den Zugriff transparent zu gestalten. Aber dann hätten Sie nicht auf einem Blick gesehen, wie die Zusammenhänge sind. So aber ist ziemlich klar, welche Möglichkeiten Sie bei der Namensgebung haben und was wie heißen darf bzw. was wie heißen muss. Innerhalb einer Abfrage werden nun alle Tabellen, die mit Hilfe dieses Links aus der angebundenen Datenbank verwendet werden sollen, mit einem Klammeraffen (@) und dem Linknamen erweitert (z.B. select ... from xxxx@ein2). Erstellt werden solche Datenbankverknüpfungen mit Hilfe des Befehls create database link, und mit Hilfe der Anweisung drop database link kann eben beschriebene Verbindungstür wieder geschlossen werden. Welche Möglichkeiten das Tor zur Abfrage der entfernten Tabellen wirklich bietet, hängt vor allem davon ab, mit welchem Benutzer sich die Datenbankinstanz an der Zieldatenbank anmeldet. Dieses Verfahren müssen Sie beim Erstellen des Links festlegen. Hierbei können Sie beispielsweise eine feste Benutzer-Id mit Passwort vorgeben oder der Zugriff soll mit dem Benutzer nebst den zugehörigen Rechten erfolgen, der die Abfrage mit dem Link ausführt.
122
Datenbankobjekte in einer Oracle-DB
Beispiele Wenn Sie Lust auf ein Beispiel haben und entsprechend dem ersten Kapitel die Datenbanken „orcl“ und „db01“ besitzen, dann können Sie das eben Beschriebene leicht einmal ausprobieren und zwischen den beiden Datenbank einen Verknüpfung erstellen. Erweitern Sie hierzu zunächst auf dem Server die Dienstnamenkonfiguration TNSNAMES.ORA um folgenden Eintrag: orcl-l.world = (DESCRIPTION = (ADDRESS = (PROTOCOL = TCP) (HOST = ray_dell) (PORT = 1521) ) (CONNECT_DATA = (SID = orcl) ) ) Listing 2.6: Erweitern TNSNAMES.ORA für den Database Link
Hierdurch erstellen Sie den Dienstnamen „orcl-l”, der für den Zugriff auf die OrclInstanz genutzt werden kann. Melden Sie sich anschließend als Administrator (Benutzer-ID system oder internal) an der Datenbankinstanz db01 an, und geben Sie dort folgende Anweisung ein: create database link oralink connect to system identified by manager using 'orcl-l'; commit; Listing 2.7: Erstellen des Database Links „oralink“
Dieser Befehl erstellt die Datenbankverknüpfung mit dem Namen „oralink“ und weist diese an, über den Dienstnamen „orcl-l“ eine Verbindung als Benutzer „system“ mit dem Passwort „manager“ aufzubauen. Der commit-Befehl in der zweiten Zeile schließt diese Transaktion ab, wodurch der neue Link verfügbar wird. Geben Sie nun die folgende Abfrage (Listing 2.8) ein, um Informationen über die Struktur der „entfernten“ Datanbank zu erhalten. SQL> select name from v$datafile@oralink; NAME ----------------------------------------------------E:\ORANT\DATABASE\SYS1ORCL.ORA E:\ORANT\DATABASE\USR1ORCL.ORA E:\ORANT\DATABASE\RBS1ORCL.ORA E:\ORANT\DATABASE\TMP1ORCL.ORA SQL> Listing 2.8: Abfrage der Dateinamen der orcl-Datenbank
Beschreibung der Objekte
123
Mit Hilfe einer Abfrage der View all_db_links können Sie sich übrigens eine Übersicht aller in der Datenbank definierten Datenbanklinks erzeugen.
2.2.4
Funktion (Functions)
Wenn Sie schon einmal mit einer moderneren Programmiersprache oder mit einer Tabellenkalkulation wie zum Beispiel MS-Excel gearbeitet haben, dann ist Ihnen die Verwendung einer Funktion sicherlich vertraut. Funktionen stellen üblicherweise spezielle Miniprogramme zur Verfügung, mit deren Hilfe Sie bestimmte Aufgaben erledigen können. Dabei werden diese Funktionen üblicherweise mit einem oder mehreren Parametern aufgerufen und liefern als Ergebnis genau einen Wert zurück. Auch innerhalb einer relationalen Datenbank spielen Funktionen heute eine wichtige Rolle. Eigentlich jeder ernstzunehmende Vertreter dieser Datenbankgattung stellt in seinem SQL-Sprachumfang eine umfangreiche Funktionssammlung zur Verfügung, mit deren Hilfe Sie verschiedene Aufgaben, wie zum Beispiel das Formatieren von Datumswerten, die Durchführung spezieller Berechnungen oder das Bearbeiten von Zeichenketten erledigen können. Des weiteren werden die benötigten Methoden der noch zu besprechenden Objekte mit Hilfe solcher selbstdefinierten Funktionen realisiert. So weit - so gut, doch eigentlich rede bzw. schreibe ich die ganze Zeit am eigentlichen Thema vorbei, oder einigen wir uns darauf, das ich mich nicht geradlinig, sonder kreisförmig dem eigentlichen Kern der Sache nähere. Hier und jetzt geht es nämlich nicht um die Verwendung irgendwelcher vorgefertigten Programme, sondern unter Oracle haben Sie die Möglichkeit auch eigene Funktionen zu erstellen, deren spätere Verwendung sich in keiner Weise von den vom Hersteller gelieferten Vorbildern unterscheidet. Das ist eine Option, die zur Zeit in vielen anderen Datenbanksystemen noch ihresgleichen sucht. Eigene Funktionen werden in Oracle mit Hilfe des Schlüsselwortes create function angelegt und mit Hilfe des Befehls drop function wieder gelöscht. Um das Bearbeiten einer schon bestehenden Funktion zu vereinfachen, wurde die create-Anweisung um eine replace-Option erweitert, mit der Sie die bestehende Funktion ersetzen können. Diese Ersetzungsoption existiert im Übrigen für viele Objekte und erspart Ihnen das bei vielen anderen DB-Systemen notwendige vorherige Löschen (droppen). Das hat unter Umständen wiederum weitreichende Konsequenzen auf die Benutzeradministration, denn üblicherweise gehen beim drop die individuell auf dieses Objekt zugewiesenen Rechte verloren, wohingegen sie beim replace erhalten bleiben, d.h. hinter dem replace steckt doch mehr als ein bisschen Benutzerkomfort dahinter. Beispiele Zur Demonstration wollen wir eine Funktion basteln, die den übergebenen Parameter anders formatiert zurückliefert. Konkret erwartet die Funktionen einen Text und liefert diesen rückwärts gelesen wieder zurück.
124
Datenbankobjekte in einer Oracle-DB
create or replace function spiegeln (in_c varchar2) return varchar is begin return '{' || in_c || '}'; end; / show errors; commit; Listing 2.9: Erstellen eines einfachen Funktionsgerüsts
Betrachten Sie zunächst das Listing 2.9. Die hier gezeigte Funktion löst zwar nicht die eben beschriebene Aufgabenstellung, sondern sie demonstriert nur die Erstellung des benötigten und noch leeren Funktionsgerüsts. Beim Anlegen oder Bearbeiten von Funktionen müssen Sie hinter der create-Anweisung den Funktionsnamen, in unserem Fall „spiegeln“, angeben. Dahinter folgt in Klammern die durch Komma getrennte Aufzählung der übergebenen Parameter nebst zugehörigem Datentyp. Hierbei können neben den vordefinierten Standarddatentypen (z.B. date, number oder varchar2) auch alle selbstdefinierten Typen verwenden. Nach dieser Parameterleiste folgt eingeleitet durch das Schlüsselwort return die Definition des von der Funktion zurückgelieferten Datentyps und abschließend leitet das Wörtchen is (oder as) die Beschreibung des eigentlichen Funktionsrumpfes ein. Ähnlich wie in Pascal, C und weiteren Programmiersprachen wird auch PL/ SQL blockorientiert kodiert. Solche Programmblöcke beginnen mit dem Schlüsselwort begin und enden mit einer end-Anweisung, wobei ein solcher Block auch immer mit einem Semikolon (;) abgeschlossen wird. Innerhalb eines solchen Blockes werden die einzelnen Anweisungen oder gegebenenfalls weitere Blöcke kodiert, die ebenfalls durch ein Semikolon beendet werden. Wie schon gesagt, ist das Besondere an einer Funktion eben, dass sie einen berechneten Wert zurückliefert. Innerhalb des Funktionsrumpfes passiert dies mit Hilfe der Anweisung return. In unserem einfachen Beispiel folgt dieser Anweisung ein Ausdruck, mit dessen Hilfe der übergebene Ausdruck in geschweiften Klammern eingefasst wird. Dieses Einfassen erfolgt mit Hilfe des Operators ||, mit dem Sie Zeichenfolgen aneinanderhängen (konkatenieren) können. Nach der Kodierung der gesamten Funktion finden Sie in dem Beispiel noch die Verwendung der Anweisung show errors, bevor die ganze Transaktion mit Hilfe der commit-Anweisung abgeschlossen wird. Dieser Befehl tut eigentlich genau das, was er verheißt, d.h. im Falle einer fehlerhaften Kodierung erhalten Sie hierdurch eine Anzeige der suspekten Programmstellen nebst zusätzlicher Informationen. Nachdem Sie nun das Grundgerüst einer Funktion kennen, wollen wir es im zweiten Schritt entsprechend der einstmals beschriebenen Aufgabenstellung erweitern (vgl. Listing 2.10).
Beschreibung der Objekte
125
create or replace function spiegeln (in_c varchar2) return varchar is out_c varchar2(254); i number; begin for i in reverse 1..length(in_c) loop out_c := out_c || substr(in_c, i, 1); end loop; return out_c; end; / show errors; commit; Listing 2.10: Fertigstellung unserer spiegel-Funktion
Was hat sich nun im Einzelnen im Vergleich zum vorherigen Beispiel geändert? Zunächst einmal benötigen wir für die Aufbereitung des rückwärts gelesenen Parameters eine Hilfsvariable, deren Inhalt wir am Ende der Funktion zurückliefern wollen. In dem Fall müssen Sie diese Variable gleich am Anfang, also noch vor dem ersten Programmblock, definieren. In unserem Beispiel verwenden wir hierzu die Variable out_c vom Typ varchar2 und mit einer maximalen Länge von 254 Zeichen. Diese versteckte Definitionsanweisung wird genau wie jede andere auch mit einem Semikolon abgeschlossen. Innerhalb unsere Funktionsrumpfes definieren (deklarieren) wir zunächst eine weitere (numerische) Hilfsvariabel i. Hinter dieser Deklaration beginnt nun das eigentliche Programm innerhalb des zugehörigen Programmblocks. Wie Sie später bei der PL/SQL-Einführung noch sehen werden, kann jeder Programmblock einen eigenen Deklarationsteil besitzen, der normalerweise durch das Schlüsselwort declare eingeleitet wird. Bei der Anlage von Funktionen oder Prozeduren beginnt der Deklarationsteil direkt hinter dem Funktionskopf, also direkt hinter dem is-Schlüsselwort. Aus diesem Grund muss die declare-Anweisung für den ersten Programmblock weggelassen werden. Damit kennen Sie jetzt die grundsätzliche Struktur einer jeden Funktion, egal, ob sie vor Ausführung des eigentlichen Programms eigene Variablen vorab deklariert oder nicht. Ein bisschen was gibt es allerdings noch zur Parameterleiste der Funktion anzumerken. Zwischen den dort spezifizierten Parameternamen und Datentypen können Sie bei Bedarf noch explizit festlegen, in welche Richtung die aufgezählten Parameter verwendet werden können. Gewöhnlich dienen solche Parameter immer als Eingangstor zu dem in der Funktion gespeicherten Programm, weshalb der Zusatz „in“ dem Standard entspricht und daher weggelassen werden kann. Schematisch hat die Spezifikation eines Parameters folgenden Aufbau, wobei Sie eine Beschreibung der einzelnen Schlüsselwörter in der Tabelle 2.1 finden: <Parameter> in | out | in out
126
Datenbankobjekte in einer Oracle-DB
Schlüsselwort
Bedeutung
In
Standardwert der festlegt, dass der Parameter innerhalb der Funktion bzw. Prozedur nur gelesen werden kann.
Out
Das aufrufende Programm erwartet mit Hilfe dieses Parameters ein Ergebnis zurück.
in out
Der übergebene Parameter kann innerhalb des Programms gelesen aber auch geändert werden.
Tabelle 2.1: Festlegen der Verwendbarkeit von Parametern
Im eigentlichen Programm wird nun der übergebene Parameter mit Hilfe einer for..loop-Schleife durchlaufen. Diese Schleife zählt wegen des Zusatzwortes reverse rückwärts beginnend von der Länge der übergebenen Zeichenkette bis zur Zahl 1. Innerhalb der Schleife wird bei jedem Durchlauf die Zählvariable i entsprechend gesetzt, so dass wir mit ihrer Hilfe und der Funktion substr das dem Durchlauf entsprechende Zeichen aus dem Eingabeparameter herausschneiden und an die Ausgabevariable out_c anhängen können. SQLWKS> select parameter, spiegeln(parameter) from v$option; PARAMETER SPIEGELN(PARAMETER) ------------------------------------ -----------------------Partitioning gninoititraP Objects stcejbO Parallel Server revreS lellaraP Advanced replication noitacilper decnavdA Bit-mapped indexes sexedni deppam-tiB Connection multiplexing gnixelpitlum noitcennoC Connection pooling gniloop noitcennoC Database queuing gniueuq esabataD Incremental backup and recovery yrevocer dna pukcab latnemercnI Instead-of triggers sreggirt fo-daetsnI Parallel backup and recovery yrevocer dna pukcab lellaraP Parallel execution noitucexe lellaraP Parallel load daol lellaraP Point-in-time tablespace recovery yrevocer ecapselbat emit-ni-tnioP 14 rows selected. Listing 2.11: Verwenden der selbstgeschriebenen spiegeln-Funktion
Natürlich können die in der Praxis verwendeten bzw. erstellten Funktionen wesentlich komplexer ausfallen, zum Beispiel weil die notwendigen Berechnungen umfangreicher als in unserer kleinen Funktion sind oder weil Sie von der Möglichkeit Gebrauch machen, innerhalb des Funktionsrumpfs weitere Datenbankabfragen auszuführen. Auf die zuletzt genannten Möglichkeiten werde ich in den nächsten Kapiteln noch häufiger zurückkommen. Verfolgen Sie einfach den im Anhang befindlichen Index, um diejenigen Seiten zu finden, auf denen die Verwendung der Funktionen weiter vertieft wird, womit wir die fast perfekte Überleitung zur nächsten Kategorie des Schema-Managers gefunden haben.
Beschreibung der Objekte
2.2.5
127
Index (Indexes)
Bei einem Index handelt es sich um ein optionales Element, das zusammen mit Tabellen erstellt bzw. verwendet werden kann. Bei sachgemäßer Verwendung, können Sie die Ausführungsgeschwindigkeit von Abfragen erheblich erhöhen, gleichgültig ob es sich hierbei um Auswahl- oder Änderungsabfragen handelt. Dabei hat das Fehlen bzw. Vorhandensein eines Index keinerlei formale Auswirkungen auf die notwendige Kodierung der Abfrage. Letzteres gilt im Wesentlichen übrigens für nahezu alle marktüblichen relationalen Datenbanksysteme, auch wenn der ein oder andere Vertreter die Möglichkeit bietet, im Rahmen einer Abfrage die Verwendung eines ganz speziellen Index zu erzwingen. Stellen Sie sich vor, Sie hätten die Aufgabe, in diesem Buch jede Seite die den Begriff „Funktion“ enthält herauszusuchen. In dem Fall müssen Sie das gesamte Buch Seite für Seite durchsuchen und dabei jede Seite markieren, in der Sie das gesuchte Wort finden. Das ist eine Aufgabe, für die selbst geübte bzw. schnelle Leser mehre Stunden bis Tage benötigen. Ist für dieses Wörtchen jedoch ein entsprechender Eintrag im Buchindex vorhanden, dann reduziert sich die für diese Aufgabe benötigte Zeit auf wenige Sekunden. Zunächst müssen Sie das Wort im Index finden. Da dieser üblicherweise sortiert ist, werden Sie hierbei sicherlich direkt beim Buchstaben „F“ beginnen und erst danach die einzelnen Wörter so lange der Reihe nach durchgehen, bis Sie beim gesuchten Begriff „Funktion“ angelangt sind. Anschließend können Sie die zugehörigen Seitenzahlen ablesen und hierdurch die gesuchten Buchseiten direkt aufschlagen. Die eben beschriebene Vorgehensweise weist übrigens verblüffende Parallelen zu den von der Datenbank verwendeten Methoden auf. In diesem Workshop werden Sie im Kapitel „Abfragen“ bei der Besprechung des Abfragetunings die Ausführungspläne des Datenbanksystems kennen lernen. Hier finden Sie Begriffe wie „full table scan“ (ganzes Buch durchlesen), „index scan“ (Begriff im Index nachschlagen), „index range scan“ (im Index die Wörter „Funk“ bis „Funktion“ suchen) oder „access per rowid“ (Buchseite direkt aufschlagen). Das Vorhandensein von den (richtigen) Indexeinträgen kann in einer Datenbank ähnlich dramatische Auswirkungen haben. Ich selbst habe Situationen erlebt, wo das Löschen eines Index eine Abfrage bei sonst gleichen Bedingungen von 120 auf unter eine Sekunde beschleunigt hat, wodurch Sie sehen können, dass zu viele oder unsinnige Indices dem System auch schaden können. Ebenso ist es mir passiert, das die Ausführungsdauer einer verschachtelten Änderungsabfrage durch das Anlegen eines Index von über drei Stunden auf etwa zehn Sekunden reduziert werden konnte. Wie Sie sehen, reden wir hier also nicht über Peanuts, sondern drehen in Bezug auf die Ausführungsgeschwindigkeit an einer zentralen Stelle der Datenbank. Wie aber kann ein Index besonders nützlich oder schädlich sein? Diese Frage lässt sich pauschal leider nicht beantworten. Im Laufe der Zeit, d.h. bei entsprechender Erfahrung, werden Sie sicherlich ein gutes Gefühl dafür entwickeln, welcher Index für welche Aufgabenstellungen hilfreich sein könnte. Die letztendliche Sicherheit erhält man immer erst beim Testen der Abfragen zusammen mit entsprechend großen Tabellen und beim Studieren der zugehörigen Ausführungspläne.
128
Datenbankobjekte in einer Oracle-DB
Dennoch gibt es eine einfache Grundregel: Der Index soll helfen, die zugehörigen Sätze in der Tabelle möglichst einfach zu finden. Das hört sich banal an, ist es in der Praxis aber oft nicht. Um dies zu verdeutlichen, möchte ich noch einmal zu meinem eben beschriebenen Buchbeispiel zurückkehren. Nehmen wir einfach mal an, für das zu durchsuchende Buch existiert ein Index, der neben den eigentlichen Suchbegriffen auch immer noch die beiden vorhergehenden und das nächste folgende Wort enthält: die neue Funktion ist, 1, 8 neben der Funktion wird, 9 zu der Funktion ist, 11
Wenn Sie dieses konstruierte Beispiel betrachten, dann sollte einleuchten, dass dieser Index nicht gerade zum schnellen Suchen und Finden einlädt. In der Datenbankpraxis entsteht ein vergleichbarer Index, wenn bei der Erstellung mehrer Felder verknüpft werden und der eigentliche Suchbegriff irgendwo in der Mitte der Kette der indizierten Felder auftaucht. Außerdem haben Sie die Möglichkeit, für eine Tabelle mehrere verschiedene Indices für unterschiedliche Felder bzw. Feldketten anzulegen und genau jetzt kann das zunächst latente Problem, denn ein schlechter Index ist oft immer noch besser als keiner, eskalieren, wenn aufgrund der Indexauswahl der eigentlich bessere nicht mehr verwendet wird. Neben dem Beschleunigen von Abfragen kann es noch weitere Gründe für die Anlage eines Index geben. Nehmen wir einmal an, Sie möchten in einer Tabelle die Eindeutigkeit eines Feldes oder einer bestimmten Feldkombination erzwingen, ohne dass Sie je vorhaben, direkt nach diesem Feld bzw. der Kombination zu suchen. Trotzdem wäre in dem geschilderten Fall die Anlage eines speziellen Index für dieses Feld bzw. die Felder die Maßnahme der ersten Wahl. Wie Sie gleich noch sehen werden, existieren bei der Anlage eines Index verschiedene Varianten. Eine dieser Varianten ist die Erzwingung eindeutiger Indexeinträge, so dass bei dem Versuch des Einfügens von neuen Sätzen, deren Felder bzw. Feldkombinationen diese Regel verletzen würden, ein Fehler entsteht. Jede größere Tabelle sollte mindestens einen solchen eindeutigen Index besitzen, mit dessen Hilfe jede gespeicherte Reihe eindeutig identifiziert werden kann (Primärschlüssel). Physikalisch wird ein Index unabhängig von der zugehörigen Tabelle gespeichert, wobei die Indices üblicherweise sogar in einem eigenen Tablespace und damit in einem eigenen Datenfile und hierdurch vielleicht sogar auf einer eigenen Festplatte gespeichert werden. In Oracle können Indices explizit oder implizit angelegt werden. Im ersten Fall erfolgt die Anlage durch Eingabe einer entsprechenden create index-Anweisung bzw. die Löschung eines Index erfolgt mit Hilfe des Befehls drop index. Bei der impliziten Indexerstellung erfolgt die Definition zusammen mit der Tabellenbeschreibung, wobei wir uns diese Möglichkeit erst später bei der Beschreibung der Tabellen anschauen werden. Eine abschließende Bemerkung möchte ich noch machen, bevor wir das Ganze nun wieder an einem Beispiel ausprobieren. Wo viel Licht ist, da ist bekanntlich
Beschreibung der Objekte
129
auch ein wenig Schatten. Die hier in den Vordergrund gestellten Vorteile der Indices gibt es in einer Datenbank nicht kostenlos. Zum einen benötigen die Indices Plattenplatz und zum anderen kostet auch deren Verwaltung Rechnerressourcen. Schließlich muss das Datenbanksystem bei jeder Änderung nicht nur die eigentliche Datentabelle, sondern auch alle Indices aktualisieren. Aus diesem Grund ist es verständlich, dass in manchen Architekturen nur diejenigen Indices permanent verfügbar sind, die für die Performance der Dialoganwendung vorteilhaft sind. Andere Indices, die für bestimmte Batchläufe oder Reports vorteilhaft sind, werden erst bei Bedarf erstellt und am Ende des Batch-Programms bzw. des Reports wieder gelöscht. Beispiele Bevor Sie irgendwelche Indices anlegen können, benötigen Sie logischerweise zunächst einmal eine Tabelle, die im Rahmen des folgenden Listings angelegt wird, wobei Sie das Beispiel in der Datei LISITING12A.SQL finden. drop table mitarbeiter; / create table mitarbeiter ( persnr varchar2(11) not null, name varchar2(50), strasse varchar2(30), ort varchar2(30), plz varchar2(5) ) tablespace usr storage (initial 10000 next 10000 maxextents unlimited pctincrease 0) / commit; insert into mitarbeiter values ('4711', 'Raymans, Heinz-Gerd', 'Heinrichstr. 7','Duisburg','47231'); insert into mitarbeiter values ('4712', 'Schmidt, Harald', 'Duissernplatz 2','Düsseldorf','42231'); commit;
Nachdem Sie nun mit der Mitarbeitertabelle ein Demonstrationsobjekt haben, können Sie in einem zweiten Schritt einen Index anlegen (vgl. Listing 2.12). create unique index mitarbeiter_pk on mitarbeiter (persnr) tablespace indx storage (initial 10000 next 10000 maxextents unlimited pctincrease 0); commit; Listing 2.12: Index für die Mitarbeitertabelle anlegen
In dem Beispiel legen Sie für die Tabelle mitarbeiter einen eindeutigen Index für das Feld persnr mit dem Namen mitarbeiter_pk an, der im übrigen für das gewählte Schema eindeutig sein muss. Der Index wird in dem Tablespace indx gespeichert und erhält eine eigene Storage-Klausel. Wenn Sie diese Speicherbelegungsregel
130
Datenbankobjekte in einer Oracle-DB
weglassen, dann wird stattdessen die bei der Anlage des Tablespaces spezifizierte Klausel verwendet. Versuchen Sie doch einfach mal nach Anlage dieses Indexes die oben abgedruckten Insert-Anweisungen noch einmal auszuführen. Da dies zu einer Verletzung der Indexeindeutigkeit führt, erhalten Sie folgerichtig den entsprechenden Hinweis „unique constraint (SYSTEM.MITARBEITER_PK) violated“, wobei die Datensätze nicht in die Datenbank eingefügt werden. Varianten
X
Index für mehrere Felder Wenn Sie einen Index für mehrer Felder erstellen möchten, dann müssen Sie diese einfach innerhalb der Klammer durch Komma getrennt aufzählen. Solche zusammenhängenden Indices können maximal 32 Felder enthalten und sollten (vgl. Anmerkungen von oben) entweder einen sinnvollen Suchpfad wiedergeben oder in der vorgegebenen Kombination den Primärschlüssel der Tabelle bilden: create unique index mitarbeiter_p1 on mitarbeiter (persnr, plz)
X
Sortierung des Index festlegen Bei der Anlage eines Index können Sie dessen Sortierung festlegen, indem Sie hinter den einzelnen Spalten die Sortierreihenfolge mit Hilfe der Schlüsselwörter asc (aufsteigend, standard) bzw. desc (absteigend) festlegen. Das kann sinnvoll sein, wenn die Daten oft entsprechend sortiert ausgegeben werden müssen. create index mitarbeiter_pl on mitarbeiter (plz desc)
X
Nicht eindeutiger Index Lassen Sie das Schlüsselwort unique weg, wenn Sie keinen eindeutigen Index erstellen möchten: create index mitarbeiter_p2 on mitarbeiter (name)
X
Nicht wiederherstellbar Mit Hilfe der Option unrecoverable verhindern Sie, dass der Index bei der Protokollierung auf den Redo-Log-Dateien berücksichtigt wird. Diese Option ist allerdings ein Auslaufmodell und nur noch aus Kompatibilitätsgründen verfügbar. Verwenden Sie stattdessen das Schlüsselwort nologging, wenn sie die Protokollierung für den Index ausschalten wollen. create unique index mitarbeiter_pk on mitarbeiter (persnr) tablespace indx nologging;
X
Weitere Varianten Die bisher beschriebenen Verfahren und Varianten sind diejenigen, die Ihnen zumindest nach meiner Erfahrung in der Praxis meistens über den Weg laufen.
Beschreibung der Objekte
131
Daneben existieren noch ein paar exotischere Möglichkeiten, beispielsweise diejenige des Bitmap Index. Diese Anlageform, die allerdings nur in der Oracle Enterprise Edition verfügbar (vgl. Listing 2.1) ist, wandelt die spezifizierten Indexspalten in entsprechende Bitmuster um. Dieses Verfahren verspricht Performancevorteile bei Spalten mit geringen Wertebereichen (z.B. Geschlecht). Index verwalten Die explizit über create index angelegten Indices können mit Hilfe der Anweisung drop index wieder aus der Datenbank entfernt werden, indem Sie den Befehl zusammen mit dem vergebenen Indexnamen verwenden. drop index mitarbeiter_pk
Intern werden die Indices mit Hilfe sogenannter B*-Bäume verwaltet. Damit diese Bäume beim Suchen dauerhaft optimal funktionieren schadet es nicht, sie von Zeit zu Zeit zu reorganisieren. Hierzu ist es allerdings nicht notwendig den Index zu löschen und anschließend neu anzulegen, sondern Oracle bietet zusammen mit der Anweisung alter index eine Variante, den spezifizierten Index neu aufzubauen (vgl. Listing 2.13). alter index mitarbeiter_pk rebuild; commit; Listing 2.13: Index reorganisieren
Einen Überblick über die in der Datenbank gespeicherten Indices erhalten Sie mit Hilfe einer Abfrage auf die Views all_indexes oder all_ind_columns. Die erste der beiden Views zeigt die generelle Definition des Index, d.h. hier erhalten Sie beispielsweise Informationen über die Speicherbelegungsregel (storage-Klausel) oder den verwendeten Tablespace. Die zweite View zeigt den Aufbau des Index, d.h. sie liefert die Reihenfolge und Namen der im Index verwendeten Spalten. Vergleichen Sie hierzu die im Listing 2.14 abgedruckte Abfrage, in der diese beiden Views verknüpft werden, um die Indices und deren Zusammensetzung für die Tabelle „mitarbeiter“ zu ermitteln; das Listing 2.15 zeigt Ihnen die zugehörige Ausgabe dieser Abfrage entsprechend den eben behandelten Beispielen. select a.index_name, a.uniqueness, b.column_name from all_indexes a, all_ind_columns b where and and and
a.owner='SYSTEM' a.table_name = 'MITARBEITER' b.index_owner = a.owner b.index_name = a.index_name
order by a.index_name, b.column_position Listing 2.14: Analyse der Indexstruktur mit Hilfe vorhandener Systemviews
132
Datenbankobjekte in einer Oracle-DB
INDEX_NAME -----------------------------MITARBEITER_P1 MITARBEITER_P1 MITARBEITER_PK 3 rows selected.
UNIQUENES --------UNIQUE UNIQUE UNIQUE
COLUMN_NAME ---------------------PERSNR PLZ PERSNR
Listing 2.15: Abfrage der vorhandenen Indices entsprechend unserem vorhergehenden Beispiel
2.2.6
Objekttyp (Object Types)
Hinter dieser Kategorie verbirgt sich technisch auch wieder der Befehl create type, so dass Sie Ihnen auch diese Funktionalitäten wieder nur zusammen mit der ObjekteOption (vgl. „Array Types“) zur Verfügung steht. Konkret geht es hier entsprechend der Kategorieüberschrift in der Tat um die Definition sogenannter Objekte. Bei einem Objekt handelt es sich um eine Einheit, bestehend aus einer benutzerdefinierten Datenkomposition und einer Sammlung von Funktionen und Prozeduren, um diese Daten zum Bearbeiten oder weitergehenden Funktionalitäten bereitzustellen. Im Zusammenhang mit einem solchen Objekt werden die gespeicherten Daten üblicherweise als Attribute oder Eigenschaften bezeichnet, wohingegen man bei den Funktionen und Prozeduren, die in irgendeiner Weise das Verhalten des Objektes repräsentieren, von Methoden spricht. Natürlich würde eine systematische Einleitung in den Objekt-Themenkomplex den Rahmen dieses Buches bei weitem sprengen. Ich glaube aber trotzdem, dass selbst wenn Sie bisher noch gar keine Erfahrung in objektorientierter Programmierung sammeln konnten, die nachfolgenden Ausführungen und Beispiele für Sie nachvollziehbar sind und Klarheit in den einen oder anderen Aspekt des objektorientierten Ansatzes bringen werden. Ich bin immer wieder belustigt über den manchmal stattfindenden Glaubenskrieg zwischen objektorientierter und klassischer rein prozeduraler Programmierung. Ich denke für beide Ansätze gibt es entsprechende Existenzberechtigungen. Was ich allerdings auch nicht mag ist, wenn in mancher Literatur in Bezug auf Objektorientierung der Eindruck entsteht, hierdurch würde – ich übertreibe mal ein wenig – quasi ein neues Weltbild entstehen und die Einführung erfolgt mit Hilfe von derartigen Beispielen, dass man anschließend glaubt, Objekte sind nur etwas für über den Dingen stehende Freaks. Die Welt ist die gleiche geblieben, es sind lediglich neue Verfahren und Werkzeuge zu deren Beschreibung hinzugekommen. Vor etwa fünfzehn Jahren habe ich mein erstes Cobol-Programm für eine Düsseldorfer Versicherungsgesellschaft geschrieben. Für alles und jedes gab es irgendwelche Strukturen, beispielsweise die Struktur „Versicherungsnehmer“, die aus den Komponenten Name, Straße, Ort, Geburtsdatum und vielen weiteren Felder bestand. Heute formuliert man anders, das Objekt „Versicherungsnehmer“ verfügt über die Eigenschaften Name, Straße, Ort usw., aber es hat sich eigentlich nichts geändert, denn es geht darum zusammengehörende Informationen geeignet zu bündeln.
Beschreibung der Objekte
133
Jeder Versicherungsnehmer verfügt bzw. besitzt beliebig viele Verträge, mit zumindest gleichem Vertragsstamm. Heute beschreibt man das natürlich eleganter und ordnet dem Versicherungsnehmer eine Kollektion des Objekts „Vertragsstamm“ zu. Bei der Arbeit mit diesen Strukturen war mein eigentlich permanent auf der Suche nach schon existierenden Unterprogrammen, beispielsweise einem solchen, das mit Hilfe der übergebenen Struktur die Anschrift oder die Anrede korrekt aufbereitet. Spätestens jetzt treten die Vorteile der Objektorientierung stärker in den Vordergrund, denn hierbei sind Sie in der Lage, auch diese Verfahren an die Struktur bzw. das Objekt zu binden. Neben der Bündelung der Daten erfolgt also auch die Konzentration der benötigten Funktionalitäten unter ein und demselben Begriff, eben diesem Objekt. Bei diesen Funktionalitäten spricht man üblicherweise von Methoden. In meinem Beispiel des Versicherungsnehmers erfolgt der Zugriff auf den Namen beispielsweise durch Kodierung der Anweisung x = versicherungsnehmer.name versicherungsnehmer.name = ’Meier, Huber’
und in Abhängigkeit davon, auf welcher Seite des Ausdrucks das Gleichheitszeichen steht, fragt man den Namen ab oder weist ihn zu. Möchte man die Anschrift formatiert ausdrucken, dann spezifiziert man jetzt vielleicht einfach nur noch folgende Anweisung: print versicherungsnehmer.print_anschrift().
Beispiele Als Beispiel für diesen Themenkomplex möchte ich das Objekt „mensch“ erstellen. Damit das Beispiel übersichtlich bleibt, erhält unser mensch-Objekt natürlich nur wenige Eigenschaften wie Namen, Anschrift und das Geburtsdatum. Entsprechend zurückhaltend gehen wir auch mit der Definition von Methoden um. Zum einen soll eine Methode zur Berechnung und Ausgabe des Alters, und zum anderen die eben beschriebene Funktion zur formatierten Anschriftenausgabe erstellt werden. create or replace type mensch as object ( name varchar2(30), vname varchar2(30), strasse varchar2(30), plz varchar2(5), ort varchar2(30), geschl varchar2(1), gebdat date, member function g_alter return number, pragma restrict_references(g_alter, wnds, wnps), member function g_anschrift return varchar2,
134
Datenbankobjekte in einer Oracle-DB
pragma restrict_references(g_anschrift, wnds, wnps) ); / commit; Listing 2.16: Erstellung unseres Objekts „mensch“
Zunächst legen wir mit Hilfe des schon bekannten Befehls create type das Objekt „mensch“ an (vgl. Listing 2.16). Das funktioniert auf den ersten Blick ganz ähnlich wie die Anlage einer Tabelle, denn die einzelnen Eigenschaften werden genau wie die Spalten einer Tabelle angelegt. Im zweiten Schritt werden die beiden Methoden g_alter und g_anschrift definiert. Dieser Teil erinnert wiederum sehr stark an die Anlage eines Packages und es macht durchaus Sinn, an dieser Stelle kurz zu unterbrechen und zunächst einmal den zu dem Schema-Objekt zugehörigen Abschnitt mitsamt dem Beispiel durchzuarbeiten. Dort finden Sie auch eine genaue Erklärung zu dem Compilerschalter restrict_references, mit der wir für beide Funktionen festlegen, dass sie keine Datenbankänderungen durchführen und keine globalen Variablen ändern können. Danach legen wir die Tabelle im nächsten Schritt zunächst einmal in der klassischen Form an (vgl. Listing 2.17). drop table menschen; / create table menschen (xmensch mensch ) tablespace usr; commit; Listing 2.17: Anlage der Tabelle „menschen“.
„Klassisch“ heißt hierbei, dass wir die Tabelle spaltenweise anlegen, wobei eine oder mehrere dieser Spalten halt zufällig aus einem selbstdefinierten Datentyp gebildet wird. Wie damals bei den Datenfeldern (varray) erhalten wir hierdurch einen abstrakten, interpretationsbedürftigen Datentyp und damit wieder Probleme oder zumindest Mehraufwand innerhalb von SQL-Ausdrücken. Aber auch beim Einfügen bzw. Ändern von Daten müssen wir den Datentyp konstruieren oder jedes Mal entsprechende selbst zu programmierende Member-Funktionen verwenden. SQLWKS> desc menschen Column Name Null? Type ------------------------------ -------- ---XMENSCH ADT(212)
Wie gesagt, benötigt man selbst beim Anlegen von Sätzen einen entsprechenden Konstruktur. In Oracle kodiert man den durch Nennung des Datentyps, dem in Klammern eine Aufzählung aller einzelnen Elemente folgt:
Beschreibung der Objekte
135
insert into menschen values ( mensch('Raymans', 'Heinz-Gerd', 'Harmoniestr. 2a', '47302', 'Duisburg', 'M', to_date('1964.12.03','YYYY.MM.DD') )); commit;
Um diese dauernden Schwierigkeiten zu umgehen, haben Sie auch die Möglichkeit, die Tabelle grundsätzlich objektorientiert anzulegen (sog. Object Tables). In dem Fall besteht die Tabelle jedoch aus einem bzw. aus lauter Objekten, d.h. die Verwendung einzelner „normaler” Spalten ist hierbei nicht mehr möglich. Der Vorteil dabei ist allerdings, dass Oracle das Objekt anschließend auflöst und Sie die einzelnen Eigenschaften wie gewöhnliche Spalten innerhalb von SQL-Ausdrücken verwenden können (vgl. Listing 2.18). drop table menschen; / create table menschen of mensch tablespace usr; commit; Listing 2.18: Anlegen einer objektorientierten Tabelle
Betrachten Sie die Tabellenbeschreibung wieder mit Hilfe des Befehls desc. Obwohl die Tabelle mit Hilfe eines Objekts definiert wurde, ist sie von einer klassischen Tabelle nicht mehr zu unterscheiden, d.h. alle Objekteigenschaften erscheinen wie ganz gewöhnliche Tabellenspalten. SQLWKS> desc menschen Column Name Null? ------------------------------ -------NAME VNAME STRASSE PLZ ORT GESCHL GEBDAT
Type ---VARCHAR2(30) VARCHAR2(30) VARCHAR2(30) VARCHAR2(5) VARCHAR2(30) VARCHAR2(1) DATE
Damit stellt aber auch das Einfügen von Datensätze keine besondere Herausforderung mehr da, d.h. Sie können hierfür ganz gewöhnliche insert-Anweisungen ohne irgendwelche Konstruktoren verwenden (CD: LISTING218A.SQL). insert into menschen values ('Raymans', 'Heinz-Gerd', 'Harmoniestr. 2a','42110', 'Duisburg','M', to_date('1964.12.03','YYYY.MM.DD')); insert into menschen values ('Raymans','Irina',
136
Datenbankobjekte in einer Oracle-DB
'Harmoniestr. 2a', '42110', 'Duisburg', 'W', to_date('1965.12.27','YYYY.MM.DD')); insert into menschen values ('Zschoge','Helgo', 'Am kleinen Weg 11','10000','Berlin', 'M', to_date('1962.07.13','YYYY.MM.DD')); insert into menschen values ('Heger','Sascha', 'Hansaring 111a','50000','Köln','M', to_date('1969.05.12','YYYY.MM.DD')); commit;
Ähnlich wie bei den Packages unterteilt sich auch das Objekt in zwei Teile. Den erste Teil, in dem die Eigenschaften und sichtbaren Methoden veröffentlicht wurden, haben Sie bereits fertiggestellt. Was noch fehlt ist die Programmierung der veröffentlichten Methoden. Hierzu existiert bei den Objekten ähnlich wie bei den Paketen der „Type body“, in dem die eigentliche Programmierung erfolgt (vgl. Listing 2.19). create or replace type body mensch as member function g_alter return number is begin return floor(months_between(sysdate, gebdat) / 12) ; end; member function g_anschrift return varchar2 is anschrift varchar2(2000) := ' '; begin if geschl = 'M' then anschrift := 'Herrn' || chr(10) || chr(13); else anschrift := 'Frau' || chr(10) || chr(13); end if; anschrift := anschrift || vname || ' ' || name || chr(10) || chr(13); anschrift := anschrift || strasse || chr(10) || chr(13); anschrift := anschrift || plz || ' ' || ort; return anschrift; end; end; / show errors; commit; Listing 2.19: Programmierung der zum Objekt gehörenden Methoden
Beschreibung der Objekte
137
Die erste Methode liefert das Alter, indem zunächst mit Hilfe der Standardfunktion month_between die Differenz zwischen dem Tages- und Geburtsdatums in Monaten berechnet wird. Diese Differenz teilen wir durch zwölf und schneiden von der Division alle Nachkommastellen mit Hilfe der floor-Funktion ab. Die zweite Member-Funktion sieht komplizierter aus, ist es aber nicht. Mit Hilfe des Operators “||” werden alle zur Anschrift gehörenden Einzelfelder aneinandergehängt (konkateniert), wobei an bestimmten Stellen auch die Steuerzeichen chr(10) und chr(13) eingestreut werden, die bei der späteren Ausgabe den gewünschten Zeilenwechsel veranlassen. Mit Hilfe der nun folgenden Abfrage, können Sie das Ergebnis Ihres Schaffens bewundern: SQLWKS> select b.g_anschrift(), name, gebdat, b.g_alter() 2> from menschen b 3> where b.g_alter() > 18 B.G_ANSCHRIFT() NAME GEBDAT B.G_ALTER( ----------------------------- -------------------- -- ------ --------Herrn Heinz-Gerd Raymans Harmoniestr. 2a 42110 Duisburg Raymans 03-DEC-64 35 Frau Irina Raymans Harmoniestr. 2a 42110 Duisburg Raymans 27-DEC-65 34 Herrn Helgo Zschoge Am kleinen Weg 11 10000 Berlin Zschoge 13-JUL-62 38 Herrn Sascha Heger Hansaring 111a 50000 Köln Heger 12-MAY-69 31 4 rows selected.
2.2.7
Paketrumpf (Package bodies)
Diese Kategorie der Schema-Objekte beschreibt die in einem Package (Paket) enthaltenen Funktionen und Prozeduren. Mehr Informationen zu diesem Thema finden Sie im nächsten Abschnitt, der das Schema-Objekt Packages mitsamt den zugehörigen Package bodies beschreibt. Die Anweisung zum Anlegen eines Package bodies lautet create package body bzw. verwenden Sie create or replace package body, um das Schema-Objekt zu ersetzen. Mit Hilfe der Anweisung drop package body können Sie das zugehörige Objekt aus der Datenbank entfernen. Vergleicht man den Aufbau eines solchen Pakets, dann mit herkömmlichen Programmierkonstrukten, beispielsweise einem C-Programm, dann entspricht dieses Objekt dort am ehesten den üblichen Modulen, in dem die einzelnen Prozeduren oder Funktionen programmiert werden.
138
2.2.8
Datenbankobjekte in einer Oracle-DB
Paket (Packages)
Nicht nur bei der (gelben) Post gibt es Pakete. In einer Oracle-Datenbank versteht man unter einem Package (Paket) die Zusammenfassung oder Verpackung verschiedener Prozeduren oder Funktionen zu einer Einheit. Genau wie im richtigen Leben besteht ein solches Paket meistens aus zwei Teilen, nämlich aus der Verpackung und (hoffentlich zu Weihnachten) auch aus (reichlich) Inhalt, wobei Letzteres in einer Oracle-Datenbank dem Package body gleichzuordnen ist. Aber manchmal erhält man halt auch nur ein leeres Päckchen, was in unserer Datenbank einem Package ohne vorhandenem Package body entspricht. Zurück zum Ernst der Lage. Das Gesamtpaket besteht also (zumindest meistens) aus zwei Teilen, nämlich zum einen aus dem eigentlichen Package, das die Definition aller aufrufbaren Funktionen oder Prozeduren enthält und damit die Hülle oder Verpackung darstellt und zum anderen aus dem Package body, in dem die einzelnen Funktionalitäten ausprogrammiert werden, d.h. das Package body enthält den Programmkode für die im Package aufgezählten Funktionen. Damit fungiert das Package auch als eine Art Schnittstelle zwischen dem Anwender oder anderen aufrufenden Programmen. Die im Paket enthaltenen Funktionen werden durch das Package sichtbar, wobei der zugehörige Programmcode im Package body verborgen bleibt (vgl. Abb. 2.5).
Anwendung Datenbank -SQL*Plus -PL/SQL-Block -Anderes Package -Sonstige Anwendung
Package
Package Body
Abbildung 2.5: Schematischer Aufbau eines Packages
Wie schon im vorhergehenden Kapitel gesagt, kann man diese Konstruktion eines Packages seinem Package body durchaus mit herkömmlichen Programmiersprachen vergleichen. Dabei entspricht das Package body am ehesten den dort verwendeten Modulen, in den sich das eigentliche Programmcoding der einzelnen Funktionen und Prozeduren befindet. Daneben verwenden Sie in diesem Modulen üblicherweise auch Prozeduren oder Funktionen, die in anderen Modulen realisiert wurden und vielleicht sogar erst zur Laufzeit Ihres Programms zur Verfügung stehen. Damit Sie Ihr Programm aber trotz des Fehlens dieser externen Funktionen umwandeln können, müssen Sie zumindest deren Namen und Parametrierung vorab definieren. Wenn Sie Erfahrungen mit den Programmiersprachen C oder Pascal haben, dann kennen Sie ja das ent-
Beschreibung der Objekte
139
sprechende Verfahren, solche Deklarations- bzw. Headerdateien via include oder uses einzubinden. Diese deklarative Eigenschaft weisen die Packages im Übrigen auch auf, denn sobald Sie das Package erstellt haben, können Sie die dort definierten Prozeduren oder Funktionen in anderen PL/SQL-Programmen ansprechen. Eine weitere Parallele ergibt sich auch im Bezug auf die Abstraktion der eigentlichen Programmierung. Wie Sie im weiteren Verlauf noch sehen werden, ist die eigentliche Programmierung im Package body für andere Anwender nicht unbedingt sichtbar, d.h. eine Übersicht des Inhalts ergibt sich alleine aus den im Package vorliegenden Definitionen. In anderen Programmierumgebungen erhalten Sie bestimmte Module oftmals nur in kompilierter Form, d.h. die eigentliche Kodierung der Funktionen ist auch hier nicht sichtbar. Die Verwendungen von Packages bietet einige Vorteile, vor allem dann, wenn man größere zusammenhängende Anwendungen entwickelt:
X
X X X
Selbst ein kleines Programm kann schnell zehn, zwanzig oder mehr einzelne Prozeduren beinhalten. Benötigt Ihre Anwendung nun auch noch eine Vielzahl solcher Programme, dann geht einem bei der Verwendung einzelner Prozeduren bzw. Funktionen neben der Übersicht auch irgendwann einmal bei der Namensgebung die Phantasie aus. Aber selbst mit viel Phantasie haben Sie immer das Problem, dass die Namen der Prozeduren neu entwickelter Anwendungsteile noch nicht in der Datenbank vorhanden sein dürfen. Durch die Zusammenfassung der einzelnen Prozeduren zu den Packages, treten die auf den ersten Blick in den Hintergrund, was einer besseren Übersicht sehr zuträglich ist. Außerdem müssen die Namen der Prozeduren bzw. Funktionen nur innerhalb eines Paketes eindeutig sein; Ausnahmen bestätigen auch hier mal wieder die Regel, da es die Möglichkeit gibt, Prozeduren mehrfach mit unterschiedlichen Parametern zu überladen. Nicht nur bei verteilten Entwicklungsaufgaben haben Packages den Vorteil, dass die enthaltenen Funktionen schon alleine mit deren Definition für andere Anwendungen, quasi als Dummy, verfügbar sind. Die konkrete Ausprogrammierung im Package body kann erst zu einem späteren Zeitpunkt erfolgen. Wird eine in einem Package definierte Prozedur oder Funktion angesprochen, so wird in dem Fall zunächst das gesamte Paket geladen, wodurch auch alle anderen vorhandenen Funktionalitäten verfügbar werden, was die Performance positiv beeinflussen kann. Werden in einem Package globale Variablen deklariert, dann sind die so lange gültig, bis sich der Benutzer wieder von der Datenbank abmeldet bzw. die aktuelle Sitzung (Session) beendet wird. Aufgrund dieses Verhaltens entsteht die Möglichkeit, quasi sitzungsglobale Variablen zu definieren. Ein Effekt, der in der Praxis durchaus genutzt wird und zu den schon angesprochenen leeren Paketen führen kann, d.h. in dem Fall existiert zwecks Erhaltung globaler Variablen zwar ein Package aber kein zugehöriges Package body.
140
X
Datenbankobjekte in einer Oracle-DB
Als letzten Aspekt möchte ich wieder etwas aus der Richtung Ordnung und Übersicht anführen, wenn auch aus einer anderen Blickrichtung. Wenn Sie Anwendungen in die Datenbank implementieren und hierzu entsprechende Prozeduren oder Funktionen entwerfen, dann müssen Sie in einem späteren Schritt üblicherweise auch Ausführungsberechtigungen für die Anwendung bzw. der zugehörigen Prozeduren vergeben. Sind die zu der Anwendung gehörigen Prozeduren in einem Paket vereint, dann reicht es aus, lediglich die Ausführung des Pakets zu erlauben.
Ein Paket wird mit Hilfe der Anweisung create or replace package und der Paketinhalt mit der Anweisung create or replace package body erstellt bzw. bearbeitet. Ersetzen Sie jeweils die Wörter create or replace durch das Wörtchen drop, um die Anweisung zum Löschen eines Paktes zu erhalten. Beachten Sie hierbei, dass Sie durch das Löschen eines Packages auch automatisch das zugehörige Package body löschen. Beispiele Es ist immer schwierig ein Beispiel zu finden, dass auf der einen Seite nicht zu lang und schwierig ist, aber auf der anderen Seite jedoch alle oder zumindest viele der möglichen Aspekte und Varianten berücksichtigt. In dem folgenden Beispiel, das zwar sicherlich wieder zur Klasse der EFOS-Funktionen (=Eine Funktion ohne Sinn) gehört, ist das dennoch so wie ich glaube ganz gut gelungen. Konkret möchte ich die an der Erstellung unserer spiegeln-Funktion anknüpfen, d.h. sollten Sie die Funktion zwischenzeitlich wieder gelöscht haben, dann müssen Sie die zunächst einmal wieder anlegen. Anschließend beginnen wir mit der Programmierung unseres Pakets „tollhaus“ (vgl. Listing 2.20). create or replace package tollhaus as call_count number; function spiegeln (in_c varchar2) return varchar2; pragma restrict_references (spiegeln, WNDS); end; / show errors; commit; Listing 2.20: Definition des Packages „tollhaus“
Die Anlage des Pakets unterscheidet sich auf den ersten Blick zunächst einmal kaum von der von den Funktionen her bekannten Vorgehensweise. Lediglich das Wörtchen is habe ich zur Abwechslung einmal durch as ersetzt, was aber nicht unbedingt nötig gewesen wäre. Aber auf den zweiten Blick treten mehr und mehr Unterschiede zum Vorschein. Zum Beispiel hat ein Paket nicht einmal einen einzigen Programmblock, d.h. die zugehörige begin-Anweisung fehlt und trotzdem wird eine Package-Definition mit end abgeschlossen.
Beschreibung der Objekte
141
Innerhalb der Paketdefinition wird als erstes die globale Variable call_count vom Datentyp number (Zahl) und anschließend die Funktion spiegeln mit ihren Ein- und Ausgabeparametern definiert. call_count number; function spiegeln (in_c varchar2) return varchar2;
Jede Variable, die einer create package-Anweisung folgt, ist global und für die Lebensdauer der zugehörigen Session gültig. Jede definierte Funktion oder Prozedur kann vom Anwender oder einem anderen Programm aus gestartet (Zugriffsberechtigung vorausgesetzt) werden und muss im Rahmen der Erstellung des Package bodies angelegt bzw. programmiert werden. Nach der Funktion folgt noch eine weitere Anweisung „pragma restrict_references“, wobei es sich hierbei um eine sogenannte Compiler-Direktive handelt, mit der Sie für die betreffende Funktion verschiedenste Einschränkungen festlegen können. pragma restrict_references (function_name, WNDS [, WNPS] [, RNDS] [, RNPS]);
Verwenden müssen Sie diese Compiler-Direktive zusammen mit dem betreffenden Funktions- oder Prozedurnamen und den gewünschten Einschränkungen „WNDS“, „WNPS“, „RNDS“ oder „RNPS“. Dabei ist nur die Einschränkung „WNDS“ zusammen mit dieser pragma-Anweisung zwingend, die anderen Einschränkungen können wahlfrei verwendet werden. Diese Einschränkungen, deren genaue Beschreibung Sie der Tabelle 2.2 entnehmen können, sind notwendig, wenn Sie die im Paket verfügbare Funktion innerhalb eines SQL-Ausdrucks verwenden möchten. Die Ausführung von Funktionen innerhalb von SQL-Ausdrücken ist an bestimmte Spielregeln gebunden, beispielsweise dürfen innerhalb der Funktion keine Tabellendaten geändert werden. Bei gewöhnlichen Funktionen, dass sind die außerhalb eines Pakets, achtet Oracle beim kompilieren der SQL-Anweisung selbst auf die Einhaltung dieser Regel. Ist die Funktion jedoch Bestandteil eines Paketes, dann ist der Code zunächst versteckt. Aus dem Grund müssen Sie die Funktion für den Gebrauch innerhalb von SQL-Ausdrucken zertifizieren, was mit Hilfe der pragma-Anweisung passiert. In dem Fall achtet Oracle beim Anlegen der Funktion im Rahmen der Erstellung des Package bodies darauf, dass in den markierten Funktionen keine datenbankändernden Befehle verwendet werden. Einschränkung
Beschreibung
WNDS
(writes no database state) Es können in der Datenbank keine Tabellenänderungen durchgeführt werden. Wichtig, damit die Funktion beispielsweise in der select-Klausel verwendet werden kann.
RNDS
(reads no database state) Innerhalb der Datenbank können keine Tabellen gelesen werden.
142
Datenbankobjekte in einer Oracle-DB
Einschränkung
Beschreibung
WNPS
(writes no package state) Die globalen Paketvariablen können nicht verändert werden. Das erlaubt die Verwendung der Funktion in der whereKlausel einer SQL-Abfrage.
RNPS
(reads no package state) Die globalen Paketvariablen können nicht gelesen werden.
Tabelle 2.2: Optionen der restrict_references-Einstellung
Nachdem Sie nun das Package erstellt haben, müssen Sie im nächsten Schritt dessen Inhalt definieren. Bevor ich das konkrete Beispiel hier vorstelle und erkläre, möchte ich zunächst einmal die grundsätzliche Struktur (vgl. Listing 2.21) des Paketinhalts vorstellen. create or replace package body tollhaus as 1) Deklarieren lokaler Funktionen und Prozeduren function kleingross(in_c varchar2) return varchar2; 2) Programmierung der globalen Funktion function spiegeln (in_c varchar2) return varchar2 is begin end; 3) Programmierung der lokalen Funktion function kleingross(in_c in varchar2) return varchar2 is out_c varchar2(254); begin end; 4) Programmieren des Initialisierungsteils begin -- Initialisieren globaler Variablen -- Spezielle Startroutinen end; Listing 2.21: Schematischer Aufbau eines „Package bodies“
Das im Listing 2.21 gedruckte Schema eignet sich ganz gut, die prinzipielle Struktur eines Package bodies darzustellen und im folgenden Listing 2.22 finden Sie anschließend das vollständige Programm. Klar ist eigentlich, dass Package und Package body den gleichen Namen besitzen müssen. Den Rest finden Sie in der nun folgenden Aufzählung:
Beschreibung der Objekte
143
1. Deklarieren Sie alle lokalen Funktionen. Genau genommen müssen Sie dies nur dann tun, wenn Sie die Funktion schon vor der eigentlichen Programmierung in einem anderen Programmteil aufrufen. In unserem Beispiel wäre das genau dann der Fall, wenn Sie die Funktion kleingross erst an der mit 3. markierten Position erstellen, aber schon in dem spiegeln-programm verwenden müssen. Es schadet aber nicht, wenn Sie sich angewöhnen, zunächst alle lokalen Funktionen und Prozeduren aufzuzählen. 2. Programmieren Sie die globalen Funktionen und Prozeduren, also alle diejenigen, die im Package veröffentlicht wurden. 3. Erstellen Sie die lokalen Funktionen und Prozeduren. Natürlich ist die bisher genannte Reihenfolge wie in den meisten Fällen nicht zwingend einzuhalten. Es doch zu tun, schadet aber in keinem Fall. 4. Programmieren Sie bei Bedarf einen speziellen Initialisierungsabschnitt. Hierzu finden Sie am Ende der create package body-Anweisung einen entsprechenden begin..end Block. Dieser Block wird direkt nach dem Laden des Pakets ausgeführt und kann dazu genutzt werden, die globalen Variablen zu initialisieren oder spezielle Startroutinen aufzurufen. create or replace package body tollhaus as function kleingross(in_c varchar2) return varchar2; function spiegeln (in_c varchar2) return varchar2 is begin call_count := call_count +1; if mod(call_count, 2) = 0 then return system.spiegeln(in_c); else return kleingross(in_c) || '>' || to_char(call_count) || '<'; end if; end; function kleingross(in_c in varchar2) return varchar2 is out_c varchar2(254); i number; begin for i in 1..length(in_c) loop if mod(i, 2) = 0 then out_c := out_c || lower(substr(in_c, i, 1)); else out_c := out_c || upper(substr(in_c, i, 1)); end if; end loop;
144
Datenbankobjekte in einer Oracle-DB
return out_c; end; begin call_count := 0; end; / show errors; commit; Listing 2.22: Die vollständige Tollhaus-Programmierung
Das eben gezeigte Listing entspricht genau der weiter oben beschrieben Struktur, weshalb ich im Wesentlichen auf die Beschreibung der einzelnen Funktionen konzentriere; nehmen wir das Programm im Folgenden also Stück für Stück auseinander. Beginnen möchte ich mit dem kurzen Initialisierungsteil, in dem die globale Variable call_count auf Null gesetzt wird. Am Anfang der globalen Funktion spiegeln wird die globale Variable call_count bei jedem Aufruf um den Wert eins erhöht. Innerhalb der nachfolgenden if-Abfrage wird nun mit Hilfe der mod-Funktion wird geprüft, ob die globale Variable aktuell einen geraden oder ungeraden Wert enthält. if mod(call_count, 2) = 0 then
Diese mod-Funktion führt eine sogenannte Ganzzahldivision aus, d.h. nur bei geradezahligen Werten des Zählers call_count liefert die oben abgebildete Berechnung den Wert Null. In dem Fall ruft unser Programm die schon erstellte spiegeln-Funktion auf und geben deren Rückgabe unverändert als Ergebnis aus. Damit klar ist, dass wir allerdings keineswegs unsere globale Paketfunktion noch einmal (rekursiv) aufrufen wollen, spezifizieren wir den Funktionsnamen vollständig, indem wir dem Funktionsnamen den Namen des zugehörigen Schemas und einen Punkt als Trennzeichen voranstellen. if mod(call_count, 2) = 0 then return system.spiegeln(in_c); else return kleingross(in_c) || '>' || to_char(call_count) || '<'; end if;
Bei ungeradezahligen Aufrufen verzweigen wir in die lokale kleingross-Funktion, dessen Rückgabewert wir zusammen mit dem Wert der Zählervariablen ausgeben. Den konvertieren wir vorab mit Hilfe der Funktion to_char in eine Zeichenfolge, so dass wir die Funktionsrückgabe und den umgewandelten Zähler aneinanderhängen und zurückgeben können. for i in 1..length(in_c) loop if mod(i, 2) = 0 then out_c := out_c || lower(substr(in_c, i, 1));
Beschreibung der Objekte
145
else out_c := out_c || upper(substr(in_c, i, 1)); end if; end loop;
Innerhalb unserer kleingross-Funktion durchlaufen wir den übergebenen String, so ähnlich wie damals beim Spiegeln, wieder zeichenweise. Dabei kopieren wir Zeichen für Zeichen in die Ausgabevariable out_c, wobei das jeweilige Zeichen in Abhängigkeit von der Stelle (gerade oder ungerade) mit Hilfe der Funktionen lower bzw. upper in Klein- bzw. Großschreibweise konvertiert wird. Die Verwendung bzw. der Aufruf unserer neuen Funktion erfolgt so ähnlich wie in dem vorherigen Beispiel bei der Beschreibung der Funktionen. Einen kleinen Unterschied gibt es natürlich immer, und so müssen Sie bei Paketen der benötigten Funktion oder Prozedur den Namen des Packages gefolgt von einem Punkt als Trennzeichen voranstellen. Das gleich folgende Abfragebeispiel verwendet die neue spiegeln-Funktion wieder zusammen mit der v$option-View. SQLWKS> select parameter, tollhaus.spiegeln(parameter) from v$option 2> PARAMETER TOLLHAUS.SPIEGELN(PARAMETER) ------------------------------------ ---------------------------------Partitioning PaRtItIoNiNg>1< Objects stcejbO Parallel Server PaRaLlEl sErVeR>3< Advanced replication noitacilper decnavdA Bit-mapped indexes BiT-MaPpEd iNdExEs>5< Connection multiplexing gnixelpitlum noitcennoC Connection pooling CoNnEcTiOn pOoLiNg>7< Database queuing gniueuq esabataD Incremental backup and recovery InCrEmEnTaL BaCkUp aNd rEcOvErY>9< Instead-of triggers sreggirt fo-daetsnI Parallel backup and recovery PaRaLlEl bAcKuP AnD ReCoVeRy>11< Parallel execution noitucexe lellaraP Parallel load PaRaLlEl lOaD>13< Point-in-time tablespace recovery yrevocer ecapselbat emit-ni-tnioP 14 rows selected.
Die Funktionsweise der globalen Zählvariable call_count können Sie in dem Beispiel gut beobachten, indem Sie die abgebildete Abfrage einfach ein paar Mal hintereinander starten. Bauen Sie im nächsten Schritt vor der Abfrage eine connectAnweisung ein, um sich von der Datenbank ab- und sofort wieder anzumelden, und beobachten Sie anschließend wieder die Werte der Zählvariablen. connect system/manager@db01; select parameter, tollhaus.spiegeln(parameter) from v$option
146
Datenbankobjekte in einer Oracle-DB
Wie nicht anders zu erwarten wird hierdurch die Zählvariable wieder auf Null gesetzt, da diese wegen der neuen Session beim Starten des Packages erneut initialisiert wird. Natürlich besteht auch die Möglichkeit, die globale Zählvariable von außen gezielt zu beeinflussen. Wie das geht, das zeigt Ihnen das nächste Beispiel: Begin tollhaus.call_count := 111; end; / select parameter, tollhaus.spiegeln(parameter) from v$option
So, damit wäre der erste Teil meines Package-Beispiels beendet und Sie müssen zugeben, auch wenn es inhaltlich nicht besonderes schwergewichtig war, kam dennoch alles Wesentliche darin vor. Sie haben ein Paket mit einer veröffentlichten und einer privaten Funktion erstellt, eine globale Variable benutzt, gesehen wie man von außen die Paketfunktion aufruft und die globale Variable verwendet. Varianten Wie Sie sich vielleicht erinnern, hatte ich am Anfang des Kapitels gesagt, dass es im Ausnahmefall möglich ist, Funktions- bzw. Prozedurnamen in einem Paket mehrfach zu verwenden. Das ist genau dann sinnvoll, wenn Sie die zugehörige Funktion mit verschiedenen Parametern verwenden wollen, wobei man in dem Fall vom „Überladen“ der Funktion bzw. Prozedur spricht. Wenn wir diese Anforderung auf unser Beispiel übertragen, dann läge eine passende Aufgabenstellung darin, die spiegeln-funktion nicht nur für eine Zeichenkette, sondern vielleicht auch mit einem Datum aufrufen zu können. Im nächsten Listing 2.23 können Sie hierzu zunächst einmal die Deklaration der Funktionen im Package betrachten. create or replace package tollhaus as call_count number; function spiegeln (in_c varchar2) return varchar2; pragma restrict_references (spiegeln, WNDS); function spiegeln (in_c date) return varchar2; pragma restrict_references (spiegeln, WNDS); function spiegeln (in_c varchar2, i integer) return varchar2; pragma restrict_references (spiegeln, WNDS); end; / show errors; commit; Listing 2.23: Mehrfachdeklaration der spiegeln-Funktion
Beschreibung der Objekte
147
Wie Sie dem Listing entnehmen können, ist die notwendige Verfahrensweise eigentlich gar nicht weiter schwierig. Die entsprechende Funktion wird so oft mit den jeweiligen Parametern aufgeführt, bis alle benötigen Parametervarianten aufgezählt sind. Hierbei müssen Sie lediglich darauf achten, dass Sie eventuell notwendige Compileroptionen ebenfalls entsprechend oft und jetzt am besten direkt hinter der Funktionsdeklaration schreiben. Naheliegenderweise müssen Sie die eigentliche Funktion im Package body jetzt auch entsprechend häufig kodieren (vgl. Listing 2.24). Auch wenn es in meinem Beispiel vielleicht den Eindruck macht, so müssen Sie hierbei die im Package verwendete Reihenfolge nicht einhalten. Aber da ich ein so ordnungsliebender Mensch bin, jetzt werden einige Leute herzhaft lachen, habe ich im folgenden Beispiel die gleiche Reihenfolge gewählt. create or replace package body tollhaus as function kleingross(in_c varchar2) return varchar2; function spiegeln (in_c varchar2) return varchar2 is begin call_count := call_count +1; if mod(call_count, 2) = 0 then return system.spiegeln(in_c); else return kleingross(in_c) || '>' || to_char(call_count) || '<'; end if; end; function spiegeln (in_c date) return varchar2 is begin return system.spiegeln(to_char(in_c,'YYYY.MM.DD')); end; function spiegeln (in_c varchar2, i integer) return varchar2 is begin return in_c; end; function kleingross(in_c in varchar2) return varchar2 is out_c varchar2(254); i number; begin for i in 1..length(in_c) loop if mod(i, 2) = 0 then out_c := out_c || lower(substr(in_c, i, 1));
148
Datenbankobjekte in einer Oracle-DB
else out_c := out_c || upper(substr(in_c, i, 1)); end if; end loop; return out_c; end; begin call_count := 0; end; / show errors; commit; Listing 2.24: Variante des Tollhauses mit überladener spiegeln-Funktion
Lediglich der fettgedruckte Abschnitt im Listing ist neu und was da im Einzelnen passiert, ist auch nicht besonders aufregend. Im Fall eines übergebenen Datums wird das innerhalb der zweiten Variante mit Hilfe der Funktion to_char in eine Zeichenkette umgewandelt, so dass wir anschließend wieder unsere altbekannte Funktion system.spiegeln benutzen können. Die dritte Variante wird verwendet, wenn wir die Paketfunktion mit zwei Parametern (Zeichenkette und Zahl) aufrufen. In dem Fall geben wir den übergebenen Wert unverändert zurück. Auf zur Tat und damit zum Test unserer neuen Funktionen. Mit Hilfe der im Folgenden gedruckten Abfrage können Sie die verschiedenen Varianten ausprobieren und damit zum letzten Mal einen Überblick über die in Ihrer Datenbank installierten Optionen erhalten, wobei Sie im Folgenden nur einen aufbereiteten Auszug der zugehörigen Abfrage sehen. SQLWKS> select 2> tollhaus.spiegeln(parameter,1), 3> tollhaus.spiegeln(parameter), 4> tollhaus.spiegeln(sysdate) 5> from v$option 6> SPIEGELN(PARAMETER,1) SPIEGELN(PARAMETER) SPIEGELN(SYSDATE) -------------------------------- -------------------------------------Partitioning PaRtItIoNiNg>15< 80.80.0002 Objects stcejbO 80.80.0002 Parallel Server PaRaLlEl sErVeR>17< 80.80.0002 Advanced replication noitacilper decnavdA 80.80.0002 Bit-mapped indexes BiT-MaPpEd iNdExEs>19< 80.80.0002 Connection multiplexing gnixelpitlum noitcennoC 80.80.0002 Connection pooling CoNnEcTiOn pOoLiNg>21< 80.80.0002 Database queuing gniueuq esabataD 80.80.0002 ... 14 rows selected.
Beschreibung der Objekte
2.2.9
149
Prozedur (Procedures)
An dieser Stelle könnte ich jetzt die im Kapitel „Functions“ geschriebenen Absätze kopieren, und danach das Wörtchen „Function“ per Suchen und Ersetzen mit „Procedure“ ersetzen, um eine geeignete Einleitung für die folgenden Abschnitte zu erhalten. Aber der Platz in diesem Buch ist kostbar und ich will Sie auch nicht unnötig langweilen. In der Tat sind Prozeduren und Funktionen nicht nur in Oracle, sondern eigentlich in jeder Programmiersprache nahe Verwandte. In beiden Fällen handelt es sich um mehr oder weniger komplexe Programme, die zusammen mit einem oder mehreren Parametern aufgerufen werden. Was sich natürlich unterscheidet, ist die genaue Verwendung dieser beiden Konstrukte. Eine Funktion können Sie wegen der besonderen Verwendungsform, Parameter rein und Ergebnis über den Funktionsnamen raus, direkt innerhalb von Ausdrücken, select- oder where-Anweisungen benutzen. Dahingegen bedürfen die Prozeduren eines speziellen Aufrufs und können daher nur im Rahmen einer einzelnen SQL-Anweisung oder innerhalb eines PL/SQL-Skripts oder Programms verwendet werden. Die Anlage einer Prozedur erfolgt in Analogie zu den Funktionen mit Hilfe der Anweisung create procedure und eine notwendige Änderung leiten Sie wieder mit Hilfe des Befehls create or replace procedure ein. Eine nicht mehr benötigte Prozedur können Sie mit der Anweisung drop procedure für immer verschwinden lassen. Beispiele Oftmals werden Prozeduren erstellt bzw. verwendet, um zusammenhängende SQLAnweisungen quasi als einen Block auszuführen. Soll beispielsweise aus einer Gehaltsdatendatenbank ein Mitarbeiter gelöscht werden, dann müssen in der Regel neben dem Stammdatensatz auch eine Reihe weitere Datensätze aus anderen Tabellen (z.B. Lohnkonten, Gehaltshistorien usw.) gelöscht werden, d.h. für jede betreffende Tabelle müsste eine entsprechende delete-Anweisung abgesetzt werden, die alle zum Mitarbeiter gehörenden Sätze löscht (vgl. Listing 2.25). create or replace procedure del_mitarbeiter (in_persnr in varchar2) is begin delete mitarbeiter where persnr = in_persnr; commit; end; / show errors; commit; Listing 2.25: Prozedur zum Löschen zusammengehörenden Tabellen
Natürlich ist dieses Beispiel sehr vereinfachend. In der Praxis würden innerhalb der Prozedur vielleicht sogar hunderter ähnlicher Löschanweisungen für andere Tabellen abgesetzt, um den Mitarbeiter nicht nur aus der einen, sondern eben aus allen nötigen Tabellen der Datenbank zu löschen. Noch besser wäre natürlich eine Prozedur, die automatisch innerhalb einer Schleife alle Tabellen findet, aus der Daten
150
Datenbankobjekte in einer Oracle-DB
für diesen Mitarbeiter gelöscht werden müssen. Sie werden später im Kapitel „PL/ SQL-Programmierung“ Anregungen für eine solche Prozedur erhalten, denn dort werden wir etwas ähnliches zum Reorganisieren von Indexen erstellen. Doch nun zurück zu unserem kleinen und harmlosen Beispiel, in dem Sie die Prozedur del_mitarbeiter erstellen, wobei die Verfahrensweise mit der einer Funktionserstellung nahezu identisch ist. Nach Nennung des Prozedurnamens und Aufzählung der Parameter erfolgt wieder die Kodierung des eigentlichen Prozedurprogramms zwischen dem mit begin und end festgelegten Block. Ähnlich wie bei den Funktionen gilt auch hier, dass Sie hinter dem Schlüsselwort is bzw. as die Variablen für den ersten Programmblock ohne explizite declare-Anweisung definiert werden können und bezüglich der Parameterlisten möchte ich hier nur auf die Tabelle 2.1 verweisen. create or replace procedure del_mitarbeiter (in_persnr in varchar2) is i number; begin delete mitarbeiter where persnr = in_persnr; commit end; / show errors; commit; Listing 2.26: Prozedurrumpf mit eigenen lokalen Variablen
Wenn Sie nun eine dieser beiden Prozeduren ausprobieren möchten, dann können Sie dies mit Hilfe von SQL*Plus oder dem SQL-Worksheet und der execute-Anweisung tun. Diese Anweisung startet eine Prozedur als einzelne SQL-Anweisung, die natürlich auch zusammen mit einer Reiher anderer Anweisungen verwendet werden kann: select count(*) from mitarbeiter; execute del_mitarbeiter('4711'); select count(*) from mitarbeiter;
Zusammen mit einem SQL-Skript oder innerhalb einer anderen Prozedur oder Funktion entfällt allerdings die execute-Anweisung für den Aufruf der Prozedur, d.h. die Prozedur wird einfach durch Verwendung ihres Namens gestartet: select count(*) from mitarbeiter; / begin del_mitarbeiter('4712'); end; / select count(*) from mitarbeiter;
Beschreibung der Objekte
151
Im Rahmen des Kapitels „PL/SQL-Programmierung“ werden Sie komplexere Beispiele zur Verwendung von Prozeduren kennen lernen. Ein weiteres oftmals genutztes Einsatzgebiet ist nämlich auch die Bündelung von notwendigen Wartungsarbeiten, beispielsweise dem reorganisieren von Indices, wofür wir, wie schon erwähnt, in dem genannten Kapitel ein Muster erstellen werden.
2.2.10 Warteschlangentabelle (Queue Tables) Es gibt Dinge, da steht eigentlich weniger die Technik in den Vordergrund als das dahinter liegende Konzept bzw. die damit verbundenen Möglichkeiten. Das gilt meiner Meinung nach auch für die hier vorgestellte Gruppe der Schema-Objekte. Falls Sie die übrigens in Ihrem Schema-Manager nicht sehen sollten, dann liegt das wieder an den verfügbaren oder installierten Optionen Ihres Oracle-Servers. Konkret benötigen Sie wenigstens die „Enterprise Edition“, um das Schema-Objekt „Queue Tables“ anlegen zu können, wobei die vollständige Palette der Funktionen nur zusammen mit den Objekten zur Verfügung steht. Im Prinzip stellt Oracle mit diesem Schema ein Werkzeug zur Verfügung, um die Kommunikation zwischen verschiedenen Programmen bzw. Prozessen zu gestalten. Oftmals kommunizieren verschiedene Programme synchron miteinander, d.h. ein Programm ruft den Dienst eines anderen Unterprogramms auf und wartet anschließend so lange, bis dieses Unterprogramm terminiert (fertig wird). Eine solche Verfahrensweise ist immer dann sinnvoll, wenn unter normalen Umständen davon ausgegangen werden kann, dass der aufgerufene Programmteil den gewünschten Dienst in einer akzeptablen Zeit erledigen kann. Was ist aber zu tun, wenn genau diese Voraussetzung nicht erfüllt ist bzw. garantiert werden kann? Manchmal kann es technische Gründe dafür geben, dass die zeitnahe Ausführung des aufgerufenen Programms nicht möglich ist. Das ist zum Beispiel dann der Fall, wenn das zugehörige Programm oder eine von dem Programm benötigte Datenbank wegen Wartungsarbeiten nicht verfügbar ist, oder der angestoßene Prozess dauert wirklich sehr lange, beispielsweise weil Sie einen komplexen Suchvorgang in einer riesigen Datenbank gestartet haben. Nach meiner Erfahrung sind es jedoch oftmals vor allem organisatorische bzw. fachliche Gründe, die gegen eine synchrone Verarbeitung sprechen. Stellen Sie sich vor, Sie ordern online ein Aktienpaket mit entsprechender Limitvorgabe. Mit dem Absenden des Auftrags kann aber überhaupt noch nicht vorhergesagt werden, wann bzw. ob diese Transaktion ausgeführt wird, denn auf der Gegenseite müssen verschiedene Voraussetzungen erfüllt sein, damit das Geschäft zustande kommt. Zum einen muss das vorgegebene Limit ziehen und zum anderen muss es jemanden geben, der eine entsprechende Aktienstückzahl zum Verkauf aufgegeben hat. Weitere Beispiele für eine derartige asynchrone Verarbeitung ergeben sich immer dann, wenn man Arbeitsprozesse nachbildet, vor allem dann, wenn an dem gesamten Ablauf mehrere verschiedene Personen beteiligt sind (vgl. Abb. 2.6).
152
Datenbankobjekte in einer Oracle-DB
Kunde
A
B
A
eingeben
prüfen
freigeben
abwickeln
Nachrichten
Hintergrundprozesse Abbildung 2.6: Schema eines Arbeitsprozesses
In dem Beispiel soll die vereinfachte Verarbeitung eines Kreditantrags dargestellt werden, die damit startet, dass der Kunde den Antrag selbständig, beispielsweise im Internet, eingibt. Anschließend wird der vom Sachbearbeiter „A“ geprüft, was gegebenenfalls die Freigabe des Antrags durch den Sachbearbeiter „B“ zur Folge hat, worauf wiederum der Sachbearbeiter „A“ die eigentliche Abwicklung durchführt, beispielsweise den Vertrag erstellt oder das Geld auszahlt. Damit diese einzelnen Arbeitsschritte wirklich wie an der Perlenkette hängend ablaufen, müssen sie miteinander kommunizieren. Diese Kommunikation muss aber asynchron erfolgen, denn der Sachbearbeiter „A“ kann nach der Prüfung eines Vertrages nicht so lange in Wartestellung verharren, bis sein Vorgesetzter endlich die Freigabe erteilt hat. Die Kommunikation der einzelnen Arbeitsschritte erfolgt mit Hilfe von Nachrichten, beispielsweise in der Form
X X X
Neuer Antrag ist eingetroffen, Antrag wurde geprüft, bitte freigeben, Antrag freigegeben, bitte abwickeln.
Damit alle Beteiligen die für sie interessanten Nachrichten lesen bzw. bearbeiten können, erfolgt eine gemeinsame Speicherung in einem Pool, der sogenannten Message-Queue. Das alleinige Vorhandensein einer solchen Queue reicht für die Bearbeitung der dort gespeicherten Nachrichten allerdings meistens nicht aus, sondern Sie benötigen üblicherweise auch noch eine Reihe von Helfern in Form von verschiedenen Hintergrundprozessen. Die überwachen beispielsweise den Verlauf der Nachrichtenkette, schlagen Alarm, wenn bestimmte Ereignisse nach Ablauf einer bestimmten Frist nicht bearbeitet wurden, löschen unvollständige oder alte
Beschreibung der Objekte
153
Nachrichten aus dem Pool heraus oder stoßen andere Batchprozesse aufgrund erledigter Nachrichten an. Wie Sie an dem Beispiel erkennen können geht es also nicht nur darum, in der Datenbank eine simple Tabelle anzulegen, in der Nachrichten in Form gewöhnlicher Datensätze gespeichert werden, sondern zu einer solchen Verarbeitung gehört auch die Möglichkeit, entsprechende Hintergrundprozesse zu definieren. Übrigens gibt es auch synchrone Verarbeitungsformen, in der die Kommunikation trotzdem über Nachrichten und entsprechender Queues erfolgt. In dem Fall wartet der einstellende Prozess so lange auf die Quittung oder Erledigung, bis diese beispielsweise durch einen Hintergrundprozess eintrifft. Das Betriebssystem Windows arbeitet zum Beispiel oftmals nach diesem Prinzip. Die Kommunikation zwischen den eigentlichen Anwendungen und dem Windows-Kern basiert auf Nachrichten und trotzdem warten viele Funktionen nach dem Einstellen einer Nachricht so lange, bis deren Verarbeitung quittiert wurde. Was Sie nun im Schema-Manager unter der Rubrik „Queue Tables“ sehen, ist die Möglichkeit, solche Nachrichtenpools zu erstellen. Dabei handelt es sich zum einen um ganz gewöhnliche Tabellen aber zum anderen mit einem speziellen Aufbau bzw. mit speziellen Feldern, denn Oracle bietet neben diesen Tabellen auch in der Datenbank implementierte Funktionalitäten, um die beschriebenen Hintergrundprozesse zu realisieren. Die Anlage bzw. Verwaltung dieser speziellen Tabellen erfolgt mit Hilfe des Paktes dbms_aqadm. Im folgenden Listing sehen Sie ein Beispiel für dessen Verwendung, die zugehörigen Definitionen erfolgten mit Hilfe des Schema-Managers, wobei Sie die wichtigen Einstellungen in Abbildung 2.7 finden. Danach erhalten Sie mit dem Listing 2.27 einen Überblick über das Aussehen der erstellten Nachrichtentabelle und zum Schluss (Listing 2.28) sehen Sie, wie Sie die Nachrichtentabelle mit Hilfe des dbms_aqadm-Pakets wieder löschen können. BEGIN dbms_aqadm.create_queue_table(queue_table=> 'SYSTEM.nachrichtenpool', queue_payload_type=>'RAW', sort_list=>'ENQ_TIME', comment=>'Beispiel für die Anlage des Pools', multiple_consumers=>FALSE, message_grouping=>DBMS_AQADM.NONE, storage_clause=>' PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 STORAGE ( INITIAL 12K NEXT 12K MINEXTENTS 1 MAXEXTENTS 249 PCTINCREASE 0) TABLESPACE "SYSTEM" '); COMMIT; END; Listing 2.27: Beispiel für das Anlegen einer Nachrichten-Queue
154
Datenbankobjekte in einer Oracle-DB
Hinweis: Das abgebildete Beispiel verwendet bei der Spezifizierung der Prozedurparameter die namensbezogene Übergabe. Bisher haben wir in allen unseren Beispielen die sogenannte positionsbezogene Parameterübergabe verwendet, d.h. in dem Fall muss die verwendete Reihenfolge der Notierung der Variablen im Prozedurkopf entsprechen. Bei der namensbezogenen Übergabe müssen Sie jedem übergebenen Wert den zugehörigen Parameternamen zusammen mit dem Operator „=>“ voranstellen.
Abbildung 2.7: Definition der Queue im Schema-Manager
SQLWKS> desc nachrichtenpool Column Name Null? ------------------------------ -------Q_NAME MSGID CORRID PRIORITY STATE DELAY EXPIRATION TIME_MANAGER_INFO LOCAL_ORDER_NO CHAIN_NO CSCN DSCN
Type ---VARCHAR2(30) RAW(16) VARCHAR2(128) NUMBER NUMBER DATE NUMBER DATE NUMBER NUMBER NUMBER NUMBER
Beschreibung der Objekte
ENQ_TIME ENQ_UID ENQ_TID DEQ_TIME DEQ_UID DEQ_TID RETRY_COUNT EXCEPTION_QSCHEMA EXCEPTION_QUEUE STEP_NO RECIPIENT_KEY DEQUEUE_MSGID USER_DATA
155
DATE NUMBER VARCHAR2(30) DATE NUMBER VARCHAR2(30) NUMBER VARCHAR2(30) VARCHAR2(30) NUMBER NUMBER RAW(16) BLOB(4000)
Listing 2.28: Aufbau des angelegten Nachrichtenpools
Wie Sie der Abbildung 2.7 bzw. auch der Übersicht (Listing 2.28) entnehmen können, dienen solche Nachrichten nicht nur unbedingt zum Weiterleiten der Ereignisse, sondern enthalten unter Umständen auch die zum Bearbeiten benötigten Daten. BEGIN dbms_aqadm.drop_queue_table(queue_table=> 'SYSTEM.NACHRICHTENPOOL', force=> TRUE); COMMIT; END; / EXIT; Listing 2.29: Löschen des Nachrichtenpools
Weitergehende Informationen zu diesem Thema finden Sie mal wieder in dem Buch „Oracle8 Concepts“ im Kapitel „Advanced Queuing“. Da die Erstellung und Verwendung solcher Nachrichtentabellen natürlich auch eine Herausforderung an die gesamte Anwendungsentwicklung stellt, finden Sie auch dort („Oracle8 Application Developer’s Guide”) ebenfalls im Kapitel „Advanced Queuing“ viele Hinweise und Beispiele. Resume Was ist an dem eben beschriebenen Verfahren und Möglichkeiten eigentlich so neu und aufregend? Nun, zunächst einmal gar nichts. Die eben beschriebenen Verfahrensweisen wurden bei Bedarf eigentlich schon immer verwendet und stellen keine Neuerfindung von Oracle dar. Allerdings mussten die benötigten Pooltabellen selbst definiert und vor allem die benötigten Hintergrundprozesse selbst programmiert werden. Meistens erfolgte dies je nach Betriebsplattform mit entsprechenden C, Cobol oder SQR-Programmen, die auf einem zentralen Server mit Hilfe von Betriebssystemfunktionen getriggert wurden.
156
Datenbankobjekte in einer Oracle-DB
Manchmal liefern sogar Standardprogramme (z.B. PeopleSoft oder SAP) selbst geeignete Werkzeuge, um solche Hintergrundjobs zu planen und zu überwachen, wobei es sich auch hierbei um separate Anwendungsprogramme handelt, die außerhalb der Datenbank gestartet und administriert werden. Um auf die Eingangsfrage zurückzukommen: Neu ist im Unterschied zu den eben beschriebenen Verfahrensweisen, dass Sie jetzt die Möglichkeit haben, die gesamte Steuerung, Administration und Programmierung des Nachrichtensystems innerhalb der Datenbank durchzuführen. Dabei sind neben den Tabellen nicht nur die Programme der Hintergrundprozesse Bestandteil der Datenbank, sondern sie beinhaltet auch alle Funktionalitäten zur Planung, Steuerung und Überwachung dieser Prozesse.
2.2.11 Abgleichengruppe (Refresh groups) Eine Refresh group wird im Zusammenhang mit den sogenannten Snapshots verwendet und von mir auch erst weiter unten in dem zugehörigen Abschnitt ausführlicher behandelt. Vorwegnehmend möchte ich an dieser Stelle nur soviel sagen: Die Refresh groups helfen, die Aktualisierung der Schnappschusstabellen (Snapshots) zu organisieren, indem alle zu einer solchen Aktualisierungsgruppe gehörenden Schnappschüsse gleichzeitig bzw. zusammenhängend aufgefrischt werden. Außerdem bietet Ihnen dieses Objekt noch die zusätzliche Option, die Schnappschussaktualisierungen in regelmäßigen Abständen automatisch durchzuführen. Zum Anlegen bzw. Verwalten der Refresh groups finden Sie diesmal keine der bisher üblichen SQL-Befehle, sondern das erfolgt mit Hilfe des Standard-Pakets dbms_refresh, genauer genommen sys.dbms_refresh, da der Benutzer sys Eigentümer des Pakets ist. Wenn Sie eine solche Gruppe für die automatische Aktualisierung konfigurieren, dann müssen Sie auch zusätzlich am Datenbankserver noch einige Einstellungen vornehmen. Die automatische Aktualisierung erfolgt mit Hilfe sogenannter SNP Hintergrundprozesse, die mit Hilfe der Konfigurationsdatei der Datenbankinstanz eingeschaltet werden. Mehr Informationen hierzu finden Sie in der Dokumentation im „Oracle8 Administrator’s Guide“ und dort im Kapitel „Managing Job Queues“.
2.2.12 Sequenz (Sequences) Mit diesem Schema-Objekt erhalten Sie ein Hilfsmittel, um automatisch eindeutige Integerzahlen zu erzeugen. Ein solcher Zahlengenerator ist oftmals recht praktisch, beispielsweise wenn Sie in einer Tabelle einen Primärschlüssel anlegen wollen, sich hierfür aber kein eigenes Feld bzw. keine Feldkombination anbietet. In dem Fall können Sie von dem hier beschriebenen Zahlengenerator für jeden Datensatz eine eindeutige fortlaufende Nummer abfordern, so dass Sie auf einfache Weise Ihren Hilfs-Primärschlüssel erhalten. Die Anlage solcher Sequences erfolgt mit Hilfe der Anweisung create sequence. Mit dem Befehl alter sequence können Sie die Einstellungen des Zahlengenerators ändern und wenn Sie ihn nicht mehr brauchen, dann löschen Sie Ihn mit der Anweisung drop sequence aus der Datenbank.
Beschreibung der Objekte
157
Beispiele In einer Tabelle soll zur späteren Identifizierung eines jeden Datensatzes ein automatischer Zähler eingebaut werden. Zu dem Zweck legen wir in der Datenbank als ersten den Zähler unter dem Namen „meinzaehler” (vgl. Listing 2.30) an. create sequence meinzaehler increment by 1 start with 1 maxvalue 5 minvalue 1 cycle cache 4 noorder; commit Listing 2.30: Anlage des Zahlengenerators „meinzaehler“
Wie Sie sehen, besitzt die Anweisung eine ganze Reihe von Parametern, die ich in der folgenden Tabelle 2.3 kurz beschreiben möchte. Parameter
Beschreibung
increment by
Legt die Schrittfolge des Zahlengenerators und damit die nächste gelieferte Zahl fest. Möglich sind positive, aber auch negative Zahlen, d.h. der Zahlengenerator kann auch rückwärts zählen.
start with
Anfangswert für die Zahlenfolge.
maxvalue / nomaxvalue
Maximal möglicher Wert bzw. verwenden Sie die Option nomaxvalue, wenn der Zähler bis zum technischen Limit (28-stellige Zahlen) weiterlaufen soll.
minvalue / nominvalue
Kleinster zu liefender Wert. Wenn Sie stattdessen die Option nominvalue verwenden, dann beträgt die kleinste lieferbare Zahl – 1 * 1026.
cycle / nocycle
In dem Fall (cycle) läuft der Zahlengenerator wie eine Uhr immer im Kreis, d.h. nach Lieferung der maximal eingestellten Zahl geht es wieder von vorne los. Die Option nocycle entspricht dem Standardwert.
cache / nocache
Mit dem cache-Parameter können Sie die Anzahl der Zahlen festlegen, die vorausberechnet und im Speicher vorgehalten werden. Eine ausgewogene Einstellung dieses Wertes kann die Geschwindigkeit des Zählers erhöhen.
noorder / order
Die Standardoption noorder legt fest, dass die Reihenfolge der Nummernvergabe nicht unbedingt der ihrer Anforderung entsprechen muss. Fordern zwei Transaktionen eine neue Nummer an und soll die erste Transaktion garantiert auch die nächsthöhere Nummer erhalten, dann müssen Sie die Option order verwenden.
Tabelle 2.3: Einstellmöglichkeiten des Zahlengenerators
158
Datenbankobjekte in einer Oracle-DB
Ist der Zahlengenerator erst einmal definiert, dann können Sie von ihm mit Hilfe des Befehls nextval den nächsten Wert anfordern bzw. mit der Anweisung currval den aktuellen Wert abfragen. Viel mehr ist bei der Verwendung eines solchen Zählers eigentlich auch nicht zu beachten. Allerdings sollte man berücksichtigen, dass ein solcher Zahlengenerator grundsätzlich unabhängig von irgendwelchen Tabellen konzipiert ist. Konkret kann ein solcher Zähler sowohl parallel für verschiedene Tabellen oder auch vielleicht zum Zählen bestimmter Programmaufrufe verwendet werden. Mit Hilfe des nächsten Listings können Sie eine kleine Tabelle erstellen, mit der Sie die Verwendung des Zahlengenerators demonstrieren können. drop table bekannte; / create table bekannte ( lfdnr number, name varchar2(50)) tablespace urs; commit;
Anschließend werden in diese Tabelle beliebige Daten eingefügt, wobei die laufende Nummer mit Hilfe des Zahlenlieferanten meinzaehler erzeugt wird. insert into insert into insert into insert into insert into insert into commit;
bekannte bekannte bekannte bekannte bekannte bekannte
values(meinzaehler.nextval, values(meinzaehler.nextval, values(meinzaehler.nextval, values(meinzaehler.nextval, values(meinzaehler.nextval, values(meinzaehler.nextval,
'Heidi'); 'Bibi'); 'Räuber Hotzenplotz'); 'Tina und Sabrina'); 'Rotkäppchen'); 'Aschenputtel');
Führen Sie nun eine Abfrage auf diese Tabelle aus, um die Vergabe der laufenden Nummer zu kontrollieren. Aufgrund des verwendeten Parameters cycle und des eingestellten Maximalwertes von fünf haben wir bei den sechs eingefügten Datensätzen halt schon einen Rundlauf hinter uns gebracht. SQLWKS> select * from bekannte 2> LFDNR NAME ---------- --------------------------------1 Heidi 2 Bibi 3 Räuber Hotzenplotz 4 Tina und Sabrina 5 Rotkäppchen 1 Aschenputtel 6 rows selected.
Mit Hilfe der Anweisung alter sequence können Sie fast alle oben beschriebenen Einstellungen verändern. Ausgenommen hiervon ist lediglich der Anfangswert des Zahlengenerators, den Sie bei der Anlage mittels start with festgelegt haben. Im
Beschreibung der Objekte
159
nächsten Beispiel werden wir an unserem Generator ein paar Einstellungen verändern, beispielsweise den Maximalwert auf fünfzig Zahlen hochsetzen, den Rundlauf ausschalten und die Vorausberechnung auf zwanzig Werte einstellen. alter sequence meinzaehler maxvalue 50 nocycle cache 20; commit;
Mit Hilfe des nächsten Skripts (vgl. Listing 2.20) probieren wir den Zähler auch gleich wieder aus. Zunächst löscht es die Tabelle noch einmal und fügt die gleichen Datensätze anschließend noch einmal ein. Allerdings werden zwischendurch auch im Rahmen einer gewöhnlichen Auswahlabfrage Werte vom Zahlengenerator abgefragt. Hierdurch soll dieses Beispiel demonstrieren was passiert, wenn ein Zahlengenerator für verschiedene Zwecke oder für unterschiedliche Tabellen verwendet wird. Das Ergebnis dieser Aktionen können Sie mit Hilfe der anschließend abgebildeten Abfrage (siehe Listing 2.31) betrachten. truncate table bekannte; insert into bekannte values(meinzaehler.nextval, insert into bekannte values(meinzaehler.nextval, select meinzaehler.nextval from v$database; select meinzaehler.nextval from v$database; select meinzaehler.nextval from v$database; select meinzaehler.nextval from v$database; insert into bekannte values(meinzaehler.nextval, insert into bekannte values(meinzaehler.nextval, insert into bekannte values(meinzaehler.nextval, select meinzaehler.nextval from v$database; insert into bekannte values(meinzaehler.nextval, commit;
'Heidi'); 'Bibi');
'Räuber Hotzenplotz'); 'Tina und Sabrina'); 'Rotkäppchen'); 'Aschenputtel');
Listing 2.31: Erneutes Einfügen der Daten mit Zweckentfremdung des Zählers
SQLWKS> select * from bekannte 2> LFDNR ---------- ------------------------3 Heidi 4 Bibi 9 Räuber Hotzenplotz 10 Tina und Sabrina 11 Rotkäppchen 13 Aschenputtel 6 rows selected. Listing 2.32: Kontrolle der vergebenen Nummern
160
Datenbankobjekte in einer Oracle-DB
Wie nicht anders zu erwarten war, entstehen bei einer derartigen Zweckentfremdung des Zahlengenerators Lücken in der Nummerierung, d.h. die durch dieses Objekt gelieferte Zahlenfolge ist bei entsprechender Einstellung (nocycle) zwar eindeutig, aber nicht unbedingt fortlaufend. Gründe dafür sind die schon erwähnte bzw. gezeigte Zweckentfremdung des Zählers, die Verwendung des gleichen Generators für verschiedene Tabellen aber auch eine nicht zu Ende geführte Einfügetransaktion kann solche Lücken hervorrufen, denn die vergebenen Nummern werden im Unterschied zu den eigentlichen Datensätzen natürlich nicht zurückgerollt.
2.2.13 Log Materialisierte View (Snapshot logs) Wie Sie sicher schon bemerkt haben dürften, benutze ich zumindest in den Überschriften neben den englischsprachigen Bezeichnungen der Objekte auch die deutschsprachige Benennung, die Sie beispielsweise im DBA Studio der 8i-Version finden. Bisher fand ich die Übersetzungen eigentlich gar nicht so schlecht, aber die Überschrift dieses und des nächsten Abschnitts sind meiner Meinung nach schon etwas schräg. Genau wie die schon kurz beschriebenen „Refresh groups“ wird auch dieses Schema-Objekt im Zusammenhang mit den sogenannten Snapshots interessant und daher erst im nächsten Kapitel eingehender behandelt. Bei den „Snapshot logs“, die mit Hilfe der Anweisungen create snapshot log bzw. drop snapshot log angelegt bzw. wieder gelöscht werden handelt es sich um ein Protokoll, mit dem die Replizierung verteilter Daten beschleunigt werden kann. Allerdings steht Ihnen das Feature nur zur Verfügung, wenn Ihr Datenbankserver über die Option „Advanced replication“ (vgl. Kapitel 2.1) verfügt, was Sie bei Bedarf mit Hilfe der View v$option abfragen können.
2.2.14 Materialisierte View (Snapshots) Das Snapshot-Objekt liefert Ihnen eine Kopie, eben einen Schnappschuss, einer Tabelle aus einer anderen Datenbank. In der Oracle-Dokumentation wird dieses gesamte Thema unter dem Begriff „Replication“ (Replikation) geführt. Sie finden hierzu sogar einen eigenen Link „Oracle8 Replication“. Daneben finden Sie auch eine ausführliche Beschreibung bzw. ein eigenes Kapitel im Buch „Oracle8 Concepts“. Unter Replikation versteht man nun zunächst einmal den Prozess zur Verwaltung von gemeinsamen Daten auf verschiedenen Datenbanken (vgl. Abb. 2.8). In einem solchen verteilten System existieren üblicherweise mehrere Kopien gleicher Daten und es geht jetzt um Mechanismen und Werkzeuge, diesen Zustand zum einen zu erreichen und zum anderen auch zu erhalten. Den Zustand zu erreichen heißt konkret, dass Sie ein Verfahren benötigen, um auf einfache Weise eine Kopie der Daten aus einer anderen Datenbank zu bekommen und Erhalten bedeutet, diese Kopie wenigstens von Zeit zu Zeit zu aktualisieren.
Beschreibung der Objekte
161
Update Abfrage db01
db02
Tabelle xxx (Kopie)
Tabelle xxx
Replikation
(Master)
Abbildung 2.8: Verwendung einer „ nur lese“ Kopie
Wie Sie der Abbildung 2.8 entnehmen können geht es manchmal auch darum, aus einem bisher linearen Prozess zwischen Datenabfrage und Aktualisierung eine Art Kreisverkehr zu bauen. Datenänderungen werden zunächst in der Mastertabelle, die sich in der db02-Datenbank befindet, ausgeführt und über den Replikationsprozess in der lokalen Kopie zurückerwartet. Ich finde, dass sich da die Frage stellt, warum man sich so etwas antut. Ein wesentlicher Grund für eine solche Konstruktion heißt schlicht und ergreifend „Performance“. Wie Sie schon wissen, besteht als Alternative für den Zugriff auf die Tabelle einer anderen Datenbank auch die Verwendung einer Datenbankverknüpfung (vgl. Database links). Wie Sie aber später noch bei den Abfragen bzw. beim Abfragetuning sehen werden, kann die Verwendung einer Datenbankverknüpfung bei gleichzeitiger Benutzung verschiedener lokaler und entfernter Tabellen den Zugriffsgau auslösen. Dahingegen ist die Verwendung bzw. Ausführung einfacher Änderungsabfragen (Einfügen, Ändern und Löschen einzelner Datensätze) auch in der verknüpften Datenbank immer schnell. Auch der nächste Grund resultiert aus Geschwindigkeitsaspekten. Sie können das schnellste Dialogsystem lahm legen, wenn Sie gleichzeitig nur genügend Hintergrundprozesse wie Reports oder Batch-Jobs starten. Im Extremfall kann es daher sinnvoll sein, für diese Reports und Batch-Jobs eine eigene Datenbank, vielleicht sogar auf einem eigenen Server, zu installieren, zumal die meisten der mir bekannten Reports und Batch-Jobs auch mit tagesaktuellen Daten zurechtkämen. In dem Fall müsste man also eine Datenbank erstellen, die im Wesentlichen nur aus Snapshots bzw. Kopien der eigentlichen Tabellen aus der primären Dialogdatenbank besteht. Diese Kopien müssten dann im Rahmen eines täglichen Replikationsverfahrens aktualisiert werden. Als dritten und letzten Grund möchte ich einen Aspekt einführen, der diesmal nicht auf Geschwindigkeit und damit auf technischen Gründen beruht, sondern aus organisatorischen Maßnahmen resultiert.
162
Datenbankobjekte in einer Oracle-DB
Es gibt viele Beispiele, wo zentrale Steuerungstabellen wie Kostenstellen, Preise, Artikelsortimente usw. zunächst innerhalb eines bestimmten Fachbereichs so lange optimiert werden, bis eine oder alle diese Tabellen der eigentlichen Anwendungsdatenbank zur Verfügung gestellt werden sollen. In dem Fall würde auch unser Kreisverkehr aus Abbildung 2.8 aufbrechen, da in solchen Fällen die Endanwender sowieso nur eine Leseberechtigung auf diese speziellen Tabellen haben. Die zentralen Steuerungstabellen würden vielleicht sogar mit Hilfe einer speziellen Anwendung aufbereitet und bei Bedarf über den Replikationsprozess verteilt werden. In all den bisher von mir genannten Beispielen war jedoch immerhin noch die Datenquelle und die Kopie eindeutig zu bestimmen. Dieses in Bezug auf die Administration sicherlich einfachere Verfahren wird innerhalb der Oracle-Dokumentation unter dem Begriff „Basic Replication“ abgehandelt. Daneben existiert auch noch das als „Advanced Replication“ beschriebene Verfahren, das eine echte verteilte Datenhaltung bzw. Bearbeitung ermöglicht. Hierbei können die verteilten Tabellen in jeder Datenbank geändert werden und es ist quasi Sache der Datenbank, für einen entsprechenden Abgleich zu sorgen und bei Bedarf die notwendigen Aktualisierungen durchzuführen (vgl. Abb 2.9).
Abfrage / Ändern db01
Abfrage / Ändern db02
Tabelle xxx
Tabelle xxx
Replikation
Abbildung 2.9: Verteilte Datenhaltung mit Überkreuzreplikation
Natürlich sind bei dem in Abbildung 2.9 skizzierten Verwahren Konflikte in Form von Datenkollisionen vorprogrammiert, es sei denn man hat das Glück, dass die verteilte Datenhaltung bei näherer Betrachtung eigentlich nur ein technisches Feature ist, denn aufgrund organisatorischer Maßnahmen werden die Daten immer nur getrennt bearbeitet. Ein gutes wenn auch schon leicht abgenutztes Beispiel hierfür ist immer der länderübergreifend operierende Konzern. Vor allem aus Gründen der Zugriffsgeschwindigkeit wird mindestens in jedem Land, wenn nicht sogar in jedem größeren Standort eine eigene Datenbank installiert. Und damit alle Anwendungsteile und Reports überall laufen, sollen in jeder Datenbank jeweils alle Daten gespeichert werden.
Beschreibung der Objekte
163
Da aber aufgrund organisatorischer Maßnahmen beispielsweise deutsche Kunden nur direkt in der deutschen Datenbank bzw. brasiliansiche Kunden nur im dortigen Standort mit der dort vorhandenen lokalen Datenbank bearbeitet werden, ist eine Datenkollision bei der Replikation nicht zu erwarten. Beispiele Um den Bogen nicht zu überspannen, möchte ich hier und jetzt nur die einfache Replikation an einem Beispiel demonstrieren. Dabei begnügen wir uns auch damit, dass wir selbst für die Aktualisierung der kopierten Daten sorgen müssen, d.h. ich verzichte ebenfalls auf die Einrichtung eines entsprechenden SNP-Hintergrundprozesses. Um mit Snapshots arbeiten zu können, benötigen Sie in der Regel wenigstens zwei Datenbanken. Wenn Sie bisher dem roten Faden des Buches gefolgt sind, dann haben Sie die orcl-Starterdatenbank und unsere selbstdefinierte Datenbank db01 zur Verfügung, wobei Sie das Beispiel natürlich auch mit beliebigen anderen Datenbanken ausprobieren können. Zunächst einmal muss im ersten Schritt die Mastertabelle angelegt werden. Hierzu definieren wir in der orcl-Datenbank die Tabelle „lohnarten“ (vgl. Listing 2.33) und fügen in ihr anschließend eine Reihe von Datensätzen ein. drop table lohnarten; / create table lohnarten ( la varchar2(3), la_text varchar2(55), constraint lohnarten_pk primary key (la)) tablespace usr; commit; insert into insert into insert into insert into insert into insert into insert into insert into commit;
lohnarten lohnarten lohnarten lohnarten lohnarten lohnarten lohnarten lohnarten
values values values values values values values values
('100', ('110', ('120', ('200', ('210', ('300', ('310', ('400',
'Grundgehalt'); 'Freiw. Zulage'); 'Sonstige Zulage'); 'Überstunden'); 'Pauschalausgleich Überstunden'); 'Sonderzahlung'); 'Bonus'); 'Direktversicherung');
Listing 2.33: Anlegen und Füllen der Mastertabelle
Die Lohnartentabelle hat einen recht einfachen Aufbau. Sie besteht nur aus einem eindeutigen Schlüssel, der Lohnart, und einer Bezeichnung. Das war zunächst auch schon alles, was in der orcl-Masterdatenbank zu tun ist. Die jetzt folgenden Arbeitsschritte müssen wieder in der db01-Datenbank durchführt werden. Dort sorgen wir als Nächstes für die gewünschte Kopie der Lohnartentabelle und legen hierzu einen entsprechenden Schnappschuss (vgl. Listing 2.34) an.
164
Datenbankobjekte in einer Oracle-DB
drop snapshot lohnarten; create snapshot lohnarten tablespace usr refresh force as select * from lohnarten@oralink; / commit; Listing 2.34: Anlage des lohnarten-Schnappschusses
Wie Sie sehen, ähnelt die Definition des Schnappschusses in gewisser Weise der Anlage einer Tabelle. Neben einem Namen können Sie den verwendeten Tablespace und wenn es denn sein muss, sogar eine eigene Speicherbelegungsregel (storageKlausel) vergeben. Was sich von der Anlage einer Tabelle unterscheidet, ist allerdings die Definition des Inhalts, denn der ergibt sich aufgrund einer spezifizierten Abfrage. In unserem Beispiel erstellen wir aufgrund der verwendeten Abfrage eine vollständige Kopie der Lohnartentabelle, die wir mit Hilfe des Datenbanklinks oralink aus der Quelldatenbank abholen. Wenn Sie nun vermuten, dass Sie hier anstelle derartig einfacher Ausdrucke auch durchaus komplexere Abfragen verwenden können, dann haben Sie Recht. Aus dem Grund erfolgt die Vorgabe der zu verwendenden Abfrage im Schema-Manager auch nicht in der bisher gewohnten formatgebunden Weise, sondern hierfür stellt Ihnen das Programm ein schlichtes mehrzeiliges Textfeld zur Verfügung, indem Sie die nötige Abfrage formatfrei eingeben können. Wichtig für die Funktionsweise des Snapshot-Objekts ist die verwendete refreshKlausel, die festlegt, auf welche Weise der Schnappschuss im Bedarfsfall aktualisiert wird. Die in meinem Beispiel verwendete Variante force bemüht sich, den Schnappschuss optimal wiederherzustellen. Konkret heißt das, dass Oracle zunächst versucht, lediglich die Änderungen zu übertragen was der Option fast entspricht. Ist das nicht möglich (zum Beispiel weil kein Snapshot Log vorliegt), dann wird der gesamte Schnappschuss neu aufgebaut, was der Option complete entspricht, d.h. mit der Klausel refresh complete würde das Snapshot-Objekt grundsätzlich komplett aktualisiert. Die force-Variante wird von Oracle im Übrigen standardmäßig verwendet, wenn Sie die refresh-Klausel beim Anlegen des Snapshot-Objekts weglassen. Das somit erzeugte Schnappschuss-Objekt können Sie eigentlich wie eine gewöhnliche Tabelle verwenden, d.h. es kann selbst wieder in jeder denkbaren Abfrage verwendet werden. Damit solche Abfragen gerade auch bei größeren Kopien schön schnell sind, können Sie für den Schnappschuss auch beliebige Indices definieren, wie Sie auch dem folgenden Beispiel entnehmen können. create index lohnarten_i on snap$_lohnarten (la_text);
Ist das nicht ein merkwürdiger Tabellenname, den ich bei der Anlage des Index verwendet habe? In der Tat führt die Definition des Snapshot-Objekts zu einer Reihe von Hintergrundaktivitäten. Zum einen wird für den gewählten Schnappschuss eine zugehörige Tabelle angelegt, die zusammen mit dem Präfix snap$_ den glei-
Beschreibung der Objekte
165
chen Namen erhält. Dabei wird ein in der Quelle definierter Primärschlüssel ebenfalls übernommen bzw. für die Schnappschusstabelle angelegt. Daneben legt Oracle auch eine View mit dem gleichen Namen des SnapshotObjekts an, wobei die üblicherweise für den eigentlichen Zugriff auf die kopierten Daten verwendet wird. Letztendlich erhalten Sie im Schema natürlich auch noch das eigentliche Schnappschuss-Objekt, in dem die Herkunft der Daten und die gewünschten Aktualisierungsregeln gespeichert werden. Nachdem nun der Schnappschuss erstellt wurde, können wir die kopierten Daten in unserer Datenbank ganz gewöhnlich verwenden, indem wir beispielsweise eine Abfrage der vorhandenen Lohnarten erstellen. SQLWKS> select * from lohnarten 2> LA LA_TEXT --- ------------------------------------------------------100 Grundgehalt 110 Freiw. Zulage 120 Sonstige Zulage 200 Überstunden 210 Pauschalausgleich Überstunden 300 Sonderzahlung 310 Bonus 400 Direktversicherung 8 rows selected.
Kehren wir nun noch einmal zu unserer Abbildung 2.8 zurück, dann als Nächstes wollen wir das dort skizzierte Verfahren nachstellen. Hierzu müssen Sie zuerst eine Änderung in der Mastertabelle durchführen und danach die Aktualisierung des Schnappschusses einleiten (vgl. Listing 2.35). Die hierzu notwendigen SQL-Befehle finden Sie im folgenden Beispiel. insert into lohnarten@oralink values ('900','Auszahlung'); commit; execute dbms_snapshot.refresh('lohnarten'); Listing 2.35: Änderung der Mastertabelle und auffrischen der Kopie
Mit Hilfe der ersten Anweisung fügen wir in der Mastertabelle einen neuen Datensatz mit der Lohnart „900“ ein. Damit diese Änderung auch in unserer Datenbank sofort sichtbar wird, lösen wir die Aktualisierung des Schnappschusses aus, indem wir die refresh-Prozedur aus dem dbms_snapshot-Paket aufrufen. Die Prozedur erhält als ersten Parameter den Namen oder eine Liste der zu aktualisierenden Schnappschüsse. Was ich hier gezeigt habe, ist die einfachste Verwendungsform dieser Paketprozedur, die für spezielle Aufgaben noch weitere Parameter erhalten kann. Außerdem enthält das Paket neben der refresh-Prozedur auch noch weitere Funktionalitäten. Wenn es Sie interessiert, dann finden Sie eine weitergehende Beschreibung des Paketes in der Dokumentation „Oracle8 Replication“ im Kapitel „Replication Management API Reference“.
166
Datenbankobjekte in einer Oracle-DB
Snapshot Logs Ein sogenannter Snapshot Log (soll ich lieber „Log der materialisierten View“ sagen) ist eine spezielle Tabelle, in der die in der Mastertabelle durchgeführten Änderungen aufgezeichnet werden, d.h. dieser Log-Bereich wird in der gleichen Datenbank wie die Mastertabelle angelegt. Bei einer späteren angeforderten Aktualisierung eines Schnappschusses kann Oracle dieses Protokoll verwenden, um im Rahmen des Snapshot-Refreshs nur die geänderten Sätze zu übertragen. Es liegt auf der Hand, dass eine solche Verfahrensweise (fast refresh) üblicherweise wesentlich schneller abläuft als eine vollständige Wiederherstellung der Kopie (complete refresh). Die Anlage eines solchen Logs ist denkbar einfach und erfolgt, wie Sie dem folgenden Beispiel entnehmen können, mit Hilfe der Anweisung create snapshot log. drop snapshot log on lohnarten; create snapshot log on lohnarten tablespace usr; commit;
Da beim Snapshot Log wieder Daten in einer speziellen Tabelle gespeichert werden, haben Sie die Möglichkeit, den zu verwendenden Tablespace und bei Bedarf auch eine eigene storage-Klausel vorzugeben. Die hinter dem Log liegende Tabelle heißt übrigens wie die Mastertabelle, erhält jedoch zusätzlich den Präfix mlog$_ (z.B. mlog$_lohnarten). Probieren wir das ganze einfach mal aus. Wechseln Sie hierzu in die orcl-Datenbank und erstellen Sie dann als erstes wie oben beschrieben das Snapshot Log für die Tabelle „lohnarten“. Fügen sie anschließend eine neue Lohnart in die Mastertabelle ein. insert into lohnarten values ('800','Kindergeld');
Wenn Sie danach einfach mal eine Abfrage auf die Tabelle des Snapshot Logs ausführen, dann erhalten Sie als Ergebnis genau die eben eingefügte Lohnart. SQLWKS> select * from mlog$_lohnarten 2> LA SNAPTIME$$ D O CHANGE_VECTOR$$ --- -------------------- - - --------------800 01-JAN-00 I N FE 1 row selected.
Wechseln Sie nun in die andere Datenbank und führen Sie danach die Aktualisierung des Schnappschusses durch und kontrollieren Sie danach noch einmal die Log-Tabelle. Die enthält jetzt keine Daten mehr, da sie nach einer durchgeführten Aktualisierung automatisch gelöscht wird. Refresh Groups Wie schon angedeutet, müssen die einzelnen Snapshots nicht unbedingt einzeln aktualisiert werden, sondern Sie haben auch die Möglichkeit, diesen Vorgang zu automatisieren oder zumindest in Gruppen zu organisieren. Selbst in unserem ein-
Beschreibung der Objekte
167
fachen Beispiel wäre es denkbar, das für die Mastertabelle nicht nur einer, sondern mehrere Schnappschüsse mit jeweils unterschiedlichen Abfragen existieren. In dem Fall müssten Sie nach einer Änderung alle Schnappschüsse aktualisieren, um in jeder dieser Tabellen den gleichen Stand zu haben. Das ginge zwar auch mit Hilfe eines einzigen dbms_snapshot-Befehls, jedoch liegt das eigentliche Problem darin, immer genau zu wissen welche Schnappschüsse gerade überhaupt zu aktualisieren sind. Aus diesem Grund können Sie zusammenhängende Snapshot-Objekte zu einer logischen Gruppe zusammenfassen und erhalten hierdurch die Möglichkeit, im Bedarfsfall die Aktualisierung der gesamten Gruppe zu veranlassen. Auch das möchte ich im Rahmen eines kleinen Beispiel darstellen. Hierzu erzeugen wir uns zunächst ein weiteres Snapshot-Objekt, das ebenfalls auf der Mastertabelle „lohnarten“ basiert. drop snapshot lohnarten1; create snapshot lohnarten1 tablespace usr refresh force as select * from lohnarten@oralink where la > '900'; / commit;
Wenn Sie nun wieder entsprechend der gezeigten Vorgehensweise im Listing 2.35 in der Mastertabelle eine weitere Lohnart (z.B. 901, „Überweisung“) einfügen, dann wird zwar die Kopie „lohnarten“ aktualisiert, aber der neue Schnappschuss „lohnarten1“ wird, wie eigentlich nicht anders zu erwarten war, eben nicht aufgefrischt. Hierzu hätten Sie in der refresh-Anweisung beide Schnappschüsse spezifizieren müssen. execute dbms_snapshot.refresh('lohnarten1,lohnarten');
Um nun die gemeinsame Auffrischung dieser beiden Kopien zu vereinfachen, legen wir hierzu die Refresh Group „lohnarten“ an. Wie schon angedeutet, erfolgt die Anlage einer solchen Gruppe mit Hilfe der make-Prozedur aus dem dbms_refresh-Pakets, wobei Sie die genaue Beschreibung des Pakets ebenfalls wieder im Kapitel „Replication Management API Reference“ finden. Die Prozedur erwartet eine Reihe von Parametern, weshalb es nicht schadet, sich zumindest das zugehörige SQL-Skript mit Hilfe des Schema-Managers zu erzeugen. Der zugehörige Dialog im Schema-Manager besteht aus zwei Registerkärtchen. Auf der ersten Seite (vgl. Abb. 2.10) müssen Sie die generellen Daten der neuen Gruppe, wie beispielsweise den Namen, das zugehörige Schema oder das Intervall für eine eventuelle automatische Aktualisierung vorgeben. Entsprechend dem eingedeutschten Sprachgebrauch des deutschen 8i-Clients müssen Sie jetzt natürlich eine neue „Abgleichengruppe“ anlegen, (vgl. Abb. 2.11) was allerdings genauso funktioniert, da es schließlich ja auch das Gleiche ist.
168
Datenbankobjekte in einer Oracle-DB
Abbildung 2.10: Definition einer neuen Refresh Group
Abbildung 2.11: Erstellen einer „ Abgleichengruppe“ in der 8i-Version
Mit Hilfe des zweiten Registerkärtchens (vgl. Abb. 2.12) erfolgt die Zuordnung der einzelnen Snapshots zu der neuen Gruppe. Hierbei finden Sie im unteren Fensterteil eine Liste aller vorhandenen und noch nicht zugeordneten Schnappschussdefinitionen und in der oberen Liste befinden sich die zur Gruppe gehörenden SnapshotObjekte.
Beschreibung der Objekte
169
Abbildung 2.12: Zuordnen der benötigten Snapshots
Nach Eingabe aller Definitionen können Sie die neue Gruppe mit Hilfe der CreateSchaltfläche erzeugen, oder Sie führen das zugehörige SQL-Skript (vgl. Listing 2.36) mit Hilfe eines beliebigen SQL-Editors in der Datenbank aus. begin dbms_refresh.make('"SYSTEM".lohnarten', '"SYSTEM"."LOHNARTEN", "SYSTEM"."LOHNARTEN1"', NULL, '', FALSE, TRUE, 0, 'RB0', FALSE, FALSE ); commit; end; Listing 2.36: Anlegen einer Aktualisierungsgruppe
Die Aktualisierung aller zur Gruppe gehörnenden Schnappschüsse erfolgt jetzt mit Hilfe der refresh-Prozedur des dbms_refresh-Pakets, deren Verwendung Sie dem nächsten Beispiel entnehmen können. insert into lohnarten@oralink values ('907','VL-3'); commit; execute dbms_refresh.refresh('LOHNARTEN'); commit;
Das Löschen einer solchen Aktualisierunggruppe erfolgt auch mit Hilfe des Pakets dbms_refresh und zwar genau mit der dort enthaltenden Prozedur destroy. execute dbms_refresh.destroy('LOHNARTEN'); commit;
170
Datenbankobjekte in einer Oracle-DB
Varianten Snapshots bzw. vor allem die Snapshot Logs können nicht nur im Rahmen der ganz am Anfang beschriebenen Beispiele und Herausforderungen bei verteilter Datenhaltung verwendet werden, sondern gerade bei den Logs ergeben sich noch andere Einsatzgebiete. Das ist wie im richtigen Leben: mit einem Feuerzeug können sie eine Kerze anzünden oder eine Flasche Bier öffnen; erfunden wurde das Feuerzeug wohl eher für die Kerze und dennoch ist es für die andere Aufgabenstellung auch ein prima Werkzeug. Im Folgenden erhalten Sie vielleicht ein paar Anregungen für die zweckentfremdete Verwendung dieses Oracle-Werkzeugs.
X
X
Stichtagsbestände Gerade bei Statistiken ist es oft störend, wenn deren Ergebnis mehr oder weniger zufällig vom Datum und der Uhrzeit des Programmstarts abhängt. Aus dem Grund werden für solche Auswertungen oftmals spezielle Stichtagsbestände der benötigten Daten bereitgestellt. Diese Bestände werden anschließend nur noch kontrolliert geändert, beispielsweise jeweils am Monatsende. Bevor Sie nun lange über Programme und Verfahren zur Produktion solcher Stichtagsbestände nachdenken, sollten Sie einmal überlegen, ob Sie diese Bestände nicht mit Hilfe von Snapshots liefern können. Wenn Sie nun glauben, das ginge nicht, weil die Stichtagsbestände in der selben Datenbank wie die Mastertabellen liegen, dann liegen Sie falsch, denn Snapshots können auch für Tabellen in der gleichen Datenbank angelegt werden, d.h. die Verwendung von Daten aus anderen Datenbanken ist keine zwingende Option. Änderungsdienst Wer schon einmal Schnittstellen geschrieben hat, der weiß, dass vor allem bei großen Datenmengen irgendwann einmal die Bestellung auf dem Tisch liegt, nur noch geänderte Datensätze zu liefern. Nichts ist einfacher als das, denn in dem Fall legen Sie für diese Tabellen einfach jeweils ein Snapshot Log an. Anschließend besitzen Sie für Ihr Schnittstellenprogramm für jede zu liefernde Tabelle eine Liste allen geänderten oder gelöschten Primärschlüsseln und genau die übertragen Sie anschließend auch nur noch. Danach löschen Sie die Snapshot Logs, so dass Sie beim nächsten Schnittellenlauf wieder nur noch neue Änderungen übertragen. In der Tabelle des Snapshot Logs finden Sie das Feld „dmltype$$“. Dieses Feld enthält die Wert „I“, „U“ und „D“, wenn der in der Mastertabelle zugehörige Datensatz eingefügt (insert), geändert (update) oder gelöscht (delete) wurde.
2.2.15 Synonym (Synonyms) Endlich mal wieder ein einfaches Schema-Objekt ohne tieferen Hintergrund oder besonderer Tragweite. Was dieses Objekt macht bzw. wozu es dient, wird eigentlich schon mit der Überschrift des Kapitels aussagekräftig beschrieben: Es geht um die Definition von Alternativbegriffen für vorhandene Schema-Objekte.
Beschreibung der Objekte
171
Bisher haben wir im Rahmen dieses Workshops die verschiedenen Schema-Objekte mit Hilfe irgendeines Benutzers (z.B. system) angelegt und anschließend einfach unter dem verwendeten Namen angesprochen. Dadurch, dass wir die ganze Zeit mit der Benutzer-Id des Objektbesitzers gearbeitet haben, konnten wir die angelegten Objekte demzufolge ohne besondere Qualifizierung verwenden. Das ist aber ein spezieller Luxus, den normalerweise nur der Objekte-Eigentümer geniest, denn der vollständige Zugriffspfad auf ein Objekt beginnt immer mit dem Namen des zugehörigen Schemas. Hiernach folgt als Trennzeichen ein Punkt (.) und erst dann kommt der eigentliche Name des Objekts (<Schema>.). Nun haben wir aber in den bisherigen Beispielen oftmals, vielleicht sogar ohne es zu wissen, Objekte verwendet, die bei genauerem Hinschauen Eigentum anderer Benutzer bzw. Schemata sind. Beispiele hierfür sind zum einen die schon mehrfach verwendeten dbms-Pakete oder v$-Views, die eigentlich alle dem Benutzer sys gehören bzw. dem gleichnamigen Schema zugeordnet sind. Das wir hierauf aber bisher keine Rücksicht nehmen mussten liegt einfach daran, dass für diese Objekte in der Datenbank Synonyme, Aliasnamen oder Alternativbezeichnungen, oder welchen Begriff Sie auch immer dafür verwenden möchten, definiert sind. Bei der Anlage solcher Synonyme gibt es prinzipiell zwei Strategien. Zum einen können Sie beispielsweise in Ihrem aktuellen Schema ein Synonym für ein Objekt aus einem anderen Schema oder sogar einer anderen Datenbank anlegen. Zum anderen können Sie öffentliche (public) Alternativbezeichnungen definieren, so dass das zugrundeliegende Objekt bei entsprechenden Zugriffsrechten grundsätzlich auch über seinen Aliasnamen angesprochen werden kann. Die Anlage solcher Synonyme erfolgt grundsätzlich mit Hilfe der Anweisung create synonym und das Löschen der Alternativbezeichnung können Sie mit dem Befehl drop synonym veranlassen. Doch genug der Theorie, mit Hilfe eines einfachen Beispiels soll das Ganze nun wieder veranschaulicht werden. Beispiele Im letzten Kapitel haben Sie mit Hilfe des Datenbanklinks „oralink“ häufiger die Tabelle „lohnarten“ aus einer anderen Datenbank abgefragt. Stellen wir uns nun vor, diese Aufgabe stünde regelmäßig ins Haus und Sie hätten irgendwann keine Lust mehr, immer an die korrekte Vorgabe des Links zu denken. In dem Fall könnten Sie sich für die Tabelle aus der anderen Datenbank ein Synonym anlegen und die Tabelle anschließend in jeder Abfrage nur noch über den vergebenen Aliasnamen ansprechen (vgl. Listing 2.37). drop synonym r_lohnarten; create synonym r_lohnarten for system.lohnarten@oralink; commit; Listing 2.37: Anlage des Synonyms r_lohnarten
172
Datenbankobjekte in einer Oracle-DB
In dem Beispiel legen Sie für die in der orcl-Datenbank angelegte Tabelle system.lohnarten in Ihrem eigenen Schema die Alternativbezeichnung r_lohnarten an. Folglich können Sie von nun an in allen Abfragen diese neue Tabellenbezeichnung verwenden. select * from r_lohnarten; -- mit Synonym select * from system.lohnarten@oralink; -- ohne Synonym
Das war also ein Beispiel für die Anlage eines Synonyms im eigenen Schema, wo durch dessen Verwendung die eigene Arbeit ein wenig vereinfacht wurde. Im nächsten Fall geht es um die Anlage eines öffentlichen Synonyms. Nehmen wir an, Sie sind von dem im Kapitel 2.2.8 erstellten tollhaus-Paket so begeistert, dass Sie den Zugriff auf die dort enthaltenen Funktonalitäten für andere Benutzer vereinfachen möchten, wozu sich ein öffentlicher Aliasname anbietet. drop public synonym tollhaus; create public synonym tollhaus for system.tollhaus; commit;
Anschließend können alle Benutzer mit Zugriff auf das tollhaus-Paket dieses beim Aufruf verwenden, als wenn es ihr eigenes wäre. select tollhaus.spiegeln(parameter) from v$option; -- mit Synonym select system.tollhaus.spiegeln(parameter) from v$option; -- ohne Synonym
2.2.16 Tabellentyp (Table Types) Willkommen zurück bei den Objekten. Wir sind nun innerhalb der SchemaObjekte bei der letzten objektorientierten Kategorie angelangt, die Sie damit wieder nur dann sehen, wenn Ihr Datenbankserver über die Objekte-Option verfügt. Genau wie bei den schon beschriebenen „Array Types” geht es auch jetzt wieder darum, sogenannte Kollektionen (Collections) zu verwalten. Im Unterschied zu den Datenfeldern, bei denen es um die Anlage einer festen Anzahl von Elementen ging und die aus Sicht der Tabelle in einer einzigen Spalte gespeichert wurden, werden die gleich besprochenen Kollektionen in einer separaten Tabelle abgelegt und über interne Verweise angesprochen. Dadurch ist die Anzahl der in der Kollektion gespeicherten Elemente nicht mehr festgelegt, d.h. sie kann im beliebigen Rahmen und vor allem von Fall zu Fall variiert werden. Mit Hilfe einer Kollektion beschreibt man üblicherweise eine Liste gleichartiger Elemente. Bleiben wir bei unserem letzten Objekt-Beispiel „mensch”, wofür Kollektionen für die Objekte Autos, Bücher oder sonstige gleichartige Besitztümer aber auch Aufzählungen für Urlaubsreisen, Seminare oder ähnliches denkbar wären. In der klassischen relationalen Beschreibung entspricht jede dieser Kollektionen einer separaten (Child-Tabelle) meistens mit gleichem Schlüssel plus mindestens einem weiteren Hilfsschlüssel zur Identifizierung jedes Eintrags (vgl. Abb 2.13).
Beschreibung der Objekte
173
Mensch
0:n
Autos
0:n
Bücher
0:n
Reisen
Abbildung 2.13: Kollektionen für das Objekt „ mensch“
Die Erstellung der hier besprochenen Sammlungen erfolgt wieder mit einer Variante des Befehls create type, d.h. bei dem Befehl handelt sich hierbei um eine sehr universelle Anweisung, mit der insgesamt die drei verfügbaren objektorientierten Schema-Einträge erstellt werden können. Beispiele In unserem Beispiel möchte ich das Ganze an dem oben beschriebenen Buchbeispiel demonstrieren, d.h. wir erstellen für jeden Menschen eine Sammlung der ihm gehörenden Exemplare. Damit das Ganze einfach und übersichtlich bleibt, speichern wir für jedes Buch lediglich den Titel und den Anschaffungspreis. Hierzu legen wir als Erstes ein weiteres Objekt mit dem Namen „buch” an, das neben den eben genannte Eigenschaften noch eine laufende Nummer zur Identifizierung erhält (vgl. Listing 2.38). create type lfdnr titel wert
buch as object ( number, varchar2(50), number ) ;
commit; Listing 2.38: Anlage des neuen Objekts „buch“
Hierdurch erhalten wir zunächst aber nur wieder eines neues Objekt der Kategorie „Object Type“. Erst im nächsten Schritt entsteht durch die erneute Verwendung des create type-Befehls endlich der neue Objekttyp „Table Type“.
174
Datenbankobjekte in einer Oracle-DB
create type buchliste as table of buch; commit;
Jetzt verfügen wir also über ein Objekt, mit dem eine Sammlung von Büchern beschrieben werden kann, d.h. jedes in der Sammlung enthaltene Element entspricht dabei dem Datentyp bzw. dem Objekt „buch“. Der nächste Schritt besteht nun darin, unser bestehendes Objekt „mensch“ entsprechend zu erweitern. Normalerweise geht das immer mit Hilfe einer Anweisung der Art create or replace type. Was in unserem Fall dabei allerdings passieren wird, dass können Sie dem Listing 2.39 entnehmen. SQLWKS> create or replace type mensch as object ( 2> name varchar2(30), 3> vname varchar2(30), 4> strasse varchar2(30), 5> plz varchar2(5), 6> ort varchar2(30), 7> geschl varchar2(1), 8> gebdat date, 9> 10> buecher buchliste, 11> 12> member function g_alter return number, 13> pragma restrict_references(g_alter, wnds, wnps), 14> 15> member function g_anschrift return number, 16> pragma restrict_references(g_anschrift, wnds, wnps), 17> 18> member function g_buchzahl return number, 19> pragma restrict_references(g_buchzahl, wnds, wnps) ); create or replace type mensch as object ( * ORA-02303: cannot drop or replace a type with type or table dependents Listing 2.39: Misslungener Versuch, das vorhandene Objekt zu erweitern
Die Weigerung von Oracle, das schon vorhandene Objekt aufgrund von existierenden Abhängigkeiten zu erweitern, ist im Übrigen nicht nur auf selbstdefinierten Typen bzw. Objekte beschränkt, sondern so etwas kann Ihnen auch beim nachträglichen Ändern von gewöhnlichen Tabellen passieren. Was nun folgt, entspricht daher auch immer dem gleichen Schema. Zunächst wird für die alte Tabelle eine Sicherungskopie angelegt, dann wird die Tabelle neu definiert und anschließend unter Berücksichtigung der neuen Struktur zurückkopiert. In unserem konkreten Fall ergibt sich folgendes Szenario: 1. Entladen der Tabelle „menschen“ in eine Hilfstabelle 2. Tabelle „menschen“ löschen. Hierdurch wird das Objekt „mensch“ freigegeben. 3. Objektdefinition „mensch“ ändern.
Beschreibung der Objekte
175
4. Tabelle „menschen“ neu anlegen. 5. Tabelle zurückkopieren. 6. Hilfstabelle bzw. Sicherungskopie löschen. Das Listing 2.40 zeigt die notwendigen Anweisungen zur Erledigung des ersten Arbeitsschritts. Zunächst wird eine gewöhnliche Tabelle mit all den notwendigen Feldern erstellt. Diese Tabelle wird klassisch und nicht mit Hilfe des mensch-Objekts angelegt, da ansonsten die mit dem Objekt „mensch“ verbundenen Abhängigkeiten nicht entfernt, sondern nur verschoben würden. drop table h_mensch; / create table h_mensch ( name varchar2(30), vname varchar2(30), strasse varchar2(30), plz varchar2(5), ort varchar2(30), geschl varchar2(1), gebdat date ) tablespace usr; commit; insert into h_mensch select name, vname, strasse, plz, ort, geschl, gebdat from menschen commit; Listing 2.40: Anlegen der Hilfstabelle und Erstellen der Sicherungskopie
Im letzten Teil des Skripts werden die Daten in die neue Tabelle kopiert, womit der erste Teil der Aufgabe erledigt wäre, doch wie heißt es so schön: dieses war der erste Streich, doch der zweite folgt sogleich... drop table menschen; commit;
Nach Eingabe der eben abgebildeten Befehle ist die Tabelle „menschen“ gelöscht, wodurch das Objekt „mensch“ zum Ändern freigegeben wird, so dass Sie den dritten Arbeitsschritt einleiten und das Objekt in der gewünschten Weise ändern können (vgl. Listing 2.41). create or replace type mensch as object ( name varchar2(30), vname varchar2(30), strasse varchar2(30), plz varchar2(5), ort varchar2(30), geschl varchar2(1),
176
Datenbankobjekte in einer Oracle-DB
gebdat date, buecher buchliste, member function g_alter return number, pragma restrict_references(g_alter, wnds, wnps), member function g_anschrift return varchar2, pragma restrict_references(g_anschrift, wnds, wnps), member function g_buchzahl return number, pragma restrict_references(g_buchzahl, wnds, wnps) ); / commit; Listing 2.41: Ändern der Objektdefinition „mensch“
Im Prinzip wird das Objekt im Wesentlichen um die Eigenschaft der Bücherliste erweitert. Zusätzlich definieren Sie die neue Methode g_buchzahl, mit der später einmal die Anzahl der in der Liste gespeicherten Bücher abgerufen werden soll. Nach der Aktualisierung des Objekts kann die darauf basierende Tabelle „menschen“ wieder angelegt werden, allerdings funktioniert das wie Sie gleich sehen werden aufgrund der dort enthaltenen Kollektion etwas anders als vorher. create table menschen of mensch tablespace usr nested table buecher store as buch_tab; commit;
Diese Anweisung legt zusammen mit der Tabelle „menschen“ implizit noch eine weitere Tabelle „buch_tab“ an, in der die jeweils vorhandene Büchersammlung gespeichert werden soll. Konkret sieht die angelegte Tabelle damit folgendermaßen aus: SQLWKS> desc menschen Column Name Null? ------------------------------ -------NAME VNAME STRASSE PLZ ORT GESCHL GEBDAT BUECHER
Type ---VARCHAR2(30) VARCHAR2(30) VARCHAR2(30) VARCHAR2(5) VARCHAR2(30) VARCHAR2(1) DATE RAW(36)
Beschreibung der Objekte
177
Im nächsten Schritt geht es nun um das Zurückladen der Sicherungskopie in unsere neue Tabelle, was wieder mit Hilfe einer gewöhnlichen insert-Anweisung passiert. insert into menschen select name, vname, strasse, plz, ort, geschl, gebdat, buchliste() from h_mensch; commit;
Beachten Sie hierbei allerdings die verwendete Anweisung buchliste(). Diese Anweisung sieht aus wie eine Funktion und verhält sich eigentlich auch so ähnlich. Im objektorientierten Sprachgebrauch bezeichnet man diese Funktion allerdings gewöhnlich als Konstruktor. Ein solcher Konstruktor tut was sein Name sagt, d.h. er konstruiert das unter dem selben Namen definierte Objekt. In unserem Beispiel erstellt der Ausdruck also eine Bücherliste. Dabei werden normalerweise die in der Liste zu speichernden Elemente (Bücher) innerhalb der Klammern aufgezählt, d.h. in unserem konkreten Fall konstruiert der Ausdruck buchliste() einfach nur eine leere Bücherliste. Damit haben wir den fünften Arbeitsschritt abgearbeitet und fahren gleich mit dem sechsten und letzten Schritt, dem Löschen der Sicherungskopie, fort. drop table h_mensch; commit;
Bevor wir nun für unser Objekt „mensch“ die neue Methode g_buchzahl erstellen, wollen wir uns einmal an einem Beispiel anschauen, wie die Aktualisierung unserer Datensätze vor allem in Bezug auf die Bücherliste aussehen könnte (vgl. Listing 2.42). update menschen set buecher = buchliste(buch(1, 'Oracle Workshop', 20)) where name = 'Raymans' and vname = 'Irina'; update menschen set buecher = buchliste(buch(1, 'dBASE für Fortgeschrittene', 20), buch(2, 'Meine Rezepte', 23)) where name = 'Zschoge'; update menschen set buecher = buchliste(buch(1, 'Donald Duck', 5), buch(2, 'Herr der Ringe', 9.30), buch(3, 'Elementarteilchen', 17.95)) where name = 'Heger'; commit; Listing 2.42: Aktualisierung der Bücherliste mit Hilfe von update-Anweisungen
178
Datenbankobjekte in einer Oracle-DB
Diese Anweisungen sehen wirklich schlimmer aus als sie sind. Zusammen mit Objekten erzeugt man oft geschachtelte Konstruktionen, die bisher nur bei komplexen ineinandergeschachtelten Funktionsaufrufen möglich waren. Wie schon gesagt, stellt jedes Objekt auch eine namensgleiche besondere Konstruktions-Funktion zur Verfügung mit dem das Objekt gebildet werden kann. Dieser Konstruktor erwartet für jede Objekteigenschaft einen Parameter und liefert als Ergebnis einen Verweis auf das gebildete Objekt. Dabei definiert das von uns erstellte Objekt „buchliste“ eine unbestimmte Anzahl von Elementen, weshalb die zugehörige Konstruktionsfunktion auch mit einer variablen Anzahl von Parametern verwendet werden kann. Jeder dieser Parameter muss allerdings vom Typ „buch“ sein. Da das wiederum ein Objekt ist, wird ese mit dem zugehörigen Konstruktor buch() erzeugt, der hierzu genau drei Werte für die entsprechenden Eigenschaften (Lfd.Nr., Titel und Wert) des Buchobjekts benötigt. Der erste Datensatz erhält mit der Aktualisierungsabfrage also genau ein Buch, wohingegen beim dritten Datensatz eine Buchliste mit drei verschiedenen Büchern konstruiert wird. Jetzt wird es so langsam Zeit für die Erstellung unsere neue Methode g_buchzahl, was im folgenden Listing 2.43 passiert. create or replace type body mensch is member function g_alter return number is begin return floor(months_between(sysdate, gebdat) / 12) ; end; member function g_anschrift return varchar2 is anschrift varchar2(2000) := ' '; begin if geschl = 'M' then anschrift := 'Herrn' || chr(10) || chr(13); else anschrift := 'Frau' || chr(10) || chr(13); end if; anschrift := anschrift || vname || ' ' || name || chr(10) || chr(13); anschrift := anschrift || strasse || chr(10) || chr(13); anschrift := anschrift || plz || ' ' || ort; return anschrift; end; member function g_buchzahl return number is begin return buecher.count; end;
Beschreibung der Objekte
179
end; / show errors; commit; Listing 2.43: Programmierung der neuen Methode g_buchzahl
Wie Sie dem Beispiel entnehmen können, stellt die Programmierung der neuen Methode keine größere Herausforderung dar. Alle Kollektionsobjekte besitzen standardmäßig schon eine Reihe bereitgestellter Methoden, mit deren Hilfe beispielsweise ein Durchblättern und die Verwaltung der Listen möglich ist. Eine dieser Methoden heißt count und liefert die Anzahl der in einer Kollektion enthaltenen Elemente. SQLWKS> select name, gebdat, p.g_alter(), p.g_buchzahl() 2> from menschen p NAME GEBDAT P.G_ALTER( P.G_BUCHZA ------------------------------ ----------------- ---------- ---------Raymans 03-DEC-64 35 0 Raymans 27-DEC-65 34 1 Zschoge 13-JUL-62 38 2 Heger 12-MAY-69 31 3 4 rows selected. Listing 2.44: Abfrage unserer neuen Struktur unter Verwendung der neuen Methode
Eine Aufstellung und Beschreibung der insgesamt vorhandenen Kollektionsmethoden finden Sie in der PL/SQL-Dokumentation „PL/SQL User's Guide and Reference“. Gehen Sie dort in das Kapitel „Using Collection Methods“ bzw. den dortigen Abschnitt „Collection and Records“. Neue Member-Funktion hinzufügen Keine Probleme mit eventuell verschachtelten Abhängigkeiten der einzelnen Objekte erhalten Sie, wenn es lediglich darum geht, die vorhandenen MemberFunktionen zu erweitern oder zu bearbeiten. Wir wollen unser Beispiel noch ein wenig erweitern, und eine weitere Funktion hinzufügen, mit der Sie die Summe der in der Buchliste gespeicherten Werte ermitteln können. Im Listing 2.45 finden Sie die Erweiterung des Objekts und im Listing 2.46 wird die Programmierung der neuen Methode gezeigt. alter type mensch replace as object ( name varchar2(30), vname varchar2(30), strasse varchar2(30), plz varchar2(5), ort varchar2(30), geschl varchar2(1), gebdat date,
180
Datenbankobjekte in einer Oracle-DB
buecher buchliste, member function g_alter return number, pragma restrict_references(g_alter, wnds, wnps), member function g_anschrift return varchar2, pragma restrict_references(g_anschrift, wnds, wnps), member function g_buchzahl return number, pragma restrict_references(g_buchzahl, wnds, wnps), member function g_werte return number, pragma restrict_references(g_werte, wnds, wnps) ); / commit; Listing 2.45: Erweitern des Objekts um eine neue Member-Funktion
create or replace type body mensch is member function g_alter return number is begin return floor(months_between(sysdate, gebdat) / 12) ; end; member function g_anschrift return varchar2 is anschrift varchar2(2000) := ' '; begin if geschl = 'M' then anschrift := 'Herrn' || chr(10) || chr(13); else anschrift := 'Frau' || chr(10) || chr(13); end if; anschrift := anschrift || vname || ' ' || name || chr(10) || chr(13); anschrift := anschrift || strasse || chr(10) || chr(13); anschrift := anschrift || plz || ' ' || ort; return anschrift; end; member function g_buchzahl return number is begin return buecher.count; end; member function g_werte return number is
Beschreibung der Objekte
i total
181
integer; number := 0;
begin for i in 1..buecher.count loop total := total + buecher(i).wert; end loop; return total; end; end; / show errors; commit; Listing 2.46: Erstellen der neuen Member-Funktion g_werte
Innerhalb der neuen Funktion g_werte werden mit Hilfe einer Schleife alle vorhanden Elemente der Bücherliste durchlaufen und dabei werden die gespeicherten Werte mit Hilfe der Variablen total summiert. Der Wert dieser Variablen wird am Ende der Funktion als Ergebnis zurückgegeben. Wie Sie dem Beispiel weiterhin entnehmen können, erfolgt der Zugriff auf ein einzelnes Element der Bücherliste genau wie bei einem Datenfeld mit Hilfe eines Index. Danach erfolgt getrennt durch einen Punkt die Nennung der gewünschten Eigenschaft, d.h. die beiden folgenden Ausdrücke buecher(2).wert := 22; x := buecher(2).wert;
ändern die Eigenschaft „wert“ des zweiten Buchelements bzw. kopieren den aktuellen Inhalt in eine gewöhnliche Variable. Schauen Sie sich das Ergebnis doch einfach mal an, indem Sie die nun folgende Abfrage eingeben: SQLWKS> select name, gebdat, p.g_alter() as a, 2> p.g_buchzahl() as b, p.g_werte() as w 3> from menschen p 4> NAME GEBDAT -------------------------- ------------------Raymans 03-DEC-64 Raymans 27-DEC-65 Zschoge 13-JUL-62 Heger 12-MAY-69 4 rows selected.
A ----------35 34 38 31
B W -----------0 0 1 20 2 43 3 32.25
182
Datenbankobjekte in einer Oracle-DB
Varianten Ich möchte noch mal auf das Laden der Buchtabelle zurückkommen. In unserem Beispiel haben wir die Buchliste mit Hilfe individueller Änderungsabfragen erstellt, was in der Praxis natürlich nicht denkbar wäre, es sei denn, die zu ladende Tabelle besitzt wirklich nur eine Handvoll Datensätze. In Wirklichkeit stünden Sie jetzt vor der Herausforderung, eine wahrscheinlich als gewöhnliche Tabelle vorliegende Bücherliste in das Kollektionsobjekt zu übertragen, was mit Hilfe von PL/SQL-Sprachelementen eigentlich auch kein größeres Problem darstellt. Doch bevor wir uns eine Lösungsmöglichkeit für die eben beschriebene Aufgabe anschauen, erstellen wir zuerst einmal die relationale Hilfstabelle mit den gespeicherten Büchern (vgl. Listing 2.36). drop table h_buch; / create table h_buch ( name varchar2(30), vname varchar2(30), lfdnr number, titel varchar2(50), wert number ) tablespace usr; commit; / insert into h_buch values insert into h_buch values insert into h_buch values insert into h_buch values insert into h_buch values insert into h_buch values commit;
('Raymans','Irina', 1, 'Oracle Workshop', 20); ('Zschoge', 'Helgo', 1, 'dBASE für Fortgeschrittene', 20); ('Zschoge', 'Helgo', 2, 'Meine Rezepte', 23); ('Heger', 'Sascha', 1, 'Donald Duck', 5); ('Heger', 'Sascha', 2, 'Herr der Ringe', 9.30); ('Heger', 'Sascha', 3, 'Elementarteilchen', 17.95);
Listing 2.47: Erstellen und Füllen der Hilfstabelle zum automatischen Erzeugen der Buchlisten
Zum Laden dieser Buchliste erstellen wir die Funktion make_buchliste (vgl. Listing 2.48). Diese Funktion erhält zur Identifizierung den Namen und Vornamen des Menschen, für den es die vorhandenen Bücher aus unserer Hilfstabelle auslesen und die Bücherliste erstellen soll. create or replace function make_buchliste(name in varchar2, vname in varchar2) return buchliste is x_buchl buchliste; begin declare cursor sel_buecher(s_name varchar2, s_vname varchar2) is select lfdnr, titel, wert from h_buch
Beschreibung der Objekte
183
where name = s_name and vname = s_vname order by lfdnr; b_lfdnr integer; b_titel varchar2(50); b_wert number; begin x_buchl := buchliste(); open sel_buecher(name, vname); loop fetch sel_buecher into b_lfdnr, b_titel, b_wert; Exit when sel_buecher%notfound; x_buchl.extend; x_buchl(x_buchl.count) := buch(b_lfdnr, b_titel, b_wert); end loop; close sel_buecher; return x_buchl; end; end; / show errors; commit; Listing 2.48: Funktion zum Konstruieren eines Objekts aus einer Tabelle
Die Funktion enthält eine Reihe bisher noch nicht bekannter Befehle, weshalb ich im Folgenden auf einzelne Passagen des Programms näher eingehen möchte. Zunächst einmal finden Sie am Anfang der Funktion verschiedene Deklarationen. Dabei wird neben drei gewöhnlichen Variablen auch ein sogenannter Cursor definiert. cursor sel_buecher(s_name varchar2, s_vname varchar2) is select lfdnr, titel, wert from h_buch where name = s_name and vname = s_vname order by lfdnr;
Mehr Informationen zu einem solchen Cursor und zusätzliche Beispiele zu deren Verwendung finden Sie noch reichlich im weiteren Verlauf dieses Buches. An dieser Stelle sollen folgende Hinweise genügen: Der Cursor ermöglicht uns innerhalb der Funktion die spezifizierte SQL-Abfrage auszuführen. In unserem Fall handelt es sich dabei um eine parametrisierte Abfrage, d.h. zur Laufzeit werden die beiden Parameter s_name und v_name innerhalb der where-Bedingung mit ihren konkreten
184
Datenbankobjekte in einer Oracle-DB
Werten berücksichtigt. Außerdem erhält jeder Cursor einen Namen, mit dem er im weiteren Verlauf des Programms angesprochen werden kann. Damit die im Cursor definierte Abfrage ausgeführt wird, muss dieser zunächst geöffnet werden. Anschließend haben Sie die Möglichkeit, die abgefragten Daten innerhalb einer Schleife der Reihe nach (sequentiell) abzurufen. Nach dem Lesen des letzten Satzes liefert der Cursor einen entsprechend Status, durch den die Schleife beendet bzw. verlassen werden kann und spätestens am Ende Ihres Programms sollten Sie die geöffneten Cursor wieder schließen. Der nun folgende Auszug des Programms zeigt die hierzu benötigten Befehle. open sel_buecher(name, vname); loop fetch sel_buecher into b_lfdnr, b_titel, b_wert; Exit when sel_buecher%notfound; end loop; close sel_buecher;
Innerhalb der Schleife rufen Sie die vom Cursor gelieferten Datensätze mit Hilfe der fetch-Anweisung ab und speichern die in der zugehörigen Abfrage spezifizierten Spalten in die hierfür angelegten Variablen b_lfdnr, b_titel und b_wert. Was nun folgt, ist die Überführung dieser Werte in ein Buchobjekt, und letztendlich muss dieses Buchobjekt an die aktuelle Bücherliste angehängt werden. x_buchl.extend; x_buchl(x_buchl.count) := buch(b_lfdnr, b_titel, b_wert);
Die Bücherliste wird innerhalb der Funktion über die Variable x_buchl geführt bzw. am Ende der Funktion zurückgegeben. Damit in die aktuelle Liste ein neues Buch passt, wird sie zuerst mit Hilfe der Methode extend erweitert. Ohne spezielle Parameter erweitert diese Methode die Liste um genau ein Element und wie Sie aus dem ersten Teil des Satzes folgerichtig schließen können, bietet die extend-Methode verschiedene Varianten, mehrere Elemente auf einmal anzulegen oder sogar zu kopieren. Die Methode count haben Sie schon kennen gelernt. Sie liefert immer die aktuelle Zahl der in der liste enthaltenen Elemente, d.h. die Verwendung von x_buchl(x_buchl.count)
verschafft Ihnen Zugriff auf das letzte Element der Buchliste. Der rechts vom Gleichheitszeichen stehende Ausdruck konstruiert dabei mit Hilfe der drei gelesenen Variablen ein Buchobjekt, so dass im Ergebnis dem neuen Element der Liste ein Buch zugewiesen wird. Beachten Sie allerdings noch die folgende notwendige Anweisung, damit das hier beschriebene Programm funktioniert. x_buchl := buchliste();
Beschreibung der Objekte
185
Wie Sie schon wissen, liefert der Ausdruck buchliste() eine leere Büchersammlung und ist damit in der Lage entsprechende Variablen zu initialisieren. Ohne diese Initialisierung würden Sie bei der Programmausführung eine Fehlermeldung erhalten, da die impliziten Methoden wie count oder extend nur bei initialisierten Listen funktionieren. Sofern Sie kein großer Fan von geschachtelten Ausdrücken sind, so können Sie die Zuweisung des Buches in die Buchliste auch anders programmieren. Statt der Verwendung des buch-Konstruktors x_buchl(x_buchl.count) := buch(b_lfdnr, b_titel, b_wert);
können Sie über die Bücherliste auch direkt auf ein einzelnes Buchobjekt zugreifen und dessen Eigenschaften damit direkt auslesen bzw. verändern. x_buchl(x_buchl.count).lfdnr := b_lfdnr; x_buchl(x_buchl.count).titel := b_titel x_buchl(x_buchl.count).wert := b_wert
Das war auch schon alles. Was (vom Tag) noch übrig bleibt ist die neue Funktion noch schnell auszuprobieren. Erstellen Sie hierzu noch einmal die Hilfstabelle h_mensch, in die Sie alle vorhandenen Daten kopieren. Löschen Sie danach die Tabelle menschen, so dass die nachfolgende SQL-Anweisung die Tabelle zusammen mit den Bücherlisten auflädt. insert into menschen select name, vname, strasse, plz, ort, geschl, gebdat, make_buchliste(name, vname) from h_mensch; commit Listing 2.49: Aufladen der Tabelle „menschen“ zusammen mit den Bücherlisten
2.2.17 Tabelle (Tables) Jetzt sprechen wir über Tabellen, dem sicherlich zentralsten und wichtigsten Element der Schema-Objekte, denn ohne Tabellen ist die Speicherung irgendwelcher Daten in der Datenbank nicht möglich. Die Anlage neuer Tabelle erfolgt mit Hilfe der Anweisung create table. Daneben existieren die Befehle alter table und drop table, um vorhandene Definitionen zu bearbeiten bzw. zu löschen. Vereinfachend betrachtet, enthält der Befehl create table neben dem Namen der neuen Tabelle im Wesentlichen nur noch die Definition der anzulegenden Spalten, den zu verwendenen Tablespace und bei Bedarf auch eine Regel, wie die zum Speichern notwendigen Ausdehnungen des Segments angelegt werden sollen. create table ( ) <Storage-Klausel>;
186
Datenbankobjekte in einer Oracle-DB
Dabei können Sie die Storage-Klausel weglassen, wenn die Tabelle die im Tablespace verankerten Standardwerte verwenden soll. Die Vorgabe eines speziellen Tablespace kann ebenfalls entfallen. In dem Fall verwendet Oracle entweder den dem Benutzer zugeordneten Standardwert oder den Tablespace „system“. Speicherbelegung Im Folgenden möchte ich einmal etwas genauer auf die Speicherbelegung des zu einer Tabelle gehörenden Segments eingehen. Wie schon gesagt, können Sie mit Hilfe der Storage-Klausel festlegen, wie und wie viele Segmentbereiche (vgl. Abb. 1.5) zum Speichern der Tabellendaten angelegt werden. Den letzten Teil des vorhergehenden Satzes könnte man auch allgemeiner formulieren, denn er gilt nicht nur für Tabellen, sondern überall da, wo Sie eine storage-Klausel angeben können (z.B. Indices, Snapshots und Rollback-Segmente). Die einzelnen Ausprägungen dieser Klausel werden dabei hinter dem Wörtchen storage in Klammern spezifziert. storage (initial .. next .. minextents .. maxextents .. pctincrease ..)
Hinter den verschiedenen Schlüsselwörtern müssen Sie die jeweils benötigten Werte vorgeben. Ansonsten ist neben der Bedeutung der einzelnen Ausprägungen eigentlich nur noch wichtig, dass Sie deren Reihenfolge zum einen wahlfrei und zum anderen auch einzeln bzw. in beliebiger Kombination verwenden können. Die genaue Bedeutung der einzelnen storage-Parameter können Sie der Tabelle 2.4 entnehmen. Storage-Klausel
Bedeutung
initial
Legen Sie hier bei Bedarf die Größe des ersten Segmentbereichs in Kilo(K) oder Megabyte (M) fest.
next
Mit diesem Parameter können Sie die Größe der weiteren Segmentbereiche (Extents) in Kilo- (K) oder Megabyte (M) festlegen.
minextents
Hier können Sie die Anzahl der Extents festlegen, die beim Anlegen der Tabelle bzw. des Objekts erzeugt werden. Standardmäßig legt Oracle immer ein Extent an, denn ohne einen einzigen Segmentbereich ist das Objekt gar nicht vorhanden.
maxextents
Geben Sie mit diesem Parameter die maximale Anzahl der zusätzlichen Segmentbereiche vor oder verwenden Sie statt einer Zahl das Wörtchen „unlimited“, damit die Tabelle bzw. das Objekt uneingeschränkt wachsen kann.
pctincrease
Die hier vorgegebene Zahl wird als Prozentwert interpretiert, um die jeder neue Extent vergrößert wird. Der Standardwert hierfür beträgt 50%. Sollen die neuen Segmente nicht automatisch vergrößert werden, dann müssen Sie pctincrease zusammen mit dem Wert 0 verwenden.
Tabelle 2.4: Beschreibung der storage-Klausel
Einen guten Überblick über alle möglichen Varianten erhalten Sie im Übrigen auch wieder mit Hilfe des Schema-Managers bzw. DBA Studios, in dem Sie dort beispielsweise verschiedene Optionen bzw. deren Kombination auswählen und anschließend die von dem Programm generierte SQL-Anweisung betrachten (vgl. Abb. 2.14).
Beschreibung der Objekte
187
Abbildung 2.14: Generieren einer storage-Klausel mit Hilfe des Schema-Managers
Zwei weitere Klauseln, die Sie bei der Anlage einer Tabelle oder auch anderer datenspeichernder Objekte verwenden können, heißen pctfree bzw. pctused. Mit pctfree reservieren Sie in jedem Extent des Segments eine Art Platzreserve in Prozent, die dem Wachstum durch Änderungen von bestehenden Datensätzen vorbehalten ist. Standardmäßig verwendet Oracle für diesen Wert 10%, d.h. nach dem vollständigen Auffüllen des Extents sind immer noch 10% des Platzes frei, der anschließend für Änderungen der in dem Segmentbereich gespeicherten Daten verwendet wird. Mit der anderen pctused-Klausel legen Sie eine Art Schwellenwert (in Prozent) fest, der vorgibt, wie weit die Belegung eines einstmals vollen Segmentbereichs wieder sinken muss, bevor wieder neue Datensätze hineingeschrieben werden. Der Standardwert für diesen Parameter liegt bei 40 Prozent. Die pctused-Klausel verhindert somit, dass der Status eines Extents permanent zwischen voll und leer wechselt, denn entsprechend dem Standardwert beginnt Oracle erst wieder damit neue Datensätze in den Extent zu schreiben, wenn seine Belegung unter 40% sinkt. Danach wird er allerdings wieder so lange mit neuen Datensätzen gefüllt, bis der in pctfree festgelegte Wert erneut erreicht wird. Unterlegen wir das Ganze mal mit Hilfe eines kleinen Beispiels. Legen Sie hierzu die Tabelle „lohnarten“ mit Hilfe des folgenden Skripts (vgl. Listing 2.50) an.
188
Datenbankobjekte in einer Oracle-DB
drop table lohnarten; create table lohnarten (la varchar2(3)) tablespace usr storage (initial 4k next 10k minextents 4 pctincrease 0); commit; Listing 2.50: Anlage der Tabelle „lohnarten“ mit spezieller Speicherbelegungsregel
Mit Hilfe einer Abfrage auf die View dba_segments können Sie die definierte Speicherbelegung einer jeden Tabelle bzw. eines jeden Segments abfragen (vgl. Listing 2.51). Bei einer solchen Abfrage können Sie je nach Bedarf den Namen des Eigentümers, den Tablespace oder den Namen des Segments, das ist beispielsweise der Name der Tabelle oder des Index oder den Segmenttyp (z.B. „TABLE“ oder „INDEX“) als einschränkende Kriterien verwenden. SQLWKS> select bytes, blocks, extents, initial_extent, next_extent, 2> min_extents, max_extents, pct_increase 3> from dba_segments where segment_name = 'LOHNARTEN'; BYTES BLOCKS EXTENTS INITIAL NEXT_ MIN_ MAX__INCREA ----- ------- ------- ----------- ---------- -------- -------- -------34816 17 4 4096 10240 4 121 1 1 row selected. Listing 2.51: Analyse des Segments „lohnarten“ mit Hilfe der View dba_segments
In unserem Beispiel beträgt die Größe des ersten Extents also 4096 Byte (4k). Entsprechend der Konfiguration der Datenbankinstanz (db_block_size = 2048) war die Blockgröße auf 2048 Bytes eingestellt, also entspricht die Größe des ersten Extents genau Blöcken. Sofern Sie die Speicherbelegung einmal nachrechnen möchten, dann sollten Sie Ihre vorgegebenen Werte zunächst immer in Blöcken umrechnen, ggf. aufrunden (es gibt keine halben Blöcke) und die erhaltene Blockzahl danach wieder mit der konfigurierten Blockgröße multiplizieren. Mit der Anlage der Tabelle wurden aufgrund unserer storage-Klausel drei weitere Extents angelegt. Für den Parameter pctincrease hatten wir den Wert 0 vergeben, so dass alle Segmenterweiterungen die gleiche Größe besitzen. Jedes dieser weiteren Extents wird mit einer Größe von jeweils 10k angelegt bzw. je 5 Blöcken angelegt. Somit belegt das ganze Segment bzw. die Tabelle insgesamt 17 Blöcke, was zusammen die Summe von 34816 Byte ergibt. Wie bei den meisten anderen Dingen, so ist auch hierbei die Entscheidung über viel oder wenig letztendlich Ansichtssache. Dennoch, so finde ich, sind 34k für eine leere Tabelle ganz schön viel Platz. Sie können die Erweiterungen (Extents) einer Tabelle auch mit Hilfe einer Abfrage auf die Tabelle dba_extents (vgl. Listing 2.52) untersuchen. Der wesentliche Unterschied zu der vorhin gezeigten Abfrage ist, dass hierbei jeder Bereich des Segments einzeln angezeigt wird.
Beschreibung der Objekte
189
SQLWKS> select extent_id, bytes, blocks 2> from dba_extents 3> where segment_name = 'LOHNARTEN'; EXTENT_ID BYTES BLOCKS ---------- ---------- ---------0 4096 2 1 10240 5 2 10240 5 3 10240 5 4 rows selected. Listing 2.52: Analyse des Segments „lohnarten“ mit Hilfe der View dba_extents
Wie leer eine Tabelle bzw. das zugehörige Segment wirklich ist, können Sie im Nachhinein übrigens mit Hilfe des Pakets dbms_space feststellen, denn dort ist die Prozedur unused_space enthalten, mit der Sie den freien Platz in einem Segment ermitteln können (vgl. Listing 2.53). Diese Paketprozedur erhält als Eingabeparameter im Wesentlichen den Namen des zu untersuchenden Segments und liefert die Belegungsdaten in Form verschiedener Ausgabeparameter zurück. Für uns bedeutet das, dass wir die Prozedur innerhalb eines PL/SQL-Skripts verwenden müssen, in dem wir die Ausgabeparameter als Variable definieren und nach dem Aufruf der Prozedur für deren Anzeige am Bildschirm sorgen. set serveroutput on; declare total_blocks number; total_bytes number; unused_blocks number; unused_bytes number; last_used_fid number; last_used_id number; last_used_block number; begin dbms_space.unused_space('SYSTEM', 'LOHNARTEN','TABLE', total_blocks, total_bytes, unused_blocks, unused_bytes, last_used_fid, last_used_id, last_used_block); dbms_output.put_line('total_blocks to_char(total_blocks)); dbms_output.put_line('total_bytes to_char(total_bytes)); dbms_output.put_line('unused_blocks to_char(unused_blocks)); dbms_output.put_line('unused_bytes to_char(unused_bytes)); dbms_output.put_line('last_used_extend_file_id to_char(last_used_fid));
: ' || : ' || : ' || : ' || : ' ||
190
Datenbankobjekte in einer Oracle-DB
dbms_output.put_line('last_used_extend_block_id: ' || to_char(last_used_id)); dbms_output.put_line('last_used_block : ' || to_char(last_used_block)); end; Listing 2.53: Verwenden von dbms_space.unused_space in einem Skript
In dem Skript werden verschiedene Variablen für die im Paket dbms_space vorhandene Prozedur unused_space deklariert und anschließend wird die Prozedur aufgerufen. Die Ausgabe der einzelnen Ergebnisse erfolgt danach mit Hilfe der Prozedur put_line aus dem Paket dbms_output. Diese Prozedur wird uns im weiteren Verlauf des Buches noch häufiger begegnen. Mit ihr können Sie innerhalb von PL/SQLProgrammen Meldungen sammeln und am Ende der Prozedur am Bildschirm ausgeben. Oftmals wird sie auch verwendet, um mit ihrer Hilfe in einer Prozedur eine Ablaufverfolgung durchzuführen. dbms_output.put_line('irgendein Text')
Mit Hilfe der Prozedur können Sie eine beliebige Zeichenfolge, die innerhalb der Klammern vorgegeben wird, in einen speziellen Ausgabepuffer kopieren. Dieser Ausgabepuffer wird am Ende der Prozedur oder des Skripts am Bildschirm ausgegeben, wobei Letzteres nur dann passiert, wenn Sie die Serverausgabe eingeschaltet haben. set serveroutput on;
Diese Serverausgabe können Sie mit einem speziellen set-Kommando einschalten, so dass am Ende unseres Skripts folgende Informationen am Bildschirm erscheinen: total_blocks : total_bytes : unused_blocks : unused_bytes : last_used_extend_file_id : last_used_extend_block_id: last_used_block :
17 34816 16 32768 3 12 1
In unserem Beispiel sind in dem Segment „lohnarten“ zur Zeit also über 30k frei. Wenn Sie die wieder freigeben wollen, dann müssen Sie sich in unserem Fall zunächst einmal von der minextents-Klausel trennen, denn die würde aufgrund ihrer aktuellen Einstellung jegliche Freigabeaktivitäten verhindern. alter table lohnarten storage (minextents 1); commit;
Beschreibung der Objekte
191
Die Änderung der Speicherbelegungsregel erfolgt mit Hilfe einer alter-Anweisung und da es sich in unserem Beispiel um eine Tabelle handelt, heißt der vollständige Befehle entsprechend alter table. Anschließend erfolgt die Vorgabe einer neuen storage-Klausel, wobei es hierbei ausreicht, nur die zu ändernden Parameter der Klausel vorzugeben. Beachten Sie hierbei allerdings, dass Sie gerade nur die Regel zur Speicherbelegung verändert haben, d.h. in der Datenbank ist in Bezug auf das Segment sonst noch gar nichts passiert. In der Praxis wird die storage-Klausel für existierende Objekte häufig zusammen mit den Parametern pctincrease und maxextents verwendet, um im laufenden Betrieb die Vergrößerung neuer Erweiterungen (Extents) zu verändern bzw. die zulässige Anzahl dieser Erweiterungen zu vergrößern. Im nächsten Schritt geben wir den nicht mehr benötigten Speicherplatz wieder frei. Hierzu verwenden wir wiederum die Anweisung alter table zusammen mit der Klausel dellocate unused. alter table lohnarten deallocate unused keep 7k; commit;
In unserem Beispiel spezifizieren wir zusätzlich die optionale Erweiterung „keep 7k”, die dazu führt, dass Oracle nicht den gesamten freien Speicher freigibt, sondern mindestens 7k bleiben für das gesamte Segment reserviert. Wenn Sie nach Ausführung dieser Anweisungen das Segment noch einmal mit Hilfe der vorgestellten Methoden analysieren, dann werden Sie feststellen, dass Oracle die Extents mit der Id 2 und 3 gelöscht hat. Die Ausdehnung mit der Id 1 konnte aufgrund unserer keep-Vorgabe nicht gelöscht werden, allerdings wurde es entsprechend gekürzt. Ohne die keep-Klausel wäre auch dieser erste Zusatzextent aus der Datenbank gelöscht worden. SQLWKS> select extent_id, bytes, blocks 2> from dba_extents 3> where segment_name = 'LOHNARTEN'; EXTENT_ID BYTES BLOCKS ---------- ---------- ---------0 4096 2 1 6144 3 2 rows selected. total_blocks : total_bytes : unused_blocks : unused_bytes : last_used_extend_file_id : last_used_extend_block_id: last_used_block :
5 10240 4 8192 3 12 1
192
Datenbankobjekte in einer Oracle-DB
Anhand des Beispiels der Tabellen bin ich nun einmal etwas ausführlicher auf die Speicherbelegung der Segmente eingegangen. Wie schon mehrfach angedeutet, gilt das hier Gesagte allerdings nicht nur für Tabellen, sondern eben für alle mit Hilfe von Segmenten gespeicherten Daten, d.h. die hier beschriebenen Möglichkeiten sind auch bei den Indices, Rollback-Segmenten oder Snapshots verfügbar. Beispiele Die einfachste Form des Befehls create table besteht also aus einem Tabellennamen, einer oder mehreren Spaltendefinitionen und dem Ort bzw. der Regel zur Speicherbelegung. Hierzu haben Sie im bisherigen Verlauf dieses Workshops schon mehrfach Beispiele gesehen. Als Erstes legen wir noch einmal die Lohnartentabelle (vgl. Listing 2.54) an, und sorgen anschließend auch sofort mit Hilfe einzelner insertAnweisungen für ein paar Daten in dieser Tabelle. drop table lohnarten; create table lohnarten( la varchar2(3), la_text varchar2(50) ) tablespace usr storage(initial 4k minextents 1); insert into insert into insert into insert into insert into commit;
lohnarten lohnarten lohnarten lohnarten lohnarten
values values values values values
('100','Grundgehalt'); ('110','Übertraifliche Zulage'); ('120','Sonstige Zulage'); ('200','Überstunden'); ('300','Urlaubsgeld');
Listing 2.54: Anlegen der Tabelle „lohnarten“
Bei vielen Standardprodukten entspricht die Anlage von Tabellen genau dieser Struktur. Das ist vor allem dann der Fall, wenn die zum Arbeiten benötigten Tabellen mit Hilfe der Standardsoftware angepasst und erzeugt werden können. Der Grund dafür ist auch einleuchtend, denn, wie hier beschrieben, können in nahezu allen relationalen Datenbanken Tabellen angelegt werden, wenn man mal von der Vorgabe des Tablespaces und der storage-Klausel absieht. Oracle bietet allerdings noch eine Reihe weiterer Möglichkeiten, schon mit der Tabellendefinition auch Indices oder bestimmte Regelwerke an- bzw. festzulegen. Wie man explizit Indices anlegen kann, das haben Sie bei der Behandlung des entsprechenden Schema-Objekts schon gesehen. Im nächsten Schritt geht es darum, im Rahmen der Tabellendefinition implizite Indices anzulegen. Bei solchen Optionen, gleichgültig ob Indices, Regeln oder Abhängigkeiten spricht man üblicherweise von sogenannten Constraints (Zwänge). Sie werden entweder im Rahmen der Tabellendefinition oder nachträglich mit Hilfe einer alter tableAnweisung definiert. Die Varianten, Verfügbarkeit und Anwendung der verschiedenen Constraints ist in den unterschiedlichen Datenbanksystemen, im Unterschied zur eigentlichen create table-Anweisung sehr verschieden.
Beschreibung der Objekte
193
Generell folgt hinter den Schlüsselwörtern create table der Name der Tabelle und danach folgt in Klammern eine Aufzählung der benötigten Spalten. Die einzelnen Spaltendefinitionen werden dabei durch Komma getrennt. Jede einzelne Spaltendefinition besteht wiederum aus dem Spaltennamen, dem der benötigte Datentyp folgt. Danach folgen zwei weitere optionale Parameter. Zum einen können Sie festlegen, ob eine Spalte überhaupt mit einem Wert gefüllt werden muss oder ob sie auch leer (null) bleiben darf, wobei Letzteres dem Standardwert von Oracle entspricht. Zum anderen können Sie die Spalte einen Standardwert definieren. Damit ergibt sich insgesamt folgendes Schema zur Definition jeder einzelnen Spalte in einer Tabelle: <Spaltenname> [default <Standardwert>] [null|not null]
Die am meisten verwendeten Datentypen sind sicherlich varchar2 für das Speichern variabel langer Zeichenfolgen, date zum Speichern von Datums- und/oder Zeitwerten und number ggf. mit Vorgabe der möglichen Vor- bzw. Nachkommastellen (number(v,n)). zeichenspalte varchar2(10), nummer number, nummermitlaenge number(7,2), datum date
Als Standardwert dürfen Sie im Rahmen der Spaltendefinition einen beliebigen gültigen Ausdruck verwenden. Der darf allerdings keinen Bezug auf andere Spalten der Tabelle bzw. verschiedene vom System vorgehaltene Systemspalten (z.B. rownum) enthalten und muss in Bezug auf den Datentyp und Länge zur zugehörigen Spalte passen. Meistens werden als Standardwerte irgendwelche Konstanten (z.B. ’100’ oder 43) bzw. die Variable sysdate zum Einmelden des Systemdatums verwendet. Beachten Sie allerdings, dass die Verwendung des Standardwerts beim Einfügen neuer Datensätze nur dann greift, wenn Sie das zugehörige Feld in der insert-Anweisung nicht spezifizieren. zeichenspalte varchar2(10) default 'XXL', nummer number default 5*2-2, nummermitlaenge number(7,2) default (–33.2), datum date default sysdate
Wenn Sie eine Spalte zusammen mit der Regel „not null“ anlegen, dann müssen Sie ihr beim Einfügen eines Datensatzes oder beim Ändern immer einen konkreten Wert zuweisen. Zeichenfolgen müssen in dem Fall beispielsweise mindestens ein Leerzeichen, numerische Felder mindestens die Zahl 0 und Datumsfelder irgendein gültiges Datum enthalten. zeichenspalte varchar2(10) default 'XXL' not null, nummer number default 5*2-2 not null, nummermitlaenge number(7,2) default –33.2 not null, datum date default sysdate not null
194
Datenbankobjekte in einer Oracle-DB
Manche Datenbanksystem unterscheiden sich in Bezug auf die Vergabe eines Standardwertes zusammen mit der Option „not null“. Bei manchen Systemen schließen sich diese beiden Option sogar gegeneinander aus, da schon die insert-Anweisung eine Verletzung der Klausel „not null“ erkennt und die Vergabe des Standardwertes erst später erfolgt. In Oracle ist das anders, d.h. werden beide Optionen zusammen verwendet, dann greift der Standardwert, wenn Sie die zugehörige Spalte beim insert-Befehl weglassen und auf der anderen Seite haben Sie nicht die Möglichkeit, die Spalte im Rahmen einer update-Anweisung auf „null“ zu setzen. Indices Im folgenden Beispiel legen wir für unsere Tabelle „lohnarten“ im Nachhinein einen Primärschlüssel mit Hilfe einer constraint-Klausel (vgl. Listing 2.55) an. Der Vorteil das mit Hilfe einer solchen Klausel anstelle eines gewöhnlichen eindeutigen Indices zu tun ist, dass Oracle diese Definition bei anderen Aktivitäten berücksichtigen und sein Verhalten dementsprechend optimieren kann. alter table lohnarten drop constraint lohnarten_pk; commit; alter table lohnarten add constraint lohnarten_pk primary key (la) using index tablespace indx storage(initial 2k next 4k) ; commit; Listing 2.55: Löschen und Anlegen eines Primärschlüssels
Mit dem Beispiel schlagen wir gleich zwei Fliegen mit einer Klappe. Das nachträgliche Löschen oder Anlegen von Constraints erfolgt immer mit Hilfe des Befehls alter table zusammen mit der Klausel drop constraint bzw. add constraint, je nachdem, ob Sie eine vorhandene Regel löschen oder eine neue Regel anlegen wollen. Außerdem besitzen die verschiedenen Constraints einen bei der Anlage festgelegten Namen, mit dessen Hilfe sie beispielsweise wieder gelöscht werden können. Die Schlüsselwörter „using index“ sind optional und leiten bei deren Verwendung die Spezifizierung von Speichervorgaben für das zugehörige Indexsegment ein. Hier können Sie also den Tablespace vorgeben oder mit Hilfe der storage-Klausel die Speicherbelegung für den Index vorgeben. In unserem Beispiel (Listing 2.55) löschen wir zunächst das Constraint mit dem Namen „lohnarten_pk“. Anschließend legen wir es erneut an, wobei wir den Typ „primary key“ zur Anlage eines Primärschlüssels verwenden. Hierbei müssen Sie im Anschluss alle zum Primärschlüssel gehörenden Felder in Klammern aufzählen. Das hier durchgeführte Szenario, erst drop dann add, entspricht übrigens der üblichen Vorgehensweise beim Ändern, da es eine Anweisung der Art „change constraint“ nicht gibt.
Beschreibung der Objekte
195
Natürlich hätten wir den Primärschlüssel der Tabelle „lohnarten“ auch gleich bei deren Anlage definieren können, wobei wir die zugehörigen Anweisungen in dem Fall folgendermaßen erweitern müssen: create table lohnarten( la varchar2(3), la_text varchar2(50), constraint lohnarten_pk primary key (la) using index tablespace indx storage(initial 2k next 4k) ); tablespace usr storage(initial 4k minextents 1);
Wie Sie an dem Beispiel sehen, werden Constraints, wenn Sie bei der Anlage von Tabellen spezifiziert werden, quasi wie zusätzliche Extraspalten angelegt. Dabei ist es gleichgültig, ob Sie die Constraints am Anfang, zwischen den einzelnen Spalten oder ganz am Ende, d.h. nach der letzten Spaltendefinition angeben. Ich persönlich finde es übersichtlicher, wenn sie am Ende stehen, aber das ist wie so vieles im Leben sicherlich mal wieder Geschmacksache. Primärschlüssel kann es, genau wie in dem entsprechenden Kinofilm, nur einen geben. Insgesamt können eindeutige Felder oder Feldkombinationen natürlich mehrfach auftreten und anstelle zusätzlicher expliziter eindeutiger Indices können Sie auch beliebige weitere unique-Constraints definieren, was eigentlich fast genauso wie die Anlage des Primärschlüssels funktioniert: constraint unique () using index ...
Überwachung von Spalten Im Rahmen der Spaltendefinition haben Sie mit der „null“ bzw. „not null“ Option schon eine Art Miniüberwachung der Spaltenwerte kennen gelernt. Hier geht es jetzt um die Erstellung sogenannter Check-Constraints, mit deren Hilfe sich komplexere Prüfungen für einzelne oder eine Kombination von Spalten definieren lassen. Als Demonstrationsobjekt soll hierzu eine weitere Tabelle „bezuege“ (vgl. Listing 2.56) erstellt werden, in der später die einzelnen Gehaltsbestandteile jedes Mitarbeiters gespeichert werden können. Bei der Definition der Tabelle werden wir auch gleich einige Regeln zur Überwachung der enthaltenen Spalten anlegen. drop table bezuege; create table bezuege( persnr varchar2(10) la varchar2(3) stunden number(5,2) default tage number(5,2) default lohnsatz number(5,2) default betrag number(9,2) default
0 0 0 0
not not not not not not
null, null, null, null, null, null,
196
Datenbankobjekte in einer Oracle-DB
constraint bezuege_pk primary key (persnr, la), constraint check_lohnsatz check (lohnsatz >= 0), constraint check_record check (betrag<>0 and (tage=0 and stunden=0) or betrag=0 and (tage<>0 or stunden<>0)) ) tablespace usr; commit; Listing 2.56: Anlage der Tabelle „bezuege“ mit verschiedenen Feldprüfungen
In unserem Beispiel kann ein Gehaltsbestandteil neben einem Lohnsatz immer aus der Vorgabe eines Betrags oder einer Kombination aus Tagen und Stunden bestehen. Der Lohnsatz darf dabei nur positive Werte annehmen, was wir mit Hilfe der Regel check_lohnsatz festlegen. Die andere Regel heißt check_record und überprüft die Felder betrag, tage und stunden, so dass die oben beschriebene Eingaberegel beachtet wird. Die Verarbeitung eines check-Constraints entspricht im Prinzip einer wenn- bzw. ifBedingung, d.h. die zugehörige Regel gilt als erfüllt, wenn der spezifizierte Ausdruck den Wahrheitswert „wahr“ zurückliefert. Das Hinzufügen oder Löschen solcher Prüfungen erfolgt natürlich wieder mit Hilfe der alter table-Anweisung entsprechend der bei den Indexen beschriebenen Verfahrensweise. Integritätsprüfungen Wenn Sie noch unerfahren im Umgang mit relationalen Datenbanksystemen sind, dann stellen Sie sich beim Lesen der Überschrift vielleicht die Frage, was hiermit gemeint sein könnte. Genau genommen geht es bei der Überschrift um das Thema der sogenannten „referenziellen Integrität“. Ich habe mal wieder keine Kosten und Mühen gescheut und bin auf die Suche nach einer einfachen aber dennoch halbwegs zutreffenden Erklärung gegangen. Was ist referenzielle Integrität? Referenzielle Integrität ist ein Regelsystem, mithilfe dessen die Datenbank (Oracle) sicherstellt, dass Beziehungen zwischen Datensätzen in Detailtabellen gültig sind und dass verknüpfte Daten nicht versehentlich gelöscht oder geändert werden. Sofern Ihnen dieser Erklärungsversuch noch zu oberflächlich ist, dann wird das Ganze sicherlich klarer, wenn wir es im Rahmen eines Beispiels betrachten. Betrachten Sie hierzu noch einmal unsere beiden bisher angelegten Tabellen „lohnarten“ und „bezuege“ (vgl. Abb. 2.15).
Beschreibung der Objekte
197
bezuege lohnarten persnr la tage stunden lohnsatz betrag
la la_text
Fremdschlüssel, der auf der „lohnarten“-Tabelle beruht. Abbildung 2.15: Beziehungen verknüpfter Tabellen
In unserem Beispiel werden die einzelnen Bezüge mit Hilfe einer Lohnart gespeichert. Dabei soll sichergestellt werden, dass die verwendete Lohnart überhaupt gültig ist bzw. existiert. Das wäre zwar sicherlich auch im Rahmen einer Prüfung auf konstante Werte möglich, doch hätte ein solches Verfahren den Nachteil, dass Sie mit jeder Lohnartenerweiterung die Tabellendefinition anpassen müssten. Außerdem wäre für den Endanwender nicht transparent, für welche Lohnarten in der Tabelle eine Prüfung stattfindet bzw. welche Lohnarten vielleicht versehentlich vergessen wurden. Komfortabler und auch flexibler wäre da schon ein Verfahren, das diese Prüfung mit Hilfe der Lohnartentabelle vornimmt, d.h. alle in der Lohnartentabelle definierten Schlüssel dürfen auch in der Tabelle „bezuege“ vorkommen. In dem Fall verweist (referenziert) die Tabelle „bezuege“ über das Feld „la“ auf die Lohnartentabelle und leiht sich bei dieser quasi deren Schlüssel aus (Fremdschlüssel), d.h. bei dem Lohnartenfeld („la“) handelt es sich in der Tat um einen fremden Schlüssel aus der Lohnartentabelle. Das Verfahren der referenziellen Integrität verlangt neben dieser noch recht einfachen Werteprüfung mittels Referenztabelle jedoch auch noch eine Rückkopplung aus dieser Tabelle zu den jeweiligen Fremdschlüsseln. Wird beispielsweise eine Lohnart gelöscht, dann darf das nur möglich sein, wenn diese nirgendwo mehr als Fremdschlüssel benötigt wird. Das Gleiche gilt natürlich auch, wenn eine Lohnart in der Referenztabelle geändert wird. Das zur Durchführung einer solchen Prüfung verwendete Constraint heißt sprechenderweise foreign key und wird prinzipiell wie die anderen bisher besprochenen Regeln verwendet (vgl. Listing 2.57). drop table bezuege; create table bezuege( persnr varchar2(10) la varchar2(3)
not null, not null,
198
stunden number(5,2) tage number(5,2) lohnsatz number(5,2) betrag number(9,2)
Datenbankobjekte in einer Oracle-DB
default default default default
0 0 0 0
not not not not
null, null, null, null,
constraint bezuege_pk primary key (persnr, la), constraint check_lohnsatz check (lohnsatz >= 0), constraint check_record check (betrag<>0 and (tage=0 and stunden=0) or betrag=0 and (tage<>0 or stunden<>0)), constraint check_la foreign key (la) references lohnarten(la) ) tablespace usr; commit; Listing 2.57: Anlegen der Tabelle zusammen mit Integritäts-Constraints
Selbstverständlich können Sie die Regel auch wieder im Nachhinein mit Hilfe einer alter table-Anweisung hinzufügen. In jedem Fall müssen Sie hinter den Schlüsselwörtern foreign key die zu überwachenden Felder in Klammern vorgeben. Mit Hilfe des Schlüsselworts references leiten Sie den Verweis auf die Prüftabelle ein, indem Sie deren Namen dahinter vorgeben. Zum Schluss folgen dann in Klammern die entsprechenden Spaltennamen aus der Prüftabelle. Damit ergibt sich das im Folgenden gezeigte Schema: foreign key (feld1 [, feld2] ...) references (rfeld1 [, rfeld2] ...)
Beachten Sie weiterhin, dass die Referenzierung auf eine solche Prüftabelle nur möglich ist, wenn dort für die einzelnen Spalten ein Primärschlüssel oder zumindest ein eindeutiger Index angelegt ist. Selbst ohne diesen Zwang wäre die Anlage eines solchen Indexes naheliegend, damit der Zugriff auf die Prüftabelle entsprechend schnell durchgeführt werden kann. Genau genommen muss Oracle wegen des Indexes sogar gar keinen Zugriff auf die eigentliche Tabelle machen, denn schon mit der Existenz des Indexeintrags ist die Überwachungsregel erfüllt. Halten wir an dieser Stelle mal kurz inne und probieren das Ganze einfach mal aus, bevor wir unser Konstrukt im Rahmen des weiteren Verlaufes des Kapitels vielleicht wieder zerpflücken. So können Sie die beiden nachfolgenden Datensätze beispielsweise problemlos einfügen, da hierbei alle definierten Regeln erfüllt werden. insert into bezuege values ('4711','100',0,0,0,1000); insert into bezuege values ('4711','300',4,3.5,0,0);
Die drei nun folgenden Datensätze können sie nicht in die Datenbank einfügen, da hierbei immer eine der definierten check-Constraints verletzt wird. insert into bezuege values ('4712','100',3,0,0,1000); insert into bezuege values ('4712','300',0,3.5,0,1); insert into bezuege values ('4712','300',0,0,-100,1);
Beschreibung der Objekte
199
Im ersten Fall werden die Felder Tage und Betrag und im zweiten Fall die Felder Stunden und Betrag gleichzeitig bestückt, was entsprechend der Regel check_record verboten ist. Mit dem letzten Datensatz wird versucht, einen negativen Lohnsatz zu speichern, was durch die Regel check_lohnsatz verhindert wird. Mit Hilfe der nächsten beiden Datensätze versuchen wir nun Datensätze mit einer ungültigen Lohnart einzufügen, was aufgrund der Regel check_la ebenfalls verhindert wird. insert into bezuege values ('4712','101',0,0,0,1000); insert into bezuege values ('4712','301',4,2.5,0,0);
Als letzten Schritt greifen wir unser System nun noch von der Lohnartenseite her an, indem wir versuchen, eine verwendete Lohnart zu löschen bzw. umzuschlüsseln. delete lohnarten where la = '100'; update lohnarten set la = '301' where la = '300';
Beide Änderungen werden aufgrund der Regel check_la verhindert. Die Lohnarten 100 und 300 sind in der Tabelle bezuege vorhanden und können deshalb in der Lohnartentabelle weder gelöscht noch umgeschlüsselt werden. Das Löschen oder Ändern nicht verwendeter Lohnarten funktioniert dahingegen auch weiterhin einwandfrei, wie das nun folgende letzte Beispiel zeigt. delete lohnarten where la = '110'; update lohnarten set la = '121' where la = '120';
Nachträgliches Anlegen von Prüfungen Wie ich schon angedeutet habe, können die soeben beschriebenen Constraints zur Überprüfung der in den Tabellen gespeicherten Daten auch im Nachhinein mit Hilfe einer entsprechenden alter table-Anweisung erstellt werden. Für jede zu löschende Regel müssen Sie dabei eine drop- und für jedes neue Constraint eine entsprechende add-Klausel verwenden (vgl. Listing 2.58). alter table bezuege add constraint bezuege_pk primary key (persnr, la) add constraint check_lohnsatz check (lohnsatz >= 0) add constraint check_record check (betrag<>0 and (tage=0 and stunden=0) or betrag=0 and (tage<>0 or stunden<>0)) add constraint check_la foreign key (la) references lohnarten(la) ; commit; Listing 2.58: Nachträgliches Anlegen von Constraints
Beim nachträglichen Anlegen solcher Prüfregeln führt Oracle diese zunächst auf den gesamten Datenbestand aus und erst nach der erfolgreichen Durchführung aller Prüfungen werden die neuen Constraints in der Tabelle installiert. Falls das stört, dann können Sie die Prüfungen mit einer speziellen Klausel anlegen, so dass nur alle neuen oder geänderten Datensätze geprüft werden.
200
Datenbankobjekte in einer Oracle-DB
add constraint check_la foreign key (la) references lohnarten(la) enable novalidate
Diese besondere Klausel heißt enable novalidate. Standardmäßig werden neue Constraints mit der Klausel enable validate angelegt, so dass die oben beschriebene Vorabprüfung durchgeführt wird. Neben diesen beiden Varianten existiert auch noch die Klausel disable, die zwar zur Anlage, jedoch nicht zur Aktivierung der Prüfroutine führt. add constraint check_la foreign key (la) references lohnarten(la) disable
Generell können Sie die Constraints im Nachhinein je nach Bedarf ein- oder ausschalten, was wieder mit Hilfe einer entsprechenden alter table-Anweisung möglich ist. alter table bezuege enable validate constraint check_lohnsatz disable constraint check_record; commit;
Hinzufügen, Ändern von Spalten Natürlich können auch vorhandene Tabellen nachträglich in Ihrer Struktur verändert werden, jedoch haben Sie hierbei nur sehr eingeschränkte Möglichkeiten:
X X X X X
Das Ändern des Datentypes (z.b. von varchar2 in date) ist nur möglich, wenn die zugehörige Spalte in allen Datensätzen den Wert „null“ enthält. Das Gleiche gilt auch, wenn Sie die definierte Länge verkürzen wollen. Das Verlängern eines Datenfeldes ist dagegen immer möglich. Ein eventuell für eine Spalte definierte Standardwert kann nachträglich geändert werden. Das hat natürlich keine Auswirkungen auf die schon gespeicherten Datensätze, sondern wirkt sich erst bei anschließend neu eingefügten Sätzen aus. Die Option null bzw. not null kann ebenfalls im Nachhinein verändert werden. Allerdings wird beim Ändern der Option auf die Einstellung not null die gesamte Tabelle entsprechend geprüft, d.h. Sie müssen die zugehörige Spalte vorher gegebenenfalls mit irgendeinem Wert füllen. In allen anderen Fällen erfolgt die Änderung der Tabellenstruktur über den Umweg einer manuell erstellten Sicherungskopie, dem Löschen und Neuanlegen der zu ändernden Spalte und dem manuellen Zurückkopieren der gesicherten Daten, wobei die in der geänderten Spalte gespeicherten Werte entsprechend umformatiert werden können. Weitere Hinweise zu diesem Szenario finden Sie im nächsten Abschnitt, wo es um das Löschen von nicht mehr benötigten Spalten geht.
Beschreibung der Objekte
X
201
Das Ändern einer Spaltendefinition erfolgt mit Hilfe der alter table-Anweisung zusammen mit der modify-Klausel. Dabei müssen Sie hinter dem Schlüsselwort modify die zu ändernde Spalte mit ihren neuen Attributen in Klammern spezifizieren. Dabei müssen Sie nur die Attribute angeben, die wirklich geändert werden sollen. Im Folgenden finden Sie hierfür ein paar Beispiele:
alter table bezuege modify (betrag number(10,2)) modify (la default 'xxx') modify (tage null); commit;
Mit Hilfe dieses Listings werden drei Spalten der Tabelle „bezuege“ verändert. Im ersten Fall wird der Datentyp um eine Vorkommastelle verlängert. Für das Lohnartenfeld wird der Standardwert angepasst und bei den Tagen werden ab sofort keine Eingaben mehr benötigt. Löschen von Spalten Für das Löschen einer Spalte existiert überhaupt kein entsprechender Befehl, d.h. in dem Fall müssen Sie für die Tabelle zunächst eine Sicherungskopie anlegen. Danach wird die zu ändernde Tabelle gelöscht und anschließend mit der neuen Struktur wieder angelegt. Am Ende des gesamten Szenarios werden die noch benötigten Spalten aus der Sicherungskopie in die eigentliche Tabelle zurückkopiert. Beim Anlegen der Sicherungskopie können Sie dabei ein ganz spezielle Form des Befehls create table verwenden, denn mit Hilfe dieser besonderen Variante können Sie eine neue Tabelle durch Kopie bzw. aus einem Extrakt einer anderen Tabelle erstellen (vgl. Listing 2.59). drop table cpy_bezuege; create table cpy_bezuege tablespace usr as select * from bezuege; commit; Listing 2.59: Kopieren einer Tabelle
In anderen Datenbanksystem existiert hierfür manchmal die spezielle Variante einer „select… into“ Auswahlabfrage. Das Praktische an dieser Form des create tableBefehls ist, dass Sie sich hierbei das explizite Anlegen der neuen Tabelle durch Aufzählen der benötigten Spalten mit allen notwendigen Attributen ersparen. Allerdings funktioniert das Ganze nur dann so reibungslos, wenn in der spezifizierten Abfrage nur Spaltennamen aus anderen Tabellen und keine Ausdrücke verwendet werden. Wenn Sie innerhalb der Abfrage Ausdrücke verwenden, dann müssen Sie diesen berechneten Spalten zum einen eine Überschrift zuweisen, die später als Feldnamen verwendet wird, und zum anderen ermittelt Oracle den geeigneten Datentyp automatisch.
202
Datenbankobjekte in einer Oracle-DB
drop table cpy_bezuege; create table cpy_bezuege tablespace usr as select a.*, 4+5 as wert2, 'haha' as text from bezuege a; commit;
In der hier gezeigten Variante erhält die Sicherungskopie cpy_bezuege zwei zusätzliche Spalten. Die erste heißt „WERT2“ und ist numerisch und die zweite neue Spalte hat den Namen „TEXT“ und wird als vierstellige Zeichenfolge angelegt. Nach dem erfolgreichen Erstellen der Sicherungskopie kann die alte Tabelle gelöscht und mit der neuen Struktur wieder angelegt werden. Danach werden die Daten aus der Sicherungskopie zurückgeladen. Im Rahmen der dabei zu verwendenden insert-Anweisung werden gelöschte Spalten weggelassen und geänderte Spalten müssen entsprechend umformatiert werden. Objektorientiert Bei der Vorstellung der objektorientierten Daten- und Tabellentypen haben Sie auch schon einige Beispiele zur Anlage von objektorientierten Tabellen gesehen. Auch bin ich auf den Unterschied zwischen der Verwendung des Objektes im Rahmen einer gewöhnlichen Spalte und der objektorientierten Verwendung eingegangen. Zur Erinnerung können Sie die alternativen Verfahrensweisen dem folgenden Listing noch einmal entnehmen. create table h_mensch (h_m mensch) create table menschen of mensch
Wenn Sie mit Objekten arbeiten möchten oder müssen, dann würde ich die objektorientierte Verfahrensweise bevorzugen, da der anschließende Umgang mit der Tabelle einfacher ist; möglich sind natürlich beide Verfahren. Die ansonsten hier beschriebenen Möglichkeiten zur Anlage von Constraints, ganz gleich ob zur Anlage von Indexen, Prüfungen oder Integritätsüberwachung, stehen Ihnen natürlich auch bei den Objekten bzw. den objektorientierten Tabellen zur Verfügung. Im folgenden Listing finden Sie hierzu noch einmal zwei kleine Beispiele. alter table h_mensch add constraint check_geschl check (h_m.geschl in ('M', 'W') disable alter table menschen add constraint check_geschl check (geschl in ('M', 'W') disable
Varianten Mit zwei besonderen Variationen möchte ich das Kapitel „Tabellen“ zunächst einmal abschließen. Zum einen geht es um den Parameter cache, den Sie zusammen mit der Anlage einer Tabelle verwenden können, und der dazu führt, dass Oracle versucht die zugehörigen Dabenblöcke möglichst lange im Speicher vorzuhalten.
Beschreibung der Objekte
203
Das ist für kleine und häufig genutzte Tabellen interessant, weil diese damit quasi immer sofort zur Verfügung stehen. Der Standardwert für diese Option ist nocache. Zum anderen geht es um die Klausel nologging, die dazu führt, dass zusammen mit Einfügeoperationen weniger Protokollmaterial im redo log-Bereich anfällt. Gerade im Rahmen von Batchprozessen werden häufiger Arbeitstabellen am Anfang des Programms mit Hilfe eines truncate table-Befehls abgeschnitten, um danach wieder über eine insert-Anweisung gefüllt zu werden. In solchen Fällen oder beim Urladen irgendwelcher Daten kann diese Option interessant sein, um die Performance der Einfügeanweisung zu erhöhen. Beide Klauseln können zusammen mit der Anlage der Tabelle oder im Nachhinein über eine entsprechende alter table-Anweisung verwendet werden. Die nachfolgenden Beispiele zeigen Ihnen die prinzipielle Verwendung dieser Klauseln. alter alter alter alter
table table table table
lohnarten lohnarten lohnarten lohnarten
cache; nocache; logging; nologging;
2.2.18 Trigger (Triggers) Bei den sogenannten Triggern handelt es sich um in der Datenbank gespeicherte PL/SQL-Programme, die durch bestimmte Datenereignisse automatisch aufgerufen (getriggert) werden. Konkret können diese Ereignisse in einer Tabelle durch Verwendung einer Einfüge-, Änderungs- oder Löschanweisung ausgelöst werden (vgl. Abb. 2.16).
Datenbank
Before-Trigger
delete from ...
Löschen durchführen
After-Trigger
Abbildung 2.16: Schema der Verwendung getriggerter Programme
204
Datenbankobjekte in einer Oracle-DB
Die Abbildung 2.16 zeigt Ihnen stark vereinfacht den internen Ablauf einer Löschanweisung bei vorhandenen Trigger-Programmen. Wird jetzt im Rahmen einer SQL-Anweisung ein delete-Kommando abgesetzt, so startet Oracle zunächst das Programm eines vorhandene „Vorab-Löschtriggers“. Anschließend werden die Daten gelöscht und danach startet die Datenbank automatisch das Programm des vorhandenen „Danach-Löschtriggers“. Dabei haben beide Trigger-Programme Zugriff auf die im Rahmen der Löschtransaktion betroffenen Datensätze und beide Programm könnten durch entsprechende Anweisungen die Transaktion abbrechen. Wie ich schon angedeutet habe, werden Trigger immer zusammen mit einer Tabelle definiert, d.h. bei der Erstellung des Triggerprogramms müssen Sie angeben, für welche Tabelle der Trigger gestartet werden soll. Außerdem müssen Sie festlegen, ob der Trigger vor oder nach Durchführung der Änderungstransaktion und für welche Transaktionen der Trigger ausgeführt werden soll, d.h. Sie haben die Möglichkeit, spezielle Einfüge-, Änderungs- oder Löschtrigger zu erstellen. Des weiteren müssen Sie definieren, wie der Trigger zusammen mit der durch die Änderungstransaktion betroffene Datenmenge aufgerufen werden soll. Hierbei haben Sie zum einen die Möglichkeit, den Trigger zusammen mit allen betroffenen Datensätzen bzw. mit der zugehörigen SQL-Anweisung oder mit jedem Datensatz einzeln starten zu lassen. Nach Kenntnis dieser ganzen Informationen macht es Sinn, dass in Abbildung 2.16 vereinfachte Ausführungsmodell von Triggerprogrammen ein wenig zu präzisieren. Der nun folgenden Aufstellung können Sie entnehmen, wie bzw. in welcher Reihenfolge Trigger gestartet werden und wie diese mit eventuell vorhandenen Constrains zusammenarbeiten: 1. Ausführung aller Vorab-Trigger, die nicht für jeden einzelnen Datensatz gestartet werden sollen. 2. Durchlaufen aller im Rahmen der Transaktion selektierten Datensätze. –
Ausführung aller datensatzbezogenen Vorab-Trigger.
–
Ändern, Löschen, Einfügen des Datensatzes entsprechend des SQL-Befehls. Durchführen alle vorhandenen Constraint-Prüfungen.
–
Ausführung aller datensatzbezogenen Danach-Trigger.
3. Ausführen aller nicht datensatzbezogenen Danach-Trigger. Ein ganz beachtlicher Unterschied zu einigen anderen Datenbanksystemen ist, dass Sie für eine Tabelle mehrere Trigger gleichen Typs, beispielsweise mehrere Vorab-Löschtrigger, definieren können. In dem Fall werden beim Löschen eines oder mehrere Datensätze einfach alle zugehörigen Triggerprogramme nacheinander gestartet. Aber alleine schon die Tatsache, dass Sie in Oracle die Trigger vor oder nach Durchführung der zugehörigen Transaktion starten können, ist ein beachtlicher Unterschied im Vergleich zu anderen Datenbanksystemen. Aus meiner Sicht sind die Trigger eines der interessantesten Features einer relationalen Datenbank. Es gibt unendlich viele Einsatzmöglichkeiten, man könnte auch sagen, die Trigger sind die „Eierlegendewollmilchsau“ in Ihrer Werkzeugkiste. In der folgenden Aufstellung habe ich ein paar Einsatzmöglichkeiten für Trigger auf-
Beschreibung der Objekte
205
gezählt und dabei habe ich in Klammern festgehalten ob es sich bei dem Einsatzgebiet eher um einen Vorab- bzw. Danach-Trigger handelt.
X X X X X
X
X X
X
(Vorab) Überprüfung der Transaktionsberechtigung. Dürfen die Daten beispielweise aufgrund anderer Aktivitäten gerade nicht geändert werden, so können Sie darauf mit Hilfe eines Triggers achten. Beispiel: Während die Gehaltsabrechnung läuft, dürfen bestimmte Stammdaten nicht geändert werden. (Vorab) Verhindern bestimmter Transaktionen, beispielsweise Massenupdates oder Mehrfachlöschungen in Stammdatentabellen. (Danach) Automatische Berechnung verschiedener, meistens redundanter Felder. Ein einfaches Beispiel hierfür wäre das Mitführen einer Arbeitsspalte mit einer besonderen Aufbereitung der Suchbegriffe (vgl. nlssort). (Vorab + Danach) Überwachung komplexer Prüfroutinen, die beispielweise im Rahmen der einfachen check- oder Integritäts-Constraints nicht programmiert werden können, weil die Regeln zu umfangreich oder die Datenstrukturen hierfür ungeeignet sind. (Vorab + Danach) Sicherstellen der referentiellen Integrität. Beispielsweise durch automatisches Löschen oder Umbenennen abhängiger Datensätze. Mit Hilfe eines Danach-Triggers könnte beispielsweise jeder Elternsatz seine Kinder löschen. Wenn Sie dieses Prinzip in Ihrer Datenbank durchhalten, dann können Sie anschließend an jeder Stelle mit einer Löschanweisung ansetzen und es ist sichergestellt, dass abhängige Datensätze ebenfalls automatisch gelöscht werden. (Danach) Erstellen von kontrollierten Datenredundanzen. Aus Performancegründen kann es manchmal sinnvoll sein, bestimmte Informationen mehrfach in der Datenbank vorzuhalten. Allerdings müssen Sie dann darauf achten, dass die zugehörigen Felder und Sätze immer die gleichen Werte enthalten, wozu Sie Trigger-Programme verwenden können. (Danach) Erstellen einer Protokollierung von Änderungen auf Datensatzebene. (Vorab + Danach) Ereignisgesteuerte Administration oder soll ich besser Workflow-Management sagen. Ein Trigger bietet Ihnen das ideale Werkzeug, Datenänderungen zu erkennen und daraufhin die benötigten Aktionen auszulösen, beispielsweise neue Datensätze in sogenannte „todo“-Listen einzutragen. (Vorab) Programmierung komplexer Zugriffsschutzbestimmungen, beispielsweise in der Form von datensatzbezogenen Änderungsrechten.
Das waren nur einige Beispiele für einen möglichen Einsatz von Trigger-Programmen, wobei ich glaube, dass das große Spektrum der Einsatzmöglichkeiten deutlich geworden ist. Wenn Sie innerhalb eines Triggers weitere SQL-Änderungskommandos absetzen, dann führt das unter Umständen zum Auslösen weiterer Triggerprogramme, sofern für die entsprechenden Tabellen und Transaktionen entsprechende Programme angelegt wurden (vgl. Abb. 2.17).
206
Datenbankobjekte in einer Oracle-DB
update tabelle1 ...
tabelle1 update-trigger... { insert tabelle2 }
tabelle2 insert-trigger... { delete tabelle3 }
Abbildung 2.17: Verschachtelte Ausführung von Triggern
Das ist im Übrigen auch wieder ein wichtiger Aspekt im Vergleich zu anderen Datenbanksystemen, wo es unter Umständen Restriktionen bei der Verwendung geschachtelter Trigger gibt bzw. deren kaskadierende Ausführung überhaupt nicht möglich ist. Ein Trigger wird mit Hilfe der Anweisung create or replace trigger erstellt bzw. geändert. Löschen können Sie ihn mit Hilfe des Befehls drop trigger. In beiden Fällen müssen Sie hinter dem Befehl als Erstes den Namen des Triggers spezifizieren. Weitergehende Informationen zu Triggern finden Sie in der Oracle-Dokumentation vor allem an zwei Stellen. Zum einen enthält das Buch „Oracle8 Concepts“ ein eigenes Kapitel „Database Triggers“ und zum anderen finden Sie auch in der „Oracle8 SQL Reference“ unter dem Stichwort „create trigger“ Informationen und Beispiele zur Anlage und Verwendung dieser speziellen Programme. Beispiel An dieser Stelle möchte ich zunächst nur ein sehr einfaches Beispiel anführen und als Variation lediglich die Anlage unterschiedlicher leerer Triggerrümpfe darstellen. Der Grund dafür ist, dass die Erstellung von Triggern schnell zu umfangreicheren Programmpassagen führt und ich möchte hier und jetzt nicht dem gesamten Kapitel „PL/SQL-Programmierung“, in dem Sie weitere und vor allem komplexere Beispiele finden, schleichend vorgreifen müssen. Als Erstes erstellen wir einen Trigger für die Lohartentabelle, der dafür sorgt, dass jegliche Änderungen für die Tabelle nur noch an bestimmten Wochentagen möglich sind (vgl. Listing 2.60). create or replace trigger lohnarten_upd before update on lohnarten begin if to_char(sysdate,'D') not in ('5','7') then
Beschreibung der Objekte
207
raise_application_error(-20001, 'Die Tabelle kann nur am DO und SA geändert werden.'); end if; end; / show errors; commit; Listing 2.60: Beispiel für die Anlage eines Vorab-Triggers
Wie Sie dem Listing 2.60 entnehmen können, erhält jeder Trigger bei der Anlage einen Namen („lohnarten_upd”). Anschließend folgen verschiedene Anweisungen die festlegen, wann und wofür der Trigger gestartet werden soll, was ich gleich noch ausführlicher erläutern werde. Das eigentliche Trigger-Programm befindet sich dann wieder in einem begin..end-Block. In unserem Beispiel ermitteln wir innerhalb des Programms mit Hilfe der Funktionen to_char und sysdate den Wochentag aus dem aktuellen Tagesdatum. Ist dieser Wert nicht fünf und nicht sieben (Donnerstag bzw. Samstag), dann brechen wir mit Hilfe der Funktion raise_application_error die Transaktion ab und stellen dem aufrufenden Anwendungsprogramm eine entsprechende Fehlermeldung zur Verfügung. Varianten Die Deklaration eines Triggers folgt im Prinzip folgendem Schema: create or replace trigger on
Der vergebene Name des Triggers muss innerhalb des verwendeten Schemas eindeutig sein. Zur Spezifizierung des Zeitpunkts können Sie die Schlüsselwörter before oder after verwenden, um entsprechend einen Vorab- bzw. Danach-Trigger zu erstellen. Die den Trigger aufrufende Transaktion legen Sie mit Hilfe der Schlüsselwörter insert, update oder delete (Einfügen, Ändern und Löschen) fest. Hierbei können Sie die einzelnen Änderungsarten miteinander kombinieren, indem Sie diese mit dem Wörtchen or verbinden. insert or update or delete
In dem Fall können Sie innerhalb des Programms mit Hilfe der Schlüsselwörter inserting, updating bzw. deleting auf die jeweils verantwortliche Änderungsanweisung reagieren; ein Beispiel hierfür finden Sie am Ende des Abschnitts. if inserting then ... if updating then ... if deleting then ...
208
Datenbankobjekte in einer Oracle-DB
Zusammen mit einer Änderungstransaktion (update) können Sie dabei auch noch einzelne Spalten festlegen, so dass der Trigger bei einer update-Anweisung lediglich dann aufgerufen wird, wenn eine der aufgeführten Spalten dabei geändert wird. insert or update of la, la_text or delete
Nachdem nun die Transaktionsarten für den Trigger festgelegt sind, folgt als Nächstes hinter dem Wörtchen on der Namen der zugehörigen Tabelle. Danach legen Sie fest, ob der Trigger für die gesamte Transaktion oder für jeden einzelnen betroffenen Datensatz gestartet werden soll. Dabei entspricht das einmalige Starten des Triggers für die gesamte Transaktion dem Standardverfahren. Um den Trigger für jeden betroffenen Datensatz aufzurufen, müssen Sie die Klausel for each row spezifizieren. Zusammen mit den datensatzbezogenen (for each row) Triggern können Sie auch noch eine Ausführungsbedingung festlegen, die für den Datensatz erfüllt sein muss, damit das Triggerprogramm gestartet wird. Diese Klausel leiten Sie mit dem Schlüsselwort when ein, dem die konkrete Ausführungsbedingung in Klammern folgt. Hierbei können Sie mit Hilfe der Schlüsselwörter new und old auf die geänderten bzw. ursprünglichen Werte der betroffenen Datensätze zugreifen. Im folgenden Beispiel wird das Triggerprogramm nur dann gestartet, wenn die neue Lohnart den Wert „400” oder die alte Lohnart den Wert „233” enthält. for each row when (new.la = '400' or old.la = '233')
Innerhalb des Programms eines datensatzbezogenen Triggers erfolgt der Zugriff auf die alten bzw. neuen Werte durch Verwendung von :new bzw. :old. If :new.la = '400' or :old.la = '233' then
Das war schon fast alles und bevor ich noch kurz auf eine ganz besondere Triggervariante eingehe, finden Sie im Folgenden noch verschiedene Beispiele und Variationen zur Definition von Triggerprogrammen.
X
Erstellen eines Vorab-Triggers für alle Änderungstransaktionen create or replace trigger mtabelle_t1 befor insert or update or delete on mtabelle
X
Erstellen eines Vorab-Triggers für jeden betroffenen Datensatz, der allerdings nur dann gestartet wird, wenn die neue Lohnart größer „900“ ist. create or replace trigger mtabelle_t2 before insert or update(la) or delete on mtabelle for each row when (new.la > '900')
X
Erstellen eines Danach-Triggers, der nach einer Löschoperation aufgerufen wird. create or replace trigger mtabelle_t3 after delete on mtabelle
Beschreibung der Objekte
X
209
Erstellen eines Danach-Triggers, der nach dem Löschen für jeden betroffenen Datensatz gestartet wird, sofern die Lohnart kleiner dem Wert „788“ ist. create or replace trigger mtabelle_t4 after delete on mtabelle for each row when (old.la < '788')
Instead of-Trigger Mit dieser besonderen Variante können Sie das eigentlich Verbotene möglich machen. Konkret geht es darum, Views, die aufgrund ihrer komplexen bzw. speziellen Definition eigentlich nur gelesen werden können, mit Hilfe des Triggers updatefähig zu machen. Hierbei müssen Sie im Triggerprogramm die benötigten Regeln definieren, unter welchen Bedingungen die jeweils an der View beteiligten Tabellen geändert werden sollen. Bevor Sie jetzt allerdings die Ärmel hochkrempeln, um das gleich folgende Beispiel einzutippen, sollten Sie zunächst einmal wieder die View „v$options“ befragen, ob dieses spezielle Feature überhaupt in Ihrer Datenbank verfügbar ist. Mein Beispiel habe ich in Anlehnung an das in der Oracle-Dokumentation vorgestellte Muster erstellt. Stellen wir uns vor, in unserer Datenbank, die wir zusammen mit einem anderen Anwender gemeinsam nutzen, existieren die beiden Tabellen „meine_freunde“ und „deine_freunde“ mit jeweils folgendem Aufbau. drop table meine_freunde; drop table deine_freunde; / create table meine_freunde ( name varchar(50), telefon varchar(30) ) tablespace usr; / create table deine_freunde ( name varchar(50), telefon varchar(30) ) tablespace usr; commit Listing 2.61: Vorbereiten eines instead of-Triggers
In diesen beiden Tabellen können anschließend unabhängig voneinander entsprechende Datensätze gespeichert werden. Damit die Tabellen im Folgenden nicht ganz so leer sind, werden wir einmal jeweils zwei Datensätze einfügen. insert into insert into insert into insert into commit;
meine_freunde meine_freunde deine_freunde deine_freunde
values values values values
('Hildegard', '983932'); ('Ernst', '587332'); ('Paul', '213984'); ('Sonja', '390928');
210
Datenbankobjekte in einer Oracle-DB
Mit Hilfe einer View können Sie nun eine Abfrage erstellen und dauerhaft speichern, mit der alle vorhandenen Freunde angezeigt werden. Hierbei soll mit Hilfe einer zusätzlichen Spalte dargestellt werden, aus welchem Töpfchen der jeweilige Freund selektiert wurde. create view alle_freunde as select name, telefon, 'meine' as mein_dein from meine_freunde union all select name, telefon, 'deine' from deine_freunde; commit; Listing 2.62: Mischen zweiter Tabelle mit Hilfe einer union-View
Im Rahmen der in der View gespeicherten Abfrage werden zwei exakt gleich aufgebaute select-Abfragen mit Hilfe der union-Anweisung gemischt. Als Ergebnis erhalten Sie eine logische Tabelle, deren Abfrage (vgl. Listing 2.63) den Eindruck erweckt, als würden die zugehörigen Einzelsätze gemeinsam gespeichert werden. SQLWKS> select * from alle_freunde 2> NAME ----------------------------------Hildegard Ernst Paul Sonja 4 rows selected.
TELEFON -----------------------983932 587332 213984 390928
MEIN_ ----meine meine deine deine
Listing 2.63: Abfrage des gemeinsamen Freundeskreises
Das Bearbeiten der in der logischen Tabelle bzw. der View zusammengefassten Datensätze ist aber von Hause aus nicht möglich. Woher soll die Datenbank bei einer Einfügeanweisung der Art insert into alle_freunde values ('Helgo', '758473', 'meine')
auch wissen, in welchen Topf sie den neuen Datensatz einfügen soll, zumal der als Letztes übergebene Wert nicht einmal einer konkret vorhandenen Spalte entspricht? Mit Hilfe der hier besprochenen instead of-Trigger können Sie dieses Dilemma allerdings auflösen und der Datenbank erklären, was sie mit den einzelnen Änderungen tun soll (vgl. Listing 2.64). create or replace trigger alle_freunde instead of insert on alle_freunde for each row
Beschreibung der Objekte
211
begin if :new.mein_dein = 'meine' then insert into meine_freunde values(:new.name, :new.telefon); else insert into deine_freunde values(:new.name, :new.telefon); end if; end; / show errors; commit; Listing 2.64: Erstellen eines einfachen instead of-Triggers
Erst jetzt können Sie die oben abgebildete Einfügeanweisung ausführen und erhalten als Ergebnis einen neuen Datensatz in der Tabelle „meine_freunde”. Wie Sie dem Beispiel entnehmen können, wird mit Hilfe des übergebenen Wertes für die konstruierte Spalte „meine_deine“ innerhalb des Programms entschieden, in welcher Tabelle die Einfügeoperation durchgeführt werden soll. Natürlich können Sie das Programm auch noch um Methoden für das Ändern oder Löschen von Datensätzen oder sogar eine spezielle Variante zur Behandlung von Datensätzen, die mit dem Zusatz „unsere“ geändert werden, erweitern. Das Letztere überlasse ich Ihnen, für das Ändern und Löschen möchte ich aber noch eine vollständige Programmversion vorstellen. create or replace trigger alle_freunde instead of insert or update or delete on alle_freunde for each row begin if inserting then if :new.mein_dein = 'meine' then insert into meine_freunde values(:new.name, :new.telefon); else insert into deine_freunde values(:new.name, :new.telefon); end if; elsif updating then if :new.mein_dein = 'meine' then update meine_freunde set name = :new.name, telefon = :new.telefon where name = :old.name and telefon = :old.telefon; else update deine_freunde set name = :new.name, telefon = :new.telefon where name = :old.name and telefon = :old.telefon; end if; else if :old.mein_dein = 'meine' then
212
Datenbankobjekte in einer Oracle-DB
delete meine_freunde where name = :old.name and telefon = :old.telefon; else delete deine_freunde where name = :old.name and telefon = :old.telefon; end if; end if; end; / show errors; commit; Listing 2.65: Triggervariante zur Herstellung der Änderungsfähigkeit einer View
Mit Hilfe einer if-Anweisung reagiert das Programm auf die einzelnen zum Aufruf des Triggers geführten Änderungsabfragen. Dabei wird in allen Fällen mit Hilfe der Spalte mein_dein unterschieden, in welcher Tabelle die Änderung konkret durchgeführt werden soll. Nach Erstellung des Triggers können Sie auch Änderungs- oder Löschabfragen zusammen mit der View alle_freunde verwenden. update alle_freunde set telefon = '1111' where name = 'Hildegard' and mein_dein = 'meine' delete alle_freunde where name = 'Paul' and mein_dein = 'deine' commit
Varianten Jeder Segen kann auch mal zum Fluch werden. Hiermit meine ich die Situation, in dem die automatisch gestarteten Triggerprogramme beispielsweise im Rahmen von Wartungs- oder Korrekturaktionen lästig werden. In dem Fall müssen Sie den Trigger allerdings nicht unbedingt löschen, sondern es besteht auch die Möglichkeit, vorübergehend aus- bzw. einzuschalten. Hierzu finden Sie im SQL-Befehlssatz die Anweisung alter trigger, die zusammen mit den schon bekannten Klauseln disable bzw. enable zum Aus- oder Einschalten eines Triggers verwendet werden kann. alter trigger lohnarten_upd disable; alter trigger lohnarten_upd enable;
2.2.19 Ansicht (Views) Bei einer sogenannten View handelt es sich um in der Datenbank gespeicherte Abfrage, die sich nach außen wie eine logische Tabelle verhält, d.h. die View können Sie in einem gewissen Umfang wie eine gewöhnliche Tabelle verwenden, obwohl sie keine eigenen Daten enthält. Mit Hilfe einer solchen View können Daten aus einer anderen Tabelle bzw. anderen Tabellen abgefragt werden, d.h. bei
Beschreibung der Objekte
213
deren Verwendung innerhalb einer Abfrage werden zusätzlich auch die in der View gespeicherten Anweisungen ausgeführt. Die Anlage einer View erfolgt mit Hilfe des Befehls create or replace view. Gelöscht wird sie mit Hilfe des Befehls drop view. Dabei sieht das Anlegen einer View sieht so ähnlich aus wie das Kopieren einer Tabelle mit dem create table as-Befehl. Allerdings wird hierbei statt des Wörtchens „table“ das Wort „view“ verwendet und im Unterschied zum Kopiervorgang werden bei der Anlage der View keine Daten bewegt, sondern es wird lediglich die SQL-Abfrage gespeichert. create or replace view as select
Innerhalb der in der View verwendeten Abfrageanweisung können Sie eine einzige Tabelle verwenden oder aber auch mehrere Tabellen miteinander verknüpfen. Ebenfalls möglich ist die Verwendung von mischenden (union) oder gruppierenden (group by) Abfragen. Insgesamt ergibt sich für das Einsatzgebiet von Views das folgende Spektrum:
X X
X
Speichern komplexer Abfragen, um zum einen deren Verwendbarkeit zu vereinfachen oder aber auch zum anderen dem Endanwender die Ausführung der Abfrage überhaupt zu ermöglichen. Views können auch ein wichtiger Bestandteil eines Sicherheitskonzepts sein. Wie Sie später in diesem Buch noch erfahren werden, muss der Zugriff auf die einzelnen Schema-Objekte für andere Benutzer explizit freigegeben werden. Hat ein Benutzer Zugriff auf eine View, so kann er die dort definierten Spalten und Sätze abrufen, ohne dass er Zugriffsrechte auf die darunter liegenden Tabellen besitzen muss. Konstruktion einer logischen Schicht zwischen Anwendungen, beispielsweise Reports, und der Datenbank, um diese Anwendungen zumindest in einem gewissen Maße resistent gegen Strukturänderungen zu machen.
Gerade für den ersten der aufgeführten Punkte finden Sie schon alleine im Systembereich von Oracle viele Beispiele. Im Rahmen der Tabellenerstellung haben wir häufiger einfache Abfragen auf die View dba_extents gemacht. select extent_id, bytes, blocks from dba_extents where segment_name = 'LOHNARTEN'
Dass diese Abfrage so einfach war, liegt daran, dass die eigentliche Komplexität in der View versteckt ist, denn die dort gespeicherte Auswahlanweisung ist folgende: select ds.owner, ds.segment_name, ds.partition_name, ds.segment_type, ds.tablespace_name, e.ext#, f.file#, e.block#, e.length * ds.blocksize, e.length, e.file# from sys.uet$ e, sys.sys_dba_segs ds, sys.file$ f where e.segfile# = ds.relative_fno and e.segblock# = ds.header_block and e.ts# = ds.tablespace_id and e.ts# = f.ts# and e.file# = f.relfile#
214
Datenbankobjekte in einer Oracle-DB
Die in einer View gespeicherte Anweisung können Sie übrigens durch eine Abfrage auf das Objekt all_views ermitteln. Ansonsten passt das Beispiel auf alle drei von mir genannten Kategorien. Die Zusammenfassung dieser doch recht umfangreichen Abfrage zu einem Objekt vereinfacht uns allen deren Verwendung. Zum zweiten benötigen und besitzen wir üblicherweise überhaupt keine Zugriffsrechte auf die in der View verwendeten Objekte (z.B. sys_dba_segs). Und drittens könnte Oracle die konkrete Speicherung von Segment- bzw. Extent-Informationen ändern, ohne dass wir das überhaupt mitbekommen, sofern die View dba_extents nach einer solchen Änderung immer noch die gleichen Daten liefert. In Bezug auf die technische Verwendung bzw. Ausführung eines View-Objekts, unterscheidet sich Oracle von manchen anderen Datenbanksystemen. Primäres Ziel ist es nämlich, nicht die eigentliche View auszuführen und das zugehörige Abfrageergebnis zusammen mit der eigentlichen auslösenden Abfrage zu verknüpfen, sondern die in der View gespeicherte Abfrageanweisung mit dem aufrufenden SQL-Statement zu mischen (vgl. Abb. 2.18).
Abfrage: select * from v_bezuege where la = ‘100‘
View v_bezuege: select persnr, la from bezuege where la < ‘900‘
select a.persnr, a.la from bezuege a where (a.la < ‘900‘) and (a.la = ‘100‘)
Datenbank
Abbildung 2.18: Mischen der View-Auswahlanweisung mit dem rufenden SQL-Statement
Diese Vorgehensweise hat den Vorteil, dass bei der Ausführung der View die in den Tabellen spezifzierten Indices bei der Datenauswahl berücksichtigt werden können, so dass mit der Verwendung von Views meistens keine Geschwindigkeitseinbußen einhergehen. Meistens bedeutet demnach aber, dass es auch Ausnahmen gibt. Das ist beispielsweise dann der Fall, wenn Oracle die in der View gespeicherten Auswahlanweisungen nicht zu der aktuell verwendeten Abfrage hinzumischen kann, was unter anderem immer dann passiert, wenn die View folgende Anweisungen enthält:
X X X X
eine connect by-Klausel. die Verwendung von Mengenoperationen (union, union all, minus usw.). gruppierende Abfragen (group by, sum, max, min usw.). die Pseudospalte rownum.
Beschreibung der Objekte
215
In solchen Fällen wird die View ausgeführt und das Ergebnis wird zunächst einmal in einem temporären Bereich zwischengespeichert. Diese temporäre Tabelle wird anschließend mit den anderen an der Abfrage beteiligten Tabellen verknüpft, was die Performance der Abfrage durchaus negativ beeinflussen kann. Beim Analysieren Ihrer SQL-Abfrage können Sie genau ablesen, ob Ihre View ausgeführt oder zur Abfrage hinzugemischt wird. Genauere Informationen hierzu erhalten Sie in diesem Workshop bei der Behandlung des Abfragetunings. Ein weiterer Aspekt bei den Views ist die Fragestellung, ob diese Objekte auch zusammen mit Änderungsabfragen verwendet werden können. Dabei sind einfache Views, deren Abfrage lediglich eine Tabelle verwendet, meistens änderungsfähig, sofern innerhalb der Views folgende Anweisungen nicht verwendet wurden: –
der distinct-Befehl.
–
Aggregatfunktionen (z.B. avg, count, max, min, sum usw.) bzw. Gruppieroperationen (z.B. group by).
–
Mengenoperationen (union, union all, minus usw.).
–
die Befehle start with oder connect by.
–
die Pseudospalte rownum.
Auch eine View, in der verschiedene Tabellen verknüpft werden, kann in einem gewissen Umfang änderungsfähig sein. Generell gilt allerdings, dass die eingesetzte Änderungsabfrage nur eine der in der View verwendeten Tabellen ändern darf. Welche Spalten einer View im Zweifel geändert werden dürfen, können Sie mit Hilfe einer Abfrage auf die View user_updatable_colums ermitteln. select column_name, updatable from user_updatable_columns where table_name = ''
Wie Sie im vorhergehenden Kapitel jedoch gesehen haben, gibt es mit Hilfe der instead of-Trigger immer eine Möglichkeit, den einzelnen Views die Updatefähigkeit selbst dann beizubringen, wenn sie standardmäßig gar keine Änderungsanweisung zulässt. Beispiel
X
Als Beispiel erstellen wir jetzt eine View (vgl. Listing 2.66), die uns alle vorhandenen Bezüge zusammen mit dem Lohnartentext aus der Lohnartentabelle anzeigt.
create or replace view ma_bezuege as select a.persnr, a.la, b.la_text, a.betrag from bezuege a, lohnarten b where b.la = a.la and a.betrag <> 0; commit; Listing 2.66: Erstellen einer einfachen View
216
Datenbankobjekte in einer Oracle-DB
Wie Sie dem Beispiel entnehmen können, wird die View ma_bezuege mit Hilfe der Anweisung create or replace view angelegt. Die Verwendung dieser Form hat als Alternative zu der Kombination einzelner drop view bzw. create view-Anweisungen wieder den Vorteil, dass eventuell zugewiesene Zugriffsrechte hierbei erhalten bleiben. Hinter dem Namen der View folgt das Wörtchen as, dem die eigentliche in der View gespeicherte Abfrage folgt. Die Verwendung unseres neuen Objekts in einer Abfrage führt nun alle betragsmäßig gespeicherten Bezüge zusammen mit der Lohnartenbezeichnung zu Tage. SQLWKS> select * from ma_bezuege 2> PERSNR LA LA_TEXT BETRAG ---------- --- -------------------------------------------- ---------4711 100 Grundgehalt 1000 1 row selected.
Eine weitere Abfrage auf die View user_updatable_columns beschert uns Gewissheit, dass ohne zusätzliche Objekte in Form eines instead of-Triggers unsere View eine reine Leseveranstaltung ist. SQLWKS> select column_name, updatable from user_updatable_columns 2> where table_name = 'ma_bezuege' 3> COLUMN_NAME UPD ------------------------------ --0 rows selected.
2.2.20 Zusammenfassung Am Anfang des Kapitels hatte ich Ihnen einen Überblick über die in Oracle verfügbaren Objekte versprochen. Wie gut, dass das Wörtchen „Überblick“ nicht automatisch den zugehörigen Umfang impliziert, denn bei meinem Streifzug durch die Schema-Objekte der Oracle-Datenbank ist ein beachtliches Häufchen Papier entstanden. Trotzdem gibt es natürlich immer Leser, die gerade die eine spezielle Variante eines Befehls vermissen. Doch irgendwie muss man den Rahmen abstecken; schon alleine für die Befehle create oder alter table könnte man locker ein eigenes Buch mit über 200 Seiten erstellen, wenn man alle Variationsmöglichkeiten dieser beiden Befehle beschreibt. Dabei ist es zum Teil sicher auch ein bisschen willkürlich, dass man die eine Variante noch beschreibt, eine andere Variation aber weglässt. In meiner mittlerweile zehnjährigen Erfahrung mit verschiedenen Datenbanken habe ich von Zeit zu Zeit auch immer neue Systeme kennen gelernt. Dabei wäre ich jedes Mal froh gewesen, wenn ich eine ähnliche Übersicht für den Schnelleinstieg zur Verfügung gehabt hätte. Ich hoffe, das geht Ihnen ebenso, wobei ich für konstruktive Verbesserungsvorschläge natürlich immer offen bin.
Beschreibung der Objekte
217
Vielleicht sind Sie nach dieser Einführung aber auch ein wenig beeindruckt. Wenn Sie schon andere Datenbanksysteme kennen, dann müssen Sie zugeben, dass der Katalog der Möglichkeiten ziemlich umfangreich ist. Sofern Sie mit einer Standardsoftware (z.B. PeopleSoft) arbeiten, dann fragen Sie sich jetzt vielleicht aber auch, warum von diesen Möglichkeiten fast kein Gebrauch gemacht wird. Es ist eigentlich nur selten möglich, mit der Einführung einer neuen Software auch den Einsatz eines ganz bestimmten DBMS zu erzwingen. Dies muss man bei der Konzeption bzw. Entwicklung von Standardsoftware berücksichtigen, wenn man sich nicht von vornherein auf einen bestimmten Markt beschränken will. Vergleicht man nun aber die verschiedenen gängigen DBMS-Systeme (Oracle, Sybase, SQL-Server, Informix, DB/2, Gupta), dann stellt man fest, dass der kleinste gemeinsame Nenner in der Tat ziemlich klein ist. Aus Sicht der Oracle-Datenbank werden bestimmte Funktionalitäten (z.B. selbstdefinierte Funktionen) von den meisten anderen Produktion überhaupt nicht unterstützt. Andere Objekte (z.B. Trigger) gibt es zwar auch, aber sie haben im Vergleich zu Oracle zum Teil einen eingeschränkten Funktionsumfang (z.B. nur ein Trigger pro Tabelle) oder wichtige Details in Bezug auf die Erstellung oder Verwendung unterscheiden sich sehr stark. Konkret bedeutet das für den Entwickler, dass wenn er den universellen Markt bedienen will, nur Tabellen, explizit definierte Indices und Views verwenden darf, wobei er selbst hierbei noch Zurückhaltung in Bezug auf die verwendeten Datentypen und die Gestaltung der benötigten Abfragen Zurückhaltung üben muss. Das ist der Grund, warum die Datenbank einer Standardsoftware oftmals so einfach gestrickt ist; gehen Sie ruhig davon aus, dass die zuständigen Programmierer es besser gekonnt hätten, wenn Sie ein Oracle-Produkt entworfen hätten. Wenn Sie spezifisch auf Oracle-Datenbanken zugeschnittene Entwicklung betreiben, dann muss Sie das alles natürlich nicht kümmern. Selbst wenn Sie im Rahmen eines Einführungsprojekts Erweiterungen durchführen kann es sinnvoll sein im Rahmen der jeweiligen Aufgabenstellung weitergehenden Datenbankfunktionalitäten zu nutzen. Allerdings sollte dabei allen klar sein, dass der Wechsel des Standardprodukts auf ein andere Datenbank immer noch möglich, die Portierung der individuellen Erweiterungen jedoch vielleicht nur mit zusätzlichem Aufwand oder sogar gar nicht möglich ist. Was ich hiermit sagen will und sich vor allem an meine Beraterkollegen richtet ist, dass der tiefe Griff in die Trickkiste ist nicht immer das Maß aller Dinge ist. Es dürfte jeder Fachabteilung schwer verständlich zu machen sein, dass sie auf der einen Seite ein nach Herstellerangaben überall lauffähiges Produkt gekauft hat, das aber auf der anderen Seite wegen der individuell durchgeführten Anpassungen nicht mehr portierbar ist. Wenn natürlich Klarheit über diesen Sachverhalt herrscht und in absehbarer Zeit auch kein Wechsel des DBMS geplant ist, dann spricht aus meiner Sicht allerdings nichts dagegen, im Rahmen von Anpassungen und Erweiterungen weitergehende Datenbankfunktionalitäten einzusetzen.
218
Datenbankobjekte in einer Oracle-DB
Nach diesem mahnenden Fingerzeit an die Spieler und Fummler noch ein abschließender Hinweis. Grundsätzlich finden Sie in der Oracle-Dokumentation unter dem Link „Oracle8 Concepts“ weiterführende und zusammenhängende Erläuterungen zu den verschiedenen Schema-Objekten. Im Buch „PL/SQL User’s Guide and Reference“ finden Sie weitergehende Informationen zu den mit PL/SQL-Erstellten Schema-Objekten wie zum Beispiel Funktionen oder Prozeduren. Daneben gibt es noch den Link „Oracle8 SQL-Reference“. Hier finden Sie für alle Befehle die vollständige Beschreibung der Syntax aller Befehle sowie eine detaillierte Beschreibung der möglichen Parameter und Varianten. Sollten Sie während der Objekterstellung oder beim Testen derselben mal eine Fehlermeldung erhalten, dann können Sie die genaue Bedeutung des Fehlers nebst Hinweise zu dessen Behebung ebenfalls der Oracle-Dokumentation entnehmen, indem Sie dem Link „Oracle8 Error Messages“ folgen. Dort finden Sie eine nach Kategorie bzw. Produkt und Fehlernummer sortierte Auflistung aller Fehlermeldungen.
2.3
Die Beispieldatenbank anlegen
Im weiteren Verlauf des zweiten Kapitels werden wir nun eine kleine Beispieldatenbank anlegen, genauer gesagt legen wir dabei natürlich nur ein eigenes Schema in unserer vorhandenen Datenbank an. Das besteht natürlich nicht wie im richtigen Leben gleich aus hunderten von Tabellen, aber eine kleine Struktur (vgl. Abb. 2.19) kommt dennoch schon zustande. Nach dem Anlegen der entsprechenden Objekte, die wir vor allem bei den der Behandlung der Abfragen im nächsten und bei der PL/ SQL-Programmierung im übernächsten Kapitel benötigen, werden wir die leeren Tabellen mit Daten füllen. Hierbei werden Sie verschiedene Möglichkeiten kennen lernen. In jedem Fall stammen die Daten aus einer Access-Datenbank, die Sie auf der Begleit-CD im Verzeichnis \DATEN finden. Konkret finden Sie dort die beiden Access-Datenbanken MISCH97.MDB und MISCH2000.MDB entsprechend der jeweiligen Access-Versionen. Ebenfalls dort gespeichert sind auch eine Reihe von Textdateien, die auf verschiedene Verfahrensweisen aus der Access-Datenbank exportiert wurden und die im Anschluss an die Fertigstellung des eigenen Schemas importiert werden sollen. Die beiden Access-Datenbanken benötigen Sie in diesem Kapitel also nicht direkt, es sei denn, Sie wollen den Export der beschriebenen Textdateien ausprobieren. Außerdem besteht die Möglichkeit, sich eine Migrationshilfe für Access-Datenbanken zu installieren und sofern Sie diese einmal ausprobieren möchten, dann finden Sie in diesen beiden Access-Datenbanken entsprechendes Eingabematerial für den Migrations-Assistenten.
Die Beispieldatenbank anlegen
219
personalien ( persnr varchar2(11) not null, name varchar2(50) not null, strasse1 varchar2(35), strasse2 varchar2(35), strasse3 varchar2(35), strasse4 varchar2(35), ort varchar2(30), land varchar2(3), bland varchar2(6), plz varchar2(12), telefon varchar2(24), geschlecht varchar2(1) not null, familienstd varchar2(1) not null, familienstd_dt date, gebdatum date not null, gebort varchar2(30), gebland varchar2(3) not null)
blaender ( land varchar2(3) not null, bland varchar2(6) not null, bezeichnung varchar2(30))
austrittsgruende (
bvs (
ausgrd varchar2(3) not null, gab date not null, status varchar2(1) not null, bezeichnung varchar2(30) not null )
persnr varchar2(11) not null, lfdnr smallint not null, eintrt date not null, austrt date, ausgrd varchar(3), unternehmen varchar(3) not null)
gehalt ( persnr varchar2(11) not null, lfdnr smallint not null, gab date not null, kst varchar2(10) not null, gehalt number(9,2) not null, zulage number(9,2) not null )
laender ( land varchar2(3) not null, bland varchar2(1) not null, bezeichnung varchar2(30))
unternehmen ( unternehmen varchar2(3) not null, gab date not null, status varchar2(1) not null, bezeichnung varchar2(30) not null )
kostenstelle ( unternehmen varchar2(3) not null, kst varchar2(10) not null, gab date not null, status varchar2(1) not null, bezeichnung varchar2(30) not null )
Abbildung 2.19: Struktur unseres Beispiel-Schemas
2.3.1
Anlage des Schema-Eigners
Am Anfang des Kapitels haben Sie den Begriff des Schemas kennen gelernt. Bisher haben wir bei allen unseren Beispielen immer mit einem der standardmäßig vorhandenen Administratorenkennungen (z.B. system) gearbeitet, was sich im Folgenden nun ändern soll. Aus diesem Grund legen wir gleich zunächst einmal eine neue Benutzerkennung an. Im Vorgriff auf das übernächste Kapitel „Benutzer und Rollen“ will ich an dieser Stelle allerdings nur soviel dazu sagen: In Oracle existieren für die Zuweisung von Benutzerrechten im Prinzip zwei Möglichkeiten. Zunächst einmal können Sie einem einzelnen Benutzer individuelle Einzelrechte
220
Datenbankobjekte in einer Oracle-DB
zuweisen oder entziehen. Das würde aber selbst bei kleinen Datenbanken mit vielleicht zehn oder zwandzig Benutzern selbst bei nur geringen Einzelrechten nicht sehr lange Spaß machen, weshalb es in Oracle das Konzept der Rollen gibt. Hierbei werden verschiedene Einzelrechte zu einer sogenannten Rolle zusammengefasst und im Anschluss daran werden den jeweiligen Benutzern die einzelnen Rollen zugewiesen, wodurch der indirekt seine Einzelrechte erhält. Aus dem Grund legen wir zunächst die neue Rolle „rbeispiel“ an und weisen der Rolle eine Reihe von weitreichenden Einzelrechten zu (vgl. Listing 2.67). Was diese einzelnen Rechte bedeuten und welche wirklich gebraucht werden oder vielleicht auch entfallen könnten, das erfahren Sie wie schon gesagt ein „paar“ Seiten weiter im Kapitel „Benutzer und Rollen“. Die in dem Zusammenhang gleich abgebildeten und beschriebenen Listings finden Sie auf der CD in der Datei \DATEN\SCHEMA.SQL. drop role rbeispiel; create role rbeispiel; commit; / grant alter session, alter tablespace, alter user, analyze any, become user, create cluster, create database link, create procedure, create public database link, create public synonym, create rollback segment, create sequence, create session, create snapshot, create synonym, create table, create tablespace, create trigger, create user, create view, drop public database link, drop public synonym, drop rollback segment, drop tablespace, drop user, grant any role, manage tablespace, resource, exp_full_database, imp_full_database
Die Beispieldatenbank anlegen
221
to rbeispiel with admin option; commit; Listing 2.67: Anlegen unserer Musterrolle „rbeispiel“
Zugegebenerweise züchten wir uns einen kleinen Administrator heran, aber bei der Anlage eines Schemas, zum Beispiel während der Installation einer Anwendung sind solche Rechte gar nicht schlecht. Nach einer Installation können die Befugnisse des Schema-Eigners im Bedarfsfall ja wieder reduziert werden. Die Befehle zum Löschen bzw. Anlegen einer Rolle heißen drop role bzw. create role, wobei in beiden Fällen der Name der jeweiligen Rolle folgt. Anschließend weisen wir der Rolle mit Hilfe entsprechender grant-Kommandos die gewünschten Einzelrechte zu. Im nächsten Schritt legen wir den Schema-Eigner an, den wir mit „ubeispiel“ benennen wollen, wobei Sie das Skript in der Datei \DATEN\USER.SQL finden. drop user ubeispiel cascade; commit; / create user ubeispiel identified by manager default tablespace usr temporary tablespace temporary profile default account unlock quota unlimited on usr quota unlimited on indx quota unlimited on temporary; commit; Listing 2.68: Anlegen des Schema-Eigners „ubeispiel“
Der erste Teil des Skripts löscht den Benutzer „ubeispiel“. Hierbei sorgt die Klausel cascade dafür, dass dies auch dann passiert, wenn der Benutzer Eigentümer irgendwelcher Objekte ist, wobei seine Besitztümer in dem Fall natürlich auch gelöscht werden. Im zweiten Teil des Skripts wird der Benutzer wieder angelegt. Während der Anlage erhält er das Passwort „manager“, bekommt als Tablespace-Standard „usr“ zugewiesen und erhält durch die quota-Klauseln ein uneingeschränktes Platzangebot für die Tablespaces „usr“, „indx“ und „temporary“. Im nächsten Schritt müssen Sie dem neuen Benutzer noch die Rolle „rbeispiel“ zuweisen, was wiederum mit Hilfe einer grant-Anweisung passiert. grant rbeispiel to ubeispiel;
222
2.3.2
Datenbankobjekte in einer Oracle-DB
Verwalten der Tablespaces
Beim Anlegen eines umfangreichen Schemas bzw. vor allem beim Laden der zugehörigen Daten kann es schon einmal passieren, dass ein Tablespace voll wird, was dann zum Abbruch aller gerade aktiven und auf den ihn zugreifenden Transaktionen führt. In dem Fall müssen Sie den Tablespace vergrößern, wobei Sie hierfür verschiedene Variationsmöglichkeiten haben. Vergrößern des zugrundeliegenden Datenfiles Das ist sicherlich die einfachste aller möglichen Varianten. Sie setzt allerdings voraus, dass auf dem Datenträger noch ausreichend Platz zur Verfügung steht. In dem Fall können Sie versuchen die zum Tablespace gehörende Datei zu vergrößern oder den automatischen Extent-Modus für die Datei einzuschalten bzw. zu erweitern. alter database datafile 'E:\ORANT\DATABASE\DB01\USR1DB01.ORA' resize 9606K; alter database datafile 'E:\ORANT\DATABASE\DB01\USR1DB01.ORA' autoextend on next 3K maxsize unlimited; Listing 2.69: Vergrößern der zum Tablespace gehörenden Datei
Beides passiert mit Hilfe des Befehls alter database datafile, dem die betriebssystemspezifische Vorgabe des zum Tablespace zugehörigen Dateinamens folgt. Mit Hilfe der Klausel resize können Sie die Datei vergrößern, indem Sie hinter der Klausel die neue Größe in Kilo- (K) oder Megabyte (M) vorgeben. Durch die Klausel autoextend on wird die automatische Vergrößerung der Datei eingeschaltet. Hierbei gibt die Klausel next an, in welcher Schrittgröße das passieren soll und die Klausel maxsize legt die maximale Größe der Datei fest, wobei die durch Verwendung von „unlimited“ anschließend uneingeschränkt wachsen kann. Hinzufügen einer weiteren Datei Ist beispielsweise auf dem aktuellen Datenträger kein Platz mehr, dann können Sie den Tablespace auch dadurch vergrößern, dass Sie ihm eine weitere Datei auf einem anderen Datenträger zuweisen. alter tablespace usr add datafile 'E:\ORANT\DATABASE\DB01\USR2DB01.ORA' size 50K autoextend on next 3K maxsize 200K; Listing 2.70: Vergrößern des Tablespace durch Hinzufügen einer neuen Datei
Dies passiert im Unterschied zu vorhin mit einer alter tablespace-Anweisung. Im Rahmen des Befehls können Sie zusammen mit der Klausel add dem Tablespace eine zusätzliche Datei zuweisen, wobei Sie mit Hilfe der schon bekannten Parametern die Anfangsgröße, sowie bei automatischem Wachstum auch die Schritt- und Maximalgröße der Datei vorgeben müssen.
Die Beispieldatenbank anlegen
223
Umzug der Datendateien Wird es insgesamt eng, dann kann vielleicht ein Umzug der einen oder anderen Datei helfen. Das geht allerdings nicht mit einem einzigen Befehl, sondern hierzu benötigen Sie mal wieder einen kleineren Ablauf, bei dem auch Betriebssystemaktivitäten notwendig werden. Die prinzipielle Vorgehensweise zum Umzug der zu einem Tablespace gehörenden Dateien ist folgender: 1. Inaktivieren Sie den betreffenden Tablespace. Verwenden Sie hierzu den Zusatz „normal“, damit alle noch laufenden Transaktionen ordentlich abgeschlossen werden. alter tablespace usr offline normal;
2. Kopieren bzw. verschieben Sie die betreffenden Dateien an die gewünschten neuen Stellen. Bei Bedarf können Sie die Dateien dabei auch umbenennen. In meinem Fall habe ich die Dateien einfach nur umbenannt, da ich eigentlich keinen Anlass für einen Umzug hatte. 3. Ändern Sie die Dateizuordnung in der Tablespacedefinition. Hierzu gibt es eine spezielle Variante des alter tablespace-Befehls. alter tablespace usr rename datafile 'E:\ORANT\DATABASE\DB01\USR1DB01.ORA', 'E:\ORANT\DATABASE\DB01\USR2DB01.ORA' to 'E:\ORANT\DATABASE\DB01\USR1DB01a.ORA', 'E:\ORANT\DATABASE\DB01\USR2DB01a.ORA';
Mit Hilfe dieser Variante wird die Liste der hinter den Schlüsselwörtern rename datafile spezifizierten Dateien gegen die hinter dem Wörtchen to aufgezählten Dateinamen ausgetauscht. 4. Machen Sie den Tablespace wieder verfügbar, indem Sie ihn in Analogie zum Schritt Nr. 2 wieder einschalten. alter tablespace usr online;
5. Denken Sie immer daran: Wer sichert, ist feige :-). Der letzte Punkt war natürlich nicht so ernst gemeint. Denken Sie bei solchen Aktionen immer daran, dass Sie quasi am offenen Herzen operieren. Ich würde mich ohne Netz und doppelten Boden an kein Trapez hängen, Sie etwa? Von daher würde ich die betroffenen Dateien immer kopieren (statt verschieben) und auch erst dann Löschen, wenn der Tablespace wieder online ist und funktioniert. Noch sicherer ist natürlich das vorherige Herunterfahren der gesamten Datenbank und das anschließend Backup aller Daten- und Kontrolldateien.
2.3.3
Anlegen des Schemas
Jetzt ist es endlich soweit und wir beginnen damit, die in Abbildung 2.19 dargestellte Struktur unserer kleinen Personaldatenbank anzulegen. Alle gleich folgen-
224
Datenbankobjekte in einer Oracle-DB
den kleinen Skripte finden Sie auf der Begleit-CD in dem \DATEN-Verzeichnis. Dabei entsprechen die Dateinamen den Namen der angelegten Tabellen zuzüglich der Endung .SQL (z.B. PERSONALIEN.SQL). Personalien In der Tabelle speichern wir später unsere Personalstammdaten. Wie Sie der Abbildung 2.19 entnehmen können, enthält sie eine Reihe von Fremdschlüsseln, deren Überwachung wir allerdings erst zu einem späteren Zeitpunkt mit Hilfe verschiedener Constraints implementieren werden. Die Tabelle erhält einen Primärschlüssel und einen expliziten Index für das Feld „name“. drop table personalien; / create table personalien ( persnr varchar2(11) not null, name varchar2(50) not null, strasse1 varchar2(35), strasse2 varchar2(35), strasse3 varchar2(35), strasse4 varchar2(35), ort varchar2(30), land varchar2(3), bland varchar2(6), plz varchar2(12), telefon varchar2(24), geschlecht varchar2(1) not null, familienstd varchar2(1) not null, familienstd_dt date, gebdatum date not null, gebort varchar2(30), gebland varchar2(3) not null, constraint persnr_pk primary key (persnr) using index tablespace indx storage (initial 100K next 100K) ) tablespace usr storage (initial 100K next 100K ); / create index personalien1 on personalien (name) tablespace indx storage (initial 100K next 100K) / commit; Listing 2.71: Anlage der Tabelle „personalien“
Die Beispieldatenbank anlegen
225
Länder Mit Hilfe der Ländertabelle werden die in den Personalien erfassten Länderschlüssel vertextet. Der Schalter im Feld bland gibt vor, ob es für das Land in der Tabelle blaender eine weitere Unterteilung (z.B. Bundesländer) gibt oder nicht. Für das Feld „bland“ sind nur die Werte „Y“ bzw. „N“ gültig, was mit Hilfe eines entsprechenden check-Constraints geprüft wird. Außerdem erhält die Tabelle wieder einen Primärschlüssel. drop table laender; / create table laender ( land varchar2(3) not null, bland varchar2(1) default 'N' not null , bezeichnung varchar2(30), constraint laender_pk primary key (land) using index tablespace indx storage (initial 10K next 10K), constraint bland_check check (bland in ('Y','N')) ) tablespace usr storage (initial 20K next 20K ); / commit; Listing 2.72: Anlage der Ländertabelle „laender“
Bundesländer Mit Hilfe der jetzt folgenden Tabelle kann eine weitergehende Unterteilung der Länder in Bundesländer oder Provinzen durchgeführt werden. Die Tabelle besitzt als ersten Schlüssel das Feld „land“, der wiederum ein geliehener Schlüssel aus der Ländertabelle ist. Der zweite Schlüssel kodiert das jeweilige Bundesland und wird in den Personalstammdaten erfasst. Dabei regelt der in der Ländertabelle vorhandene Schalter „bland“, ob Bundesländer verfügbar sind bzw. erfasst werden müssen. drop table blaender; / create table blaender ( land varchar2(3) not null, bland varchar2(6) not null, bezeichnung varchar2(30), constraint blaender_pk primary key (land, bland) using index tablespace indx storage (initial 10K
226
Datenbankobjekte in einer Oracle-DB
next 10K) ) tablespace usr storage (initial 20K next 20K ); / commit; Listing 2.73: Anlegen der Bundesländer in der Tabelle „blaender“
Beschäftigungsverhältnisse In unserer Personaldatenbank kann ein Mitarbeiter mehr als ein Beschäftigungsverhältnis haben. Solche unterschiedlichen BV’s können beispielsweise dadurch entstehen, dass ein Mitarbeiter im Laufe seines Lebens mehrfach in einem Unternehmen ein- bzw. austritt. Aber auch die Versetzung in ein anderes Konzernunternehmen führt zu einem neuen Beschäftigungsverhältnis. Diese BV’s hängen direkt an den Personalien und werden mit Hilfe einer laufenden Nummer fortlaufend durchnummeriert. Ansonsten erhält die Tabelle zunächst einmal zwei Constraints, mit denen der Primärschlüssel und eine Werteprüfung für das Feld „lfdnr“ erstellt wird. Beachten Sie auch noch einmal die vorhandenen Verknüpfungen zu den Tabellen „unternehmen“ und „austrittsgruende“ über die entsprechenden Datenfelder. Die Realisierung der hier notwendigen Maßnahmen zur Überwachung der referentiellen Integrität wird uns aufgrund der Historisierung der Referenztabellen im weiteren Verlauf des Buches noch Freude machen. drop table bvs; / create table bvs ( persnr varchar2(11) not null, lfdnr smallint not null, eintrt date not null, austrt date, ausgrd varchar(3), unternehmen varchar(3) not null, constraint lfdnr_check check (lfdnr >= 0), constraint bvs_pk primary key (persnr, lfdnr) using index tablespace indx storage (initial 10K next 10K) ) tablespace usr storage (initial 20K next 20K ); / commit; Listing 2.74: Anlage der Beschäftigungsverhältnisse
Die Beispieldatenbank anlegen
227
Unternehmen Mit Hilfe der folgenden Tabelle speichern und vertexten wir die verfügbaren Unternehmen. Dabei ist die Tabelle historisch aufgebaut, d.h. pro Unternehmen kann es mehrere Datensätze mit unterschiedlichen gültig-ab-Daten („gab“) geben. In dem Zusammenhang ist des Feld „status“ besonders beachtenswert. Selbst ohne echte Historien können Sie ein Unternehmen ab einem bestimmten Termin inaktivieren, indem Sie einen neuen Datensatz einfügen und anschließend im Statusfeld den Wert „I“ (=inaktiv) speichern. Der Wertebereich für das Statusfeld und der Primärschlüssel der Tabelle werden mit Hilfe entsprechender Constraints angelegt. drop table unternehmen; / create table unternehmen ( unternehmen varchar2(3) not null, gab date not null, status varchar2(1) default 'A' not null, bezeichnung varchar2(30) not null, constraint status_chk check (status in ('A','I')), constraint unternehmen_pk primary key (unternehmen, gab) using index tablespace indx storage (initial 10K next 10K ) ) tablespace usr storage (initial 20K next 20K ); / commit; Listing 2.75: Erstellen der Unternehmenstabelle
Kostenstellen Mit Hilfe der Kostenstellentabelle werden die Kostenstellen definiert, die selbst wiederum unternehmensbezogen sind, d.h. in unserem Datenmodell besitzt jedes Unternehmen seine eigenen Kostenstellen. Genau wie die Unternehmen, besitzen auch die Kostenstellen ein Historiendatum („gab“) und ein Statusfeld, mit dem die Verwendung einer nicht mehr gültigen Kostenstelle ausgeschlossen werden soll. Die Wertebereichsprüfung für den Status und der Primärschlüssel der Tabelle werden auch hier wieder mit Hilfe entsprechender Constraints realisiert. drop table kostenstelle; / create table kostenstelle ( unternehmen varchar2(3) not null, kst varchar2(10) not null, gab date not null,
228
Datenbankobjekte in einer Oracle-DB
status varchar2(1) default 'A' not null, bezeichnung varchar2(30) not null, constraint kst_status_chk check (status in ('A','I')), constraint kostenstelle_pk primary key (unternehmen, kst, gab) using index tablespace indx storage (initial 10K next 10K) ) tablespace usr storage (initial 20K next 20K); / commit; Listing 2.76: Anlage einer Tabelle zum Speichern der Kostenstellen
Gehälter Von Steuern und Sozialversicherungsabgaben wollen wir nichts wissen, aber wenigstens Gehalt sollen unsere fiktiven Mitarbeiter bekommen. Aus diesem Grund existiert in unserem Schema ein kleiner Gehaltsdatensatz, mit dessen Hilfe wir das Gehalt und die Kostenstelle des Mitarbeiters speichern können. Wie im richtigen Leben, so hoffen auch unsere virtuellen Mitarbeiter natürlich auf häufige und entsprechend üppige Gehaltsveränderungen, weshalb die Gehaltstabelle wieder historisch aufgebaut ist. Außerdem hängt sie natürlich nicht direkt an den Personalien, denn das Vorhandensein eines Beschäftigungsverhältnisses ist eine Voraussetzung für jegliche Gehaltszahlung. Ansonsten wird hier nur die Kostenstelle als Fremdschlüssel aus der Kostenstellentabelle entliehen. Neben der Erstellung des Primärschlüssels finden Sie hier zur Zeit nur zwei weitere Constraints, mit denen die Beträge der beiden Gehaltsfelder überprüft werden. drop table gehalt; / create table gehalt ( persnr varchar2(11) not null, lfdnr smallint not null, gab date not null, kst varchar2(10) not null, gehalt number(9,2) not null, zulage number(9,2) not null, constraint gehalt_ck check (gehalt >= 0), constraint zulage_ck check (zulage >= 0), constraint gehalt_pk primary key (persnr, lfdnr, gab) using index tablespace indx storage (initial 10K next 10K) ) tablespace usr
Die Beispieldatenbank anlegen
storage
229
(initial 30K next 30K );
/ commit; Listing 2.77: Erstellen der Gehaltstabelle
Austrittsgründe Damit kommen wir zunächst einmal zur letzten unserer Stammdatentabellen. Wie Sie wieder der Abbildung 2.19 entnehmen können, werden die Austrittsgründe aus den Beschäftigungsverhältnissen heraus referenziert. Die Historisierung der Tabelle wird auch diesmal wieder primär aus der Notwendigkeit getrieben, einen vorhandenen Schlüssel ab einem bestimmten Datum zu inaktivieren. Aus dem Grund finden Sie also auch hier wieder die beiden Felder „gab“ und „status“. Durch die vorhandenen Constraints wird zunächst wieder der Primärschlüssel gebildet und es erfolgt die Überwachung der im Statusfeld verwendeten Werte. drop table austrittsgruende; / create table austrittsgruende ( ausgrd varchar2(3) not null, gab date not null, status varchar2(1) default 'A' not null, bezeichnung varchar2(30) not null, constraint aus_status_chk check (status in ('A','I')), constraint austrittsgruende_pk primary key (ausgrd, gab) using index tablespace indx storage (initial 10K next 10K) ) tablespace usr storage (initial 10K next 10K); / commit; Listing 2.78: Anlegen einer Tabelle zum Speichern der Austrittsgründe
Nach Anlage dieser letzten Tabelle ist unser Schema zunächst einmal fertiggestellt. Zu einem späteren Zeitpunkt werden wir uns um die Einrichtung der referentiellen Integritätsüberwachung kümmern, d.h. am Ende des Kapitels zur PL/SQL-Programmierung finden Sie mögliche Verfahrensweisen hierzu.
230
2.4
Datenbankobjekte in einer Oracle-DB
Datenmigration
Wenn Sie schon einmal an einem größeren Umstellungsprojekt mitgearbeitet haben, dann wissen Sie, dass irgendwann der Zeitpunkt kommt, an dem man die grüne Entwicklungswiese verlassen muss und wo es darum geht, die Daten aus den Altanwendungen in das neue System zu übertragen. Da es dabei in der Praxis meistens schon um mehr als eine Handvoll von Datensätzen geht, ist es schon ein interessanter Aspekt, sich einmal anzuschauen, welche Hilfsmittel und Möglichkeiten Oracle hierbei bietet. Normalerweise werden die alten Daten nicht deckungsgleich in die neue Struktur passen, d.h. Sie haben es im Wesentlichen mit den drei folgenden Problemen zu tun:
X
X
X
Die Daten sind im neuen System anders strukturiert. Dabei gibt es nicht nur neue und andere Schlüsselbegriffe, sondern auch die Stammdaten werden im neuen System vielleicht anders gruppiert, bestimmte Informationen (z.B. Historien) werden anders dargestellt. Die benötigten Daten müssen üblicherweise zunächst aus den Altsystemen bereitgestellt bzw. exportiert werden, wobei wir einfach mal davon ausgehen, dass es sich hierbei nicht um eine Oracle-Datenbank handelt. Manchmal müssen alle benötigten Informationen sogar aus verschiedenen Quellen eingesammelt werden. Letztendlich existiert dann auch noch ein Importproblem, denn die aus den Altsystemen erhaltenen Daten kommen schließlich nicht von alleine in die neue Datenbank.
Gerade der zuerst genannte Punkt würzt das insgesamt vorhandene Problem ganz pikant, denn wir haben es hier nicht nur mit einer rein technischen Aufgabenstellung zu tun, sondern benötigen während der Migration eine Menge von Ablauflogik, um die gelesenen Daten umzuformatieren, zu mischen oder entsprechend der neuen Struktur zu speichern. Welche Möglichkeiten Sie in Bezug auf den Export der Daten haben, das hängt natürlich wesentlich von dem konkret vorliegenden Altsystem ab. In unserem Beispiel geht es auch weniger darum, wie die Daten aus dem Altsystem kommen, sondern es interessiert vornehmlich die Frage, wie diese Daten aussehen sollten, damit sie besonders einfach in eine Oracle-Datenbank importiert werden können. Insgesamt denke ich, erhalten Sie im weiteren Verlauf dieses Kapitels viele Informationen und Anregungen, auf welche Art und Weise Sie eine solche Migrationsaufgabe anpacken können.
2.4.1
Variationsmöglichkeiten
Es gibt bekanntlich viele Wege nach Rom. Alle diese Wege unterscheiden sich, allerdings nicht immer unbedingt in gut oder schlecht bzw. lang oder kurz. So gibt es auch hier mal wieder kein Patentrezept, kein schwarz oder weiß in Bezug auf den optimalen Lösungsweg Ihre Oracle-Datenbank mit Altdaten zu versorgen. Nach
Datenmigration
231
meiner Erfahrung, und die ist was Migrationsaufgaben angeht schon ganz beachtlich, schadet es nicht, wenn Sie in Ihrem Konzept über die nachfolgende Aspekte zumindest einmal nachdenken.
X
X
X
X
X
X
Artenreichtum lässt das Herz eines jeden Botanikers höher schlagen. Bei Migrationsaufgaben sollte man die Anzahl der Varianten eher gering halten. Wenn Sie hunderte von Tabellen migrieren und dabei für jede zweite Tabelle ein neues Verfahren erfinden, dann ist die Aufgabenstellung irgendwann nur noch von Ihnen selbst durchführbar und mich würde es nicht wundern, wenn in Folge der Hektik des Tages „x“ selbst Ihnen Fehler unterlaufen. Vermeiden Sie Mischformen. Wie schon angedeutet, benötigt man im Rahmen des Datentransports häufig eine gewisse Ablauflogik. Nach meiner Erfahrung hat es sich bewährt, wenn man sich dabei entscheidet, diese Logik entweder im Ziel oder in der Quelle zu realisieren und diese selbstauferlegte Restriktion auch weitestgehend einzuhalten. Als Konsequenz dieses Handelns ist eine der beiden beteiligten Schnittstellen dann meistens sehr einfach aufgebaut. Liegen die Daten beispielsweise aufgrund komplexer Exportprogramme genau in der benötigten Form vor, dann können Sie diese mit Hilfe von Standardtools in die Datenbank einspielen. Umgekehrt können Sie die Daten vielleicht mit Hilfe von vorhandenen Utilities exportieren, wenn Sie die benötigten Importfunktionalitäten im neuen System geplant haben (vgl. Abb. 2.20). Migration ist kein Einmalgeschäft. Sicherlich spielen die durchgeführten Importaktivitäten nach dem Projektende keine große Rolle mehr und trotzdem sollten Sie sich vom dem Gedanken des einmaligen Verwendungszwecks nicht zu allzu abenteuerlichen Verfahrensweisen, womöglich mit vielen manuellen Eingriffen, verleiten lassen. Meistens stellt sich während des Projekts nämlich heraus, dass die Migrationsaktiviäten zumindest bis zum Projektende aufgrund verschiedener Testdatenbanken, eventueller Vorabpilotierung bestimmter Anwendungsteile doch regelmäßiger als zunächst geplant eingesetzt werden. Berücksichtigen Sie von vornherein das zu erwartende Mengengerüst. Mit zehn Datensätzen klappt jedes denkbare Verfahren. Bei mehren Millionen Datensätzen sieht das ganz anders aus. Selbst bei ein paar tausend Datensätzen würde ich nach meiner Erfahrung davon abraten ein Verfahren zu konstruieren, das stark von der Existenz einer vorhandenen Netzwerkverbindung abhängt. Verlagern Sie das gesamte Migrationsgeschäft möglichst direkt auf die Server, d.h. verwenden oder schreiben Sie nur Programme, die direkt auf den Datenbankservern lauffähig sind. Ebenso sollten Sie von vornherein Mechanismen vorsehen, die es Ihnen ermöglichen, abgebrochene Migrationsjobs wieder aufzusetzen und das gesamte Übernahmeverfahren sollte möglichst modular aufgebaut werden, damit im Fehlerfall nicht immer nur alle sondern auch einzelne Tabellen neu aufgebaut werden können. Überlegen Sie sich, ob Sie den Datenimport mit oder ohne aktivierte Prüfroutinen, egal ob in der Form spezieller Constraints oder über Triggerprogramme, durchführen wollen. Ohne diese Prüfroutinen geht das Laden der Daten sicherlich schneller, allerdings sollten Sie in dem Fall auch schon gewissen Konsis-
232
Datenbankobjekte in einer Oracle-DB
tenzprüfungen in den Importprogrammen vorsehen. Manchmal hat man aber auch gar keine Wahl für die eine oder andere Variante, beispielsweise dann, wenn aufgrund komplexer Abhängigkeiten quasi alle Daten in einem logischen Schritt geladen werden müssten.
Quelle
Exportprogramm(e)
Exportdateien
Export-Utilitie
Import
Import-Utilitie Abfragen, SQL-Programme
Ziel
Abbildung 2.20: Gegenüberstellung zweier gängiger Migrationsvarianten
Die Abbildung 2.20 verdeutlicht den beschriebenen Zusammenhang der Mischformen noch einmal. Entsprechend der links dargestellten Verfahrensweise wurden die benötigten Daten mit Hilfe komplexer Exportprogramme gemäß der neuen Datenbankstruktur aufbereitet. Aus diesem Grund können die Exportdateien mit Hilfe eines Oracle-Ladetools direkt in die neuen Zieltabellen importiert werden, d.h. im Ziel findet keine Aufbereitungslogik der Daten mehr statt. Im Gegensatz dazu werden die Daten im rechten Teil der Abbildung 2.20 mit Hilfe eines Exporttools oder zumindest sehr einfacher Programme aus dem Quellsystem ausgegeben. Anschließend werden die Daten wieder mit Hilfe eines Oracle-Werkzeugs in entsprechende Arbeitstabellen geladen, von wo aus sie mit Hilfe entsprechender Abfragen oder PL/SQL-Programme in die jeweiligen Zieltabellen verteilt werden. Alternativ hierzu wären auch noch spezielle Importprogramme denkbar, die die einzelnen Importdateien verarbeiten und dabei die neuen Zieltabellen direkt beschreiben. Allerdings sollte man in dem Fall darauf achten, dass hierbei nur Programme eingesetzt werden, die direkt auf dem Datenbankserver lauffähig sind.
Datenmigration
233
Ich persönlich würde meistens den im rechten Teil der Abbildung skizzierten Weg bevorzugen. Das liegt zum einen schon an der von mir geforderten Einschränkung an Varianten, denn in der Praxis existiert in größeren Projekten mehr als eine Datenquelle mit einer Bandbreite von der Mainframe-IMS-Datenbank bis hin zum manuell erstelltem Excel-Spreadsheet. In dem Fall haben Sie eigentlich gar keine Chance, die Daten mit Hilfe der Exportprogramme fertig aufzubereiten. Zum anderen liegt es sicherlich auch an den Quellen selbst, die oft nur mit Hilfe schwerfälliger Programme oder anfälligen Makros abgefragt werden können. Sind die Daten aber erst einmal in der Oracle-Datenbank angekommen, dann steht Ihnen mit SQL bzw. PL/ SQL ein nahezu unerschöpfliches Repertoire an Möglichkeiten zur Verfügung.
2.4.2
Laden unserer Beispiel-DB
Nach diesen allgemeinen Vorbemerkungen wollen wir uns nun wieder unserer kleinen Personaldatenbank widmen und damit beginnen, sie mit Daten zu füllen. Dabei entspricht die von uns verwendete Methode dem linken Teil der Abbildung 2.20, d.h. die zu importierenden Daten liegen in einer dem Ziel entsprechenden Form vor, und obwohl ich vorhin gesagt habe, dass es so etwas im richtigen Leben eigentlich gar nicht gibt, so ist es für uns dennoch interessant, weil es uns hier vornehmlich darauf ankommt, zu schauen, wie die Daten mit Hilfe von Oracle-Tools oder anderen Methoden geladen werden können. Austrittsgründe Für kleine Tabellen kann es sinnvolle Variante sein, den Importvorgang mit Hilfe eines SQL-Skripts durchzuführen. In dem Fall müssen Sie die zu importierenden Daten in entsprechende insert-Anweisungen verpacken. Die Daten der Austrittsgründe finden Sie auf der Begleit-CD in der Datei \DATEN\AUSTRITTSGRUENDE.TXT und das Importskript heißt IMP_AUSTRITTSGRUENDE.SQL. Ursprünglich stammen Sie aus der vorhandenen Access-Datenbank und wurden mit Hilfe der dort vorhandenen Abfrage „export austrittsgruende“ als Datei mit fester Satzlänge exportiert. insert into austrittsgruende (ausgrd, gab, status, bezeichnung) to_date('01.01.1990','DD.MM.YYYY'),'A','Kündigung AN'); insert into austrittsgruende (ausgrd, gab, status, bezeichnung) to_date('01.01.1990','DD.MM.YYYY'),'A','Kündigung AG'); insert into austrittsgruende (ausgrd, gab, status, bezeichnung) to_date('01.01.1990','DD.MM.YYYY'),'A','Versetzung'); insert into austrittsgruende (ausgrd, gab, status, bezeichnung)
values ('55', values ('56', values ('60', values
Wir betten diese Textdatei in folgende Anweisungsfolge ein, mit der zum einen die Tabelle vorab gelöscht und am Ende die ganze Transaktion mit einem commitBefehl abgeschlossen wird. truncate table austrittsgruende; @c:\temp\austrittsgruende.txt; commit; Listing 2.79: Einspielen der Austrittsgründe (IMP_AUSTRITTSGRUENDE.SQL)
234
Datenbankobjekte in einer Oracle-DB
Wenn Sie sich nun fragen, warum ich das so kompliziert mache, und die truncatebzw. commit-Befehle nicht einfach mit in die Textdatei schreibe, dann kann ich darauf nur entgegnen, dass ich mich ja wenigstens in diesem Buch an meine eigenen Regeln halten will, denn durch diese Trennung wird der gesamte Prozess ohne manuelle Eingriffe wiederholbar. Einfach die Austrittsgründe neu exportieren und danach das Importskript erneut starten. Länder Die Ländertabelle importieren wir genau wie die Austrittsgründe. Die zugehörigen Daten finden Sie wieder in Form von insert-Anweisungen in der Datei \DATEN\LAENDER.TXT; das Importskript befindet sich in der Datei IMP_LAENDER.SQL. Die Datendatei wurde aus der Access-Datenbank mit Hilfe der Abfrage „export laender“ erstellt. insert insert insert insert insert insert
into into into into into into
laender laender laender laender laender laender
(land, (land, (land, (land, (land, (land,
bland, bland, bland, bland, bland, bland,
bezeichnung) bezeichnung) bezeichnung) bezeichnung) bezeichnung) bezeichnung)
values values values values values values
('DEU','Y','Deutschland'); ('AUT','Y','Österreich'); ('BEL','N','Belgien'); ('GBR','N','Grossbritannien'); ('FIN','N','Finnland'); ('FRA','N','Frankreich');
Die Datei können Sie folgendermaßen einspielen: truncate table laender; @c:\temp\laender.txt; commit; Listing 2.80: Einspielen der Ländertabelle (IMP_LAENDER.SQL)
Bundesländer Genau wie bisher gehen wir auch bei den Bundesländern vor. Die zum Importieren benötigten insert-Anweisungen und das Importskript finden Sie in den Dateien BLAENDER.TXT bzw. IMP_BLAENDER.SQL. Die Datendatei wurde aus der AccessDatenbank mit Hilfe der Abfrage „export blaender“ erstellt. insert len'); insert insert insert
into blaender (land, bland, bezeichnung) values ('DEU','NW','Nordrhein-Wesfahinto blaender (land, bland, bezeichnung) values ('DEU','BY','Bayern'); into blaender (land, bland, bezeichnung) values ('DEU','BE','Berlin'); into blaender (land, bland, bezeichnung) values ('DEU','HH','Hamburg');
Die Datei spielen Sie wieder mit Hilfe folgender Anweisungen in die Datenbank ein: truncate table blaender; @c:\temp\blaender.txt; commit; Listing 2.81: Importieren der Bundesländer (IMP_BLAENDER.SQL)
Datenmigration
235
Unternehmen Die nächste kleine Referenztabelle enthält die Unternehmen unserer Datenbank und wird mit Hilfe der Dateien UNTERNEHMEN.TXT bzw. IMP_UNTERNEHMEN.SQL importiert. Die Daten stammen ebenfalls aus der Access-Datenbank und wurden mit Hilfe der dortigen Abfrage „export unternehmen“ erstellt. insert into unternehmen (unternehmen, gab, status, bezeichnung) values to_date('01.01.1990','DD.MM.YYYY'),'A','Raymans EDV Beratung'); insert into unternehmen (unternehmen, gab, status, bezeichnung) values to_date('01.01.1998','DD.MM.YYYY'),'A','Raymans Medienberatung'); insert into unternehmen (unternehmen, gab, status, bezeichnung) values to_date('01.11.2001','DD.MM.YYYY'),'I','Raymans Medienberatung'); insert into unternehmen (unternehmen, gab, status, bezeichnung) values to_date('01.01.1992','DD.MM.YYYY'),'A','Raymans Consulting GmbH');
('001', ('002', ('002', ('001',
Das Einspielen der Unternehmensdaten erfolgt in der mittlerweile gewohnten Weise: truncate table unternehmen; @c:\temp\unternehmen.txt; commit; Listing 2.82: Einspielen der Unternehmen (IMP_UNTERNEHMEN.SQL)
Kostenstellen Die letzte unserer Referenztabellen enthält die Kostenstellen und da das ganze Verfahren mittlerweile langweilig wurde, habe ich hier einmal das Hinzufügen manuell erfasster Kostenstellen simuliert. Zum einen finden Sie daher die Datei KOSTENSTELLEN.TXT, deren Inhalt mit Hilfe der Abfrage „export kostenstellen“ aus der Access-Datenbank exportiert wurde. Zum anderen enthält Ihre CD aber noch eine zweite Datei mit dem treffenden Namen MANUELLE_KST.TXT, die eine selbsterfasste Kostenstelle enthält. Das zum Import verwendete Skript finden Sie in der Datei IMP_KOSTENSTELLEN.SQL. insert into kostenstelle (unternehmen, kst, gab, status, bezeichnung) values ('001','PSOFT', to_date('01.01.1992','DD.MM.YYYY'),'A','PeopleSoft-Beratung'); insert into kostenstelle (unternehmen, kst, gab, status, bezeichnung) values ('001','PERS', to_date('01.01.1990','DD.MM.YYYY'),'A','Personalabteilung'); insert into kostenstelle (unternehmen, kst, gab, status, bezeichnung) values ('002','MARK', to_date('01.01.1998','DD.MM.YYYY'),'A','Marketing');
Das folgende Skript zum Einspielen der Kostenstellen bindet ausnahmsweise einmal zwei Datendateien ein. truncate table kostenstellen; @c:\temp\kostenstellen.txt; @c:\temp\manuelle_kst.txt; commit; Listing 2.83: Einspielen der exportierten und manuell erfassten Kostenstellen
236
2.4.3
Datenbankobjekte in einer Oracle-DB
Der Data Manager
Eigentlich alle aktuellen Datenbanksystem liefern ein Werkzeug, um sequentielle Dateien (sogenannte „Flatfiles“) zu importieren. Der Vorteil dieser Programme besteht dabei im Unterschied zu allen anderen denkbaren Alternativen eigentlich immer in der rasanten Arbeitsgeschwindigkeit, mit der die importierten Daten in die Zieltabellen geschrieben werden. Bei Oracle ist so ein Werkzeug natürlich auch vorhanden. Es handelt sich hierbei um den sogenannten Data Manager, mit dem allerdings nicht nur die hier benötigten Importaufgaben erledigt werden können. Bei dem Data Manager handelt es sich um ein Programm, mit dem Sie zum einen einzelne Tabellen bis hin zu ganzen Datenbanken exportieren und auch wieder einspielen können. Damit eignet sich dieses Werkzeug beispielsweise zum Sichern einzelner Tabellen oder ermöglicht auch den Umzug einer ganzen Datenbank beispielsweise in eine neue Rechnerumgebung. Zum anderen können Sie das Programm auch zum Importieren von gewöhnlichen Textdateien verwenden. Ähnlich wie bei SQL*Plus besitzen auch die zum Data Manger gehörenden Programme die Eigenschaft, dass sie sowohl auf allen denkbaren Plattformen zu Hause sind, als auch, dass sie überall gleich bedient werden. Für den NT-Client bietet Oracle allerdings mal wieder eine Luxusversion an, und hat den drei genannten Funktionen (Export, Import, Laden) eine übliche Windows-Oberfläche übergestülpt. Bei der 8er-Version finden Sie das Programm-Icon für diese Oberfläche normalerweise in der Programmgruppe „Oracle Enterprise Manager“; ansonsten können Sie auch das zugehörige Programm VAD.EXE direkt aufrufen. Bei der 8i-Version erhalten Sie den Zugang zu dem Programm mit Hilfe des Werkzeuge-Menüs. Folgen Sie dort dem Eintrag „Datenverwaltung“ und anschließend dem Menü „Laden“, um zu den entsprechenden Ladeassistenten zu gelangen. Die wesentlichen Bestandteile des Programms finden Sie im Data-Menü. Dort verzweigen Sie mit Hilfe der Einträge Export, Import und Load in jeweils unterschiedliche Assistenten (vgl. Abb. 2.22), die alle notwendigen Eingaben und Parameter abfragen, um anschließend das eigentliche Programm zu starten oder einen entsprechenden Job innerhalb der Datenbank zu triggern. Letzteres funktioniert natürlich nur dann, wenn das zugehörige System zur Jobsteuerung auf Ihrem Datenbankserver aktiviert wurde. Wie schon gesagt, benötigen wir in unserem Fall zum Laden der übrigen Tabellen unserer Personaldatenbank die Ladefunktion (SQL*Loader) des Data Managers. Konkret verbirgt sich dahinter unter NT das Programm SQLLDR80.EXE bzw. SQLLDR.EXE bei der 8i-Version (bzw. einfach nur SQLLDR, z.B. unter AIX). In jedem Fall sollten Sie darauf achten, dass Sie das Programm auf dem Datenbankserver starten, damit seine rasante Arbeitsgeschwindigkeit voll zur Geltung kommt und nicht durch ein eventuelle lahmes Netzwerk ausgebremst wird.
Datenmigration
Abbildung 2.21: NT-Oberfläche des Data Managers in der 8er-Version
Abbildung 2.22: Assistent zum Abfragen der benötigten Ladeparameter
237
238
Datenbankobjekte in einer Oracle-DB
Starten des SQL*Loaders Das Programm erwartet beim Aufruf eine Reihe von Kommandozeilenparametern, die zum Teil allerdings auch in einer sogenannten Kontroll- bzw. Steuerungsdatei, die ebenfalls Teil der Kommandozeilenparameter ist, verwendet werden können. Bei dem Verwenden von Parametern müssen Sie entweder eine strikte Reihenfolge beachten, oder die einzelnen Parameterwerte mit Hilfe eines Schlüsselworts in der Form <Schlüssel>=<Wert> übergeben. Da ich es praktischer finde, die Steuerung des Ladeprogramms mit Hilfe einer Parameterdatei durchzuführen, finden Sie die Erläuterung der aus meiner Sicht wichtigsten drei Parameter in der folgenden Tabelle 2.5.
Parameter
Beschreibung
USERID
Übergeben Sie mit Hilfe dieses Schlüssels eine gültige Verbindungszeichenfolge in der üblichen Form /<Passwort>@<Servicename> (z.B.„system/ manager@db01“). Den Servicenamen müssen Sie natürlich weglassen, wenn Sie beim Laden die direkte Methode verwenden.
CONTROL
Verwenden Sie den Parameter, um mit ihm den Namen und Pfad der Steuerdatei zu spezifizieren. Auf den Aufbau dieser Steuerdatei werde ich später noch zurückkommen; kurz zusammengefasst enthält die Steuerdatei den Namen und den Aufbau der Importdatei.
DIRECT
Diesen Parameter müssen Sie nur zusammen mit dem Wert „TRUE“ verwenden, d.h. ansonsten können Sie ihn weglassen, so dass das Ladeprogramm von „FALSE“ ausgeht. Was beim direkten Laden genau passiert, das können Sie ein paar Zeilen weiter erfahren.
Tabelle 2.5: Tabelle 2.5: Wichtige Parameter für den Aufruf des SQL*Loaders
Direktes Laden Bezüglich der Option des direkten Ladens (DIRECT=TRUE) findet man in manchen Handbüchern schon manchmal mystisch anmutende Erklärungen. Ich will einmal versuchen das was eigentlich dahinter steckt, mit einfachen Worten auf den Punkt zu bringen. Bei dieser Methode umgeht Oracle im Prinzip seine internen Strukturen und schreibt die Daten quasi direkt in die zugehörige Tabelle der Datenbank. Eigentlich logisch, dass dies in einer unglaublichen Arbeitsgeschwindigkeit mündet. Aber auch das gibt es mal wieder nicht umsonst, sondern schlägt sich in folgenden Restriktionen nieder.
X
X
Keine Ausführung von in der Datenbank verankerter Logik. Konkret heißt das, dass konsitenzüberwachende Regelwerke, beispielsweise in der Form von definierten Constraints oder Triggern nicht beachtet bzw. ausgeführt werden. Die zu ladenden Daten müssen also in jeglicher Hinsicht in Ordnung sein oder die notwendigen Prüfungen müssen später in einem zweiten, separaten Schritt erfolgen. Die Methode funktioniert nur dann, wenn Sie bei der Ausführung des SQL*Loaders das NET8-Protokoll umgehen können. Letzteres ist in der Regel nur möglich, wenn das Programm direkt auf dem Datenbankserver ausgeführt wird.
Das direkte Laden eignet sich damit vor allem zum Laden von Arbeitstabellen zum Beispiel im Rahmen einer Schnittstelle oder beim erstmaligen Aufbau der Daten-
Datenmigration
239
bank. Weitere Informationen zum Gebrauch dieses Werkzeugs finden Sie im nächsten Kapitel, in dem wir mit dem Aufladen unserer Beispieldatenbank fortfahren und hierzu den SQL*Loader einsetzen. Ansonsten hält auch die Oracle-Dokumentation eine umfangreiche Beschreibung des Werkzeugs bereit. Folgen Sie einfach dem Link „Oracle8 Utilities“, um zur zugehörigen Dokumentation zu finden.
2.4.4
Laden der Stammdaten per SQL*Loader
Im letzten Abschnitt des zweiten Kapitels wollen wir nun das Aufladen unserer Personaldatenbank fortsetzen, indem wir die Stammdaten mit Hilfe des gerade vorgestellten Ladetools von Oracle importieren. Hierbei werden Sie verschiedene Verfahrensweisen kennen lernen, um auf einen eventuell unterschiedlichen Aufbau der Importdateien reagieren zu können. Feste Satzlänge Ein oftmals verwendetes Format bei der Bereitstellung von Exportdateien ist die Verwendung fester Satz- und Feldlängen. Gerade im Großrechnerbereich ist dieses Format häufig anzutreffen, da es in den dort üblichen Programmen so leicht zu verwenden ist. Für unsere kleine Datenbank wollen wir die Personalstammdaten mit Hilfe einer Datei mit fester Satzlänge importieren. Hierzu finden Sie auf der CD die Datei \DATEN\PERSONALIEN.TXT, die per Export aus der in der Access-Datenbank vorliegenden Tabelle gewonnen wurde.
Abbildung 2.23: Öffnen der Personalstamm-Textdatei mit Hilfe eines Editors
240
Datenbankobjekte in einer Oracle-DB
Wie Sie der Abbildung 2.23 entnehmen können, habe ich der exportierten Textdatei anschließend vier Zeilen vorangestellt, um mir das Zählen der Stellen zu vereinfachen. In den ersten drei Zeilen habe ich eine Art Lineal eingefügt und mit Hilfe der vierten Zeile haben ich die einzelnen Felder mit ihrer maximalen Länge angezeichnet. Diese vier Zeilen dürfen natürlich nicht importiert werden, was Sie im Rahmen der Steuerdatei für den Loader entsprechend einstellen können. Bei dieser Steuerdatei handelt es sich ebenfalls um eine gewöhnliche Textdatei, die Sie mit jedem beliebigen Editor erstellen können. Im Listing 2.84 finden Sie einen Abdruck der zum Laden der Personalien benötigten Steuerdatei, die Sie auf der CD ebenfalls im \DATEN-Verzeichnis unter dem Namen PERSONALIEN.CTL finden. -- Laden der Personalien options ( skip=4 ) load infile badfile discardfile discardmax truncate into table
'c:\temp\personalien.txt' 'c:\temp\personalien.bad' 'c:\temp\personalien.dsc' 999 ubeispiel.personalien
( persnr name strasse1 strasse2 strasse3 strasse4 ort land bland plz telefon geschlecht familienstd familienstd_dt gebdatum gebort gebland
position(01:11) position(12:61) position(62:96) position(97:131) position(132:166) position(167:201) position(202:231) position(232:234) position(235:240) position(241:252) position(253:276) position(277:277) position(278:278) position(279:288)
char, char, char, char, char, char, char, char, char, char, char, char, char, date "DD.MM.YYYY" nullif familienstd_dt=blanks, position(298:307) date "DD.MM.YYYY", position(317:346) char, position(347:349) char
) Listing 2.84: Steuerdatei zum Importieren der Personalien
Datenmigration
241
Wie Sie dem Listing 2.84 entnehmen können, erfolgt die Erfassung der für den Ladevorgang benötigten Steuerdatei in freier Form. In unserem konkreten Beispiel erstellen wir hierbei die beiden Abschnitte options und load. Mit Hilfe des ersten Abschnittes (options) können Sie verschiedene Parameter festlegen, die im Bedarfsfalle auch per Kommandozeilenparameter festgelegt werden können. Entnehmen Sie der Tabelle 2.6 die Bedeutung der wichtigsten Optionen. Option
Standardwert
Beschreibung
skip
0
Anzahl der Zeilen, die vom Loader aus der Datendatei überlesen werden.
load
Alle
Anzahl der zu importierenden Datensätze.
errors
50
Maximal erlaubte Fehler, bevor das Programm abbricht.
direct
FALSE
Schaltet bei Bedarf den direkten Ladevorgang ein.
Tabelle 2.6: Wichtige Optionen innerhalb der Steuerdatei des SQL*Loaders
Die einzelnen Optionen werden hinter dem Schlüsselwort options in Klammern einfach untereinander aufgezählt und für jede verwendete Option schreiben Sie den benötigten Wert mit Hilfe eines Gleichheitszeichens direkt hinter den jeweiligen Optionsschlüssel. Die Verwendung irgendwelcher Optionen in der Steuerdatei verhindert im Übrigen nicht deren Überschreibung mit Hilfe eines entsprechenden Kommandozeilenparameters. In unserem Beispiel würde also der folgende Programmaufruf sqlldr80 userid=ubeispiel ... skip = 20 sqlldr userid=ubeispiel ... skip = 20
zum Überlesen von 20 anstatt der standardmäßig eingestellten vier Datensätze führen. Der zweite und wichtigere Abschnitt load enthält alle benötigten Dateinamen und auch den Aufbau der Importdatei. Dabei folgen zunächst eine Reihe von Anweisungen, mit denen beispielsweise der Name und Pfad der Importdatei, die zu verwendende Datenbanktabelle und weitere gleich näher beschriebene Optionen festgelegt werden können. Anschließend folgt in Klammern der Aufbau der Importdatei, in dem bei einer festen Satzlänge alle Tabellenfelder den entsprechenden Satzpositionen zugeordnet werden. Parameter
Beschreibung
infile
Pfad und Name der Importdatei. Befinden sich die zu importierenden Daten ebenfalls in der Kontrolldatei, dann müssen Sie für den Parameter infile den Wert „*“ verwenden, wobei die zu importierenden Daten dem Schlüsselwort begindata folgen müssen.
badfile
Hier können Sie eine Datei spezifizieren, in die das Programm alle fehlerhaften Datensätze (z.B. Formatfehler) kopiert.
discardfile
Mit Hilfe dieses Parameters können Sie eine Datei vorgeben, in die das Programm alle Datensätze kopiert, deren Einfügeoperation beispielsweise wegen einer Regelverletzung abgewiesen wurde.
242
Datenbankobjekte in einer Oracle-DB
Parameter
Beschreibung
discardmax
Anzahl der zulässigen Abweisungen. Die Überschreitung führt zu einem Programmabbruch.
truncate
Verwenden Sie dieses Schlüsselwort, wenn die Tabelle vor dem Importieren gelöscht werden soll. Ohne diesen Parameter werden die importierten Daten an die Tabelle angefügt.
into table
Geben Sie hier den Namen der aufnehmenden Tabelle an.
Tabelle 2.7: Wichtige Ladeparameter
Nach Vorgabe aller benötigten Ladeparameter erfolgt die Definition der zu importierenden Daten. Bei einer festen Satzlänge müssen Sie hierbei jedem Tabellenfeld die entsprechende Satzposition zuweisen, was mit Hilfe des Schlüsselworts position passiert, hinter dem die Anfangs- und Endeposition im Datensatz in Klammern eingetragen wird. Danach folgt die Festlegung des Datentyps, wobei Sie vor allem bei Datumsfeldern das im Datensatz verwendete Format festlegen müssen. Beachten Sie hierbei auch die Klausel nullif familienstd_dt=blanks
die Ihnen ermöglicht, bei leeren Feldern in der Datenbank den Wert „null“ zu speichern. Damit hätten wir alle notwendigen Definitionen durchgeführt und es bleibt nur noch übrig, das Ladeprogramm zu starten und damit die Personalstammdaten zu importieren. Öffnen Sie hierzu ein MS-DOS-Fenster und stellen Sie die Umgebungsvariable oracle_sid auf die zu verwendende Datenbankinstanz ein. Anschließend können Sie den SQL*Loader starten, wobei Sie als Kommandozeilen wenigstens die Anmeldeinformationen und die zu verwendende Steuerdatei übergeben müssen. set oracle_sid=db01 sqlldr80 userid=ubeispiel/manager control=c:\temp\personalien.ctl direct=true log=c:\temp\personalien.log Listing 2.85: Starten des SQL*Loaders zum Importieren der Personalstammdaten
In dem Beispiel finden Sie als letzten Parameter einen Wert, der mit Hilfe des Schlüssels log übergeben wird, und mit dem Sie den Namen und das Ziel des erstellten Ladeprotokolls festlegen können. Als Alternative zur Vorgabe so vieler einzelner Kommandozeilenparameter können Sie diese auch in einer sogenannten Parameterdatei zusammenfassen und anschließend nur die Parameterdatei als Kommandozeilenparameter verwenden. set oracle_sid=db01 sqlldr80 parfile=personalien.par
Innerhalb dieser Parameterdatei können Sie anschließend alle benötigten Kommandozeilenparameter spezifizieren, was vor allem dann Sinn macht, wenn der Ladevorgang mehrfach oder sogar regelmäßig wiederholt wird.
Datenmigration
243
userid=ubeispiel/manger control= c:\temp\personalien.ctl direct=true log=c:\temp\personalien.log
Variable Satzlänge Dieses Format geht sparsamer mit dem für die Exportdatei benötigten Plattenplatz um, in dem für jedes Feld nur der benötigte Platz im Datensatz verwendet wird. Damit ein solcher Datensatz im Nachhinein wieder in die einzelnen Felder zerlegt werden kann, werden diese im Satz mit Hilfe eines speziellen Trennzeichens verbunden. Außerdem ist es üblich, Zeichenketten zusätzlich in Anführungszeichen zu setzen, da ja zumindest theoretisch die Möglichkeit besteht, dass das Trennzeichen selbst auch in der Zeichenkette auftaucht. "mit mir nicht; na ja"; 333; 23.11.1998
Datensätze mit variabler Satzlänge können heutzutage eigentlich mit jeder gängigen Standardsoftware erstellt und direkt mit Hilfe des SQL*Loaders geladen werden. In unserem Beispiel liegen die Gehaltsdaten (Datei: GEHALT.TXT) in diesem Format vor. Die Daten wurden aus der Access-Datenbank mit Hilfe der Exportspezifikation „Gehalt“ erstellt und haben folgenden Aufbau: "PERSNR";"LFDNR";"GAB";"KST";"GEHALT";"ZULAGE" "7000001";0;01.01.1990 00:00:00;"PERS";4500.00;500.00 "7000002";0;01.04.1998 00:00:00;"PERS";4750.00;300.00 "7000002";0;01.04.1999 00:00:00;"PERS";4980.00;290.00 "7000003";0;01.08.1999 00:00:00;"EDV";6500.00;0.00
Diesmal wurde die erste Datenzeile mit den Feldnamen beim Export automatisch hinzugefügt. Bei vielen Programmen können Sie beim Export explizit vorgeben, ob die erste Datenzeile die Feldnamen enthalten soll oder nicht. Genau wie beim Laden der Personalstammdaten benötigen wir natürlich wieder eine Steuerdatei, die für den SQL*Loader den Satzaufbau beschreibt. -- Laden der Gehälter options ( skip=1 ) load infile 'c:\temp\gehalt.txt' badfile 'c:\temp\gehalt.bad' discardfile 'c:\temp\gehalt.dsc' discardmax 999 truncate into table ubeispiel.gehalt fields terminated by ';' optionally enclosed by '"' (
244
persnr lfdnr gab kst gehalt zulage
Datenbankobjekte in einer Oracle-DB
position(*) position(*) position(*) position(*) position(*) position(*)
char, integer external, date "DD.MM.YYYY HH24:MI:SS", char, decimal external, decimal external
) Listing 2.86: Steuerdatei zum Importieren der variabel langen Gehaltsdatensätze
Die Steuerdatei finden Sie auf Ihrem Datenträger unter dem Namen GEHALT.CTL. Eigentlich hat sich im Unterschied zu vorhin gar nicht soviel geändert. Zunächst einmal überlesen wir mit Hilfe der skip-Klausel nur noch den ersten Datensatz, der die Feldnamen enthält. Des weiteren sind im load-Abschnitt zwei weitere Optionen hinzugekommen, mit denen wir über die Anweisungen fields terminated by das Feldtrennzeichen und optionally enclosed by das die Textfelder umschließende Sonderzeichen festlegen. Die Definition des Datensatzes erfolgt für die einzelnen Felder jetzt natürlich ohne eine feste Positionsangabe. Der als Platzhalter verwendete Stern führt innerhalb des Programms immer zur Verwendung des nächsten Feldes. Gestartet wird das Programm natürlich genau wie bei der festen Satzlänge, d.h. es wird lediglich eine andere Steuerdatei als Kommandozeilenparameter übergeben. set oracle_sid=db01 sqlldr80 userid=ubeispiel/manager control=c:\temp\gehalt.ctl direct=true log=c:\temp\gehalt.log Listing 2.87: Starten des Imports der Gehaltsdatensätze
Logische Datensätze Mit Hilfe der beiden vorhergehenden Abschnitte haben Sie sicherlich die wichtigsten Verwendungsformen des SQL*Loaders kennen gelernt. Allerdings fehlt uns noch eine Datei zur Vervollständigung unserer Personaldatenstruktur, und so habe ich mir für die noch fehlenden Beschäftigungsverhältnisse ein ganz besonders Format ausgedacht, das aber trotzdem direkt per SQL*Loader in die Datenbank importiert werden kann. 1:PERSNR LFDNR EINTRT 2:UNTERNEHMEN 3:AUSGRD AUSTRT 1:[23456] [23456789] 2:[0] 3:[][23456789] 1:7000002001.04.1998 00:00:00 2:001 3: 15.07.1990 00:00:00 1:7000003001.08.1999 00:00:00 2:001
Datenmigration
245
In dem abgedruckten Muster dienen die ersten sechs Zeilen wieder zur Dokumentation bzw. zum Anzeichnen der in den Sätzen enthaltenen Felder. Die eigentlichen Daten beginnen mit der Zeile sieben. Wie Sie dem Muster entnehmen können, bilden immer zwei bis drei Datensätze zusammen einen logischen Datensatz. Dabei werden die einzelnen Datensätze in der Datei mit Hilfe einer Satzart, die Sie auf den ersten beiden Stellen finden, unterschieden. Außerdem besteht die Möglichkeit, dass der letzte Datensatz einer Gruppe fehlt, weil die dort enthaltenden Daten (Austrittsgrund und Austrittsdatum) nicht vorhanden sind. Innerhalb der einzelnen Datensätze gilt wieder das Format der festen Feld bzw. Satzlänge, d.h. die Satzsätze vom Typ „1:“ sind beispielsweise alle gleich formatiert. Auch diese Datei finden Sie wieder auf der Begleit-CD zum Buch unter dem Namen \DATEN\BVS.TXT. Ebenfalls dort finden Sie auch die für den SQL*Loader benötigte Steuerdatei BVS.CTL. -- Laden der BV's options ( skip = 2 ) load infile 'c:\temp\bvs.txt' badfile 'c:\temp\bvs.bad' discardfile 'c:\temp\bvs.dsc' discardmax 999 truncate continueif next (1:2) <> '1:' into table ubeispiel.bvs ( persnr lfdnr eintrt unternehmen ausgrd austrt
position(1:7) position(8:8) position(9:27) position(28:30) position(31:32) position(33:51)
char, integer external, date "DD.MM.YYYY HH24:MI:SS", char, char, date "DD.MM.YYYY HH24:MI:SS"
) Listing 2.88: Steuerdatei zum Einspielen der Beschäftigungsverhältnisse
Betrachten Sie zunächst einmal den Parameter für den Wert skip und werfen Sie dabei auch noch mal ein Auge auf den Musterausdruck der Datendatei. An dem Beispiel erkennen Sie, dass die bisherige Erläuterung dieses Parameters nicht ganz richtig ist. Entweder Sie merken sich, dass mit Hilfe des skip-Parameters logische Datensätze überlesen werden oder sie interpretieren den Wert einfach als eine Anzahl von Schreibaussetzern, bevor das Programm mit dem Einfügen der neuen Datensätze beginnt.
246
Datenbankobjekte in einer Oracle-DB
Wirklich neu in unserer Steuerdatei ist die Anweisung continueif, mit deren Hilfe wir das Ladeprogramm anweisen, solange Sätze aus der Datei einzulesen und aneinanderzuhängen, bis an den ersten beiden Stellen die Satzart „1:“ auftaucht. continueif next (1:2) <> '1:'
Entsprechend sind auch die Positionierungen auf den gedachten, logischen Datensatz ausgerichtet. Dabei müssen Sie allerdings beachten, dass die Satzarten, also in unserem Beispiel jeweils die ersten beiden Stellen, nicht in dem logischen Datensatz enthalten sind. Das nachfolgende Beispiel verdeutlicht dieses Zusammenhang noch einmal, indem dort die ersten drei Sätze aus der Datei und der hieraus gewonnenen logischen Datensatz gezeigt werden. 1:7000002001.04.1998 00:00:00 2:001 3: 15.07.1990 00:00:00 1 2 3 4 5 6 123456789012345678901234567890123456789012345678901234567890 7000002001.04.1998 00:00:00001 15.07.1990 00:00:00
Auf die eigentliche Verwendung des Programms hat das Ganze natürlich keinen Einfluss, d.h. wir starten wieder das Programm SQLLDR80.EXE unter Verwendung der neuen Steuerdatei. set oracle_sid=db01 sqlldr80 userid=ubeispiel/manager control=c:\temp\bvs.ctl direct=true log=c:\temp\bvs.log
Varianten Neben den hier beschriebenen Möglichkeiten, bietet Ihnen der SQL*Loader noch eine Reihe von Varianten, deren konkrete Verwendung Sie bei Bedarf bzw. Interesse in der Oracle-Dokumentation nachlesen können. Von daher ist die jetzt folgende Aufzählung eher als Ideenfindung zu verstehen, eine Art Brainstorming also.
X
Anstatt nur einer Importdatei (infile) können Sie in der Steuerdatei auch mehrere zu importierende Dateien vorgeben, indem Sie einfach mehrere infile-Parameter verwenden. In dem Fall können Sie ebenfalls mehrer badfile- oder discardfile-Einstellungen vornehmen, um für die einzelnen Importdateien jeweils eigene Fehlerdateien zu erstellen. infile 'datei01' infile 'datei02'
X
badfile 'bad01' badfile 'bad02'
discardfile 'discf01' discardfile 'discf02'
Das Programm SQL*Loader ist in der Lage, eine eventuell notwendige Zeichenkonvertierung durchzuführen, beispielsweise weil Sie Daten importieren, die auf anderen Zeichensatztabellen basieren.
Datenmigration
X
X X
247
Anstelle der truncate-Anweisung, die vor dem Import zum Löschen aller eventuell vorhanden Daten führt, können Sie auch die Anweisungen insert oder append verwenden. Dabei setzt insert voraus, dass die Tabelle leer ist, d.h. das Ladeprogramm wird abgebrochen, falls dem nicht so ist. Dagegen führt die append-Anweisung immer zum Anfügen der neuen Sätze an die in der Tabelle vorhandenen Daten. Abgebrochene Ladevorgänge können wiederaufgesetzt werden. Hierzu existiert das spezielle Kommando continue_load, dass Sie in dem Fall anstatt des infile-Befehls verwenden müssen. Außerdem können Sie in dem Fall hinter dem insert into-Befehl den Aufsetzpunkt mit Hilfe eines skip-Kommandos festlegen. Ebenfalls besteht die Möglichkeit, mit einer Importdatei mehrere verschiedene Tabellen zu bedienen. In dem Fall müssen Sie zum einen mehrere into table-Befehle vorgeben und dabei mit Hilfe einer when-Klausel eine Bedingung spezifizieren, welche Datensätze in welche Tabelle zu importieren sind.
2.4.5
Wie geht es weiter?
Unsere kleine Personaldatenbank ist zumindest von seiner Struktur her weitestgehend fertiggestellt und die für die verschiedenen Tabellen vorhandenen Beispieldaten haben wir ebenfalls aufgeladen. Damit haben wir eine gute Ausgangsbasis geschaffen, um uns mit Hilfe des nächsten Kapitels verschiedene Abfragemöglichkeiten anzuschauen. Die endgültige Fertigstellung unserer Beispieldatenbank erfolgt, allerdings nur in Ansätzen, erst am Ende des Kapitels „PL/SQL-Programmierungen“. Dort finden Sie zunächst eine systematische Einführung in PL/SQL und im weiteren Verlauf des Kapitels werden wir die noch fehlenden Prüfungen anhand eines exemplarischen Beispiels erstellen bzw. programmieren. Abrechnungsergebnisse Im nächsten Schritt werden wir unsere Datenbank allerdings noch um eine Tabelle mit speziellen Ergebnisdaten erweitern. Diese Tabelle enthält monatliche Abrechnungsergebnisse, die mitarbeiterbezogen mit Hilfe einer Lohnart gespeichert werden. Dabei verzichten wir in unserem Beispiel auf eine zur Lohnart passenden Referenztabelle. Die Tabelle können Sie in Ihrer Datenbank mit Hilfe des folgenden Skripts anlegen, das Sie in der Datei \DATEN\LOHNART.SQL finden. drop table lohnarten; create table lohnarten ( persnr varchar2(11) not null, lfdnr smallint not null, gab date not null, la varchar2(3) not null, satzart varchar(2) default 'DM' not null, betr01 number(9,2) not null, betr02 number(9,2) not null,
248
betr03 betr04 betr05 betr06 betr07 betr08 betr09 betr10 betr11 betr12
Datenbankobjekte in einer Oracle-DB
number(9,2) number(9,2) number(9,2) number(9,2) number(9,2) number(9,2) number(9,2) number(9,2) number(9,2) number(9,2)
not not not not not not not not not not
null, null, null, null, null, null, null, null, null, null,
constraint satzart_check check (satzart in ('DM','TA','ST','LB')), constraint lohnarten_pk primary key (persnr, lfdnr, gab, la, satzart) ); commit; Listing 2.89: Speichern der Abrechnungsergebnisse
Eine besondere Aufmerksamkeit sollten Sie einmal dem Datenfeld „satzart“, das ebenfalls Teil des Primärschlüssels ist, schenken. Mit Hilfe dieser Satzart wird gesteuert, was in den einzelnen zur Lohnart gehörenden Betragsfeldern genau gespeichert ist. Standardmäßig in der Access-Datenbank sind die zugehörigen Daten in der Tabelle „ORCLADMIN_LOHNARTN“ gespeichert. Ich habe sie ähnlich wie die Gehaltsdaten als variabel lange Datensätze mit Verwendung eines Feldtrennzeichens exportiert, wobei Sie das Ergebnis auf Ihrer CD in der Datei \DATEN\LOHNARTEN.TXT finden. Zum Import verwenden wir wieder das Programm SQL*Loader. Eine für den Import geeignete Steuerdatei finden Sie im gleichen Verzeichnis unter dem Namen LOHNARTEN.CTL, so dass Sie die Tabelle nach dem Anlegen mit Daten füllen können.
3
Abfragen
In diesem Kapitel werden wir uns mit den verschiedenen Möglichkeiten beschäftigen, Abfragen an unsere Datenbank zu senden, d.h. wir beschäftigen uns zum ersten Mal etwas systematischer mit SQL (= Structured Query Language). Dabei werden wir uns im ersten Teil mit Auswahlabfragen beschäftigen. Im Anschluss daran lernen Sie die verschiedenen Änderungsabfragen kennen, was in der Erstellung von selbstprogrammierten Cursors zur Durchführung von Datenänderungen mündet. Das Ganze finden dann seinen Abschluss in einigen Ausführungen und Hinweisen zum Tuning bzw. der Ablaufkontrolle von Abfragen. Im letzten Teil dieses dritten Kapitels werden wir einen Abstecher ins Data Dictionary von Oracle unternehmen und uns dabei anschauen, welche Informationen die verschiednen Systemtabellen und Sichten für uns bereithalten. Dabei geht die von mir praktizierte Vorgehensweise auch in diesem Kapitel wieder davon aus, dass Sie zum einen schon gewisse Grundkenntnisse und Erfahrungen aus anderen Datenbanksystemen haben und zum anderen sich nicht scheuen, bestimmte weitergehende Informationen in der Oracle-Dokumentation nachzuschlagen. Diese finden Sie, indem Sie die hier beschriebenen Befehle in der „Oracle8 SQL-Referenz“ nachschlagen. Daneben befindet sich auch in dem Buch „Oracle8 Concepts“ ein Kapitel „SQL and PL/SQL“ mit einer zusammenhängenden Beschreibung des gesamten Themenkomplexes. Die in diesem Kapitel gezeigten kleineren Abfragebeispiele habe ich Ihnen auf Ihrer Begleit-CD in dem Verzeichnis \ABFRAGEN zusammengestellt. Für jedes größere Kapitel finden Sie hier eine eigene Datei (z.B. ABFRAGE31.SQL), in der ich die ganzen Beispiele aneinandergehängt habe.
3.1
Einfache Auswahlabfragen
Genau wie in allen anderen relationalen Datenbanksystemen erfolgt die Abfrage oder Änderung der gespeicherten Daten mit Hilfe von SQL. Damit sollte die Verwendung dieser Abfragesprache in allen relationalen Datenbanksystemen eigentlich gleich funktionieren, gäbe es da nicht die produktspezifischen Spracherweiterungen bzw. Verfahrensvarianten. Mit anderen Worten: ohne Transact-SQL, PL/SQL oder Jet-SQL wäre die Welt mal wieder übersichtlicher, aber sicherlich nicht einfacher, denn die einzelnen Spracherweiterungen wurden wohl kaum aus einer Bierlaune heraus entworfen, sondern sie entstanden im Laufe der Zeit, um Ihnen das Spektrum der Möglichkeiten zu vergrößern bzw. die Arbeit zu erleichtern; die Alternative, dass alle Hersteller das gleiche Süppchen kochen, dürfte wohl auch hier auf ewig eine Traumvorstellung bleiben.
250
Abfragen
In diesem Abschnitt möchte ich bei der Behandlung der Auswahlabfragen mit einem einfachen Beispiel beginnen und damit die grundsätzliche Struktur einer solchen Abfrage erläutern. Einfach heißt in diesem Fall, dass im Rahmen dieser Abfrage lediglich eine Tabelle gelesen wird. Schwierig, sofern man davon überhaupt reden kann, wird es nämlich frühestens dann, wenn mehrere Tabellen ins Spiel kommen, die dann im Rahmen der Abfrage sinnvoll miteinander verknüpft werden müssen, damit das gewünschte Ergebnis zustande kommt.
3.1.1
Struktur einer Auswahlabfrage
Im Prinzip besitzt eine Auswahlabfrage eine sehr einfache Struktur und besteht in ihrer Grundform aus gerade mal zwei bis vier Abschnitten. select from [where ] [order by <Sortierbedingungen>]
Der erste Abschnitt ist zwingend und wird mit Hilfe des Schlüsselwortes select eingeleitet. Diesem Schlüsselwort folgt eine Liste der gewünschten Datenfelder und Ausdrücke, d.h. im Rahmen einer solchen Auswahlabfrage können nicht nur Tabellenspalten, sondern auch komplexe Berechnungen durchgeführt werden. SQLWKS> select persnr, 2> substr(name,1,20) || '<', 3> 3*5-12, 4> sysdate 5> 6> from personalien; PERSNR ----------7000002 7000003 7000004 7000005
SUBSTR(NAME,1,20) 3*5-12 --------------------- ---------Karlo,Armin< 3 Heiden,Magareta< 3 Hardt,Andreas< 3 Nibelungen,Ellen< 3
SYSDATE -------------------21-AUG-00 21-AUG-00 21-AUG-00 21-AUG-00
Listing 3.1: Beispiel für eine einfache Auswahlabfrage
Wie Sie dem Beispiel entnehmen können, verwendet die Abfrage nur die beiden zwingenden Abschnittes select und from. Mit Hilfe der select-Klausel werden hierbei insgesamt vier Spalten gebildet, wobei nur die erste eine direkte Beziehung zu einer Tabellenspalte aufweist. Die zweite Spalte liest immerhin noch das Feld „name“, das allerdings im Rahmen eines Ausdrucks verwendet wird. Die anderen beiden mit Hilfe von Ausdrücken gebildeten Spalten haben überhaupt keinen Bezug zu irgendwelchen Werten aus der zugrundeliegenden Tabelle. Innerhalb eines solchen Ausdrucks können Sie im Prinzip das gesamte Repertoire
Einfache Auswahlabfragen
X X X X X
251
der in den mit Hilfe der from-Klausel spezifizierten Tabellen vorhandenen Spalten, aller vorhandenen Systemfunktionen, aller selbsterstellten Funktionen (Paket-, Member- und einfache Funktionen), der verfügbaren Pseudo-Spalten wie zum Beispiel sysdate oder rownum, der vorhandenen Operatoren (z.B. +, -, * usw.),
verwenden. Wichtig ist, dass der auf diese Weise zusammengebastelte Ausdruck vom System auswertbar ist und ein Ergebnis zurückliefert. Im Unterschied zu einigen anderen Datenbanksystemen können Sie in solchen Ausdrücken jedoch keine direkten Unterabfragen verwenden, d.h. das folgende Beispiel können Sie in Oracle so nicht erstellen. select a, b, c = (select sum(d) from xx2) from xx1
In solchen Fällen können Sie allerdings eine Funktion erstellen und die Unterabfrage in dieser Funktion verstecken. Die prinzipielle Vorgehensweise zum Erstellen solcher Funktionen haben Sie schon kennen gelernt und später im Kapitel „PL/ SQL-Programmierung“ werden wir auch ein Beispiel erstellen, wo wir innerhalb einer Funktion eine Auswahlabfrage verwenden. Mit Hilfe der from-Klausel erfolgt lediglich die Aufzählung der in der Abfrage verwendeten Tabellen. Ähnlich wie bei der select-Anweisungen werden die einzelnen Tabellen auch hierbei wieder mit Hilfe eines Kommas (,) getrennt. Verwenden von Aliasnamen Es schadet nicht, wenn Sie sich zumindest für die verwendeten Tabellen von vornherein angewöhnen, für jede der aufgezählten Tabellen einen Aliasnamen zu vergeben. Solche Aliasnamen vereinfachen vor allem bei Abfragen mit mehreren Tabellen eventuell mehrfach vorkommende Spaltennamen eindeutig zu identifizieren. Wie gesagt schadet es aber nicht, wenn Sie Technik derart verinnerlichen, dass Sie sie auch bei einfachen Abfragen anwenden. select a.persnr, substr(a.name,1,20) || '<', 3*5-12, sysdate from personalien a;
Ein solcher Tabellen-Aliasname wird meistens aus ein bis zwei Zeichen gebildet und innerhalb der from-Klausel direkt hinter dem Namen der Tabelle aufgeführt. Anschließend kann er an allen anderen Stellen der Abfrage zur eindeutigen Bestimmung der Spaltenherkunft verwendet werden, indem Sie den Aliasnamen der Tabelle zusammen mit einem Punkt dem Namen der Spalte voranstellen.
252
Abfragen
Natürlich können Sie eine Spalte auch ohne Aliasnamen eindeutig identifizieren, in dem Sie ihr den Namen der Tabelle voranstellen. Für meinen Geschmack ist das aber wesentlich umständlicher und artet in den allermeisten Fällen auch in wesentlich mehr Tipparbeit aus. select personalien.persnr, substr(personalien.name,1,20) || '<', 3*5-12, sysdate from personalien;
Auch für die in der select-Klausel aufgezählten Spalten können Sie Aliasnamen vergeben, die anschließend als Spaltenüberschrift verwendet werden. Das hat allerdings nicht nur kosmetische Gründe, wenn man einmal überliegt, wofür solche Auswahlabfragen üblicherweise verwendet werden. Die meisten Abfragen dieser Welt werden sicherlich im Rahmen eines Programms verwendet, das die gelieferten Daten am Bildschirm anzeigt oder irgendwie anders weiterverarbeitet. Im Rahmen eines solchen Programms bestehen meistens zwei Alternativen, die Werte der einzelnen Spalten abzufragen. Zum einen können die Werte mit Hilfe einer laufenden Nummer, die der in der select-Klausel verwendeten Reihenfolge entspricht, oder mit Hilfe der Spaltenüberschrift abgefragt werden. Betrachten Sie nun noch einmal das Ergebnis unseres ersten Beispiels (vgl. Listing 3.1). Die dort gezeigten Ergebnisse mit Hilfe der vorhandenen Überschriften abzufragen, macht sicherlich wenig Spaß. Aus diesem Grund können Sie jeder Spalte mit Hilfe des Schlüsselwörtchens as eine neue Überschrift zuweisen, mit der Sie den Wert anschließend innerhalb eines Programms abfragen können. SQLWKS> select a.persnr, 2> substr(a.name,1,20) || '<' as name, 3> 3*5-12 as wert, 4> sysdate as datum 5> 6> from personalien a; PERSNR ----------7000002 7000003 7000004 7000005
NAME WERT --------------------- ---------Karlo,Armin< 3 Heiden,Magareta< 3 Hardt,Andreas< 3 Nibelungen,Ellen< 3
DATUM -------------------21-AUG-00 21-AUG-00 21-AUG-00 21-AUG-00
Listing 3.2: Einfache Abfrage mit eigenen Überschriften und Tabellen-Aliasnamen
Ein solcher Spalten-Aliasname kann nahezu beliebig vergeben werden. Allerdings müssen Sie ihn in doppelten Anführungszeichen setzen, wenn er irgendwelche Sonderzeichen enthält, wobei auch schon ein Leerzeichen aus der Sicht von Oracle ein besonderes Zeichen darstellt. select a.persnr as "deine nummer",
Einfache Auswahlabfragen
253
Eindeutige Ergebnisse erzwingen Normalerweise sollten Sie Ihre Abfrage so gestalten, dass sie genau die von Ihnen gewünschten bzw. benötigten Ergebnisse liefern. Es gibt aber Situationen, da werden bestimmte Ergebniszeilen aufgrund der vorliegenden Datenkonstellationen mehrmals ausgeworfen. In dem Fall gibt es einfaches Mittel diese doppelten Ergebnisreihen zu vermeiden, in dem Sie hinter der select-Klausel als Erstes das Schlüsselwort distinct verwenden. SQLWKS> select plz from personalien; PLZ -----------62006 87770 ... 47239 79006 23 rows selected. Listing 3.3: Abfrage der Postleitzahlen ohne „distinct“
Bei der gewöhnlichen Abfrage (vgl. Listing 3.3) des Feldes Postleitzahlen „plz“ aus unserer Personalstammtabelle erhalten Sie alle 23 importierten Datensätze. Wenn Sie die gleiche Abfrage anschließend mit Hilfe des distinct-Parameters (vgl. Listing 3.4) verwenden, dann erhalten Sie dahingegen nur 18 Datensätze, d.h. fünf Datensätze wurden wegen gleicher Postleitzahlen aus der Ergebnismenge herausgestrichen. SQLWKS> select distinct plz from personalien; PLZ -----------10006 18055 ... 87770 90546 18 rows selected. Listing 3.4: Abfrage der Postleitzahlen unter Verwendung der Einschränkung „distinct“
Meiner Meinung nach sollten Sie die distinct-Klausel nur in Ausnahmefällen und wohlbedacht einsetzen. Zum einen führt sie, wie Sie später noch sehen werden, zu einem zusätzlichen Arbeitsschritt bei der Ausführung Ihrer SQL-Abfrage. Zum anderen kann sie dazu führen, dass bestimmte Fehler in Ihren Abfragen gar nicht zu Tage treten, da sie aufgrund der distinct-Anweisung verschleiert werden. Die folgende Anweisung soll diesen Sachverhalt verdeutlichen. Bei einer Abfrage hat sich vielleicht aufgrund eines Tipp- oder Kopierfehlers noch eine weitere Tabelle in der from-Klausel eingeschlichen.
254
Abfragen
select distinct a.persnr, a.name from personalien a, gehalt;
Trotzdem liefert die Abfrage wieder die vorhandenen 23 Personalstammsätze, wohingegen Sie ohne die distinct-Klausel in unserem Fall 1104 Datensätze (Anzahl Personalien * Anzahl Gehaltsdatensätze) erhalten würden. Nun könnte man meinen, dass ein solcher Fehler in echten Produktionsumgebungen mit entsprechend vielen Datensätzen sofort auffallen würden. Dem ist aber nicht unbedingt so, denn zum einen basieren Produktionsumgebungen unter Umständen auf extrem leistungsfähiger Hardware, so dass ein solcher Abfragepatzer nicht unbedingt zwingend zur Antwortszeiteskalation führt und zum anderen wird die Abfrage vielleicht innerhalb eines Batchprogramms verwendet und da fällt es vielleicht sowieso nicht auf, wenn das Programm statt möglicher fünf insgesamt 20 Minuten läuft.
3.1.2
Where-Bedingungen
Nun beschäftigen wir uns mit der ersten optionalen Klausel in einer Abfrage, mit deren Hilfe Sie die als Ergebnis bereitgestellten Datensätze durch Hinzufügen geeigneter Auswahlbedingungen einschränken können. Wie Sie später noch sehen werden, wird die where-Klausel jedoch nicht zur Programmierung echter Auswahlbedingungen verwendet, sondern mit ihrer Hilfe werden auch die Verknüpfungen durchgeführt, wenn mehrer Tabellen an der Abfrage beteiligt sind. Im Prinzip definieren Sie mit Hilfe der where-Bedingung einen beliebig komplexen Ausdruck, der insgesamt erfüllt sein muss, damit der zugehörige Datensatz im Rahmen der Abfrage ausgewählt wird. Im Prinzip entspricht der Aufbau einer einzelnen Abfragebedingung folgendem Schema: where
Bei den einzelnen Ausdrücken kann es sich um einfache Spalten, Konstanten oder wiederum um komplexe Gebilde handeln, die mit Hilfe von jeglicher Art von Funktionen, verschiedenen Spalten und Operatoren gebildet werden. Insgesamt ist dabei eigentlich nur wichtig, dass die beiden Ausdrücke zueinander passen, also vom gleichen oder zumindest ähnlichen Datentyp sind, und damit überhaupt vergleichbar werden. Zwischen den beiden Ausdrücken befindet sich der sogenannte Vergleichsoperator, der wie sein Name schon sagt, die Vergleichsbedingung der beiden Ausdrücke festlegt. Vergleichsoperatoren Ich habe die wichtigsten Vergleichsoperatoren einmal mit Hilfe nun folgenden Übersicht zusammengestellt, und die dort zu findenden Informationen für Sie wahrscheinlich nicht besonders neu bzw. aufregend sein dürften, habe ich mich bei deren Beschreibung auch bewusst kurz gefasst.
X
= Prüft die beiden Ausdrücke auf exakte Gleichheit
Einfache Auswahlabfragen
X
255
> (<) Der linke Ausdruck muss größer (kleiner) als der Rechte sein. Wie so etwas bei Zahlen- oder Datumswerten zu verstehen ist, dürfte eigentlich jedem klar sein, aber wann ist ein Text größer (kleiner) als ein anderer? Zwei Text bzw. Zeichenketten werden eigentlich in jeder Programmiersprache von links nach rechts verglichen. Dabei findet die Entscheidung statt, sobald die Zeichen an der aktuellen Vergleichsstelle n nicht mehr übereinstimmen, bzw. das Ende einer der beiden Zeichenketten vorzeitig erreicht wird. Wird ein solches vorzeitiges Ende erreicht, d.h. die eine Zeichenfolge stellt eine Verlängerung des anderen Textes dar, dann ist diejenige größer (kleiner), die länger (kürzer) ist, d.h. nur in dem Fall hat Größe auch etwas mit Länge zu tun. where 'Haha' > 'Hah'
Weichen die an Stelle n gefundenen Zeichen voneinander ab, dann regelt genau dieses Zeichen die Beziehung zwischen den beiden Zeichenfolgen. where 'Ho' > 'Hahahahahaha'
In meinem einfachen Beispiel weicht der zweite Buchstabe der beiden Zeichenketten voneinander ab. Da aufgrund der Definition des Zeichensatzes das Zeichen „o“ hinter dem Zeichen „a“ liegt (im ASCII-Zeichensatz steht „o“ auf Stelle 111 und „a“ auf Stelle 97), ist die linke Zeichenfolge in der Tat größer als die rechte, obwohl diese wesentlich länger ist. Im ASCII-Zeichensatz gilt für die gewöhnlichen druckbaren Zeichen die folgende Größenrelation: 0, 1, ..., A, B, ..., a, b. Das Ganze sieht im EBCDIC-Zeichensatz etwas anders aus, denn dort gilt die folgende Vergleichsregel: A, B, ..., a, b, ..., 0, 1. Welches Zeichen an welcher Stelle im Zeichensatz steht, können Sie im Zweifelsfall auch mit Hilfe der Standardfunktion ascii ermitteln, indem Sie der Funktion beispielsweise das zu untersuchende Zeichen als Argument übergeben. SQLWKS> select ascii('0'),ascii('a'), ascii('A') from dual; ASCII('0') ASCII('A') ASCII('A') ---------- ---------- ---------48 97 65 1 row selected.
X
Für diejenigen, die sich beim letzten Beispiel über die komische Tabelle dual wundern noch ein Hinweis: Im Unterschied zu manch anderen Datenbanken erlaubt Oracle keine Verwendung von select, ohne gleichzeitig auch irgendeine Tabelle zu spezifizieren. Aus diesem Grund existiert diese dual-Tabelle, die keine eigentlichen Werte, sondern nur einen einzigen Dummy-Datensatz enthält. >= (<=) Der linke Ausdruck muss größer (kleiner) oder exakt gleich dem rechten Ausdruck sein.
256
X X
X
X
Abfragen
<> (!=) Der linke und rechte Ausdruck müssen verschieden sein. Die beiden Operatoren sind gleichbedeutend, d.h. welchen Sie verwenden ist reine Geschmacksache. (not) like Der linke Ausdruck soll (nicht) so ähnlich wie der rechte sein. Was ähnlich dabei genau bedeuten soll, dass können Sie mit Hilfe von Platzhaltern im rechten Ausdruck festlegen. Ohne die Verwendung von Platzhaltern funktioniert der Operator like genau wie ein gewöhnliches Gleichheitszeichen. Als Platzhalter können Sie einen Unterstrich „_“ oder ein Prozentzeichen „%“ verwenden. Dabei stellt der Unterstrich ein Platzhalter für genau ein Zeichen dar, wohingegen das Prozentzeichen einen beliebig langen Platzhalter vorgibt. (not) in Der linke Ausdruck soll (nicht) in einer Liste enthalten sein, die Sie mit Hilfe des rechten Ausdrucks spezifizieren. Dabei ist die Abfragebedingung erfüllt, wenn der linke Teil keinem (not) bzw. einem einzigen Element aus der Liste entspricht. is (not) null Wie Sie aus der Anlage von Tabellen wissen, besteht durchaus die Möglichkeit, in einer Spalte nichts (null), d.h. keinen konkreten Wert zu speichern. Das Problem dabei ist allerdings, dass nichts eben mit nichts vergleichbar ist, d.h. der Komfort, bei der Anlage der Datensätze nichts eingeben zu müssen, mutiert beim Abfragen unter Umständen zu einem kleinen Problem. Gerade bei Datumswerten, wo diese null-Werte oftmals zulässig sind, kann sich daher häufiger der Fehlerteufel in Ihre Abfragen einschleichen. Die nachfolgende Abfrage liefert alle Mitarbeiter, deren Datum im Feld „Datum Familienstand“ kleiner als das aktuelle Tagesdatum ist. select a.persnr, a.name from personalien a where a.familienstd_dt < sysdate
Nichts (null) ist aber nicht kleiner als irgendetwas anderes und deshalb werden die Mitarbeiter ohne einen Wert in diesem Feld auch nicht selektiert. Um nun auch diese Mitarbeiter mit auszuwählen, hätten Sie im vorliegenden Beispiel zwei Möglichkeiten. Entweder Sie ergänzen die Abfragebedingung um ein weiteres Auswahlkriterium, das Sie mit Hilfe eines or-Operators verknüpfen where a.familienstd_dt < sysdate or a.familienstd_dt is null
oder Sie substituieren zur Laufzeit den null-Wert durch irgendeinen anderen Standardwert. Letzteres können Sie mit Hilfe der Funktion nvl durchführen. Diese Funktion erhält als ersten Parameter die zu überprüfende Spalte und als zweiten Parameter den zu verwendenden Ersatzwert, wenn die Spalte den Wert
Einfache Auswahlabfragen
257
null enthält. Als Rückgabe liefert die nvl-Funktion dann entweder den Wert der übergebenen Spalte oder den vorgegebenen Ersatzwert. where nvl(a.familienstd_dt, sysdate-1) < sysdate
In meinem Beispiel substituiere ich nicht gefüllte Datenfelder durch das Datum des Vortags (sysdate –1 ). Selbstverständlich können Sie stattdessen auch eine beliebige andere Datumskonstante oder auch eine andere Tabellenspalte verwenden. Aus meiner Sicht ist der Umgang mit nvl oftmals einfacher, als die Programmierung zusätzlicher is null-Bedingungen, die Sie ja vor allem auch richtig bzw. sinnvoll in die übrige where-Bedingung einbauen müssen.
X
Der Operator is null entspricht also nicht ganz dem oben abgebildeten Schema, da hierbei der zweite Vergleichsausdruck fehlt. Natürlich können wir das Ganze auch mit etwas Gewalt in unser Schema hineinpressen, indem wir so tun, als sei in Wirklichkeit lediglich das Wörtchen is der Operator, der als zweiten Ausdruck zufällig die Konstante null erwartet. (not) between A-1 and A-2 Auch dieser Fall passt nicht so ganz in unser bisheriges Schema. Hierbei wird der links stehende Ausdruck mit den hinter between spezifizierten Ausdrücken „A-1“ und „A-2“ verglichen, wobei diese als Intervall interpretiert werden. Dabei werden die linke bzw. rechte Grenze jeweils durch die Ausdrücke „A-1“ bzw. „A-2“ gebildet. Ist der links stehende Ausdruck (nicht) in dem Intervall enthalten, dann gilt die zughörige Auswahlbedingung als erfüllt. where substr(a.name,1,3) between 'Bec' and 'Keu'
In dem Beispiel müssen die ersten drei Stellen des Namens im Intervall zwischen „Bec“ und „Keu“ liegen, wobei die beiden Grenzen selbst noch zur Auswahl hinzugehören. Die Anweisung stellt also eine vereinfachende Sonderform für ein geschlossenes Intervall zur Verfügung, das Sie ansonsten auch folgendermaßen programmieren könnten: where substr(a.name,1,3) >= 'Bec' and substr(a.name,1,3) <= 'Keu'
Logische Verknüpfungsoperatoren Wie Sie dem letzten Beispiel entnehmen konnten aber wahrscheinlich auch schon längst wissen, gibt es neben den Vergleichsoperatoren auch noch weitere Verknüpfungsoperatoren, mit denen Sie die einzelnen Auswahlbedingungen zu einer komplexen Gesamtbedingung verbinden können. Allerdings ist die hierbei angebotene Auswahl an Möglichkeiten im Vergleich zu einigen anderen Datenbanksystemen oder Programmiersprachen doch eher bescheiden, denn im aktuellen Werkzeugkasten finden Sie nur die drei Operatoren und, oder bzw. nicht. Die Bedeutung dieser drei Operatoren habe ich mit Hilfe der Tabelle 3.1 kurz zusammengefasst.
258
Abfragen
Operator
Beschreibung
and
Logische und-Verknüpfung zweier Auswahlbedingungen. Das Ergebnis ist nur dann erfüllt (wahr), wenn beide Einzelbedingungen gleichzeitig erfüllt (wahr) sind.
or
Logische oder-Verknüpfung. Das Ergebnis dieser Verknüpfung ist nur dann falsch, wenn beide Einzelbedingungen gleichzeitig nicht erfüllt (falsch) sind.
not
Nicht-Operator. Führt zur Umkehrung des Wahrheitswertes einer Auswahlbedingung.
Tabelle 3.1: Beschreibung der logischen Verknüpfungsoperatoren
Außer diesen drei Operatoren können Sie noch in einem beliebigen Maße Klammern setzen, um die Reihenfolge der Verknüpfungen bzw. deren Auswertung zu beeinflussen. Beachten Sie, dass ohne die Verwendung irgendwelcher Klammern die and-Operatoren Vorrang vor den or-Operatoren haben. Noch vorrangiger wird der Operator not verarbeitet, der stets als Erstes ausgewertet wird. Ich hatte anfangs von Bescheidenheit gesprochen. Was ich damit meine ist, dass logische Operatoren zur Beschreibung einer Äquivalenz (eqv), Implikation (Folge; imp) oder dem exklusiven Oder (xor) fehlen. Allerdings habe ich diese drei Operatoren bisher nicht so häufig vermisst, da sie zum einen zumindest im kaufmännischen Bereich nicht so ganz so oft benötigt werden und zum anderen mit Hilfe der vorhandenen Operatoren, wenn auch etwas umständlicher, nachgebildet werden können. Verknüpfung
Beschreibung
Äquivalenz (<=>)
Nehmen wir zwei Einzelbedingungen A und B, dann spricht man A ist äquivalent zu B genau dann, wenn die Bedingungen A und B den gleichen Wahrheitswert liefern, d.h. entweder A und B sind beide erfüllt oder beide Bedingungen sind gleichzeitig nicht erfüllt. Genau in den beiden Fällen ist also auch die Äquivalenz-Verknüpfung erfüllt.
Implikation (=>)
Werden zwei Einzelbedingungen A und B mit Hilfe einer Implikation verknüpft, dann sagt man hierzu A impliziert B oder aus A folgt B. Hierbei gilt die gesamte Verknüpfung nur dann als nicht erfüllt, wenn A gilt und gleichzeitig B nicht erfüllt ist, d.h. wenn die Folge trotz erfüllter Voraussetzung nicht eintritt. Wie viel geschundenes Beispiel für diese Sachverhalt ist die Aussage „Wenn es regnet (A), dann wird die Straße nass (B)“. Diese Aussage gilt in allen Fällen, in denen der Behauptung nicht direkt widersprochen wird als erfüllt. Es ist trocken und die Straße ist nass; na und, vielleicht fährt gerade ein Reinigungsfahrzeug vorbei (wahr). Es regnet und die Straße wird nass; prima, genau dass wurde behauptet (wahr). Es regnet nicht und die Straße ist trocken; schön, ist aber eigentlich völlig egal (wahr). Es regnet, aber die Straße bleibt trocken; oho, das steht im direkten Widerspruch zur Aussage (falsch).
Einfache Auswahlabfragen
259
Verknüpfung
Beschreibung
Exklusives Oder (>-<)
Eine per exklusivem Oder verknüpfte Einzelbedingungen A und B gilt als erfüllt, wenn die beiden einzelnen Bedingungen einen unterschiedlichen Wahrheitswert aufweisen, d.h. wenn A erfüllt ist, dann muss B falsch sein und umgekehrt. Mit anderen Worten entspricht dieser Operatore der nicht erfüllten Äquivalenz. Entsprechend der Tabelle 3.3 gilt also A>-
Tabelle 3.2: Weitere logische Verknüpfungen
Ich habe die Nachbildung dieser drei logischen Verknüpfungen einmal mit Hilfe der drei folgenden Tabellen nachgestellt bzw. demonstriert. Die in den Tabellen auftauchenden Sonderzeichen für die Äquivalenz (<=>), Implikation (=>), Konjunktion (and-Verknüpfung, ∧), Disjunktion (or-Verknüpfung, ∨) oder die Negation (not-Verknüpfung) kommen Ihnen aus der Schulmathematik sicherlich noch bekannt vor. A
B
A <=> B
A∧B
A∨B
¬(A ∨ B)
A ∧ B ∨ ¬(A ∨ B)
w
w
w
w
w
f
w
w
f
f
f
w
f
f
f
w
f
f
w
f
f
f
f
w
f
f
w
w
Tabelle 3.3: Auflösung einer Äquivalenz A
B
A => B
¬A
¬A ∨ B
w
w
w
f
w
w
f
f
f
f
f
w
w
w
w
f
f
w
w
w
Tabelle 3.4: Auflösung einer Implikation A
B
A >-< B
(A ∧ B)
¬(A ∧ B)
A∨ B
¬(A ∧ B) ∧ (A ∨ B)
w
w
f
w
f
w
f
w
f
w
f
w
w
w
f
w
w
f
w
w
w
f
f
f
f
w
f
f
Tabelle 3.5: Auflösung einer exklusiven Oder-Verknüpfung
3.1.3
Ergebnisse sortieren
Wie Sie schon der ganz am Anfang dargestellten Struktur entnehmen konnten, besteht natürlich auch die Möglichkeit, die im Rahmen einer Abfrage selektierten
260
Abfragen
Datensatze sortiert zu empfangen. Hierzu können Sie die order by-Klausel verwenden, die - sofern vorhanden – den letzten Abschnitt einer Auswahlabfrage darstellt und mit deren Hilfe alle benötigten Sortierbegriffe durch Komma getrennt aufgezählt werden. Bei der Verwendung dieser Sortierklausel haben Sie im Prinzip folgende zwei Möglichkeiten. Zum einen können Sie als Sortierbegriff nahezu jede Spalte aus den an der Abfrage beteiligten Tabellen (from-Klausel) oder einen beliebigen gültigen Ausdruck verwenden. Wichtig dabei ist eigentlich nur, dass die Spalte oder der erstellte Ausdruck ein sinnvolles Sortierkriterium darstellt. select a.persnr, a.name from personalien a order by substr(a.name,1,5), a.persnr;
Zum anderen können Sie sich innerhalb der order by-Klausel auch direkt auf die in der select-Klausel aufgezählten Spalten beziehen, indem Sie statt eines Namens oder eines Ausdrucks die entsprechende Nummer der Spalte verwenden. Im nächsten Beispiel finden Sie hierfür ein Beispiel. Statt der Verwendung der Spalte „a.persnr“, wird innerhalb der Sortieranweisung einfach als zweites Sortierkriterium einfach Bezug auf die erste Spalte der select-Anweisung genommen. select a.persnr, a.name from personalien a order by substr(a.name,1,5), 1;
Als dritte Variante können Sie auch die bei der Spaltendefinition vergebenen Aliasnamen als Sortierkriterien verwenden, wobei die sogar wiederum in einen Ausdruck eingebaut werden können. Beachten Sie auch hierbei wieder die besondere Verwendung des für die erste Spalte vergebenen Alternativnamens, da dieser ein Sonderzeichen (Leerzeichen) enthält. select a.persnr as "unsere nummern", a.name dername from personalien a order by substr(dername,1,5) desc, "unsere nummern" asc;
Auf- bzw. absteigend Sortieren Standardmäßig sortiert Oracle die Daten immer aufsteigend entsprechend den vorgegebenen Sortierkriterien. Um die Daten absteigend zu sortieren müssen Sie dem Sortierkriterium das Schlüsselwort desc (descending = absteigend) anfügen. Wie schon gesagt, entspricht die aufsteigende Sortierung dem Standard, und trotzdem können Sie dies dokumentieren, indem Sie das Schlüsselwort asc (ascending = aufsteigend) verwenden. Im nächsten Beispiel wird die Abfrage entsprechend der ersten fünf Namenszeichen absteigend und nach Personalnummern aufsteigend sortiert. select a.persnr, a.name from personalien a order by substr(a.name,1,5) desc, 1 asc;
Einfache Auswahlabfragen
3.1.4
261
Gruppierungen
Oftmals besteht die Notwendigkeit, die aus einer Abfrage resultierenden Daten nicht einzeln anzuzeigen, sondern die Ergebnisse nach bestimmten Kriterien zusammenzufassen. Ein besonders geeignetes Demonstrationsobjekt hierfür finden Sie unserer Ergebnistabelle „lohnarten“, in der für jeden Mitarbeiter pro Jahr, Lohnart und Monat die während einer Gehaltsabrechnung entstandenen Abrechnungsergebnisse gespeichert werden. Bei den gruppierten Abfragen erweitert sich unser bisheriges select-Schema um genau zwei weitere Klauseln. select from [where ] group by having [order by <Sortierbedingungen>]
Die eine der beiden neuen Klauseln heißt group by und dient zur Vorgabe der gewünschten Gruppierbegriffe, die andere Klausel hat den Namen having und ermöglicht die Erstellung zusätzlicher Auswahlbedingungen, die allerdings erst auf der Gruppierungsebene angewendet werden. Die group by-Klausel Betrachten Sie einmal die Tabelle „lohnarten“, in der die Abrechnungsergebnisse eines Mitarbeiters pro Jahr und Lohnart gespeichert werden. Üblicherweise enthält diese Tabelle pro Jahr eine Reihe von verschiedenen Lohnarten, mit denen die einzelnen Gehaltsbestandteile aufgeschlüsselt werden. SQLWKS> select * from lohnarten 2> PERSNR LFDNR GAB LA SA BETR01 BETR02 BETR03 BETR04 BETR05 ------- -------- ----------- --- -- ------ ------ ------ ------ -----7000005 0 01-JAN-98 350 DM 0 0 0 0 2148 7000005 0 01-JAN-99 350 DM 0 0 0 0 1013.38 7000005 0 01-JAN-98 370 DM 0 0 0 0 0
Sicherlich werden die Daten manchmal in dieser detaillierten Form gebraucht, jedoch benötigt man bestimmte Informationen oftmals nur in verdichteter Form. Beispielsweise interessiert gerade mal wieder die Lohnsumme pro Lohnart für das Jahr 1999 und bevor Sie nun ein Programm mit einer entsprechenden Gruppenwechsellogik schreiben, können Sie die benötigten Informationen auch mit Hilfe einer gruppierenden Abfrage ermitteln. select a.la, sum(a.betr01) as dm01 from lohnarten a where to_char(gab,'YYYY')='1999' and satzart = 'DM' group by a.la;
262
Abfragen
Hierbei müssen Sie zunächst einmal die zu gruppierenden Felder mit Hilfe der group by-Klausel festlegen, so dass die Abfrage anschließend bei jedem Wechsel der hier aufgezählten Felder oder Ausdrücke einen internen Gruppenwechsel durchführt. In unserem Beispiel war das lediglich bei der Lohnart gewünscht, weshalb diesmal eine sehr einfache Gruppierungsklausel entsteht. Alle diese gruppierten Felder dürfen Sie ganz gewöhnlich in der select-Klausel verwenden, wohingegen Sie alle anderen Spalten nur noch zusammen mit einer sogenannten Aggregatfunktion benutzen können. Eine solche Aggregatfunktion (vgl. Tabelle 3.6) legt fest, was mit dem innerhalb der Funktion verwendeten Feld oder Ausdruck beim Gruppenwechsel passieren soll. Aggregatfunktion
Beschreibung
avg
berechnet für die übergebene Spalte bzw. den verwendeten Ausdruck den Durchschnitt für alle Werte der zugehörigen Gruppe. Diese Funktion kann nur zusammen mit numerischen Spalten bzw. Ausdrücken verwendet werden.
count
zählt die zur Gruppe zugehörigen Datensätze. Als Funktionsargument können sie irgendetwas verwenden, weshalb die Funktion meistens in der Form count(*), count(1) oder count('x') verwendet wird. Sofern Sie als Argument eine reguläre Spalte verwenden, dann zählt die Funktion die Anzahl der Werte, die ungleich „null“ sind.
max
liefert den größten Wert der verwendeten Spalte bzw. des übergebenen Ausdrucks. Wie Sie bei der Beschreibung der Vergleichsoperatoren innerhalb der where-Bedingung schon gesehen haben, ist der Begriff der Größe auf nahezu jeden gängigen Datentyp anwendbar, weshalb die max-Funktion auch ziemlich universell einsetzbar ist. Eine Abfrage der Form „select max(ort)“ liefert natürlich nicht im umgangssprachlichen Sinne die größte Stadt, sondern lediglich den Ort, für dessen Namen es im Rahmen eines gewöhnlichen größer-Vergleichs für Zeichenfolgen keinen passenden Datensatz mehr innerhalb der Gruppe gibt. Den letzten Teil des Satzes kann man natürlich auch etwas verständlicher beschreiben. Stellen Sie sich die Orte bzw. das entsprechend verwendete Feld innerhalb der Gruppe aufsteigend sortiert vor, dann liefert min den obersten und max den untersten Eintrag aus dieser Liste zurück.
min
Im Unterschied zu max liefert die Funktion min den kleinsten Wert der verwendeten Spalte bzw. des übergebenen Ausdrucks. Ansonsten gilt alles das für max Gesagte auch für die min-Funktion.
sum
berechnet für das übergebene Feld bzw. den verwendeten Ausdruck die Summe für alle zur Gruppe gehörenden Datensätze. Naheliegenderweise funktioniert diese Funktion wieder nur zusammen mit numerischen Feldern bzw. Ausdrücken. Werte mit dem besonderen Inhalt „null“ werden bei der Summenbildung nicht berücksichtigt.
stddev, varance
Diese beiden Funktionen, mit deren Hilfe Sie für die übergebenen Spalten die Standardabweichung oder Varianz berechnen können, erwähne ich hier eigentlich nur, weil die Liste der Aggregatfunktionen damit vollständig aufgezählt ist.
Tabelle 3.6: Übersicht der wichtigsten Aggregatfunktionen
Einfache Auswahlabfragen
263
Wenn man sich die Beschreibung der Aggregatfunktionen durchliest, dann erhält man den Eindruck, dass diese bei ihrer Verarbeitung ziemlich intelligent mit nullWerten umgehen, indem solche Werte einfach überlesen werden. Das ist prinzipiell auch richtig, aber dennoch besteht die Möglichkeit, dass eine Aggregatfunktion, von der count-Funktion einmal abgesehen, selbst den Wert „null“ als Ergebnis zurückliefert. Das passiert beispielsweise immer dann, wenn entweder gar keine Datensätze vorhanden sind oder alle Spalten der aktuellen Gruppe den Wert „null“ besitzen. Im Übrigen funktionieren die Aggregatfunktionen auch ohne die Verwendung der group by-Klausel, wobei die Funktionen in dem Fall die gesamte Ergebnismenge der Abfrage verarbeiten. Dies wollen wir einfach mal mit Hilfe der Personalientabelle ausprobieren und dabei das variable Verhalten der count-Funktion und die minund max-Funktionen zusammen mit Zeichenfolgen beobachten. SQLWKS> select count(*), count(familienstd_dt), min(ort), max(ort) 2> from personalien; COUNT(*) COUNT(FAMI MIN(ORT) MAX(ORT) ---------- ---------- ------------------------------ ----------23 10 Baden-Baden Wiesbaden 1 row selected. Listing 3.5: Verwendung der Aggregatfunktionen ohne „group by“
Beachten Sie die Abweichung der ersten beiden Spalten. In der ersten finden Sie in der Tat die Anzahl der selektierten Datensätze, wohingegen die zweite Spalte die Anzahl der Fälle zählt, in denen das Feld „Datum Familienstand“ einen regulären Wert enthält. Die having-Klausel Die having-Klausel wird gewöhnlich zusammen mit gruppierenden Abfragen verwendet und stellt eine Art zusätzlicher where-Bedingung auf Gruppenebene dar. Mit Hilfe dieser Klausel können Sie also auf Gruppenebene zusätzliche Auswahlbedingungen definieren, die anschließend regeln, ob die zugehörige Gruppe überhaupt in der Ergebnismenge enthalten ist. Kehren wir hierzu zunächst zu unserem Eingangsbeispiel des vorherigen Buchabschnitts zurück. Dort hatten wir die Januar-Lohnsummen für das Jahr 1999 pro Lohnart ausgewertet. SQLWKS> 2> 3> 4> 5>
select la, sum(betr01) from lohnarten where gab = to_date('01.01.1999','DD.MM.YYYY') and satzart = 'DM' group by la;
LA SUM(BETR01 --- ---------300 1054.73 301 0
264
Abfragen
302 0 305 0 315 26.14 316 0 350 0 352 0 354 0 360 0 372 0 383 0 480 153.39 660 0 680 0 15 rows selected.
Wie Sie der Auswertung entnehmen können, gibt es dabei eine Menge von Lohnarten, bei denen die Januarsumme den Wert 0 ergibt, wobei wir genau diese Ergebniszeilen im nächsten Schritt herausfiltern wollen. Mit Hilfe einer gewöhnlichen where-Bedingung ist das allerdings nicht möglich, denn die greifen jeweils auf der Ebene der einzelnen Datensätze und unsere Filterbedingung kann erst auf Gruppenebene durchgeführt werden. select la, sum(betr01) from lohnarten where gab = to_date('01.01.1999','DD.MM.YYYY') and satzart = 'DM' group by la having sum(betr01) <> 0;
Mit Hilfe der having-Klausel ist genau das möglich. In unserem Beispiel geben wir vor, dass die Gruppensumme des Feldes betr01 ungleich 0 sein muss, damit die zugehörige Datensatzgruppe in die Ergebnismenge der Abfrage übernommen wird. Im Großen und Ganzen unterscheidet sich die having-Klausel eigentlich nicht von gewöhnlichen where-Bedingungen, d.h. Sie können beispielsweise die gleichen Vergleichs- und Verknüpfungsoperatoren verwenden um komplexe Gruppenfilter zu erstellen. Das Einzige was Sie eigentlich beachten müssen ist, dass Sie hierbei alle Spalten, die nicht als Gruppierbegriff verwendet werden und dementsprechend nicht innerhalb der group by-Klausel aufgezählt wurden, nur zusammen mit den schon beschriebenen Aggregatfunktionen verwenden können. Zur Verdeutlichung der ganzen Zusammenhänge finden Sie im Folgenden noch einmal ein zugegebenerweise etwas an den Haaren herbeigezogenes Beispiel. Wir suchen pro Ort den kleinsten Namen, wobei uns nur Orte interessieren, in denen genau ein Mitarbeiter verheiratet ist, d.h. es soll pro Ortsgruppe genau einen Datensatz geben, bei dem das Feld „Datum Familienstand“ ausgefüllt ist. Zusätzlich soll die Gruppe nur dann selektiert werden, wenn der kleinste und größte Name der Gruppe bestimmten Kriterien genügt.
Einfache Auswahlabfragen
SQLWKS> 2> 3> 4> 5> 6>
select ort, min(name) from personalien group by ort having count(familienstd_dt) = 1 and ( min(name) like 'B%' or max(name) < 'S');
ORT -----------------------------Duisburg Engelstadt Eschborn Frankfurt Gimbsheim Rostock 6 rows selected.
3.1.5
265
MIN(NAME) -------------------Keun,Monika Heler,Sascha Hilsamer,Kurt Beckmann,Yonne Bruckner,Yonne Karlo,Ellen
Spalten vertexten
Manchmal möchte man gar nicht die direkt in der Tabelle gespeicherten Werte lesen, sondern diese übersetzen. Normalerweise wird dies mit Hilfe einer entsprechenden Referenztabelle durchgeführt, in der die zu einem Schlüssel gehörende Überseztung gespeichert wird. In unserer Beispielsdatenbank finden Sie hierfür einige Muster, beispielsweise werden die Länder mit Hilfe der entsprechenden Ländertabelle vertextet. Alternativ lassen sich Spalten aber auch während der Abfrage mit Hilfe der decodeFunktion umwandeln bzw. vertexten, was immer dann sinvoll sein kann, wenn die umzusetzenden Schlüssel keine besonders große Streuweite aufweisen. Ein Beispiel hierfür findet sich in der Spalte geschlecht bei den Personalstammdaten. select decode(geschlecht, 'M','männlich', '1','männlich', 'W','weiblich', '2','weiblich') from personalien;
Wie Sie dem Beispiel entnehmen können, wird das Geschlecht mit Hilfe der decodeFunktion in entsprechende konstante Texte umgesetzt, was aufgrund des eingeschränkten und vor allem konstanten Wertebereichs erträglich ist. Das von der decode-Funktion für einen Schlüssel gelieferte Ergebnis muss allerdings nicht unbedingt einen solch einfachen Aufbau haben, sondern kann selbst wieder das Ergebnis eines Ausdrucks unter Verwendung weiterer Spalten sein. select decode(geschlecht, 'M','Herr '1','Herr 'W','Frau '2','Frau from personalien;
' ' ' '
|| || || ||
name, name, name, name)
266
3.2
Abfragen
Verknüpfungen
Im nächsten Schritt wollen wir uns nun von den seichten zu etwas tieferen Gewässern begeben, was übertragen auf unsere Abfragen bedeutet, dass wir jetzt mehr als eine Tabelle zusammen in einer Selektion verwenden wollen. In dem Fall ändert sich die Struktur der eben beschriebenen Abfragen eigentlich kaum. Im Wesentlichen wird zunächst einmal die from-Klausel erweitert, indem Sie dort alle benötigten Tabellen aufzählen. Außerdem benötigen Sie in der Regel zusätzlich spezielle where-Bedingungen, mit denen die einzelnen Tabellen miteinander verknüpft werden. Im folgenden Beispiel werden die Tabellen Personalien und Gehälter gemeinsam ausgewertet und hierzu über das gemeinsame Feld der Personalnummer miteinander verknüpft. SQLWKS> select a.persnr, a.name, b.gab, b.gehalt 2> from personalien a, 3> gehalt b 4> where b.persnr = a.persnr; PERSNR ----------7000001 7000002 7000002 7000003 7000004 7000004 7000004 7000005
NAME --------------------------Frisch,Berta Karlo,Armin Karlo,Armin Heiden,Magareta Hardt,Andreas Hardt,Andreas Hardt,Andreas Nibelungen,Ellen
GAB GEHALT -------------------- ---------01-JAN-90 4500 01-APR-98 4750 01-APR-99 4980 01-AUG-99 6500 01-APR-98 7250 01-JUL-98 7250 01-APR-99 7850 01-APR-98 6200
Listing 3.6: Einfache Verknüpfung zweier Tabellen
Betrachtet man diese Abfrage bzw. ihr Ergebnis, dann entsteht auf den ersten Blick vielleicht der Eindruck, die where-Bedingung hätte plötzlich sowohl erweiternden als auch restriktiven Charakter. Bestimmte Mitarbeiter tauchen auf einmal mehrfach auf, wohingegen andere in der Abfrage gar nicht mehr erscheinen. Vom Ergebnis her betrachtet, könnte man glauben, aufgrund der Verknüpfung des gemeinsamen Feldes würden die beiden abgefragten Tabellen synchronisiert (vgl. Abb 3.1). Dabei werden Datensätze herausgestrichen, die nicht in beiden Tabellen vorkommen, wohingegen manchmal auch Datensätze vervielfältigt werden, wenn das gemeinsame Feld in der anderen Tabelle mehrfach vorkommt.
Verknüpfungen
267
select a.persnr, a.name, b.persnr, b.gab, b.gehalt from personalien a, gehalt b where b.persnr = a.persnr personalien a
gehalt b
7000001 7000002 7000002 7000003
Frisch,Berta Karlo,Armin Karlo,Armin Heiden,Magareta
7000005 7000005 7000005
Niebelungen, Ellen Niebelungen, Ellen Niebelungen, Ellen
7000001 7000002 7000002 7000003 7000004 7000005 7000005 7000005
01.01.1990, 4500 01.04.1998, 4750 01.04.1999, 4980 01.08.1999, 6500 01.08.1999, 2300 01.04.1998, 6200 01.04.1998, 6200 01.04.1998, 6200
Abbildung 3.1: Synchronisieren der beiden abgefragten Tabellen
Wenn überhaupt, dann ist diese Betrachtung bzw. Erklärung allerdings nur aus Sicht des Abfrageergebnisses her richtig, denn die Verfahrensweise zur Erreichung dieses Ziels ist eine ganz andere. Zunächst einmal werden die beiden Tabellen gemischt, wobei jeder Datensatz der einen mit jedem Datensatz der anderen Tabelle kombiniert wird. Hierdurch entsteht eine logische Tabelle, die zum einen alle in der select-Klausel verwendeten Felder enthält (vgl. Tabelle 3.7) und deren Datensatzanzahl zum anderen dem Produkt der Datensätze aus den verknüpften Tabellen entspricht. a.persnr
a.name
b.persnr
b.gab
b.gehalt
7000002
Frisch, Berta
7000002
01.04.1998
4750
7000002
Frisch, Berta
7000002
01.04.1999
4980
7000002
Frisch, Berta
7000004
01.08.1999
2300
7000002
Frisch, Berta
7000005
01.04.1998
6200
...
...
7000003
Heiden, Margareta
7000002
01.04.1998
4750
7000003
Heiden, Margareta
7000002
01.04.1999
4980
7000003
Heiden, Margareta
7000003
01.08.1999
6500
...
...
Tabelle 3.7: Erstellen eines kartesischen Produkts der verknüpften Tabellen
In diesem sogenannten kartesischen Produkt wird dann die spezifizierte whereBedingung ausgeführt, wobei es sich jetzt ja eigentlich wieder um eine gewöhnliche Auswahlbedingung handelt, mit der zwei Spalten der logischen Tabelle miteinander verglichen werden. In unserem Beispiel sollen die Spalten a.persnr und b.persnr gleich sein, was dabei herauskommt, dass können Sie der Tabelle 3.7 ent-
268
Abfragen
nehmen, wobei die fettgedruckten Zeilen die ausgewählten Datensätze darstellen sollen. Also handelt es sich bei der where-Bedingung einer solchen verknüpfenden Abfrage doch nicht um ein wundersames Instrument, dass zu einer Vermehrung von Datensätzen führen kann, sondern vielmehr ist es die einzige Waffe, mit der Sie das extreme Populationsverhalten der in der from-Klausel aufgezählten Tabellen einschränken können. Betrachten wir nun noch einmal die Erstellung des im Zusammenhang mit der Tabelle 3.7 beschriebenen kartesischen Produkts. Dieses wird in Wirklichkeit natürlich möglichst niemals gebildet, denn ansonsten würden solche Abfragen in großen Datenbanken niemals zu einem Ende kommen. Stellen Sie sich einmal die folgende Aufgabe vor: Sie sollen aus zwei Büchern die Seiten mit einem bestimmten Begriff zusammenlegen. Würden Sie hierbei zunächst alle zueinander passenden Seiten beider Bücher nebeneinander legen, um anschließend doch nur eine handvoll Seiten herauszufiltern, in denen der gesuchte Begriff überhaupt vorkommt ? Wohl kaum, vielmehr, so denke ich, würden Sie entweder zunächst in beiden Büchern getrennt mit Hilfe des Index die gesuchten Seiten heraussuchen und diese anschließend zusammenmischen. Oder Sie würden zunächst nur die Seiten eines Buches mit Hilfe des Index durchsuchen, dann aber bei jeder gefundenen Seite sofort die passenden Seiten des zweiten Buches heraussuchen und dazulegen. So pragmatisch dieses Beispiel auch klingen mag, das Datenbanksystem arbeitet in einer ähnlichen Weise, wobei in der Tat beide Verfahrensweisen zum Tragen kommen können. Das in der Tabelle 3.7 angedeutete kartesische Produkt ist eigentlich nur Teil des mathematischen Modells, das den relationalen Datenbank zugrunde liegt. In der Regel versucht das Datenbanksystem dessen Erzeugung zu umgehen, da ansonsten bei großen Tabellen der Abfragegau droht („die Session hängt mal wieder“). In unserem Beispiel könnte die Datenbank vielleicht wirklich zunächst eine der beiden Tabellen (z.B. Personalien) lesen, um anschließend für jede Personalnummer die passenden Datensätze aus den Gehaltsdaten zu der logischen Tabelle hinzuzufügen. Das dies wirklich so passiert und wie man das Ganze vielleicht sogar steuern kann, dazu erhalten Sie allerdings erst später im Abschnitt „Tuning von Abfragen“ mehr Informationen. Im nächsten Schritt wollen wir die verschiedenen möglichen Verknüpfungsformen zunächst einmal katalogisieren und dabei auch deren Verwendung mit Hilfe geeigneter Beispiele erklären.
3.2.1
Inner-Joins
Ich habe lange über die Überschrift dieses Abschnittes nachgedacht. Das was ich im Folgenden beschreiben möchte und die sicherlich meistverwendete Verknüpfung darstellt, wird selbst in der deutschsprachigen Literatur zunächst einmal als Join (engl. = verbinden, vereinigen) bezeichnet, wobei dieses Wörtchen allerdings dann auch wieder schon das einzige ist, was die verschiedenen Überschriften gemeinsam haben. Konkret ist da von Inner-Joins, Equi-Joins, Theta-Joins, Natural-Joins oder
Verknüpfungen
269
Semi-Joins die Rede, wobei sich hinter diesen Begriffen eigentlich immer das gleiche oder zumindest etwas sehr Ähnliches verbirgt. Wie Sie sehen, habe ich mich letztendlich für die Überschrift „Inner-Joins“ entschieden, weil sie letztendlich wenigstens zur Überschrift des übernächsten Kapitels „Outer-Joins“ passt, für die mir beim besten Willen nichts Besseres eingefallen ist. Zunächst einmal wird bei einem Join innerhalb einer Abfrage mehr als eine Tabelle in der from-Klausel verwendet und zum anderen werden die zusammengehörenden Felder in der where-Bedingung der Abfrage mit einem Gleichheitszeichen verbunden. Ich möchte es im Folgenden auch bei dieser groben Aufteilung belassen und nicht auf weitere Feinheiten eingehen, die sich vor allem aus der Betrachtung des Abfrageergebnisses her ergeben. Uns ist es also egal, ob im Abfrageergebnis nur Spalten aus einer der beteiligten Tabellen verwendet werden oder ob die Verknüpfung zu einer logischen Verlängerung der anderen Tabelle führt. In jedem Fall werden, wie schon in der Einleitung zu diesem Thema erläutert, mehrere Tabellen in der from-Klausel der Abfrage aufgezählt und anschließend erfolgt im Rahmen der where-Klausel die voll- oder teilweise Verbindung der gemeinsamen Felder. In unserem folgenden Beispiel verbinden wir die Stammdatentabellen Personalien, Beschäftignungsverhältnisse und Gehälter zu einem logischen Datensatz, aus dem wir verschiedene Informationen benötigen. select a.persnr, substr(a.name,1,10), b.lfdnr, c.gab, c.gehalt from gehalt c, bvs b, personalien a where b.persnr = a.persnr and c.persnr = b.persnr and c.lfdnr = b.lfdnr
Wie Sie schon an dem kleinen Beispiel erkennen können, entstehen durch die Verknüpfung von Tabellen rasch komplexe where-Bedingungen, die wenn Sie unvollständig bzw. fehlerhaft kodiert werden zu umfangreichen und vor allem falschen Abfrageergebnissen führen. Aus diesem Grund ist man meiner Ansicht nach gut beraten, wenn man beim Erstellen solcher Abfragen auch eine gewissen gestalterische Komponente berücksichtig. Betrachten Sie im Vergleich doch einfach mal das folgende Beispiel, dass technisch gesehen bzw. vom Ergebnis her betrachtet der eben gezeigten Abfrage entspricht. select a.persnr, substr(a.name,1,10), b.lfdnr, c.gab, c.gehalt from gehalt c, personalien a, bvs b where b.lfdnr = c.lfdnr and a.persnr = b.persnr and c.persnr = b.persnr
In einem solchen Dickicht kann man durchaus auch mal eine Verknüpfung vergessen oder falsch verbinden. Müsste ich in einer solchen Abfrage nach Fehlern suchen oder sie erweitern, dann bestünde meine erste Aktion darin, diese zu entflechten. Ich habe mir in den letzten Jahren folgende stilistische Vorgehensweise angewöhnt:
270
X X X
Abfragen
Verwenden Sie innerhalb der from-Klausel für jede Tabelle eine eigene Zeile und vergeben Sie für jede Tabelle einen Aliasnamen, den Sie anschließend bei allen verwendeten Feldnamen konsequent verwenden. Erstellen Sie ebenfalls für jede where-Bedingung eine eigene Zeile. Hierbei habe ich mir ebenfalls angewöhnt, die logischen Verknüpfungsoperatoren an den Anfang der Zeile zu schreiben und die ganze Struktur bei notwendigen Schachtelungen entsprechend einzurücken. Bemühen Sie sich, die Verknüpfungsbedingungen der einzelnen Tabellen am Stück bzw. aneinanderhängend zu kodieren.
Nur selten ist es Absicht, für die verwendeten Tabellen nur unvollständige oder sogar gar keine Verbindungsbedingungen zu programmieren, um damit den Vervielfältigungseffekt des kartesischen Produkts zu erhalten. In jedem Fall können Sie beim erstmaligen Test Ihrer Abfrage eine zusätzliche Bedingung hinzufügen, um die maximale Größe der Ergebnismenge einzuschränken. and rownum < 100
Mit Hilfe der Pseudospalte rownum werden die Zeilen der Ergebnismenge durchgezählt, wobei Sie diese Spalte auch innerhalb einer where-Bedingung verwenden können, um die Anzahl der von der Abfrage gelieferten Datensätze einzuschränken. Das folgende Beispiel würde, wenn wir statt count irgendwelche konkreten Spalten in der select-Anweisung verwenden würden, genau 37536 Ausgabesätze erzeugen und somit schon etwas Geduld von uns abverlangen. SQLWKS> select count(*) 2> from personalien a, 3> bvs b, 4> gehalt c; COUNT(*) ---------37536 1 row selected.
Dagegen wird die Datensatzmenge im Folgenden durch den Einsatz des Feldes rownum so oder so auf 99 begrenzt. SQLWKS> select count(*) 2> from personalien a, 3> bvs b, 4> gehalt c 5> where rownum < 100; COUNT(*) ---------99 1 row selected.
Verknüpfungen
3.2.2
271
Unterabfragen
Unterabfragen sind eine spezielle Form der Tabellenverknüpfung, bei der im Rahmen einer Auswahlbedingung innerhalb der where-Bedingung Daten aus einer anderen Tabelle abgefragt werden. Hierbei gibt es im Prinzip zwei Varianten. Bei der einen Variante wird mit Hilfe der Unterabfrage lediglich die Existenz eines bestimmten Datensatzes ermittelt, wohingegen bei der anderen Variante ein konkretes Ergebnis abgerufen wird, das beispielsweise innerhalb der Auswahlbedingung mit einem Wert oder anderem Feld verglichen wird. Existenzprüfung Nehmen wir einmal an, wir stünden folgender Aufgabenstellung gegenüber: Wir sollen die Namen aller Mitarbeiter ermitteln, die im Jahr 1998 die Lohnarten 300 bis 350, erhalten haben. Dabei interessieren wirklich nur die Namen, d.h. die konkreten Beträge oder Auszahlungsmonate interessieren nicht. Ebenfalls ist nicht wichtig, in welchem Beschäftigungsverhältnis die Lohnarten gezahlt wurden. Mit den bisher beschriebenen Methoden könnten Sie diese Aufgabenstellung folgendermaßen lösen. SQLWKS> 2> 3> 4> 5> 6> 7>
select distinct a.name from personalien a, lohnarten b where b.persnr = a.persnr and b.gab = to_date('01.01.1998','DD.MM.YYYY') and b.la between '300' and '350' order by a.name;
NAME -------------------------------------------------Beckmann,Yonne Bruckner,Yonne Bätzing,Bärbel Calvo,Ulrike ... Sistermann,Willy Voltair,Samuel Zola,Wolf 16 rows selected.
In dieser Abfrage werden die Tabellen „personalien“ und „lohnarten“ über das gemeinsame Feld „persnr“ miteinander verknüpft. Außerdem wird der Zugriff auf die Abrechnungsergebnisse durch zwei weitere Auswahlbedingungen auf die gewünschten Lohnarten und das benötigte Jahr eingeschränkt. Das Problem bei dieser Selektion ist aber, dass meistens nicht nur eine der gesuchten Lohnarten, sondern oft mehrere oder sogar alle Lohnarten für einen Mitarbeiter in den Abrechnungsergebnissen vorhanden sind. Aus diesem Grund würde jeder Name mehrfach gelistet werden, wenn wir die Abfrage ohne die distinct-Klausel starten würden.
272
Abfragen
In einer solchen Konstellation kann es, vor allem wenn die Größenrelationen der beteiligten Tabellen extrem unterschiedlich sind (z.B. „personalien“ 50.000 und „lohnarten“ > 4 Mio. Datensätze), sinnvoll sein, statt der gewöhnlichen Verknüpfung die Existenz der gesuchten Lohnarten mit Hilfe einer Unterabfrage zu ermitteln und diese Existenz als Auswahlkriterium für die Namensselektion zu verwenden. SQLWKS> 2> 3> 4> 5> 6> 7> 8> 9>
select a.name from personalien a where exists(select 1 from lohnarten b where b.persnr = a.persnr and b.gab = to_date('01.01.1998','DD.MM.YYYY') and b.la between '300' and '350' ) order by a.name;
NAME -------------------------------------------------Beckmann,Yonne Bruckner,Yonne Bätzing,Bärbel Calvo,Ulrike ... Sistermann,Willy Voltair,Samuel Zola,Wolf 16 rows selected.
Mit Hilfe des Schlüsselwortes exists können Sie innerhalb der where-Bedingung eine besondere Auswahlbedingung erzeugen, die auf einer Unterabfrage basiert. Diese gesamte Unterabfrage wird hinter dem exists-Kommando in Klammern programmiert und einmal abgesehen von der sehr spärlichen select-Klausel kann die Unterabfrage alle Elemente einer gewöhnlichen Abfrage enthalten, d.h. Sie können auch in der Unterabfrage wiederum mehrere Tabellen verwenden, diese in gewohnter Weise miteinander verknüpfen oder sogar wieder eine weitere Unterabfrage einbauen, d.h. die Unterabfragen können grundsätzlich in beliebiger Tiefe geschachtelt werden. Außerdem haben Sie in jeder Unterabfrage Zugriff auf alle Felder der darüber liegenden Abfragen. Was Sie bei einer exists-Unterabfrage so genau abfragen ist eigentlich egal, denn das in der select-Klausel genannte Feld wird von der Datenbank sowieso nicht geliefert. Stattdessen wird wirklich nur die Existenz der spezifizierten Auswahl geprüft, wobei die exists-Klausel bei Erfolg den Wahrheitswert wahr und ansonsten eben falsch liefert. Aus diesem Grund finden Sie bei derartigen Abfragen oftmals gar kein Feld in der select-Anweisung, sondern so wie in unserem Beispiel wird einfach irgendeine Konstante verwendet.
Verknüpfungen
273
Nichtexistenz prüfen Etwas Vorhandenes zu selektieren war ja eigentlich noch nie besonders schwierig, ganz gleich, ob man es auf die eine oder andere Art erledigt. Die wahre Stärke der exists-Klausel offenbart sich manchmal erst, wenn es darum geht das Nichtvorhandene auszuwählen, was man mit Hilfe der exists-Auswahlbedingung ebenfalls prima bzw. einfach erledigen kann. Im Unterschied zur eben beschriebenen Aufgabenstellung geht es diesmal darum, nur die Mitarbeiter zu selektieren, die im Jahr 1998 die genannten Lohnarten nicht erhalten haben. Wie Sie an dem folgenden Beispiel sehen, ist das mit exists wirklich ganz einfach, denn aus diesem Befehl wird einfach not exists, um die Abfrageumkehrung durchzuführen. Ansonsten ändert sich in Bezug auf die Verwendung oder die zu beachtenden Regeln gar nichts. SQLWKS> select a.name 2> from personalien a 3> where not exists(select 1 4> from lohnarten b 5> where b.persnr = a.persnr 6> and b.gab =t o_date('01.01.1998','DD.MM.YYYY') 7> and b.la between '300' and '350' 8> ) 9> order by a.name; NAME -------------------------------------------------Frisch,Berta Hardt,Andreas Heiden,Magareta ... Voltair,Samuel Voltair,Samuel 8 rows selected.
Einen Wert selektieren Die dritte Variante der Unterabfrage besteht darin, diesmal wirklich einen bzw. eine Menge von Werten aus den abgefragten Tabellen zurückzuliefern. Zur Demonstration kehre ich zunächst einmal zum ersten Lohnartenbeispiel zurück und zeige Ihnen hier gleich noch eine weitere Variante, wie Sie die Abfrage durchführen könnten. SQLWKS> 2> 3> 4> 5> 6> 7> 8>
select a.name from personalien a where persnr in (select persnr from lohnarten b where b.gab=to_date('01.01.1998','DD.MM.YYYY') and b.la between '300' and '350' ) order by a.name;
274
Abfragen
NAME -------------------------------------------------Beckmann,Yonne Bruckner,Yonne Bätzing,Bärbel Calvo,Ulrike ... Sistermann,Willy Voltair,Samuel Zola,Wolf 16 rows selected.
Um eines vorwegzunehmen: wenn Sie solche Aufgabenstellungen auf diese Weise lösen, dann werden Sie unter Ihren Datenbankadministratoren schnell neue Freunde finden. Wie schön, dass es im Rahmen eines solchen Buches manchmal einfach nur darum geht, die vorhandenen Möglichkeiten und Varianten aufzuzeigen. Die verwendete Unterabfrage liefert zunächst die Menge aller Personalnummern, die innerhalb der Lohnartentabelle die bekannten Kriterien erfüllen. Das können im richtigen Leben ein paar hunderttausend Nummern sein, die zunächst einmal bestenfalls im Hauptspeicher aber wahrscheinlich eher in einer temporären Hilfstabelle gespeichert werden. Anschließend kommt die äußere Abfrage auf die Tabelle der Personalien zum tragen, wobei jetzt jede Personalnummer wegen des in-Vergleichoperators mit der temporären Tabelle abgeglichen wird. Ist die Personalnummer dort vorhanden, dann wird der Name des Mitarbeiters in die Ergebnismenge kopiert. In der Praxis sollten Sie Unterabfragen, die Mengen von Daten liefern, nur mit Bedacht einsetzen, was im Übrigen sowieso nur zusammen mit dem in-Operator funktioniert. Bei allen anderen Vergleichsoperatoren darf Ihre Unterabfrage nur genau einen Wert zurückliefern, was wie wir noch sehen werden, oftmals besondere Anforderungen an die Programmierung solcher Unterabfragen stellt. select a.name from personalien a where persnr = (select distinct persnr from lohnarten b where b.persnr = a.persnr and b.gab = to_date('01.01.1998','DD.MM.YYYY') and b.la between '300' and '350' ) order by a.name;
Da ein Mitarbeiter meistens mehrer Datensätze in der Lohnartentabelle besitzt, kann innerhalb der Unterabfrage zunächst nicht garantiert werden, dass sie nur einen Wert zurückliefert. In jedem Fall würde sie zwar nur genau eine Personalnummer liefern, diese aber vielleicht fünf oder sechsmal, weil der Mitarbeiter fünf oder sechs der gesuchten Lohnarten besitzt. Aus diesem Grund habe ich in der select-Klausel der Unterabfrage zunächst einmal das Schlüsselwort distinct verwendet, um die Ergebnismenge hierdurch auf einen einzigen Wert zu reduzieren.
Verknüpfungen
275
Allerdings ist das nicht das Gelbe vom Ei, denn wie Sie noch sehen werden, hilft das distinct-Kommando nur in Ausnahmefällen. Besser Sie gewöhnen sich im Zusammenhang solcher Unterabfragen gleich an die Verwendung einer Aggregatfunktion, um die Ergebniseindeutigkeit zu erzwingen. Die beiden Funktionen min oder max sind hierfür beispielsweise bestens geeignet. select a.name from personalien a where persnr = (select max(persnr) from lohnarten b where b.persnr = a.persnr and b.gab = to_date('01.01.1998','DD.MM.YYYY') and b.la between '300' and '350' ) order by a.name;
Damit wäre unser Lohnartenbeispiel genug strapaziert. Allerdings will ich noch einmal betonen, dass die letzten Beispiele zum Teil nur als Demonstrationsobjekt geeignet bzw. nicht zur Nachahmung empfohlen sind. Wenn es nur darum geht, im Rahmen einer Abfrage die Existenz anderer Gegebenheiten abzuprüfen, dann sollten Sie in jedem Fall die exists-Variante verwenden oder über eine ganz normale Verknüpfung nachdenken, sofern die Datenmenge und die selektierten Spalten dies zulassen. Für die wertzurückliefernden Unterabfragen gibt es allerdings auch viele sinnvolle Beispiele, für die ich eines im folgenden demonstrieren möchte. Hierzu müssen wir uns zunächst einmal an unsere Abfrage erinnern, mit der wir einen logischen Datensatz aus Personalien, Beschäftigungsverhältnisse und Gehaltshistorien gebildet hatten. Meistens hat ein Mitarbeiter innerhalb eines Beschäftigungsverhältnisses mehrere Gehaltsdatensätze. Solche Historien entstehen in unserem Beispiel durch eine Versetzung (andere Kostenstelle) oder durch echte Gehaltsveränderungen. Ferner besteht auch die Möglichkeit, dass für einen Mitarbeiter Zukunftsdaten vorhanden sind (vgl. Abb. 3.2).
Personalien
BVs
Gehälter
4711
0
01.01.1997 01.01.1998 01.04.1999 01.09.1999 01.10.2000 01.03.2001
1
Abbildung 3.2: Struktur der Gehaltshistorie eines Mitarbeiters
276
Abfragen
In einem solchen Fall würden die selektierten Mitarbeiterdaten entsprechend häufig angezeigt, wobei uns in dem Fall auch kein distinct-Kommando weiterhilft, da sich die einzelnen Gehaltsdaten üblicherweise unterscheiden. Vor allem besteht die Herausforderung ja vielleicht darin, die Gehaltsdaten zu einem ganz speziellen Zeitpunkt (z.B. 24.08.2000) auszuwählen, wobei Sie nicht genau wissen, welches Datum hierbei für den einzelnen Mitarbeiter selektiert werden muss. Betrachten Sie zusammen mit dieser Fragestellung noch einmal die Abbildung 3.2. Welcher Datensatz ist der richtige, wenn wir den aktuellen Gehaltsdatensatz zum 24.08.2000 suchen? Aus der Abbildung kann man erkennen, dass es sich in dem Beispiel um den Datensatz vom 01.09.1999 handelt. Seit diesem Termin gab es keine Änderung mehr, so dass die zugehörigen Daten auch heute immer noch gültig sind. Allgemein gilt, dass wir aus der Menge aller Gehaltshistorien eines Mitarbeiters, die kleiner oder gleich unserem Stichtag sind, denjenigen Datensatz mit dem größten Gültigkeitsdatum suchen. Diesen mitarbeiterindividuellen Termin wollen wir nun mit Hilfe einer Unterabfrage ermitteln und hierdurch eine Abfrage mit aktuellen Gehaltsdaten erzeugen. Dabei verzichten wir allerdings auf das konstante Stichtagsdatum und verwenden stattdessen die Funktion sysdate, so dass unserer Abfrage tagesaktuelle Gehaltshistorien selektiert. SQLWKS> select a.persnr, substr(a.name,1,10), b.lfdnr, c.gab, c.gehalt 2> 3> from gehalt c, 4> bvs b, 5> personalien a 6> 7> where b.persnr = a.persnr 8> and c.persnr = b.persnr 9> and c.lfdnr = b.lfdnr 10> and c.gab = (select max(gab) 11> from gehalt c1 12> where c1.persnr = c.persnr 13> and c1.lfdnr = c.lfdnr 14> and c1.gab <= sysdate 15> ) 16> PERSNR SUBSTR(A.N LFDNR GAB GEHALT ----------- ---------- ---------- -------------------- ---------7000002 Karlo,Armi 0 01-APR-99 4980 7000003 Heiden,Mag 0 01-AUG-99 6500 7000004 Hardt,Andr 0 01-APR-99 7850 7000005 Nibelungen 0 01-SEP-99 6500 7000006 Beckmann,Y 0 01-AUG-99 5899 7000007 Calvo,Ulri 0 01-JAN-99 6500 ... 7000008 Voltair,Sa 0 01-JAN-98 4750 7000008 Voltair,Sa 1 01-MAR-98 5200 34 rows selected.
Verknüpfungen
277
Wie funktioniert diese Abfrage nun genau? Betrachten wir hierzu zunächst noch einmal den äußeren Teil der Selektion. Mit Hilfe der Zeilen sieben bis neun werden die gemeinsamen Felder der drei beteiligten Tabellen miteinander verknüpft. Ohne weitere Auswahlbedingungen würden wir damit wieder für jeden Mitarbeiter alle vorhandenen Gehaltshistorien erhalten. Da wir aber immer nur genau eine Historie mit einem bestimmten gültig-ab-Datum wünschen, müssen wir hierfür ein zusätzliches Auswahlkriterium definieren. and c.gab =
Das Problem dabei ist nur, dass der aktuell gültige Gehaltsdatensatz für jeden Mitarbeiter ein individuelles gültig-ab-Datum besitzt. Aus diesem Grund können wir das Feld c.gab nicht mit irgendeiner Datumskonstante vergleichen, denn wir kennen den konkreten Wert nicht; wir kennen lediglich die Regel den Vergleichswert zu bestimmen. Betrachten wir nun als Nächstes die im inneren Teil programmierte Unterabfrage. SQLWKS> 2> 3> 4> 5>
select gab from gehalt c1 where c1.persnr = '7000002' and c1.lfdnr = 0 and c1.gab <= sysdate;
GAB -------------------01-APR-98 01-APR-99 2 rows selected.
Die Abfrage liefert für eine konkrete Personalnummer und das entsprechende Beschäftigungsverhältnis alle gültig-ab-Daten des Gehaltsdatensatzes, die kleiner oder gleich dem aktuellen Tagesdatum sind. Von diesen Datumswerten benötigen wir allerdings nur dasjenige, das dem Tagesdatum am nächsten liegt. Mit anderen Worten benötigen wir also den größten dieser Werte, den wir leicht mit Hilfe der max-Funktion ermitteln können. Natürlich kann die Abfrage der Personalnummer und des BV’s nicht wie in dem kleinen Beispiel mit Hilfe irgendeiner Konstante erfolgen, sondern wir beziehen uns hier auf die entsprechenden Felder aus der darüber liegenden Abfrage. Für jeden in der äußeren Abfrage selektierten Datensatz wird also die Unterabfrage gestartet, die anschließend das benötigte Datum zurückliefert, mit dem der richtige Gehaltsdatensatz ausgewählt wird. Sie können sich diese Unterabfrage auch als eine Art virtuelle Funktion vorstellen. In unserem Fall bekommt diese virtuelle Funktion die Parameter c.persnr und c.lfdnr übergeben und liefert als Ergebnis das gesuchte Historiendatum.
278
3.2.3
Abfragen
Outer-Joins
Bei den bisherigen Verknüpfungen wurden im Ergebnis immer nur diejenigen Datensätze gezeigt, bei denen für die verknüpften Felder in allen Tabellen entsprechende Werte bzw. Sätze vorhanden waren. Das kann manchmal zu unerwünschten Verlusten von Informationen führen, weshalb es für solche Fälle entsprechende Abfragealternativen gibt. Betrachten Sie hierzu zunächst einmal die folgende kleine Abfrage auf unserer Ländertabelle. SQLWKS> select * from laender; LAN B BEZEICHNUNG --- - -----------------------------DEU Y Deutschland AUT Y Österreich BEL N Belgien GBR N Grossbritannien FIN N Finnland FRA N Frankreich 6 rows selected.
Wie Sie dem Beispiel entnehmen können, enthält unsere Ländertabelle genau sechs verschiedene Datensätze. Verknüpfen wir nun im Anschluss diese Tabelle mit den Personalien, dann erhalten wir allerdings nur noch folgendes Ergebnis. SQLWKS> select distinct b.land, b.bezeichnung 2> from personalien a, 3> laender b 4> where b.land = a.land; LAN BEZEICHNUNG --- -----------------------------DEU Deutschland 1 row selected.
Durch die Verknüpfung sind uns insgesamt fünf Länder verloren gegangen, was ganz einfach daran liegt, dass keiner unserer Mitarbeiter aus einem der dort gespeicherten Länder stammt. Allerdings können wir auf diese Weise die Antwort auf die Frage nach allen verfügbaren Ländern und den jeweils zugeordneten Mitarbeitern nicht in einem Zug beantworten. Natürlich gibt es eine Lösung für dieses Dilemma, die in eine besondere Form der Abfragetechnik mündet. Mit Hilfe der sogenannten Outer-Joins haben Sie die Möglichkeit, die in der verknüpften Tabelle fehlenden Datensätze durch imaginäre Nullwerte aufzufüllen. Wie Sie an dem letzten Satz erkennen können, steht uns beim Outer-Join also immer ein kleines Entscheidungsproblem ins Haus, denn wir müssen festlegen, welches quasi die führende Tabelle ist bzw. für welche Tabelle die eventuell fehlenden Datensätze automatisch ergänzt werden sollen.
Verknüpfungen
279
SQLWKS> select b.land, b.bezeichnung, a.name 2> from personalien a, 3> laender b 4> where b.land = a.land (+); LAN BEZEICHNUNG --- -----------------------------AUT Österreich BEL Belgien DEU Deutschland DEU Deutschland DEU Deutschland ... DEU Deutschland FIN Finnland FRA Frankreich GBR Grossbritannien 26 rows selected.
NAME --------------------------
Beckmann,Yonne Bruckner,Yonne Bätzing,Bärbel Zola,Wolf
In der Abfrage wird die aufzufüllende bzw. unvollständige Tabelle mit Hilfe der Zeichen „(+)“ markiert, d.h. konkret müssen Sie diese Zeichenfolge in der where-Bedingung hinter jedes Feld aus der Tabelle schreiben. Dabei ist es gleichgültig, ob das Feld im Rahmen der Tabellenverknüpfung oder in einer anderen Auswahlbedingung verwendet wird. Dies wird sicherlich klarer, wenn wir unsere Beispielabfrage noch einmal erweitern. Wir möchten wie bisher alle Länder und die zugehörigen Mitarbeiter selektieren, dabei interessieren uns die Namen aber nur dann, wenn die Namen der Mitarbeiter zwischen „B“ und „F“ liegen. SQLWKS> select b.land, b.bezeichnung, a.name 2> from personalien a, 3> laender b 4> where b.land = a.land (+) 5> and substr(a.name (+),1,1) between 'B' and 'F'; LAN BEZEICHNUNG --- -----------------------------AUT Österreich BEL Belgien DEU Deutschland DEU Deutschland DEU Deutschland DEU Deutschland DEU Deutschland FIN Finnland FRA Frankreich GBR Grossbritannien 10 rows selected.
NAME ----------------------------
Beckmann,Yonne Calvo,Ulrike Frisch,Berta Bruckner,Yonne Bätzing,Bärbel
280
Abfragen
In diesem Beispiel haben wir unserer Abfrage eine weitere Auswahlbedingung mit einem Feld aus den Personalien hinzugefügt, und obwohl diese Zusatzbedingung mit der eigentlichen Tabellenverknüpfung gar nichts zu tun hat, müssen Sie die Füllmarkierung „(+)“ hinter dem Namensfeld anfügen, damit die Abfrage entsprechend funktioniert. Jetzt wird es allerdings höchste Zeit ein wenig auf die Bremse zu treten, denn das letzte Beispiel könnte vermuten lassen, dass auch innerhalb der Outer-Joins zu ziemlich alles möglich ist und geht, was aber bei weitem nicht der Fall ist. Vielmehr gibt es eine Reihe von Regeln bzw. Einschränkungen, die ich Ihnen nicht vorenthalten möchte.
X X X
Der Operator „(+)“ kann innerhalb der where-Klausel für alle Spalten der in der from-Klausel verwendeten Tabellen oder Views verwendet werden. Werden zwei Tabellen mit mehr als einer Auswahlbedingung verknüpft, dann muss der Operator „(+)“ in allen diesen Auswahlbedingungen verwendet werden. Der Outer-Join-Operator „(+)“ kann nur direkt hinter einem Spaltennamen verwendet werden. Die Benutzung hinter einem Ausdruck ist nicht möglich, d.h. die nachfolgende Verwendung ist beispielsweise nicht zulässig. substr(a.name, 1, 20) (+) between 'B' and 'F'
X X X
Der Operator „(+)“ kann nicht in einer Auswahlbedingung verwendet werden, die mit einer or-Bedingung verknüpft wurde. Ebenfalls nicht möglich ist die Verwendung des Outer-Join-Operators zusammen mit dem in-Vergleichsoperator. Zuletzt, und das ist nach meiner Meinung die am härtesten treffende Einschränkung, dürfen Sie den Outer-Join auch nicht zusammen mit einer Unterabfrage verwenden, d.h. das nachfolgende Beispiel ist ebenfalls unzulässig. select a.persnr, substr(a.name,1,10), b.lfdnr, c.gab, c.gehalt from gehalt c, bvs b, personalien a where b.persnr = a.persnr and c.persnr (+) = b.persnr and c.lfdnr (+)= b.lfdnr and c.gab (+) = (select max(gab) from gehalt c1 where c1.persnr = c.persnr and c1.lfdnr = c.lfdnr and c1.gab <= sysdate );
Die letzte der eben genannten Restriktionen kann in der Tat zu einem echten Problem führen. Erinnern Sie sich noch einmal an unser Beispiel zur Selektion der aktuellen Gehaltshistorie. Diese Abfrage lieferte für jeden Mitarbeiter den aktuellen
Verknüpfungen
281
Gehaltsdatensatz, allerdings mit der Nebenwirkung, dass er aus der Selektion vollständig herausfällt, wenn der Mitarbeiter diesen aktuellen Datensatz nicht besitzt. Das wäre zum Beispiel der Fall, wenn aus irgendwelchen Gründen noch kein Gehaltsdatensatz angelegt wurde oder wenn der erste Datensatz hinter dem aktuellen Tagesdatum liegt. Wenn Sie sichergehen wollen bzw. müssen, dass Ihre Abfrage auch solche Datensätze selektiert, dann können Sie das mit Hilfe einer der im nächsten Abschnitt beschriebenen Mengenoperationen erreichen.
3.2.4
Mengenoperationen
Neben den bisher beschriebenen Möglichkeiten, eine einzelne Abfrage mit Hilfe verschiedener Verknüpfungen zu gestalten, sind Sie des weiteren auch in der Lage, die Ergebnisse verschiedener einzelner Abfragen quasi zu einer gesamten Ausgabemenge zusammenführen. Die bisher behandelten Operatoren bezogen sich also immer auf eine einzelne Abfrage, wohingegen die im Folgenden beschriebenen Operatoren zur Verknüpfung verschiedener Abfrageergebnisse dienen. Insgesamt bietet Oracle Ihnen die in der nachfolgenden Tabelle aufgeführten Operatoren, um die Ergebnisse zweier oder mehrerer Abfragen zu verbinden. Operator
Beschreibung
union
führt zur Vereinigungsmenge der Ergebnisse der beiden verknüpften Abfragen, d.h. die Ausgabemenge enthält keine doppelten Datensätze.
union all
mischt die Ergebnisse der beiden verknüpften Abfragen.
intersect
Das Ergebnis enthält nur diejenigen eindeutigen Datensätze, die jeweils auch in den beiden einzelnen Abfrageergebnissen enthalten sind.
minus
Mit diesem Operator erhalten Sie diejenigen eindeutigen Datensätze, die in der ersten aber nicht in der zweiten Abfrage enthalten sind, d.h. das Ergebnis entspricht dem distinct-Ergebnis der ersten Abfrage abzüglich aller Datensätze, die von der zweiten Abfrage geliefert werden.
Tabelle 3.8: Übersicht der Mengenoperatoren
Die einzelnen Mengenoperatoren haben alle die gleiche Rangfolge, d.h. wenn mehr als zwei Abfragen miteinander verknüpft werden, dann erfolgt die Auswertung entsprechend der Reihenfolge des Auftretens der jeweiligen Operatoren. SQLWKS> 2> 3> 4> 5> 6> 7>
select persnr from personalien union select persnr from bvs minus
282
Abfragen
8> 9> select persnr from gehalt; PERSNR ----------7000088 1 row selected.
Ist diese natürliche Reihenfolge nicht gewünscht, dann können bzw. müssen Sie die mit Hilfe von Klammern verändern. SQLWKS> 2> 3> 4> 5> 6> 7> 8> 9> 10>
select persnr from personalien union ( select persnr from bvs minus select persnr from gehalt );
PERSNR ----------7000001 7000002 7000003 … 7000088 7000188 24 rows selected.
Neben diesen einfachen Regeln, müssen Sie bei der Verwendung der eben genannten Mengenoperatoren eigentlich nur noch beachten, dass zum einen vielleicht einmal abgesehen von den Operatoren union bzw. union all die Verwendung dieser mächtigen Verknüpfungswerkzeuge ihren Tribut in entsprechender Rechenleistung bzw. Verarbeitungszeit zollt und zum anderen die mit Mengenoperatoren verknüpften Abfragen alle keine eigene order by-Klausel besitzen. Dahingegen verfügt die gesamte Abfrage bei Bedarf am Ende über eine eigene Sortieranweisung in Form des üblichen order by-Kommandos. SQLWKS> 2> 3> 4> 5> 6> 7> 8>
select persnr from personalien union select persnr from bvs minus
Verknüpfungen
283
9> select persnr from gehalt 10> order by persnr; PERSNR ----------7000088 1 row selected.
Die zuletzt genannte Restriktion ist eigentlich auch ziemlich naheliegend, denn warum sollte jedes einzelne Abfrageergebnis nach irgendwelchen Kriterien sortiert werden, wenn diese Sortierung nach dem Ausführen der spezifizierten Verknüpfung in Bezug auf das Endergebnis überhaupt keine Relevanz mehr hat. Im richtigen Leben erhält man beim Mischen von Äpfeln und Birnen leckeren Obstsalat. Was beim Vermischen verschiedener Abfrageergebnisse am Ende dabei herauskommt, darauf kann das Datenbanksystem natürlich nicht achten. Das Einzige, was Oracle im Zusammenhang mit den Mengenoperatoren prüft, ist die Übereinstimmung der Datentypen für die entsprechenden Spalten der einzelnen Abfragen. Auf den Rest müssen Sie natürlich selber achten, d.h. liefert die erste Abfrage als zweite Spalte Straßen und die zweite Abfrage in dieser Spalte Orte, dann erhalten Sie als Endergebnis logischerweise genau diesen zubereiteten Abfragesalat. Nach dieser kurzen Einführung, werden wir die verschiedenen Mengenoperatoren in den folgenden Abschnitten nun noch etwas näher unter die Lupe nehmen. Der union-Operator Mit Hilfe des Operators union können Sie die Ergebnismengen zweier Abfragen mischen (vereinigen). Dabei handelt es sich in der Tat wie in der Schulmathematik um die Erstellung einer Vereinigungsmenge, denn das gelieferte Ergebnis enthält keine doppelten Elemente, d.h. der union-Operator verbindet die beiden Ergebnisse mit einer Art distinct-Klausel. Ist die Eliminierung doppelter Datensätze nicht erwünscht, dann müssen Sie anstelle des union-Operators die besondere Variante union all verwenden, bei der die beiden Abfrageergebnisse einfach gemischt werden. In Bezug auf die interne Verarbeitung gilt übrigens auch hier das Gleiche, was ich schon in Bezug auf das Schlüsselwort distinct gesagt habe. Die Vermischung zweier Abfrageergebnisse ist naheliegenderweise weniger aufwendig, als die zusätzliche Erzwingung eindeutiger Abfrageergebnisse, d.h. wenn Sie wissen, dass alle Ihre verwendeten Teilabfragen ausschließlich ergänzenden Charakter haben, dann sollten Sie den Operator union all verwenden. Erinnern wir uns noch einmal an unser Beispiel zur Ermittlung der aktuellen Gehaltsdatensätze, bei dem die jeweils aktuellen Werte mit Hilfe einer Unterabfrage selektiert wurden. Diese Abfrage liefert Ihnen bei Verwendung der auf der CD enthaltenen Beispieldaten genau 34 Datensätze. Wie Sie weiterhin der folgenden Abfrage entnehmen können, existiert in unserer kleinen Beispieldatenbank ein Datensatz, dessen erster und einziger Datensatz ab dem 01.01.2007 gültig ist. SQLWKS> select gab, gehalt 2> from gehalt
284
Abfragen
3> where persnr = '7000188' 4> GAB GEHALT -------------------- ---------01-JAN-07 6240 1 row selected.
Dies ist im Übrigen der einzige Datensatz, der zumindest in nächster Zeit erst nach dem aktuellen Tagesdatum gültig wird, so dass die zugehörige Personalnummer als einzige nicht in unserer tagesaktuellen Gehaltsabfrage enthalten ist. Mit Hilfe des union-Operators können Sie die Abfrage nun leicht so erweitern, dass sie zum einen alle aktuellen Gehälter aber zum anderen für diejenigen Mitarbeiter, die noch keinen aktuellen Gehaltsdatensatz besitzen, wenigstens bestimmte Rumpfdaten liefert. SQLWKS> 2> 3> 4> 5> 6> 7> 8> 9> 10> 11> 12> 13> 14> 15> 16> 17> 18> 19> 20> 21> 22> 23> 24> 25> 26> 27> 28> 29> 31<
select a.persnr, substr(a.name,1,10), b.lfdnr, c.gab, c.gehalt from gehalt c, bvs b, personalien a where and and and
b.persnr = a.persnr c.persnr = b.persnr c.lfdnr = b.lfdnr c.gab = (select max(gab) from gehalt c1 where c1.persnr = c.persnr and c1.lfdnr = c.lfdnr and c1.gab <= sysdate )
union all select a.persnr, substr(a.name,1,10), b.lfdnr, sysdate+1, 0 from bvs b, personalien a where b.persnr = a.persnr and not exists(select 1 from gehalt c where c.persnr=b.persnr and c.lfdnr = b.lfdnr and c.gab <= sysdate );
PERSNR SUBSTR(A.N LFDNR GAB GEHALT ----------- ---------- ---------- -------------------- ----------
Verknüpfungen
7000001 Frisch,Ber 7000002 Karlo,Armi 7000003 Heiden,Mag ... 7000022 Keun,Monik 7000188 Schmidt,Ul 35 rows selected.
285
0 01-JAN-90 0 01-APR-99 0 01-AUG-99
4500 4980 6500
0 01-MAR-99 0 31-AUG-00
5700 0
Der erste Teil dieser union-Abfrage entspricht unserer schon bekannten Gehaltsabfrage und liefert damit wieder den zum Tagesdatum aktuellen Gehaltsdatensatz. Der zweite Teil ab Zeile 19 ermittelt wiederum mit Hilfe einer Unterabfrage vom Typ not exists genau diejenigen Datensätze, die zum aktuellen Tagesdatum keinen Gehaltsdatensatz besitzen. Beide Einzelabfragen werden mit dem union-Operator vermischt, so dass Sie als Endergebnis dennoch für jeden Mitarbeiter einen Datensatz geliefert bekommen. Mit Hilfe der zweiten select-Anweisung (Zeile 19) selektieren wir für die nicht vorhandenen Gehaltsdatensätze irgendwelche Standardwerte. Dabei verwenden wir für das Gültigkeitsdatum den Ausdruck sysdate + 1 (=morgen), so dass wir bei der Verarbeitung der Abfrageergebnisse, beispielsweise innerhalb eines Programms, die Ergebnisse der zweiten Abfrage leicht erkennen könnten. Aufgrund der Struktur der beiden Teilabfragen, können die oben selektierten Mitarbeiter niemals in der zweiten Abfrage auftauchen, was auch umgekehrt entsprechend gilt. Aus dem Grund vermischen wir die beiden Abfrage in Zeile 17 mit dem Operator union all. Natürlich würde der union-Operator zum gleichen Ergebnis führen, wäre in der internen Verarbeitung aber etwas aufwändiger. Der intersect-Operator Kehren wir noch einmal kurz zur Schulmathematik zurück und erinnern uns, dass es neben den Vereinigungsmengen noch etwas anderes nämlich die Schnittmengen gab. Folglich finden Sie im Befehlsvorrat des Oracle-Sprachumfangs auch hierfür einen Mengenoperator, mit dem Sie als Ergebnis der verknüpften Einzelabfragen nur diejenigen Datensätze erhalten, die auch in beiden Einzelabfragen enthalten sind, wobei das Endergebnis immer nur eindeutige Datensätze enthält, d.h. zusammen mit dem insersect-Operator erhalten Sie für das Gesamtergebnis immer automatisch den distinct-Effekt. Ich muss zugeben, dass ich diesen Operator in meiner bisherigen beruflichen Praxis noch nie verwendet habe. Das mag daran liegen, dass man viele denkbare Anwendungsbeispiele auch mit Hilfe entsprechender Unterabfragen und damit in einer einzigen Abfrage realisieren kann. Deshalb fällt mir hier und jetzt, zumindest unter Berücksichtigung der in unserer Beispieldatenbank vorhandenen Objekte, auch nur ein banales Beispiel ein, das lediglich die formale Anwendung des Operators demonstriert und ansonsten keinen besonderen sinnlichen Nährwert enthält. SQLWKS> select a.persnr, b.lfdnr 2> from personalien a, 3> bvs b
286
Abfragen
4> 5> 6> 7> 8> 9>
where b.persnr=a.persnr intersect select persnr, lfdnr from gehalt;
PERSNR LFDNR ----------- ---------7000001 0 7000002 0 7000003 0 ... 7000188 0 35 rows selected.
Mit diesem Beispiel untersuchen wir unsere Datenstruktur und ermitteln alle vollständigen Stammdatensätze, d.h. wir selektieren alle Personalnummern, die wenigstens ein Beschäftigungsverhältnis und mindestens einen zugehörigen Gehaltsdatensatz besitzen. Natürlich hätten Sie die zweite mit intersect verknüpfte Abfrage auch in der Ersten als exists-Unterabfrage einbauen können. Trotzdem liefert der letzte Satz den Baustoff für die Brücke hin zu einer sinnvollen Verwendungsmöglichkeit. Die ist aus meiner Sicht beispielsweise immer dann gegeben, wenn die Unterabfrage zu komplex wird, zum Beispiel weil die in der Unterabfrage notwendige Existenzprüfung wegen des Fehlens eines eindeutigen Schlüssels nur durch Vergleich aller vorhandenen Spalten möglich wäre. In dem Fall wäre die Verwendung des intersect-Operators zumindest für den Programmierer einfacher als die Kodierung entsprechender endloser and-Verknüpfungen innerhalb der Unterabfrage. Der minus-Operator Mit Hilfe des minus-Operators können Sie von der Ergebnismenge der ersten Abfrage diejenigen Datensätze abziehen, die ebenfalls durch die zweite verknüpfte Abfrage geliefert werden. Das gelieferte Endergebnis enthält allerdings in jedem Fall keine doppelten Datensätze, d.h. die vom Datenbanksystem bereitgestellte Ergebnismenge wird quasi wie mit einem select distinct-Befehl ausgegeben. In Bezug auf die Anwendungsfähigkeit gilt aus meiner Sicht für den minus-Operator das Gleiche, was ich schon im vorherigen Abschnitt zum intersect-Operator gesagt habe, d.h. zumindest in meiner bisherigen beruflichen Praxis hat dieser Operator bisher noch keine Rolle gespielt, da Sie das gleiche Ergebnis mit Hilfe einer Unterabfrage des Typs „not exists“ meistens einfacher realisieren können. Dennoch möchte ich die Verwendung dieses Operators wieder an einem kurzen Beispiel demonstrieren. Im Kapitel „Unterabfragen“ haben wir diejenigen Mitarbeiter selektiert, die bestimmte Lohnarten im Jahr 1998 nicht erhalten haben. Damals hatten wir dieses Ergebnis mit Hilfe einer Unterabfrage vom Typ „not exists“ realisiert, wobei der hier beschriebene Mengenoperator natürlich noch
Verknüpfungen
287
einen anderen Lösungsweg offenbart, denn die Menge der Mitarbeiter ohne die gesuchten Bezugsarten entspricht der Menge aller Mitarbeiter abzüglich derjenigen, die diese Lohnarten erhalten haben. SQLWKS> 2> 3> 4> 5> 6> 7> 8> 9> 10> 11>
select a.persnr, a.name from personalien a minus select a.persnr, a.name from personalien a, lohnarten b where b.persnr = a.persnr and b.gab =to_date('01.01.1998','DD.MM.YYYY') and b.la between '300' and '350';
PERSNR NAME ----------- -------------------------------------------------7000001 Frisch,Berta 7000002 Karlo,Armin 7000003 Heiden,Magareta 7000004 Hardt,Andreas 7000008 Voltair,Samuel 7000015 Hilsamer,Kurt 7000088 Voltair,Samuel 7000188 Schmidt,Ulrike 8 rows selected.
Mit Hilfe der ersten Abfrage in den Zeilen eins bis zwei werden alle Mitarbeiter aufgelistet. Die zweite Abfrage ab Zeile sechs liefert wieder alle Mitarbeiter, die im Jahr 1998 die gesuchten Lohnarten erhalten haben. Die Verknüpfung dieser beiden Abfragen erfolgt in Zeile vier mit Hilfe des minus-Operators, so dass Sie als Endergebnis nur diejenigen Namen erhalten, die diese Lohnarten eben nicht erhalten haben.
3.2.5
Hierarchische Abfragen
An dieser Stelle merkt man mal wieder deutlich, wie weit die einzelnen Datenbanksysteme in ihrem Sprachumfang trotzt Normierungsversuche voneinander entfernt sind. Konkret geht es um die Auswertung hierarchischer Datenstrukturen, d.h. wir beschäftigen uns jetzt mit der Darstellung von Bäumen oder sogenannten „berichtet an“ Strukturen. Doch gehen wir das gesamte Thema wegen der zugrundeliegenden und einzigartigen Anweisung gemächlich an und beginnen damit unsere kleine Datenbank, um eine weitere Tabelle zu erweitern. drop table organisation; / create table organisation (
288
Abfragen
unternehmen varchar2(3) not null, kst varchar2(10) not null, berichtet_an varchar2(13), constraint organisation_pk primary key (unternehmen, kst) using index tablespace indx storage (initial 10K next 10K) ) tablespace usr storage (initial 20K next 20K); / commit; Listing 3.7: Anlage einer Tabelle zum Speichern der Organisationsstruktur
Wie anfangs gesagt finden Sie dieses Skript wieder in der Datei ABFRAGEN32.SQL. In Anlehnung zur Kostenstellentabelle speichern wir hier neben dem Unternehmen und Kostenstellenkürzel noch den Knoten, an den diese Kostenstelle berichtet, wobei die Knoten wiederum eine Kombination aus Unternehmensnummer und Kostenstellenkürzel darstellen. Betrachten wir als Nächstes die zur Zeit vorhandenen Kostenstellen, um einen Überblick über die benötigte Struktur zu erhalten. SQLWKS> select distinct unternehmen, kst, bezeichnung 2> from kostenstelle; UNT KST BEZEICHNUNG --- ---------- -----------------------------001 EDV EDV-Betrieb 001 PERS Personalabteilung 001 PSOFT PeopleSoft-Beratung 001 SERV Softwareservice 002 MARK Marketing 002 VERT Vertrieb 6 rows selected.
Mit dieser Abfrage erhalten Sie eine Liste der realen Blätter unseres zu erstellenden Strukturbaumes. Die nächste Abbildung (vgl. Abb. 3.3) zeigt Ihnen das gewünschte Endergebnis in Form eines üblichen Organigramms. Wie Sie dem Organigramm aus Abbildung 3.3 entnehmen können, benötigen Sie neben den sechs realen Kostenstellen auch noch drei Hilfseinträge, um die entsprechenden Hierarchieknoten darzustellen. Somit können Sie die Organisation mit Hilfe der im folgenden Listing dargestellten Datensätze darstellen.
Verknüpfungen
289
Geschäftsführung (GF)
Verwaltung (VERW)
Entwicklung (ENTW)
Personalabtlg. (PERS)
EDV-Betrieb (EDV)
Marketing (MARK)
Softwareservice (SERV)
Vertrieb (VERT)
PeopleSoft (PSOFT)
Abbildung 3.3: Organisation unseres Beispielkonzerns
insert into insert into insert into insert into insert into insert into insert into insert into insert into commit;
organisation organisation organisation organisation organisation organisation organisation organisation organisation
values('001','GF',null); values('001','VERW','001GF'); values('001','ENTW','001GF'); values('001','PERS','001VERW'); values('002','MARK','001VERW'); values('002','VERT','001VERW'); values('001','EDV','001ENTW'); values('001','SERV','001ENTW'); values('001','PSOFT','001ENTW');
Listing 3.8: Speichern der Organisationsstruktur
Betrachten wir nun noch einmal die eingefügten Daten (vgl. Listing 3.8). Aus diesen können Sie beispielsweise ablesen, dass die Kostenstelle „MARK“ aus dem Unternehmen „002“ an den Knoten „001VERW“ berichtet. Dieser Hilfseintrag ist seinerseits wiederum mit dem Knoten „001GF“ also der Geschäftsführung verknüpft. Da dieser Knoten selbst nicht mehr verknüpft ist, stellt er die höchste Hierarchiestufe bzw. die Wurzel der gesamten Struktur dar. Solche hierarchischen Strukturen können Sie natürlich auch leicht mit Hilfe herkömmlicher SQL-Sprachmittel auswerten, sofern Sie zum einen die tiefste Ebene der Struktur kennen und zum anderen alle Ebenen mit zumindest einem Element belegt sind, wobei Sie die zuletzt genannte Einschränkung noch umgehen können, indem Sie die gesamte benötigte Abfrage mit Hilfe geeigneter Outer-Joins erzeugen. Da wir in unserem kleinen Beispiel wissen, dass unser Organigramm über drei Ebenen verfügt und zum anderen alle Ebenen auch mit mindestens einem Eintrag belegt sind, können wir zur Auswertung der Organisationsstruktur die im Listing 3.9 gezeigte Abfrage verwenden.
290
Abfragen
SQLWKS> select '1' as "1", a.unternehmen || a.kst as lvl1, 2> '2' as "2", b.unternehmen || b.kst as lvl2, 3> '3' as "3", c.unternehmen || c.kst as lvl3 4> 5> from organisation a, 6> organisation b, 7> organisation c 8> 9> where b.berichtet_an = a.unternehmen || a.kst 10> and c.berichtet_an = b.unternehmen || b.kst; 1 1 1 1 1 1 1 6
LVL1 2 ------------- 001GF 2 001GF 2 001GF 2 001GF 2 001GF 2 001GF 2 rows selected.
LVL2 ------------001ENTW 001ENTW 001ENTW 001VERW 001VERW 001VERW
3 3 3 3 3 3 3
LVL3 ------------001EDV 001SERV 001PSOFT 001PERS 002MARK 002VERT
Listing 3.9: Auswertung der Organisationsstruktur mit Hilfe einer herkömmlichen Abfrage
Wie Sie im letzten Beispiel gesehen haben, können beliebige berichtet-an-Strukturen durchaus mit Hilfe herkömmlicher SQL-Abfragen aufgelöst werden. Das gilt vor allem auch dann, wenn dies innerhalb eines Programms passiert, so dass Sie die Möglichkeit besitzen, die benötigte Abfrage innerhalb des Programmablaufs dynamisch zu generieren, um so auf alle Eventualitäten wie zum Beispiel die Anzahl der Hierarchieebenen geeignet zu reagieren. Ansonsten entspricht die benötigte Abfrage einer der Ebenenzahl entsprechenden Verknüpfung der zugehörigen Tabelle, wobei die Verknüpfungsverweise aus der Tabelle der Ebene n mit den entsprechenden Feldern aus der Tabelle für die Ebene n-1 verbunden werden. In unserem Beispiel wird die Tabelle „organisation“ in der from-Klausel aus den Zeilen fünf bis sechs der Ebenenzahl drei entsprechend oft verwendet. Dabei steht der Aliasname „a“ für die Ebene 1, „b“ für die Ebene 2 und „c“ für die letzte Ebene 3. Anschließend wird in der where-Bedingung das Feld „berichtet_an“ der Ebene 2 mit den entsprechenden Feldern aus der Ebene 1 und in gleicher Weise auch die Ebene 3 mit der Ebene 2 verknüpft. Die Anweisung connect by Oracle bietet in seinem Sprachumfang eine spezielle Erweiterung, mit deren Hilfe Sie hierarchische Strukturen auch ohne die Konstruktion entsprechender Joins auswerten können. Konkret handelt es sich hierbei um die Anweisung connect by, mit deren Hilfe Sie die in der Tabelle gespeicherten Datensätze miteinander verketten können. Nach der Einführung dieser neuen Anweisung erweitert sich das Schema unserer select-Anweisung auf die nachfolgend aufgeführte Struktur.
Verknüpfungen
291
select from [where ] [start with ] connect by [order by <Sortierbedingungen>]
Wie Sie dem Schema entnehmen können, taucht zusammen mit der Anweisung connect by zusätzlich noch eine neue weitere Klausel mit dem Namen start with auf, mit deren Hilfe Sie den Startpunkt für die hierarchische Auswertung festlegen können. Meistens müssen Sie hier eine Auswahlbedingung spezifizieren, mit deren Hilfe die Wurzel der Struktur bestimmt werden kann. In unserem Beispiel der Organisationsstruktur konnte man die Wurzel daran erkennen, dass das Feld berichtet_an keinen Wert enthielt, d.h. in unserem Fall können wir den Startpunkt der Auswertung durch die nachfolgend gezeigte start-Anweisung festlegen. start with berichtet_an is null
Bevor man jedoch die Programmierung der Verkettungsbedingungen angeht, sollte man sich darüber im Klaren werden, welche Bedingungen bzw. Felder im schönen neudeutschen Sprachgebrauch die Parents bzw. Childs der Struktur kennzeichnen. Betrachten wir hierzu zunächst noch einmal den Inhalt unserer kleinen und übersichtlichen Organisationstabelle (vgl. Abb. 3.4). In dieser Abbildung habe ich die Eltern/Kind-Beziehung einmal für ein paar ausgewählte Beispiele mit Hilfe von Pfeilen dargestellt, wobei der Pfeil in meiner Darstellung vom Elterndatensatz auf ein zugehöriges Kind zeigt. Daneben müssen Sie natürlich auch noch beachten, dass in meinem Beispiel die Verkettung beim Kind mit einem („berichtet_an) und bei den Eltern mit zwei Datenfeldern („unternehmen“ und „kst“) erfolgt. SQLWKS> select * from organisation; UNT KST
BERICHTET_AN
--- ---------- ------------001 GF 001 VERW
001GF
001 ENTW
001GF
001 PERS
001VERW
002 MARK
001VERW
002 VERT
001VERW
001 EDV
001ENTW
001 SERV
001ENTW
001 PSOFT
001ENTW
9 rows selected.
Abbildung 3.4: Darstellung der Eltern/Kind-Beziehung
292
Abfragen
Sind alle diese Dinge klar, dann steht der Kodierung der connect by-Klausel eigentlich nichts mehr im Wege. Ähnlich wie bei einer Verknüpfungsbedingung müssen Sie die Felder des Elterndatensatzes mit den Feldern des Kinddatensatzes gleichsetzen, wobei Sie die Datenfelder aus dem Elterndatensatz mit Hilfe des Schlüsselwortes prior kennzeichnen müssen. connect by prior unternehmen || prior kst = berichtet_an
Damit haben wir alle Komponenten unserer neuen Abfrage fertiggestellt und können diese nun einmal ausprobieren, wobei Sie das Ergebnis dem Listing 3.10 entnehmen können. SQLWKS> 2> 3> 4>
select level, unternehmen, kst, berichtet_an from organisation start with berichtet_an is null connect by prior unternehmen || prior kst = berichtet_an;
LEVEL UNT KST ---------- --- ---------1 001 GF 2 001 VERW 3 001 PERS 3 002 MARK 3 002 VERT 2 001 ENTW 3 001 EDV 3 001 SERV 3 001 PSOFT 9 rows selected.
BERICHTET_AN ------------001GF 001VERW 001VERW 001VERW 001GF 001ENTW 001ENTW 001ENTW
Listing 3.10: Auswertung der Organisation unter Verwendung von „connect by“.
Wie Sie dem Listing 3.10 entnehmen können, führt die Anwendung der connect byAnweisung zur Verwendbarkeit der Pseudospalte level. Diese Pseudospalte, die bei gewöhnlichen Abfragen immer den Wert 0 enthält, wird bei verketteten Abfragen automatisch mit der entsprechenden Ebene gefüllt und kann innerhalb der Abfrage an beliebiger Stelle, beispielsweise auch als Auswahlkriterium, verwendet werden. Eine weitere Anwendung dieser Verfahrensweise ist auch die Suche nach einer Antwort auf die Frage, welche Kostenstellen alle an den Knoten „001ENTW“ berichten. Aufgrund unseres aktuellen Datenbestands könnte man dies natürlich auch leicht mit jeder anderen Abfragetechnik beantworten, weshalb wir unsere Organisation zunächst einmal um eine weitere Ebene erweitern. insert into organisation values ('001','EDV1','001EDV'); insert into organisation values ('001','EDV2','001EDV'); commit;
Verknüpfungen
293
Durch das Hinzufügen einer vierten Ebene ist die Beantwortung dieser Frage mit herkömmlichen Abfragetechniken nicht mehr so einfach möglich. Durch die Auflösung der Hierarchie mit Hilfe des connect by-Kommandos stellt die Beantwortung allerdings keine größere Herausforderung mehr da. SQLWKS> 2> 3> 4> 5> 6>
select level, unternehmen, kst, berichtet_an from organisation where level > 1 start with kst = 'ENTW' connect by prior unternehmen || prior kst = berichtet_an order by level;
LEVEL UNT KST ---------- --- ---------2 001 EDV 2 001 SERV 2 001 PSOFT 3 001 EDV1 3 001 EDV2 5 rows selected.
BERICHTET_AN ------------001ENTW 001ENTW 001ENTW 001EDV 001EDV
Listing 3.11: Herauslösen von Teilbereichen aus der Hierarchiestruktur
Im Prinzip entspricht die Abfrage dem vorherigen Beispiel aus Listing 3.10. Zunächst einmal beginnen wir die Auflösung der Organisationsstruktur nicht an seiner Wurzel, sondern, wie Sie der Zeile 4 entnehmen können, mit der gesuchten Kostenstelle „ENTW“, d.h. unser Organisationsbaum wird durch die Abfrage erst von diesem Knoten an durchlaufen, was folglich zur Ausgabe aller an diesen Knoten berichtenden Kostenstellen führt. Alleine mit dieser Erweiterung würde der Eintrag „001ENTW“ allerdings auch selbst ausgegeben und da dies nicht gewünscht ist, wird seine Selektion mit Hilfe der in Zeile 3 kodierten where-Bedingung ausgeschlossen. Genau wie bei allen anderen Abfragen können Sie zusammen mit connect by alle möglichen Datensätze von der Verarbeitung ausschließen. Das Interessante dabei ist allerdings, dass die ausgeschlossenen Datensätze beim Durchlaufen der Hierarchie trotzdem berücksichtigt bleiben. Diesen Effekt können Sie mit den vorhandenen Beispieldaten leicht demonstrieren, indem Sie die where-Bedingung beispielsweise um den Ausschluss der Kostenstelle „EDV“ erweitern. Wie Sie sich sicherlich erinnern, berichten unsere beiden zuletzt eingefügten Kostenstellen genau an diesen Knoten, d.h. man könnte zunächst vielleicht vermuten durch den Ausschluss dieses Knotens gingen auch alle daranhängenden Kinder verloren. where level > 1 and kst <> 'EDV'
Das dem genau wie soeben beschrieben nicht so ist, zeigt die im Listing 3.12 gezeigte vollständige Abfrage. Zwar wird die Kostenstelle „EDV“ selbst nicht in der Ergebnismenge angezeigt, jedoch wurde sie bei der Auflösung der Hierarchie
294
Abfragen
durchaus berücksichtigt, weshalb die beiden an sie berichtenden Knoten in der Ausgabe angezeigt werden. SQLWKS> 2> 3> 4> 5> 6> 7>
select level, unternehmen, kst, berichtet_an from organisation where level > 1 and kst <> 'EDV' start with kst = 'ENTW' connect by prior unternehmen || prior kst = berichtet_an order by level;
LEVEL UNT KST ---------- --- ---------2 001 SERV 2 001 PSOFT 3 001 EDV1 3 001 EDV2 4 rows selected.
BERICHTET_AN ------------001ENTW 001ENTW 001EDV 001EDV
Listing 3.12: Verwendung von where-Bedingung und connect by-Klausel
Wo viel Licht ist, da ist bekanntlich auch Schatten. Bezogen auf unsere neue connect by-Klausel zeigt sich dieser darin, dass Sie solche Abfragen nur zusammen mit einer einzigen Tabelle durchführen können, d.h. die Verwendung von connect by innerhalb bzw. zusammen mit irgendwelchen Joins ist obsolet. Dies schränkt die Verwendbarkeit dieser Anweisung zugegebenerweise auf den ersten Blick stark ein, aber wie immer muss man sich auch hier nur zu helfen wissen. Zum Beispiel können Sie connect by innerhalb einer Unterabfrage verwenden, um hierdurch die an einen bestimmten Knoten berichtenden Mitarbeiter zu ermitteln. In unserem Datenhaushalt finden Sie die Kostenstelle eines Mitarbeiters in seinen Gehaltsdaten und für die Ermittlung des aktuellen Gehaltsdatensatzes mit Hilfe einer Unterabfrage haben Sie in den vorherigen Abschnitten auch schon mehrfach gesehen. Eine solche Abfrage erweitern wir nun noch um eine weitere Unterabfrage, mit deren Hilfe wir die aktuelle Kostenstelle mit den an den Knoten „ENTW“ berichtenden Kostenstellen vergleichen. SQLWKS> select a.persnr, a.name 2> from personalien a, 3> gehalt b 4> where b.persnr = a.persnr 5> and b.gab = (select max(gab) 6> from gehalt b1 7> where b1.persnr = b.persnr 8> and b1.lfdnr = b.lfdnr 9> and b1.gab <= sysdate 10> ) 11> and b.kst in (select kst 12> from organisation
Verknüpfungen
13> 14> 15> 16> 17>
295
where level > 1 start with kst = 'ENTW' connect by prior unternehmen || prior kst = berichtet_an );
PERSNR NAME ----------- -------------------------------------------------7000003 Heiden,Magareta 7000009 Zola,Wolf 7000016 Voltair,Samuel 7000016 Voltair,Samuel 7000019 Karlo,Ellen 7000004 Hardt,Andreas 7000014 Scherer,Peter 7000020 Raymans,Heinz-Gerd 7000015 Hilsamer,Kurt 7000010 Müller,Frida 7000011 Laven,Rudolf 7000005 Nibelungen,Ellen 12 rows selected. Listing 3.13: Verwendung von connect by in einer Unterabfrage
Eine andere Möglichkeit besteht darin, die connect by-Abfrage zusammen mit einer temporären Tabellen zu verwenden, in der zuvor alle benötigten Datenfelder kopiert werden. Die Verwendung zusammen mit einer View, in der die beteiligten Tabellen zuvor verknüpft werden ist leider nicht möglich, denn die connect byAnweisung funktioniert nur, wenn die View nicht ausgeführt werden muss, so dass bei Ausführung des in der View gespeicherten SQL-Statements der connect by-Befehl doch wieder zusammen mit mehren Tabellen angewendet wird, was jedoch wie eben beschrieben nicht möglich ist. Die Verwendung des Befehls zusammen mit einer temporären Tabelle möchte ich Ihnen kurz an einem einfachen Beispiel demonstrieren. Konkret soll eine Abfrage erstellt werden, mit deren Hilfe die einzelnen Mitarbeiter entlang der aktuellen Organisationsstruktur ausgegeben werden können. Hierzu erstellen wir uns zunächst einmal die View „aktuelles_gehalt“, mit deren Hilfe die Namen der Mitarbeiter und deren aktuelle Kostenstelle und bei Bedarf weitere Daten aus der Gehaltshistorie bereitgestellt wird. create or replace view aktuelles_gehalt as select a.persnr, b.lfdnr, a.name, b.kst from personalien a, gehalt b where b.persnr = a.persnr and b.gab = (select max(gab) from gehalt b1 where b1.persnr = b.persnr
296
Abfragen
and b1.lfdnr = b.lfdnr and b1.gab <= sysdate ); commit; Listing 3.14: Erzeugen der View „aktuelles_gehalt“ zur Ermittlung der aktuellen Kostenstelle
Anschließend schicken wir anstelle einer einzelnen Abfrage ein kleines Skript auf die Reise, in dem wir zunächst unsere temporäre Tabelle erstellen und mit Daten füllen und diese anschließend in der gewünschten Weise auswerten. drop table tmp_aktuelle_orga; create table tmp_aktuelle_orga as select b.*, a.persnr, a.lfdnr, a.name from aktuelles_gehalt a, organisation b where b.kst = a.kst (+); commit; select level, unternehmen, kst, berichtet_an, persnr, lfdnr, name from tmp_aktuelle_orga start with berichtet_an is null connect by prior unternehmen || prior kst = berichtet_an; drop table tmp_aktuelle_orga; Listing 3.15: Erzeugen der Organisationsauswertung mit Hilfe eines Skriptes
Eine solche Verfahrensweise, eine undurchführbare oder übermäßig komplexe Abfrage mit Hilfe eines Skriptes in einzelne Teilschritte zu zerlegen, wobei am Ende eine zuvor erstellte einfache Ergebnistabelle ausgewertet wird, stellt unter Umständen nicht nur hier eine interessante Alternative dar. Zum einen werden hierdurch bestimmte Auswertungen überhaupt erst möglich und zum anderen gibt es Abfragen, die zerteilt in mehrere Einzelschritte insgesamt schneller laufen, als wenn sie als „Superabfrage“ gestartet werden. In unserem Beispiel löschen wir als Erstes die Tabelle „tmp_aktuelle_orga“, was innerhalb unseres Skriptes üblicherweise zu einer Hinweismeldung führt, da die Tabelle im Normalfall nicht vorhanden sein sollte. Anschließend wird die eben gelöschte Tabelle erstellt, wobei die neue Tabelle mit Hilfe der besonderen create table as select-Befehlsvariante, also durch Kopieren von Daten erzeugt wird. In dem hierbei verwendeten select-Befehl verknüpften wir die soeben erstellt View „aktuelles_gehalt“ mit der Organisationstabelle, wobei die von der View gelieferten Mitarbeiterdaten per Outer-Join verknüpft werden, so dass die gesamte Organisationsstruktur mit allen unbesetzten Knoten oder Kostenstellen erhalten bleibt.
Verknüpfungen
297
select b.*, a.persnr, a.lfdnr, a.name from aktuelles_gehalt a, organisation b where b.kst = a.kst (+);
Im nächsten Schritt unseres Skripts wird die erstellte Hilfstabelle unter Anwendung des connect by-Befehls ausgewertet. Dabei entspricht die Vorgehensweise eigentlich genau der Auswertung der einfachen Organisationstabelle. select level, unternehmen, kst, berichtet_an, persnr, lfdnr, name from tmp_aktuelle_orga start with berichtet_an is null connect by prior unternehmen || prior kst = berichtet_an;
Durch diese Abfrage werden die gewünschten Daten am Bildschirm angezeigt bzw. aus der Datenbank abgerufen und im Anschluss daran wird die temporäre Tabelle am Ende unseres Skripts wieder gelöscht. LVL ------1 2 3 4 4 3 4 4 3 4 4 3 4 4 3 4 4 3 3 3 3 ... 2 3 3 ... 3 48 rows
UNT --001 001 001 001 001 001 001 001 001 001 001 001 001 001 001 001 001 001 001 001 001
KST -------GF ENTW EDV EDV1 EDV2 EDV EDV1 EDV2 EDV EDV1 EDV2 EDV EDV1 EDV2 EDV EDV1 EDV2 PSOFT PSOFT PSOFT PSOFT
BERICHTET_AN PERSNR LFDNR NAME ------------- --------- ------- ----------------001GF 001ENTW 001EDV 001EDV 001ENTW 001EDV 001EDV 001ENTW 001EDV 001EDV 001ENTW 001EDV 001EDV 001ENTW 001EDV 001EDV 001ENTW 001ENTW 001ENTW 001ENTW
7000003
0 Heiden,Magareta
7000009
0 Zola,Wolf
7000016
0 Voltair,Samuel
7000019
0 Karlo,Ellen
7000016
2 Voltair,Samuel
7000004 7000005 7000015 7000020
0 0 0 0
Hardt,Andreas Nibelungen,Ellen Hilsamer,Kurt Raymans,Heinz-Gerd
001 VERW 002 MARK 002 MARK
001GF 001VERW 001VERW
7000006 7000022
0 Beckmann,Yonne 0 Keun,Monika
002 VERT selected.
001VERW
7000017
1 Hoerbinger,Julia
Listing 3.16: Ergebnis der mit Listing 3.15 erstellten Auswertung
298
3.3
Abfragen
Änderungsabfragen
Nachdem wir nun in den vorherigen Abschnitten die verschiedenen Auswahlabfragen behandelt haben, beschäftigen wir uns im Folgenden nun mit der zweiten Abfragekategorie, den sogenannten Änderungsabfragen. Wie der Name schon sagt, dienen die zu diesen Abfragen gehörenden SQL-Anweisungen dazu, die in den Tabellen gespeicherten Daten zu verändern. Hierbei kann man grundsätzlich wieder zwischen einfachen Änderungsabfragen und selbsterstellten Änderungscursorn unterscheiden. Im ersten Fall erfolgt die Änderung der Tabelle mit Hilfe eines speziellen SQL-Kommandos, wobei hierbei im Unterschied zu manch anderen Datenbanksystemen immer nur eine einzelne Tabelle verarbeitet wird. Der zweite Fall führt zur Verwendung von PL/SQL-Sprachelementen und ermöglicht Ihnen damit eigentlich so ziemlich alles was denkbar ist. Im Rahmen solcher Cursor können nicht nur mehrere Tabellen verarbeitet werden, sondern es sind ebenfalls unterschiedliche Transaktionen denkbar (Löschen der einen und Ändern der anderen Tabelle).
3.3.1
Ändern von Daten
Zum Ändern von Daten finden Sie im SQL-Sprachumfang üblicherweise den Befehl update, mit dessen Hilfe eine oder mehrere Spalten einer Tabelle geändert werden können. Im Unterschied zu manch anderem Datenbanksystem kennt der updateBefehl in Oracle allerdings keine from-Klausel, weshalb mit ihm immer nur eine Tabelle geändert bzw. verarbeitet werden kann. Insgesamt besitzt die update-Anweisung die nachfolgend aufgeführte Struktur. update set <Änderungsanweisungen> where
Zunächst müssen Sie hinter dem Schlüsselwort update die zu ändernde Tabelle spezifizieren. Als Zweites müssen Sie dahinter mit Hilfe der set-Anweisung die bzw. alle zu ändernden Spalten vorgeben. Dabei werden mehrere Spalten durch Komma getrennt und jede einzelne Spalte wird in der Form <Spaltenname> =
geändert. Die Berechnung des neuen Wertes können Sie mit Hilfe eines Ausdrucks erstellen, bei dem Sie neben Konstanten und Funktionen auch Bezug auf alle Spalten der Tabelle nehmen können. Zum Schluss können Sie mit Hilfe der whereBedingung die zu ändernden Datensätze durch Vorgabe gewöhnlicher Auswahlbedingungen einschränken. Im Listing 3.17 finden Sie zwei einfache Beispiele für die Verwendung der update-Anweisung. Aufgrund einer Änderungen der für das Geschlecht verwendeten Schlüssel müssen alle in der Datenbank vorhandenen Werte geändert werden. Männliche Mitarbeiter sollen zukünftig mit einer „1“ und die weiblichen Kollegen mit einer „2“ gekennzeichnet werden.
Änderungsabfragen
299
update personalien set geschlecht = '1' where geschlecht = 'M'; update personalien set geschlecht = '2' where geschlecht = 'W'; Listing 3.17: Verwenden einfacher update-Anweisungen
Verwenden von Unterabfragen Durch die Verwendung von Unterabfragen können Sie im Rahmen der updateAnweisung doch mehr als eine Tabelle verwenden, was sowohl für die where-Bedingung als auch für die Erstellung des Änderungsausdrucks gilt. Bei der Programmierung der set-Änderungsanweisung müssen Sie sich allerdings entscheiden, ob Sie einen Ausdruck oder eine Unterabfrage verwenden, d.h. das Vermischen dieser beiden Konstruktionen in der Form set zulage = zulage + (select ....)
ist nicht möglich. Wie Sie gleich sehen werden, besteht allerdings die Möglichkeit, die Felder der zu ändernden Tabelle innerhalb der Unterabfrage zu verwenden, so dass die eben genannte Konstruktion folgendermaßen umformuliert werden könnte. set zulage = (select zulage + ....)
Im Folgenden sollen unsere Gehaltsdaten geändert werden. Konkret soll die Zulage des aktuellsten Gehaltsdatensatzes mit einem zwölftel der Lohnsumme der Lohnart 350 aus dem Jahr 1999 erhöht werden. update gehalt a set a.zulage = (select a.zulage + nvl(sum(betr01+ betr02+ betr03+ betr04+ betr05+ betr06+ betr07+ betr08+ betr09+ betr10+ betr11+ betr12 ), 0 ) / 12 from lohnarten b where b.persnr = a.persnr and b.lfdnr = a.lfdnr and to_char(b.gab,'YYYY')='1999'
300
Abfragen
and b.la = '350' and b.satzart = 'DM' ) where a.gab = (select max(gab) from gehalt a1 where a1.persnr = a.persnr and a1.lfdnr = a.lfdnr ); Listing 3.18: Erstellen einer udpate-Anweisung unter Verwendung von Unterabfragen
Wie Sie dem Beispiel (vgl. Listing 3.18) entnehmen können, enthält sowohl die set, als auch die where-Klausel eine Unterabfrage zur Berechnung der neuen Werte bzw. zur Einschränkung der zu ändernden Datensätze. Beginnen wir bei der Beschreibung mit where-Bedingung, in der wir die schon mehrfach bekannte max-Unterabfrage zur Ermittlung des aktuellen Gehaltsdatensatzes verwenden. Da wir diesmal allerdings keine Einschränkung auf das gab-Datenfeld vornehmen, liefert die Unterabfrage in der Tat das Datum der letzten vorhandenen Gehaltssituation. Neben solchen Unterabfragen findet man zusammen mit update-Befehlen häufig auch Unterabfragen vom Typ exists bzw. not exists, so dass nur Datensätze geändert werden, wenn die in der Unterabfrage gesuchten Datensätze vorhanden bzw. eben nicht vorhanden sind. Außerdem möchte ich an dieser Stelle schon einmal vorwegnehmend bemerken, dass ich schon häufiger update-Anweisungen, bei denen im Auswahlbereich eine oder mehrere Unterabfragen verwendet wurden, durch einen entsprechenden Änderungscursor ausgetauscht habe, wodurch die gesamte Änderungstransaktion von der Datenbank schneller abgearbeitet wurde. Das liegt vor allem daran, dass Sie die Unterabfrage beim Cursor in eine gewöhnliche Verknüpfung überführen können, so dass die benötigten Datensätze eben in einigen Fällen schneller als mit der entsprechenden Unterabfrage geliefert werden. Die Berechnung der neuen Zulage erfolgt ebenfalls mit Hilfe einer Unterabfrage auf unsere Abrechnungsergebnisse, die in der Tabelle „lohnarten“ gespeichert sind. Dort suchen wir für die aktuelle Personalnummer die für die vorgegebenen Eckdaten (Lohnart, Jahr, Satzart) passenden Datensätze und ermitteln hierfür die Summe der vorhandenen Betragsfelder. Sind für eine Personalnummer gar keine Datensätze vorhanden, dann würde die verwendete sum-Funktion in dem Fall den Wert null zurückliefern, weshalb wir diese Fälle mit Hilfe der nvl-Funktion in eine reguläre 0 umwandeln. Diese Summe teilen wir anschließend durch zwölf und addieren auf diesen Quotienten den alten Zulagenbetrag, um damit den gewünschten neuen Betrag zu erhalten. Wie Sie dem Beispiel weiterhin entnehmen können, habe ich der zu ändernden Tabelle genau wie bei den Auswahlabfragen einen Aliasnamen gegeben, um die Verwendung der Felder innerhalb der Unterabfragen zu vereinfachen.
Änderungsabfragen
3.3.2
301
Löschen von Daten
Diesen Buchabschnitt hätte ich eigentlich recht schnell erstellen können, in dem ich zunächst den vorherigen Abschnitt kopiere und anschließend die Wörter Änderung durch Löschen und update durch delete ersetze. Aber da es Gott sei Dank doch ein paar kleine Unterschiede gibt, will ich es mir dann doch nicht ganz so einfach machen. Entsprechend dem SQL-Standard wird eine Löschanweisung durch das Schlüsselwort delete eingeleitet. Im Unterschied zu einigen anderen Datenbanksystemen und in Analogie zu den Änderungsabfragen kennt Oracle auch hierbei wieder keine from-Klausel, so dass die Löschabfrage prinzipiell dem folgendem Schema entspricht. delete where
Dabei folgt hinter dem Schlüsselwort delete der Name der Tabelle aus der die Datensätze gelöscht werden sollen und die where-Klausel führt in Form gewöhnlicher Auswahlbedingungen zur Auswahl der zu löschenden Datensätze. Das nächste Beispiel (vgl. Listing 3.19) zeigt hierfür ein Anwendungsbeispiel, indem mit Hilfe einer Löschabfrage alle Personalien gelöscht werden, deren Geschlecht nicht in einer Liste bestimmter Vergleichswerte enthalten ist. delete personalien where geschlecht not in ('M','W','1','2'); Listing 3.19: Verwenden einer einfachen Löschabfrage
Genau wie bei den Änderungsabfragen können Sie innerhalb der where-Bedingung wieder Ausdrücke mit Konstanten, Funktionen und allen in der Tabelle vorhandenen Feldern erstellen. Verwenden von Unterabfragen Auch beim Löschen besteht natürlich wieder die Möglichkeit, innerhalb der whereKlausel Unterabfragen zu verwenden, um das Löschen der Datensätze von Konstellationen innerhalb anderer Tabellen abhängig zu machen. Bleiben wir hierbei auch gleich bei unserem letzten Beispiel, dem Löschen spezieller Datensätze aus der Personalientabelle. Aufgrund unserer Datenstruktur müssen beim Löschen von Personalstammdaten eigentlich auch immer die eventuell zugehörigen Datensätze der anderen Tabellen gelöscht werden. Zwar sollte die Datenbank dies automatisch durchführen, und wir werden zu späterer Stunde auch noch an diesem Dilemma arbeiten, doch zur Zeit müssen wir noch selbst für die Konsistenz unserer Datenbank sorgen. Damit heißt unsere Aufgabenstellung beispielsweise zunächst alle Gehaltsdatensätze zu löschen, die mit fehlerhaften Personalstammsätzen verknüpft sind, bevor dann in einem zweiten Schritt genau diese fehlerhaften Personalien gelöscht werden.
302
Abfragen
delete gehalt a where exists(select 1 from personalien b where b.persnr = a.persnr and geschlecht not in ('M','W','1','2') ); Listing 3.20: Anwenden der delete-Anweisung zusammen mit einer exists-Unterabfrage
Mit Hilfe einer exists-Unterabfrage überprüfen wir für jeden Gehaltsdatensatz, ob der zugehörige Personalstammdatensatz fehlerhaft ist, was in dem Fall zum Löschen des aktuellen Gehaltsdatensatzes führt. Sind die Personalstammdatensätze allerdings schon weg, dann können Sie den Löschvorgang der Gehaltsdaten natürlich auch von der Nichtexistenz der zugehörigen Personalien abhängig machen, wofür Sie im Listing 3.21 ein Beispiel finden. delete gehalt a where not exists(select 1 from personalien b where b.persnr = a.persnr ); Listing 3.21: Löschen von Datensätzen mit Hilfe einer not exists-Unterabfrage
Bei dieser Variante prüfen wir für jeden Datensatz der Gehaltstabelle, ob ein zugehöriger Personalstammdatensatz existiert. Falls nicht, liegt eine fehlerhafte Datenkonstellation vor, weshalb wir den Gehaltsdatensatz in dem Fall löschen. Tabelle abschneiden Sofern Sie alle Datensätze einer Tabelle löschen wollen, können Sie das natürlich dadurch realisieren, indem Sie einfach keine where-Bedingung spezifizieren. So löscht die Anweisung delete gehalt;
alle vorhandenen Gehaltsdatensätze. Bei großen Tabellen mit vielleicht mehreren Millionen Datensätzen dürfte dieser Vorgang allerdings einen Moment dauern, da die Löschung jedes einzelnen Datensatzes im Rollback-Segment protokolliert wird, damit die gesamte Transaktion am Ende vielleicht doch noch zurückgerollt werden kann. Meistens ist das Löschen einer ganzen Tabelle aber ein Vorgang ohne „wenn und aber“, weshalb es hierfür in Oracle wie auch in vielen anderen Datenbanksystemen einen speziellen Befehl gibt. truncate table gehalt;
Mit der Anweisung truncate table löschen Sie die gesamte vorgegebene Tabelle. Beachten Sie hierbei allerdings, dass es nach dem Absenden dieser Anweisung kein Zurück mehr gibt, d.h. Sie müssen die Transaktion nicht durch einen commit-
Änderungsabfragen
303
Befehl abschließen, können sie im Gegenzug allerdings auch nicht durch ein rollback-Kommando rückgängig machen. Die Ausführung der truncate table-Anweisung führt intern zu einer Art Abschneiden des gesamten Tabelleninhalts, d.h. im Unterschied zur delete-Anweisung wird die Tabelle nicht Satz für Satz gelöscht, sondern dies passiert in einem Rutsch, weshalb der Befehl auch bei Tabellen mit vielen Millionen Datensätzen sofort fertig wird.
3.3.3
Einfügen von Daten
Die Letzte der einfachen Änderungsanweisungen dient dem Einfügen neuer Datensätze in eine Tabelle und heißt insert into. In manchen Datenbanksystemen fällt die Verwendung des zweiten Wörtchens into in die Kategorie Geschmacksache; in Oracle ist die gemeinsame Verwendung der beiden Schlüsselwörter jedoch zwingend. Bei der Anwendung der insert into-Anweisung existieren im Wesentlichen zwei unterschiedliche Varianten. Bei der einen Variante wird lediglich ein einzelner Datensatz in die Tabelle eingefügt, wobei hierbei die einzelnen Werte der verschiedenen Spalten vorgegeben werden. Im anderen Fall erfolgt das Einfügen der neuen Daten mit Hilfe einer Abfrage, so dass auf diese Weise beliebig viele Datensätze auf einmal in die Tabelle angefügt werden können. Außerdem können Sie eine Einfügeoperation mit oder ohne Aufzählung der einzelnen Feldnamen durchführen, wobei letzteres nur dann funktioniert, wenn Sie zum einen die zugehörigen Werte genau in der aus der Tabellendefinition resultierenden Reihenfolge liefern und zum anderen damit naheliegender weise alle Datenfelder der Zieltabelle spezifizieren. Einzelne Datensätze einfügen Die Kodierung einer Einfügeoperation für einen einzelnen neuen Datensatz entspricht im Prinzip der nachfolgend aufgeführten Struktur: insert into [Feldliste] values <Werteliste>
Hinter den Schlüsselwörtern insert into folgt der Tabellennamen, in der die Einfügeoperation durchgeführt werden soll. Anschließend können Sie in Klammern eine Liste von Feldnamen vorgeben, für die bzw. in deren Reihenfolge die einzufügenden Spaltenwerte geliefert werden. Diese müssen Sie nach dem Schlüsselwort values ebenfalls in Klammern spezifizieren. insert into gehalt (persnr, lfdnr, gab, kst, gehalt, zulage) values ('7000010', 1, to_date('2000-01-01','YYYY-MM-DD'), 'MARK', 7800, 100); Listing 3.22: Durchführen einer Einfügeoperation mit Hilfe einzelner Datenwerte
In unserem Beispiel (vgl. Listing 3.22) fügen wir mit Hilfe der insert into-Anweisung einen neuen Gehaltsdatensatz in die Datenbank ein. Wie Sie dem Listing entnehmen können, habe ich dabei hinter dem Tabellennamen alle vorhandenen Felder
304
Abfragen
in Klammern aufgezählt. Die dabei verwendete Reihenfolge ist gleichgültig, denn wichtig ist nur, dass die Reihenfolge der mit der values-Klausel gelieferten Werte zu dieser Feldnamensliste passt. Das Einzige, was Oracle in dem Zusammenhang überprüft, ist, ob die Datentypen entsprechender Feldnamen und Werte zueinander passen. Eigentlich sollten Sie eine solche Einfügeoperation immer genau in der eben beschriebenen Form benutzen, auch wenn Sie diese in besonderen Situationen etwas verkürzen können. Das ist der Fall, wenn Sie zum einen die genaue Reihenfolge der in der Tabelle gespeicherten Felder kennen und zum anderen in der values-Klausel für jedes Feld einen Wert in genau dieser Reihenfolge liefern. Im Zweifel können Sie die benötigte Feldreihenfolge mit Hilfe der desc-Anweisung ermitteln. SQLWKS> desc gehalt Column Name -----------------------------PERSNR LFDNR GAB KST GEHALT ZULAGE
Null? -------NOT NULL NOT NULL NOT NULL NOT NULL NOT NULL NOT NULL
Type ---VARCHAR2(11) NUMBER(38) DATE VARCHAR2(10) NUMBER(9,2) NUMBER(9,2)
Listing 3.23: Ermittlung der Struktur der Gehaltstabelle mit Hilfe der desc-Anweisung
Wenn Sie nun die einzelnen Werte genau in dieser Reihenfolge liefern, dann könnten Sie die insert into-Anweisung auch folgendermaßen verwenden: insert into gehalt values ('7000010', 1, to_date('2000-01-01','YYYY-MM-DD'), 'MARK', 7800, 100);
Ich persönlich halte davon wie ich zugeben muss, von besonderen Ausnahmefällen vielleicht einmal abgesehen, nicht so besonders viel, denn ich finde diese Form schwieriger nachzuvollziehen und fehleranfälliger. Auch wenn das vielleicht mal wieder Ansichtssache ist, so müssen Sie in jedem Fall zugeben, dass man hierbei die Struktur der Einfügetabelle im Kopf haben muss, um die Einfügeoperation verstehen zu können. Ich persönlich habe eigentlich so einiges im Kopf, wobei sich die Strukturen aller möglichen Eingefügetabellen nur selten darunter befinden. Einfügen mit Hilfe einer Abfrage Die andere Variante der Einfügeanweisung besteht darin, die Werte der neuen Datensätze mit Hilfe einer Abfrage zu ermitteln, was zu folgender Änderung unseres Befehlsschemas führt. insert into [Feldliste] select
Änderungsabfragen
305
Das Einzige was Sie hierbei beachten müssen, obwohl auch das eigentlich auf der Hand liegt, ist, dass Sie innerhalb der select-Klausel der Auswahlabfrage alle für die Zieltabelle benötigten Spalten bereitstellen müssen. Ansonsten können Sie in dieser Abfrage alle bisher kennen gelernten Konstrukte bzw. Techniken verwenden. Im Folgenden wollen wir uns diese Variante der insert into-Anweisung wieder an einem geeigneten Beispiel anschauen. In unserer Datenbank sollen alle aktiven bzw. mit keinem Austrittsdatum versehenen Mitarbeiter zum 01.01.2001 eine Gehaltserhöhung von fünf Prozent erhalten. Ein weiteres Kriterium für die automatische Erstellung dieses neuen Gehaltsdatensatzes ist allerdings auch, dass für den 01.01.2001 bzw. für spätere Termine noch keine Gehaltssituationen vorhanden sind. Bei solchen Aufgabenstellungen ist es nicht schlecht, im ersten Schritt zunächst einmal die benötigte Auswahlabfrage zu erstellen bzw. zu testen, bevor Sie diese im Rahmen der Einfügeoperation verwenden. SQLWKS> select a.persnr, a.lfdnr, to_date('2001-01-01','YYYY-MM-DD'), a.kst, a.gehalt * 1.05, a.zulage 2> from gehalt a, bvs b 3> where a.gab = (select max(gab) 4> from gehalt a1 5> where a1.persnr = a.persnr 6> and a1.lfdnr = a.lfdnr 7> ) 8> and a.gab < to_date('2001-01-01','YYYY-MM-DD') 9> and b.persnr = a.persnr 10> and b.lfdnr = a.lfdnr 11> and b.austrt is null 12> PERSNR LFDNR TO_DATE('2001-01-01' KST A.GEHALT*1 ZULAGE ----------- ---------- -------------------- -------- ---------- ------7000003 0 01-JAN-01 EDV 6825 0 7000004 0 01-JAN-01 PSOFT 8242.5 512 7000005 0 01-JAN-01 PSOFT 6825 220 7000006 0 01-JAN-01 MARK 6193.95 100 7000007 0 01-JAN-01 PERS 6825 200 7000009 0 01-JAN-01 EDV 6510 55.9 7000011 0 01-JAN-01 PSOFT 7560 0 7000012 0 01-JAN-01 PERS 5378.1 12 ... 17 rows selected.
Mit dieser Abfrage ermitteln wir für alle aktiven Mitarbeiter das neue Grundgehalt zum konstanten Datum 01.01.2001. Hierzu verknüpfen wir die Gehaltsdaten mit den Beschäftigungsdaten (Zeile 9 und 10) und legen mit Hilfe der Zeile 11 zusätzlich fest, dass das dort vorhandene Austrittsdatum leer sein muss.
306
Abfragen
Mit Hilfe der Unterabfrage der Zeilen drei bis sieben selektieren wir pro Mitarbeiter die jeweils aktuellste Gehaltshistorie. Ist deren Gültigkeitsdatum kleiner dem 01.01.2001 (siehe Zeile 8), dann existieren für den Mitarbeiter keine Zukunftsdaten und zum 01.01.2001 ist ebenfalls noch keine Gehaltshistorie vorhanden. Die hierdurch ausgewiesene Gehaltssituation entspricht also genau dem letzten Stand, mit Ausnahme des von uns konstant vorgegebenen Gültigkeitsdatums und dem neu berechneten Grundgehalt. Sofern Sie nun mit dem Ergebnis der Abfrage zufrieden sind, ich bin es, dann können Sie als Nächstes zur Erweiterung der eigentlich benötigten Einfügeanweisung übergehen. insert into gehalt (persnr, lfdnr, gab, kst, gehalt, zulage) select a.persnr, a.lfdnr, to_date('2001-01-01','YYYY-MM-DD'), a.kst, a.gehalt * 1.05, a.zulage from gehalt a, bvs b where a.gab = (select max(gab) from gehalt a1 where a1.persnr = a.persnr and a1.lfdnr = a.lfdnr ) and a.gab < to_date('2001-01-01','YYYY-MM-DD') and b.persnr = a.persnr and b.lfdnr = a.lfdnr and b.austrt is null; Listing 3.24: Verwenden von insert into zusammen mit einer Auswahlabfrage
Wie Sie dem Listing 3.24 entnehmen können, ist es von einer fertigen Auswahlabfrage zu einer Einfügeabfrage wahrlich kein weiter Weg. Eigentlich müssen Sie nur die Schlüsselwörter insert into darüber bzw. davor schreiben, den Namen der einzufügenden Tabellen vorgeben und anschließend die zur Auswahl passenden Spaltennamen auflisten.
3.3.4
Sperren von Datensätzen
Technisch sorgt das Datenbanksystem ganz alleine dafür, dass die Konsistenz der Datenbank trotz konkurrierender Änderungsabfragen im Mehrbenutzerbetrieb erhalten bleibt. Hierzu werden vom Datenbanksystem auch Sperrungen von Datensätzen vorgenommen, so dass andere Transaktionen unter Umständen warten müssen, bis die Sperrungen mit dem Beenden der zugehörigen Änderungsoperation aufgehoben werden. Manchmal ist das allerdings zu wenig und meistens aus ablauftechnischen Gründen möchte man Änderungen durch Dritte vermeiden, solange die eigene Änderungsabfrage in der Datenbank aktiv ist. Aus diesem Grund existiert die for update-Klausel, mit deren Hilfe Sie die gelesenen Daten vor Änderungen und Sperrversuchen andere Anwender schützen können. Dieser Schutz bleibt so lange erhalten, bis Sie die Transaktion mittels commitAnweisung abschließend oder durch ein rollback abbrechen bzw. zurückrollen.
Änderungsabfragen
307
Sie können die Verfahrensweise auch im Einbenutzerbetrieb gut ausprobieren bzw. simulieren, indem Sie einfach einmal Ihren SQL-Editor zweimal starten. Geben Sie anschließend im ersten Arbeitsfenster folgende Abfrage ein, um die Personalstammdaten für die selektierten Datensätze zu sperren. select * from personalien where persnr = '7000188' for update
Versuchen Sie nun im gleichen Arbeitsfenster den gelesenen und gesperrten Datensatz zu ändern, indem Sie beispielsweise folgende Änderungsabfrage verwenden. update personalien set geschlecht = 'W' where persnr = '7000188';
Wie nicht anders zu erwarten war, können Sie die gewünschte Änderungsoperation in der gleichen Sitzung ohne Probleme durchführen. Schließlich haben Sie die Sperrung ja auch für Ihren Gebrauch durchgeführt, d.h. Sie wollten anderen aber nicht sich selbst von der Bearbeitung ausschließen. Also gehen wir zum nächsten Schritt über und geben die gleiche Änderungsabfrage in der zweiten Programminstanz unseres SQL-Editors ein, wobei dieses Programm nach dem Absenden des Befehls hängt, weil die zweite Änderungsabfrage auf die Freigabe des gesperrten Datensatzes durch die erste Programminstanz wartet. Wenn Sie nun im ersten Arbeitsfenster die Befehle commit oder rollback zum Abschließen oder Zurückrollen Ihrer Transaktion eingeben, dann wird anschließend auch die Änderungsabfrage des zweiten Fensters sofort ausgeführt. Das Ganze funktioniert natürlich genauso, wenn Sie im zweiten Arbeitsfenster statt der Änderungsabfrage versuchen, den entsprechenden Datensatz ebenfalls zu sperren, d.h. Sie verwenden hier noch einmal die gleiche Selektionsabfrage mit der for updateKlausel. Wenn Sie nicht wollen, dass der Sperrversuch Ihre Abfrage für immer bzw. bis zum Auftreten eines eventuellen Timeouts hängen lässt, dann sollten Sie zusätzlich die nowait-Klausel verwenden, die dazu führt, dass Ihre Abfrage sofort mit einer entsprechenden Fehlermeldung beendet wird, sofern die zu sperrenden Objekte bzw. Datensätze aufgrund konkurrierender Zugriffe nicht verfügbar sind. select * from personalien where persnr = '7000188' for update nowait;
Verwenden mehrerer Tabellen Sind mehrere Tabellen an der Abfrage beteiligt, dann sperrt die for update-Klausel automatisch die selektierten Datensätze aus allen in der Abfrage verwendeten Tabellen. Die im folgenden Beispiel verwendete Abfrage sperrt also nicht nur den Personalstammdatensatz mit der Personalnummer „7000188“, sondern ebenfalls das Land mit dem Schlüssel „DEU“.
308
Abfragen
select a.persnr, a.name, a.land, b.bezeichnung from personalien a, laender b where b.land = a.land and a.persnr = '7000188' for update;
Ist dieser Effekt unerwünscht, dann können Sie die for update-Klausel erweitern und hierdurch die zu sperrenden Tabellen gezielt festlegen, indem Sie hinter dieser Klausel für jede zu sperrende Tabelle ein repräsentatives Feld aufzählen. Wie gesagt folgt diese Feldliste hinter der for update-Klausel und wird durch das Schlüsselwort of eingeleitet. select a.persnr, a.name, a.land, b.bezeichnung from personalien a, laender b where b.land = a.land and a.persnr = '7000188' for update of a.geschlecht, b.land;
Das letzte Beispiel bewirkt natürlich dasselbe, als wenn Sie die zusätzliche ofOption weggelassen hätten, denn konkret haben wir aus jeder beteiligten Tabelle ein Feld aufgezählt, so dass auch wieder die selektierten Datensätze jeder Tabelle gesperrt werden. Welches Feld Sie hierbei aus den Tabellen verwenden, ist in der Tat völlig egal, denn das Feld dient nur als Verweis auf die zugrundeliegende Tabelle. Sollen in unserem Beispiel jetzt aber wirklich nur die selektierten Personalstammdaten gesperrt werden, dann müssen Sie die Abfrage bzw. die for update ofKlausel folgendermaßen abändern. select a.persnr, a.name, a.land, b.bezeichnung from personalien a, laender b where b.land = a.land and a.persnr = '7000188' for update of a.geschlecht;
Sperren ganzer Tabellen Manchmal ist es nützlich, und das gilt vor allem oft für Batch-Hintergrundprozesse, alleiniger Herrscher über einen Datenbestand zu sein. Aus dem Grund besteht neben dem Sperren von selektierten Datensätzen auch noch die Möglichkeit, gleich ganze Tabellen gegen Änderungen durch Dritte zu schützen. Das nachfolgende Beispiel sperrt beispielsweise die komplette Ländertabelle vor dem Ändern bzw. Sperren von Datensätzen durch Dritte. lock table laender in share row exclusive mode;
Der hierzu notwendige Befehl heißt lock table, dem die zu sperrende Tabelle folgt. Dahinter müssen Sie das Schlüsselwort in verwenden, dem die Art der durchzuführenden Sperrung folgt. Abgeschlossen wird der Befehl durch das Schlüsselwort mode. Die hier verwendete Sperrmethode „share row exclusive“ führt wie schon gesagt zur Sperrung aller in der Tabelle vorhandenen Datensätze. Ähnliches hätten Sie auch mit der Methode „exclusive“ erreichen können, wobei Sie eine vollständige Übersicht aller verfügbaren Methoden in der Oracle-Dokumentation im Buch „Oracle8 SQL-Reference“ finden.
Änderungsabfragen
309
In jedem Fall gilt eine durchgeführte Sperrung so lange, bis Sie die Transaktion mit einem commit-Befehl beenden oder mittels rollback zurückrollen.
3.3.5
Erstellen eines Änderungscursors
Wie Sie mit Hilfe der letzten Abschnitte hoffentlich gesehen haben, ist die Verwendung der Änderungsabfragen bzw. der zugehörigen SQL-Anweisungen eigentlich recht einfach. Die zugrundeliegenden Anweisungen sind ziemlich kompakt, und wenn an einer Änderungsabfrage überhaupt etwas schwierig sein kann, dann ist das eine verwendete komplexe where-Bedingung. Allerdings können Sie mit den bisher kennen gelernten Änderungsabfragen immer nur eine Tabelle gleichzeitig ändern und manchmal zwingt einen die fehlende from-Klausel und der damit verbundene Verwendungszwang von Unterabfragen zu ineffizienten Auswahlkonstruktionen. Worum handelt es sich nun bei den jetzt beschriebenen Cursorn? Zunächst einmal handelt es sich hierbei um eine Konstruktion, die zumindest intern von Oracle bei allen bisher verwendeten Abfragetypen angewendet wird. Generell können Sie sich einen Cursor als einen Speicherbereich vorstellen, in dem die Ergebniszeilen einer Abfrage gepuffert werden, wobei zusätzliche Methoden bereitgestellt werden, die einzelnen Ergebniszeilen abzurufen. Bei jeder bisher erstellten Abfrage hat Oracle also zunächst einmal einen solchen Cursor implizit erstellt und die Daten mit seiner Hilfe bereitgestellt bzw. geändert. Worum es jetzt geht ist zum einen diesen Cursor explizit, als selbst, zu erstellen und das abrufen der einzelnen Ergebniszeilen selbst in die Hand zu nehmen, wozu Sie im PL/SQL-Sprachumfang die benötigten Hilfsmittel finden. Wie funktioniert nun aber die explizite Verwendung eines Cursors? Ich finde, man kann das Ganze recht gut mit der Verarbeitung einer sequentiellen Datei vergleichen (vgl. Abb. 3.5). Wie war das doch gleich noch: Zunächst musste eine solche Datei meistens mit Hilfe einer speziellen Anweisung (z.B. open) geöffnet werden. Anschließend kann die Datei solange Satz für Satz gelesen werden, bis das Dateiende erreicht wird, was meistens mit Hilfe einer Schleife realisiert wird. Ist das Ende der Datei erreicht, dann springen wir aus der Schleife heraus und schließen die Datei mit Hilfe einer entsprechenden Anweisung (z.B. close) wieder. Dieses Verfahren aus der Zeit von Adam und Eva (EVA = Eingabe – Vearbeitung – Ausgabe) kommt nun auch hier wieder zum Tragen, denn die eben beschriebenen Praktiken aus einem typischen EDV-Einführungskurs lassen sich auch jetzt ganz gut anwenden. Was sich hier allerdings von unseren vielleicht ersten EDV-Gehversuchen unterscheidet ist, dass wir in der Oracle-Datenbank keine sequentielle Datei zum Öffnen haben, wobei sich dieses Dilemma durch ein wenig Kreativität leicht überwinden lässt, indem wir uns die sequentielle Datei als logisches Gebilde und als Endergebnis unserer durchgeführten Abfrage vorstellen.
310
Abfragen
Öffnen der Datei
Ende der Datei erreicht?
Nein
Datensatz lesen
Ja
Schließen der Datei Abbildung 3.5: Sequentielles Verarbeiten einer Datei
Es geht im Folgenden nun wirklich darum, in einem ersten Schritt die benötigte Ergebnismenge bereitzustellen und diese Menge im zweiten Schritt sequentiell in einem der Abbildung 3.5 entsprechenden Schema zu verarbeiten. Natürlich ist die Behandlung von Cursorn an dieser Stelle im Buch eine kleine Gratwanderung, denn eigentlich gehört das Thema komplett in den Bereich PL/ SQL-Programmierung und wird von mir auch dort noch einmal aufgegriffen. Auf der anderen Seite passt es aber auch hier ganz gut ins Konzept, wo es darum geht die Möglichkeiten aufzuzeigen, die Sie zum Ändern bzw. Verarbeiten der in der Datenbank gespeicherten Daten haben. Ich werden also hier und jetzt die Verwendung eines Cursors nur an kleineren Beispielen demonstrieren und Sie für weitergehende Informationen zum Weiterlesen des Buches nötigen. Kehren wir nun zunächst noch einmal an den Ort der Löschabfragen zurück. Dort hatten wir die Aufgabenstellung, Datensätze mit ungültigen Personalstammdaten aus der Datenbank zu löschen. Dabei bestand beispielsweise beim Verarbeiten der Gehaltdaten das Problem darin, für den jeweiligen Gehaltsdatensatz den löschrelevanten Zustand der Personalstammdaten zu erkennen, was damals mit Hilfe entsprechender Unterabfragen realisiert wurde. Diesmal wollen wir anders vorgehen und zunächst eine Ergebnismenge mit allen fehlerhaften Personalstammdaten erzeugen und diese zusammen mit allen anderen zugehörigen Sätzen aus anderen Tabellen während der sequentiellen Verarbeitung der Ergebnismenge löschen. Im Listing 3.25 finden Sie das hierzu benötigte Skript, wobei ich mich wegen der besseren Übersicht auf das Löschen der Personalien und Gehälter beschränkt habe. declare cursor falsche_personalien is select persnr, name from personalien a
Änderungsabfragen
311
where geschlecht not in ('M','W','1','2') for update of a.persnr; in_record falsche_personalien%rowtype; begin open falsche_personalien; loop fetch falsche_personalien into in_record; exit when falsche_personalien%notfound; delete gehalt where persnr = in_record.persnr; delete personalien where current of falsche_personalien; end loop; close falsche_personalien; end; Listing 3.25: Löschen von Daten mit Hilfe eines PL/SQL-Cursors
Nachdem Sie nun durch das Listing 3.25 das vollständige Programm kennen, wollen wir es als nächstes Stück für Stück auseinandernehmen und dabei auch die eine oder andere Variante darstellen, wobei Sie im PL/SQL-Kapitel weitere Möglichkeiten zur Verarbeitung der Cursormenge finden. Zunächst einmal besteht unser Skript aus einem Deklarations- und einem Ausführungsteil, der zwischen den Schlüsselwörtern begin und end programmiert wird. Der Deklarationsteil wird mit dem Schlüsselwort declare eingeleitet und dient zur Anlage aller benötigten Variablen und eben auch zur Definition des eigentlichen Cursors. cursor falsche_personalien is select persnr, name from personalien a where geschlecht not in ('M','W','1','2') for update of a.persnr;
Dieser wird mit Hilfe des Schlüsselwortes cursor definiert, dem der im Skript eindeutige und ansonsten frei vergebare Name des Cursors folgt unter dem das Cursorobjekt im weiteren Skript angesprochen werden kann. Die Kodierung der dem Cursor zugrundeliegenden Abfrage erfolgt nach dem Schlüsselwort is und entspricht einer normalen Auswahlabfrage. Bei Bedarf kann der definierte Cursor einen Zeiger auf eine der an der Abfrage beteiligten Tabellen mitführen, indem Sie am Ende der Abfrage die for update of-Klausel angeben.
312
Abfragen
for update of a.persnr
Wie Sie schon wissen, führt diese Anweisung zum einen zur Sperrung der gelesenen Datensätze, aus der dem Feld zugrundeliegenden Tabelle. Zum anderen eröffnet die Klausel Ihnen innerhalb der Cursorprogrammierung allerdings auch die Möglichkeit, die zum Feld gehörende Tabelle für die aktuell gelesene Datenzeile auf besonders einfache Weise zu ändern. Mit Hilfe bzw. hinter der for update of-Klausel müssen Sie ein Feld, ggf. zusammen mit dem jeweiligen zugewiesenen Aliasnamen, aus derjenigen Tabelle angeben, für die Sie den angedeuteten internen Änderungszeiger wünschen. Das hier genannte Feld dient also wirklich nur als Platzhalter zur Erstellung eines Zeigers auf die zugehörige Tabelle und muss selbst im Rahmen des Programms nicht verändert werden. Im Umkehrschluss heißt das natürlich auch, dass die zu ändernden Felder nicht unbedingt in dieser Klausel aufgezählt werden müssen; falls Sie es dennoch tun, dann hat das höchstens Dokumentationscharakter. in_record falsche_personalien%rowtype
Neben dem Cursor definieren wir noch eine Struktur mit dem Namen in_record. Sie dient uns später zur Aufnahme der einzelnen gelesenen Datensätze und aufgrund der Verwendung des Ausdrucks falsche_personalien%rowtype enthält die Struktur automatisch alle innerhalb des Cursors selektierten Spalten. Innerhalb des begin und end-Blocks befindet sich das eigentliche Programm, das im Prinzip dem Schema der Abbildung 3.5 entspricht. Zunächst wird der Zugriff auf die Ergebnismenge eröffnet, indem der Cursor mit einem open-Befehl geöffnet wird. Am Ende des Programms wird er wieder mit Hilfe des close-Kommandos geschlossen. open falsche_personalien; loop fetch falsche_personalien into in_record; exit when falsche_personalien%notfound; ... end loop; close falsche_personalien;
Dazwischen erfolgt das Abrufen der einzelnen Datensätze mit Hilfe einer Endlosschleife. Innerhalb der wird zunächst versucht, den nächsten Datensatz der Ergebnismenge vom Cursor mit Hilfe des fetch-Befehls abzurufen und die zugehörigen Daten in die hierfür definierte Struktur in_record zu kopieren. Die danach folgende Programmzeile führt zum Ausstieg aus der eigentlichen Endlosschleife, wenn der vorherige fetch-Befehl keinen Datensatz mehr geliefert hat, weil bereits alle Datensätze des Cursors verarbeitet wurden. Im weiteren Innenleben der Schleife findet dann die eigentliche Verarbeitung statt.
Änderungsabfragen
313
delete gehalt where persnr = in_record.persnr; delete personalien where current of falsche_personalien;
Zunächst löschen wir mit Hilfe einer gewöhnlichen delete-Anweisung die Gehaltsdaten für die aktuell geladene Personalnummer. Anhand der zugehörigen whereBedingung können Sie auch erkennen, wie Sie auf die einzelnen in der Struktur in_record gespeicherten Elemente zugreifen können, wobei das Verfahren den Struktur- und Elementnamen und einen Punkt als Trennzeichen zu verwenden in vielen Programmiersprachen verwendet wird. Unsere Struktur enthält wegen des select-Befehls bei der Cursordefinition also insgesamt folgende zwei Elemente. in_record.persnr in_record.name
Die zweite delete-Anweisung dient zum Löschen der Personalstammdaten. Hierbei benutzen wir eine besondere Form der where-Klausel, die mit dem bei der Cursordefinition angelegten Datensatzzeiger zusammenarbeitet und automatisch den zugehörigen Datensatz aus der entsprechenden Tabelle selektiert. Die current ofKlausel stellt also eine Verbindung zwischen dem aktuellen Datensatz des Cursors und der mit Hilfe der for update of-Klausel spezifizierten Tabelle dar. Varianten Statt der verwendeten Struktur in_record können Sie die einzelnen Spalten des Cursors natürlich auch in gewöhnliche Variablen laden. Hierbei müssen Sie für jede im Cursor definierte Spalte eine entsprechende Variable definieren, die Sie innerhalb des fetch-Befehls hinter dem into-Schlüsselwort in der richtigen Reihenfolge aufzählen müssen. declare cursor falsche_personalien is select persnr, name from personalien a where geschlecht not in ('M','W','1','2') for update of a.persnr; xpersnr varchar2(11); xname varchar2(30); begin open falsche_personalien; loop fetch falsche_personalien into xpersnr, xname; exit when falsche_personalien%notfound; delete gehalt
314
Abfragen
where persnr = xpersnr; delete personalien where current of falsche_personalien; end loop; close falsche_personalien; end;
Außer, dass Sie jetzt statt der Strukturelemente die entsprechenden Variablen verwenden müssen, ändert sich im Programm ansonsten gar nichts. Ich persönlich bevorzuge das Laden des gesamten Datensatzes in eine Struktur, da diese Verfahrensweise resistenter gegen Änderungen bzw. Erweiterungen ist. Verwenden der Pseudo-Spalte rowid In unserem letzten Beispiel haben wir die Gehaltsdaten mit Hilfe des ersten Teils des Primärschlüssels gelöscht. Was ist aber zu tun, wenn die im Cursor verarbeitete Tabelle überhaupt keinen eindeutigen Schlüssel besitzt und wir die Änderungsoperation trotzdem möglichst effizient durchführen wollen? In dem Fall kann der Zugriff über die Pseudospalte rowid eine mögliche Lösung sein, dann der Zugriff auf einen Datensatz über die rowid ist der schnellste aller zur Zeit möglichen Varianten. SQLWKS> select rowid, persnr, lfdnr, gab from gehalt; ROWID PERSNR LFDNR GAB ------------------ ----------- ---------- -------------------AAAApHAADAAABESAAA 7000001 0 01-JAN-90 AAAApHAADAAABESAAB 7000002 0 01-APR-98 AAAApHAADAAABESAAC 7000002 0 01-APR-99 AAAApHAADAAABESAAD 7000003 0 01-AUG-99 AAAApHAADAAABESAAE 7000004 0 01-APR-98 AAAApHAADAAABESAAF 7000004 0 01-JUL-98 AAAApHAADAAABESAAG 7000004 0 01-APR-99 AAAApHAADAAABESAAJ 7000005 0 01-FEB-99 AAAApHAADAAABESAAK 7000005 0 01-SEP-99 ... 49 rows selected. Listing 3.26: Anzeige der für jeden Datensatz verfügbaren rowid
Bei dieser rowid handelt es sich um eine Art Datensatznummer, mit deren Hilfe jeder Datensatz einer Tabelle direkt und eindeutig identifiziert werden kann. Technisch handelt es sich hierbei um einen String, der hexadezimale Werte enthält und den Sie innerhalb von Abfragen in der select-Klausel und in jeder where-Klausel verwenden können. select * from gehalt where rowid = 'AAAApHAADAAABESAAK';
Änderungsabfragen
315
Dank der teilweise gut funktionierenden impliziten Konvertierung verschiedener Datentypen funktioniert das eben gezeigte Beispiel einwandfrei. Innerhalb von Oracle, beispielsweise in einem PL/SQL-Programm, können Sie den Datentyp rowid zum Speichern der Datensatzzeiger verwenden. Wenn Sie einen solchen Wert in einem anderen Programm (z.B. Cobol, Visual Basic oder SQR) speichern müssen, dann können Sie hierzu eine normale Zeichenkette bzw. Stringvariable verwenden. Allerdings bin ich kein Freund impliziter Datentypskonvertierungen, da ich hierbei schon meinen Lehrgeldbeitrag geleistet habe. Bei Programmierarbeiten für ein anderes Datenbanksystem wurde noch innerhalb des Projekts durch das Einspielen eines Miniupdates die implizite Konvertierung von Zeitwerten in Strings und umgekehrt geändert, so dass viele Teile des Programms anschließend nicht mehr korrekt funktionierten. Diese Erfahrung hat mich saniert und deshalb empfehle ich auch Ihnen, nach Möglichkeit Datentypumwandlungen immer explizit vorzunehmen. In unserem konkreten Beispiel der rowid sieht das Ganze dann folgendermaßen aus. select rowidtochar(rowid) from gehalt where rowid = chartorowid('AAAApHAADAAABESAAK');
Wie sie sehen, existiert für die Umwandlung der Pseudospalte rowid in Zeichnketten und umgekehrt die Funktionen rowidtochar bzw. chartorowid und immer wenn Sie solche Werte mit Hilfe von Stringvariablen zwischenspeichern müssen, dann sollten Sie von diesen beiden Funktionen gebrauch machen. Um die konkrete Anwendung der rowid-Verwendung kurz zu demonstrieren, kehren wir noch einmal zu unserem letzten Cursor-Beispiel zurück und ändern die dem Cursor zugrundeliegende Abfrage, indem wir anstelle der current of-Klausel die rowid für den Zugriff auf die Personalstammdaten verwenden (vgl. Listing 3.27). declare cursor falsche_personalien is select rowid, persnr from personalien a where geschlecht not in ('M','W','1','2'); xpersnr varchar2(11); xrowid rowid; begin open falsche_personalien; loop fetch falsche_personalien into xrowid, xpersnr; exit when falsche_personalien%notfound; delete gehalt where persnr = xpersnr;
316
Abfragen
delete personalien where rowid = xrowid; end loop; close falsche_personalien; end; Listing 3.27: Verwendung der rowid in unserem Löschcursor
Wie Sie in dem Beispiel sehen, laden wir den Datensatzzeiger für die selektierten Personalstammsätze im Rahmen des fetch-Befehls in die Variable xrowid. Da die vom Datentyp rowid ist, kann der vom Cursor gelieferte Wert ohne Konvertierung gespeichert werden. Ein paar Zeilen später verwenden wir diese rowid, um den zugehörigen Personalstammdatensatz zu löschen. Wenn Sie jetzt die Stirn runzeln, dann ist das sicherlich nicht ganz unberechtigt, denn im Augenblick stellt sich im Vergleich zu den vorherigen Lösungen für die beschriebene Aufgabenstellung schon die Frage, warum wir uns das Ganze hier freiwillig ein wenig komplizierter gestalten. Absetzen zwischenzeitlicher commits Die Antwort auf diese Frage will ich Ihnen natürlich nicht schuldig bleiben, denn wie für die meisten Dinge gibt es auch hierfür mal wieder einen Grund. Bei langlaufenden Programmen oder wenn während der Verarbeitung Unmengen von Daten geändert werden ist die Datenbank manchmal dankbar, wenn in regelmäßigen Abständen commit-Befehle abgesendet werden. Wie Sie aber schon wissen, führt ein commit-Befehl zur Aufhebung der for update-Sperrungen und damit auch zur Inaktivierung des current of-Datensatzzeigers. Sollen nun im Rahmen eines Änderungscursors ein commit-Befehl in regelmäßigen Abständen abgesetzt werden, dann müssen Sie auf die Verwendung der current ofKlausel verzichten und den Zugriff auf die zu ändernden Daten beispielsweise mit Hilfe der rowid selbst in die Hand nehmen. declare cursor falsche_personalien is select rowid, persnr from personalien a where geschlecht not in ('M','W','1','2'); xpersnr varchar2(11); xrowid rowid; ct number; begin open falsche_personalien; loop fetch falsche_personalien into xrowid, xpersnr; exit when falsche_personalien%notfound;
Änderungsabfragen
317
delete gehalt where persnr = xpersnr; delete personalien where rowid = xrowid; if ct > 10 then ct := 1; commit; else ct := ct +1; end if; end loop; close falsche_personalien; end; Listing 3.28: Verwenden von commit-Befehlen in einem Änderungscursor
Im letzten Beispiel wird mit Hilfe eines Zählers (vgl. Listing 3.28) nach jeweils zehn verarbeiteten Datensätzen ein commit-Befehl abgesetzt, um die bis dahin geänderten Datensatze unwiderruflich in die Datenbank zurückzuschreiben. Damit das möglich ist, müssen wir, wie schon gesagt, auf die for update- und damit auf auf die current of-Klausel verzichten und verwenden stattdessen die rowid für den direkten Zugriff auf die Tabelle. Natürlich könnten wir auch die Personalnummer, die schließlich Primärschlüssel der Personalstammdaten ist, verwenden, was im Endergebnis nicht viel aber vielleicht doch ein klitzekleines bisschen langsamer gewesen wäre. Verwenden der returning-Klausel Manchmal benötigt man in seinem Programm noch einmal die durch die Abfrage geänderten Werte, was üblicherweise zum Lesen der Tabelle vor dem Löschen bzw. direkt nach dem Ändern oder Einfügen führt. Oracle bietet Ihnen aber die Möglichkeit, diese Werte im Rahmen der Abfrage direkt in entsprechende Variablen zu überführen, so dass sich das nochmalige bzw. vorherige Lesen der zugehörigen Tabelle erübrigt. Die als Beispiel hierzu passende Aufgabenstellung verlangt, die aktuellen Gehälter unserer Mitarbeiter um fünf Prozent zu erhöhen, wobei wir eine Hinweismeldung benötigen, wenn der Mitarbeiter nach der Gehaltserhöhung die Grenze von 6000,00 (_ oder DM?) übersteigt. Die Lösung dieser Aufgabenstellung finden Sie im Listing 3.29 und daran im Anschluss finden Sie die Beschreibung der neuen Programmteile. set serveroutput on; declare
318
Abfragen
cursor neues_gehalt is select a.rowid, a.* from gehalt a, bvs b where b.persnr = a.persnr and b.lfdnr = a.lfdnr and b.austrt is null and a.gab = (select max(gab) from gehalt a1 where a1.persnr = a.persnr and a1.lfdnr = a.lfdnr ) for update of a.gehalt; gehalt_rec neues_gehalt%rowtype; ngehalt number(7,2); begin open neues_gehalt; loop fetch neues_gehalt into gehalt_rec; exit when neues_gehalt%notfound; update gehalt set gehalt = gehalt * 1.05 where rowid = gehalt_rec.rowid returning gehalt into ngehalt; if ngehalt > 6000 and gehalt_rec.gehalt < 6000 then dbms_output.put_line('Gehalt, alt: ' || to_char(gehalt_rec.gehalt,'99990.00') || ' neu: ' || to_char(ngehalt,'99999.99')); end if; end loop; close neues_gehalt; end; Listing 3.29: Verwenden eines Cursors mit anschließender Prüfung der neuen Feldwerte
Schon wieder ein neuer Cursor und schon wieder schleichen sich damit fast zwangsläufig neue PL/SQL-Sprachelemente ins Programm. Zunächst einmal möchte ich an dieser Stelle nur kurz darauf hinweisen, dass Sie mit Hilfe des im Programm verwendeten Pakets dbms_output Hinweismeldungen generieren können, die nach der Programmausführung am Bildschirm angezeigt werden, sofern zusätzlich die Option set serveroutput on gesetzt wurde. Mehr zu dem Thema erfahren Sie allerdings erst im weiteren Verlauf des Buches im Kapitel „PL/SQL-Programmierung“.
Änderungsabfragen
319
Außerdem denke ich, dass ich die dem Cursor zugrundeliegende Abfrage, die mal wieder den aktuellsten Datensatz für alle Mitarbeiter ohne Austrittsdatum liefert auch nicht mehr beschreiben muss, da Sie die in der oder ähnlicher Form schon mehrmals in diesem Workshop gesehen haben. Worauf es in dem Beispiel hauptsächlich ankommt, ist der Abschluss der update-Anweisung, wodurch der neue Gehaltsbetrag in die dafür angelegte Variable ngehalt kopiert wird. returning gehalt into ngehalt;
Hierdurch ersparen wir uns den erneuten Zugriff auf die Gehaltsdaten und können mit Hilfe der nachfolgenden if-Abfrage prüfen, ob mit dem neuen Gehaltsbetrag die sechstausender-Grenze überschritten wurde. In dem Fall geben wir mit Hilfe der Paketprozedur put_line eine Hinweismeldung aus, so dass das Ganze zumindest in meinem aktuellen Datenbestand folgendermaßen aussieht. Gehalt, alt: Gehalt, alt:
5899.00 neu: 5800.00 neu:
6193.95 6090.00
Zusammenfassung Die letzten Seiten des Buches konnte sicherlich nur einen Eindruck vermitteln, was mit Hilfe eines PL/SQL-Cursors alles möglich ist, denn allumfassend kann man die Möglichkeiten gar nicht beschreiben, da sie nahezu endlos sind. Auf der anderen Seite haben Sie sicherlich auch gemerkt, dass die klassischen datensatzbezogenen Verarbeitungsformen (Satz lesen, Verarbeitung durchführen ...) auch im SQL-Zeitalter nicht unbedingt völlig überholt sind, denn manchmal bietet diese Technik Vorteile gegenüber der rein mengenmäßigen Verarbeitung mit Hilfe der standardmäßigen SQL-Befehle. Damit möchte ich dieses Thema dann auch zunächst einmal mit der Anmerkung abschließen, dass Sie weitere Beispiele und Varianten zu Cursorn und PL/SQL-Programmen im weiteren Verlauf dieses Workshops finden werden.
3.3.6
Transaktionen
Das ist mal wieder ein Kapitel, das man durch einen Ausflug in die zugehörige Datenbanktheorie einleiten könnte. Allerdings will ich es auch hier bei einer einfachen Definition belassen und eine Transaktion einfach als eine logisch zusammengehörende Einheit einer oder mehrerer Datenbankoperationen beschreiben. Ein hierfür viel gelittenes aber einfaches Beispiel ist das einer Banküberweisung. Wie Sie alle wissen, wandert im Rahmen einer solchen Überweisung ein Geldbetrag von einem auf ein anderes Konto, was wir uns technisch als Ergebnis zweier updateAnweisungen vorstellen könnten. Außerdem wird der Geldfluss selbst noch einmal in ein Journal eingetragen, so dass zusätzlich auch noch eine Einfügeanweisung entsteht. update umsaetze set betrag = betrag + 100 where konto = '1234';
320
Abfragen
update umsaetze set betrag = betrag – 100 where konto = '2345'; insert into journal(konto_von, konto_bis, betrag) values ('1234', '2345', 100);
Diese beiden update-Anweisungen und die insert-Anweisung gehören zusammen wie das berühmte Pech und Schwefel, d.h. es sollen entweder alle oder keine der Änderungsabfragen in der Datenbank ausgeführt. Dabei ist es gleichgültig, warum eine der drei Abfragen auf der Strecke bleibt, d.h. selbst wenn ein technischer Defekt dafür verantwortlich ist, dass die zweite Änderung abbricht, dann muss die schon ausgeführte erste Anweisung beim Wiederhochfahren der Datenbank (recovery) zurückgerollt werden. Etwas allgemeiner betrachtet, weisen Transaktionen insgesamt folgende Eigenschaften aus:
X X
X X
Wie schon gesagt, werden alle Anweisungen innerhalb einer Transaktion wie eine logische Einheit betrachtet, d.h. das DBMS führt entweder alle oder gar keine Anweisungen der Transaktion aus. Eine Transaktion überführt die Datenbank von einem konsistenten Zustand in den nächsten konsistenten Zustand. Zwischenzeitlich auftretende Inkonsistenzen sind dabei für andere Transaktion unsichtbar. Auf unser Überweisungsbeispiel übertragen bedeutet das, dass sich für alle anderen Anwender die Salden der an der Überweisung beteiligten Konten nicht ändern, solange die Transaktion nicht abgeschlossen wurde. Transaktionen laufen isoliert ab, d.h. jede einzelne Transaktion läuft quasi in einem simulierten Einbenutzerbetrieb. Die Ergebnisse einer erfolgreich beendeten Transaktion sind dauerhaft in der Datenbank gespeichert.
Das bisher Gesagte ist strenggenommen zunächst einmal keine spezielle Facette einer Oracle-Datenbank, sondern resultiert aus den allgemeinen Beschreibungen bzw. Anforderungen von Datenbanksystemen, d.h. wir werden uns erst jetzt im zweiten Schritt anschauen, welche Auswirkungen das Ganze in unserem OracleDBMS hat. Im Unterschied zu manch anderen Datenbanksystemen ist die Anwendung des Transaktionsprinzips in Oracle keine optionale Komponente, die bei Bedarf eingeschaltet oder in besonderen Situationen auch weggelassen werden kann. In Oracle beginnt eine Transaktion immer mit der ersten auszuführbaren SQL-Anweisung und endet entweder erfolgreich oder bricht wegen einer Fehlersituation ab bzw. wird am Ende oder zwischendurch explizit zurückgerollt. Die Aufforderung eine Transaktion erfolgreich abzuschließen erfolgt mit Hilfe der Anweisung commit, weshalb wir im bisherigen Verlauf des Workshops diesen Befehl auch immer als letzte Anweisung in allen unseren Beispielen verwendet haben. Zurückgerollt wird eine Transaktion entweder automatisch durch einen
Änderungsabfragen
321
Fehler oder manuell durch das Absetzen des rollback-Kommandos. Ich habe mir angewöhnt, alle SQL-Befehle, ausgenommen von Auswahlabfragen, mit einer commit-Anweisung abzuschließen, was strenggenommen eigentlich nicht notwendig ist, da alle Anweisungen, die zur Anlage oder Änderung von Datenbankobjekten führen, auch eine implizite commit-Anweisung beinhalten. Die Transaktionen solcher sogenannten DDL-Anweisungen (DDL = Data Definition Language), mit denen Sie beispielsweise Tabellen, Indexe oder Prozeduren in der Datenbank anlegen, werden nach der erfolgreichen Ausführung immer sofort automatisch abgeschlossen, weshalb das manuelle Nachschicken einer commitAnweisung eigentlich „doppelt gemoppelt“ ist. Aber auf diese Weise gewöhnt man sich aber an die ansonsten notwendige commit-Verwendung. Ich habe schon Kollegen zur Fragestunde im Büro gehabt, die nicht verstanden haben, warum sich Ihre Session immer beim Absetzen einer alter table-Anweisung aufhängt, was konkret daran lag, dass bei einer vorherigen Änderungsabfrage auf diese Tabelle der abschließende commit-Befehl vergessen wurde. Deswegen konnte das Objekt zur Strukturänderung nicht gesperrt werden, was zum Warten der Session mit dem alter table-Befehl führt, so dass es den Anschein hat, als hätte sich die Sitzung aufgehängt. Wenn man von anderen Datenbanksystemen nach Oracle wechselt, dann muss man sich unter Umständen an dieses gezwungene Transaktionsprinzip erst ein wenig gewöhnen. Das gilt vor allem dann, wenn Sie bisher Batchprogramme oder Skripte mit endlosen vielen SQL-Anweisungen erstellt haben. Sie können sich sicherlich vorstellen, dass das mitführen einer Transaktion mit allen seinen Protokollen und Garantien einen gewissen Overhead darstellt. Aus diesem Grund sollten Sie falls möglich Ihre Programme oder Skripte durch den Einbau gelegentlicher commit-Anweisungen verschönern. Falls das nicht geht, dann müssen Sie in jedem Fall darauf achten, dass alle Ihre Änderungen in das bei der Transaktion verwendeten Rollback-Segement passen. Das ist allerdings kein Grund, jetzt alle definierten Segmente übermäßig groß zu gestalten, denn Sie können das während der Transaktion bzw. das während Ihres Programmlaufs zu verwendende Segment zuweisen. Rollback-Segment zuweisen Hierzu existiert in Oracle’s SQL-Sprachumfang die Anweisung set transaction in der Variante use rollback segment, hinter der Sie den Namen des zu verwendenden Rollback-Segements spezifizieren müssen. Das nachfolgende Beispiel verwendet für die durchzuführende Änderungstransaktion das Rollback-Segment mit dem Namen „rb0“. set transaction use rollback segment rb0; update gehalt set gehalt = gehalt * 1.05; rollback;
Implizite commits Wie schon gesagt, führen DDL-Anweisungen nach deren Ausführungen zu impliziten commit-Anweisungen. Das müssen Sie natürlich auch in Ihren Programmen
322
Abfragen
berücksichtigen, denn wenn Sie zwischendurch einmal eine neue Arbeitstabelle oder einen Index definieren, dann werden wegen des damit implizit verbundenen commits auch alle anderen bisher durchgeführten Änderungsabfragen abgeschlossen. Aus dem Grund kann es sinnvoll sein, alle benötigten DDL-Anweisungen an den Programmanfang zu verlagern. Außerdem macht es Sinn, bei verwendeten Tools oder Programmiersystemen (z.B. Cobol oder SQL) einmal nachzuschauen, was diese Programmsysteme bei einem Abbruch oder dem normalen Programmende in der Datenbank tun. So führt ein SQR-Programm beispielsweise automatisch ein commit in der Datenbank durch, wenn das gestartete Programm bzw. die zugehörige SQR-Workbench normal beendet wird, wobei bei einem Programmabbruch automatisch ein rollback abgesetzt wird. Längere Lesekonsistenz erzwingen Oracle garantiert für die in einer Abfrage selektierten Daten natürlich ganz von alleine, dass diese untereinander konsistent sind, wobei diese Konsistenz auf der anderen Seite nur für eine logische Sekunde gegeben sein muss, denn im Mehrbenutzerbetrieb kann es natürlich vorkommen, dass just in dem Moment jemand anderes Änderungen an dem von Ihnen gelesenen Datenbestand vornimmt. Aus dem Grund kann es natürlich leicht passieren, dass wenn Sie in einem Programm die Tabellen in einem späteren Schritt noch einmal lesen, die Daten nicht mehr vorhanden sind oder andere Werte enthalten. Sofern dieser in einer Mehrbenutzerumgebung eigentlich ganz normale Sachverhalt nicht tragbar ist, dann gibt es neben dem Kopieren aller benötigter Daten noch die Möglichkeit, auch Ihre Auswahlabfragen in einer Transaktion ablaufen zu lassen, so dass wegen der Transaktion für die gesamte Ausführungsdauer Lesekonsistenz garantiert wird. Auch hierfür können Sie die Anweisung set transaction allerdings diesmal in der Variante read only verwenden. Wir wollen das im Folgenden ausprobieren, wozu Sie wieder zwei Sitzungen Ihres SQL-Editors starten müssen. Mit Hilfe der ersten Sitzung führen wir eine einfache Abfrage auf einen Personalstammdatensatz aus, wobei wir die Abfrage diesmal innerhalb einer Lesetransaktion laufen lassen. set transaction read only; select name from personalien where persnr = '7000002';
Mit Hilfe der Abfrage, die wegen der Anwendung des set transaction-Befehls innerhalb reiner Lesetransaktion läuft, erhalten Sie das nachfolgend gezeigte Ergebnis. NAME -------------------------------------------------Karlo,Armin 1 row selected.
Änderungsabfragen
323
Wechseln Sie nun zur zweiten Sitzung Ihres SQL-Editors und schicken Sie von dort folgende Änderungsabfrage an Ihre Datenbank. update personalien set name = 'Karlo,Armin-Helgo' where persnr = '7000002'; commit;
Hierdurch wird der Name des soeben selektierten Mitarbeiters geändert, was aber für unsere erste Arbeitssitzung zunächst unsichtbar bleibt, d.h. wenn Sie von dort die eben gezeigte Auswahlabfrage noch einmal absenden, so werden Sie trotz der zwischenzeitlich durchgeführten Änderung wieder den alten Namen des Mitarbeiters erhalten. SQLWKS> select name 2> from personalien 3> where persnr = '7000002'; NAME -------------------------------------------------Karlo,Armin 1 row selected.
Dieser Zustand bleibt so lange erhalten, bis Sie die Lesetransaktion wieder mit Hilfe einer commit-Anweisung abschließen, d.h. erst die nun folgende Abfrage führt zur Anzeige des mittlerweile neuen Namens des Mitarbeiters. SQLWKS> commit; Statement processed. SQLWKS> select name 2> from personalien 3> where persnr = '7000002'; NAME -------------------------------------------------Karlo,Armin-Helgo 1 row selected.
Die Verwendung von Sicherungspunkten Die Anlage von Sicherungspunkten innerhalb von Transaktionen möchte ich Ihnen der Vollständigkeit halber nicht verschweigen. Solche Sicherungspunkte können Sie in Ihrem Ablauf zusammen mit einem rollback-Kommando anspringen, so dass Sie in Ihrem Programm auf bestimmte Fehlersituationen mit dem Zurücksetzen aller Aktionen seit dem Erreichen eines Sicherungspunktes reagieren können. Das Setzen eines Sicherungspunktes erfolgt mit Hilfe der Anweisung savepoint, dem Sie einen innerhalb der Transaktion eindeutigen Namen anhängen müssen. savepoint A
324
Abfragen
savepoint B <Weitere SQL-Anweisungen> if then rollback to savepoint B
Ist der von Ihnen vergebene Sicherungsname nicht eindeutig, sondern beispielsweise immer der gleiche, dann verschiebt sich die Sicherungsmarke beim Ablauf des Programms dynamisch auf die zuletzt erreichte savepoint-Marke. Mehr hierzu finden Sie später in diesem Workshop, wenn im Rahmen der PL/SQL-Einführung die Fehlerbehandlung besprochen wird.
3.4
Tuning von Abfragen
Damit erreichen wir ein eigentlich sehr komplexes und bizarres Thema, das schlichtweg mit der Fragestellung beginnt, woran man eine zu tunende Abfrage überhaupt erkennen kann. Die Antwort auf dieses Frage ist allerdings simpel, denn im Regelfall macht sich eine Abfrage mit Tuningpotential nicht von alleine bemerkbar. Solange eine Abfrage oder ein Programm überhaupt fertig wird, befindet man sich bei diesem Thema in einem der häufig stattfindenden Zänkereien zwischen Fachabteilung und EDV-Betrieb, wobei den einen immer alles viel zu langsam und den anderen immer alles viel zu aufwendig ist. Ich selbst musste hierbei schon so mache Gemütsschlappe hinnehmen, beispielweise einmal nachdem ich eine Dialogabfrage von über eine Minute auf zwei Sekunden beschleunigt hatte und die erste Reaktion der Fachabteilung die war, warum das nicht gleich so gemacht wurde und ob man es nicht noch etwas schneller machen könnte; doch nun zurück zum eigentlichen Thema. Worauf ich mit meiner Einleitung eigentlich nur hinweisen wollte ist, dass man zum einen pauschal niemals weiß, ob eine Abfrage oder ein Programm schneller laufen kann und dass es zum anderen Ansichts- bzw. Empfindungssache ist, ob etwas schnell genug oder zu langsam ist. Manchmal sagt einem die Erfahrung, das eine bestimmte Aktivität in der Datenbank schneller gehen sollte, wohingegen man manchmal auch nach stundenlangem Forschen und Rumprobieren keine bessere Variante aus dem Ärmel zu schütteln vermag. Außerdem muss man sich meiner Meinung nach auch immer fragen, was eine marginale Laufzeitverbesserung kosten darf. Ich finde es jedenfalls sinnlos, wenn die Laufzeit eines über Nacht laufendes Batchprogramms von 30 auf 25 Minuten verkürzt wurde und man dafür auf der anderen Seite 2 Tage experimentieren musste, zumal man nicht ausschließen kann, dass das Laufzeitverhalten bei einer sich ändernden Datenkonstellation in eine andere Richtung kippt (altes Verfahren 40 Minuten; neues jetzt auf einmal 55 Minuten).
Tuning von Abfragen
325
Das ist im Übrigen in Wirklichkeit vor allem bei relationalen Datenbanksystemen ein weiteres Problem. Das Antwortzeitverhalten einer solchen Datenbank verändert sich bei wachsenden Datenbeständen eigentlich niemals linear zum Bestand. Manchmal habe ich sogar den Eindruck, dass zwischen Antwortzeit und Datenbankgröße nicht einmal ein expotentieller Zusammenhang besteht, sondern das hierbei schlichtweg Schwellenwertproblematiken auftreten. Das resultiert aus Erfahrungen, wo Abfragen bei einer bestimmten Datensatzanzahl x Minuten, bei doppelter Satzanzahl 3x Minuten und bei Erreichen der dreifachen Datenmenge gar nicht mehr liefen. Sie sehen also, dass sie es hierbei mit einem Thema zu tun haben, für des es keine globalen Patentrezepte gibt und bei dem alle Grenzen, Rahmenbedingungen und Regeln dauernd in Bewegung sind bzw. laufend irgendwelchen Veränderungen unterliegen. Trotzdem gibt es eine Reihe von Grundsätzen und Regeln, deren Einhaltung fast immer zu ordentlich laufenden Abfragen führen, und die in den folgenden Abschnitten zusammen mit einigen persönlichen Erfahrungen zum besten geben möchte. Sie finden im Folgenden also kein Kochrezept für die perfekte Abfrage, sondern lediglich eine Reihe von Zutaten, die vielfach kombinierbar zu leckeren Gerichten führen. Daneben empfehle ich jedem interessierten den Genuss bestimmter Teile der Oracle-Dokumentation. Konkret finden Sie im Buch „Oracle8 Concepts“ das Kapitel „The Optimizer“ und im gesamten Buch „Oracle8 Tuning“ weitergehende Informationen zu diesem Themenkomplex. In der 8i-Version heißt das zuletzt genannte Buch etwas anders. Sie finden die Informationen dort unter der Überschrift „Oracle8i Designing and Tuning for Performance“.
3.4.1
Abfrageoptimierung
In nahezu allen relationalen Datenbanksystemen werden die von Ihnen erstellten Abfragen nicht einfach so ausgeführt, sondern zunächst von einem sogenannten Optimierer (engl. Optimizer) analysiert und anschließend entsprechend eines bei der Analyse erstellten Ausführungsplans ausgeführt. Auch in Oracle8 gibt es einen solchen Optimierer, wobei der mal wieder zur Entscheidungsqual des Benutzers in zwei unterschiedlichen Varianten betrieben werden kann. Regelbasierte Optimierung Hierbei wird der Ausführungsplan mit Hilfe festgelegter Regeln und Prioritäten der verwendeten Operationen erstellt. Solche Regeln ergeben sich zum Teil aus den zu den Operationen gehörenden mathematischen Grundlagen oder resultieren aus dem Einsatz konkreter Verfahrensweisen zur Ausführung bestimmter Operationen, beispielsweise der Auswahl geeigneter Verknüpfungsalgorithmen in Abhängigkeit vorhandener Indexstrukturen. Kostenbasierte Optimierung Im Unterschied zur regelbasierten Optimierung basiert die kostenbasierte Variante auf der Ausnutzung statistischer Daten über die Datenbank. Dies impliziert, dass
326
Abfragen
die kostenbasierte Optimierung nur dann funktionieren kann, wenn die zugehörigen Statistiken angelegt und in Bezug auf den Datenbankstand halbwegs aktuell sind. Generell versucht die kostenbasierte Optimierung, die zur Ausführung der Abfrage benötigten Ressourcen zu minimieren. Die Anlage solcher Statistiken erfolgt mit Hilfe des Befehls analyze table, dem zunächst einmal der Name der zu analysierenden Tabelle folgt. Anschließend müssen Sie bestimmen, ob Sie die statistischen Daten exakt berechnet oder durch Analyse eines vorzugebenden Prozentsatzes der Datensätze geschätzt werden soll. Die beiden nachfolgenden Beispiele zeigen die Verwendung des analyze table-Befehls zum Anlegen der Statistiken. analyze table gehalt compute statistics; analyze table personalien extimate statistics sample 20 percent; Listing 3.30: Anlegen der Statistiken mit dem Befehl analyze table
Klar ist eigentlich, dass die Erstellung berechneter Statistiken bei großen Tabellen wesentlich länger dauern kann als deren Schätzung. Neben diesen Statistiken spielen bei der kostenbasierten Optimierung zusätzlich auch noch sogenannte Histogramme eine Rolle, mit deren Hilfe die Wertverteilung spezieller Felder dargestellt werden können. Die Kenntnis über diese Wertverteilung ist wichtig wenn es darum geht, die Verwendung bestimmter Indexe oder Join-Algorithmen festzulegen. Oracle selbst empfiehlt in der Dokumentation die Anlage solcher Histogramme zumindest für alle in einem Index vorkommenden Spalten, da diese Felder üblicherweise häufig innerhalb der Auswahl- und Verknüpfungsbedingungen von Abfragen verwendet werden. Solche spaltenbezogenden Statistiken werden ebenfalls mit einer Variante des analyze table-Befehls erzeugt. Das nachfolgende Beispiel zeigt deren Anlage für alle Indexspalten. analyze table gehalt compute statistics for all indexed columns;
Wenn spezielle Spalten regelmäßig in Auswahlbedingungen vorkommen und nicht in irgendwelchen Indices vorhanden sind, dann können Sie für diese Spalte Statistikinformationen sammeln, in dem Sie das analyze-Kommando in der folgenden Variante verwenden. analyze table gehalt compute statistics for columns gehalt, zulage;
Mit Hilfe des letzten Beispiels werden Informationen über die Verteilung der Spalten gehalt und zulage in das Datendictionary eingetragen, wo sie dem kostenbasierten Optimierer anschließend zur Verfügung stehen.
Tuning von Abfragen
327
Sollten Sie sich irgendwann einmal gegen kostenbasierte Optimierung entscheiden, was allerdings aufgrund der zur Zeit absehbaren Produktentwicklung nicht zu empfehlen ist, dann besteht eine der notwendigen Maßnahmen darin, die vorhandenen Statistikinformationen aus der Datenbank zu löschen. Auch das können Sie ebenfalls mit Hilfe eines analyze-Kommandos bewerkstelligen: analyze table gehalt delete statistics;
Wie Sie den bisherigen Beispielen entnehmen konnten, so ist das Sammeln und Speichern von Statistiken für eine konkrete Datenbank mit vielleicht einigen hundert Tabellen eine aufwendige Angelegenheit. Hierbei müssen Sie auch berücksichtigen, dass diese Statistiken zumindest für diejenigen Tabellen, die starken Veränderungen unterworfen sind, regelmäßig aktualisiert werden müssen, was ganz einfach durch ein erneutes Ausführen des analyze-Befehls passiert. Zur Anlage von Statistiken finden Sie in der Oracle-Datenbank im Übrigen das Paket dbms_utility, in dem Sie mit der dort enthaltenden Prozedur analyze_schema ein kleines Hilfsmittel zur Hand haben, um alle im Schema enthaltenen Objekte zu analysieren. Die Anlage weiterer Statistiken für einzelne oder alle Spalten wäre dann Feinarbeit, wobei ich dennoch eine andere Verfahrensweise vorschlagen würde. Mit Hilfe einer separaten Tabelle würde ich alle zu analysierenden Objekte namentlich oder mit Hilfe von Regeln speichern. Im zweiten Schritt würde ich eine Prozedur erstellen, deren Aufruf zur Analyse aller spezifizierten Objekte führt. Etwas Ähnliches finden Sie im Übrigen in den Beispielen zur PL/SQL-Programmierung, wo wir mit Hilfe einer Prozedur die Neuerstellung aller Indices oder die Aktualisierung aller Benutzerrechte veranlassen. Optimierungsart festlegen Die Festlegung der verwendeten Optimierungsart kann von Ihnen auf drei unterschiedliche Arten beeinflusst werden:
X
X
Festlegen der zu verwendenden Variante in der Initialisierungsdatei der Datenbankinstanz. Hierbei können Sie die zu verwendende Methode mit Hilfe des Parameters optimizer_mode in der Initialisierungsdatei festlegen. Die hier eingestellte Methode (Standard: „choose“) entspricht damit dem Standardwert der Datenbankinstanz. Sie können die Variante für die Lebensdauer einer Datenbanksitzung ändern, indem Sie die Sitzungsparameter mit Hilfe einer alter session-Anweisung ändern. Wie das Ganze aussieht, das können Sie dem folgenden Beispiel entnehmen: alter session set optimizer_mode = choose;
X
Als letzte Variante haben Sie noch die Möglichkeit, die zu verwendende Optimierung in der einzelnen SQL-Abfrage festzulegen, indem Sie innerhalb der Abfrage sogenannten Hints verwenden. Solche Hints sehen aus wie Kommentare und werden innerhalb der Abfrage platziert und vom Optimierer entsprechend
328
Abfragen
ausgewertet. Das folgende Beispiel erzwingt für die zugehörigen Auswahlabfrage eine regelbasierte Optimierung: select /*+ rule */ name from personalien
Insgesamt kennt Oracle die in der Tabelle 3.9 beschriebenen Optimierungsarten: Optimierungsart
Beschreibung
choose
Hierbei wird die Auswahl zwischen regel- und kostenbasierter Optimierung vom System getroffen, was in der Praxis zur kostenbasierten Variante führt, wenn die dafür benötigten Statistiken vorhanden sind.
all_rows
In diesem Modus wird bezüglich der Zeit optimiert, die zur Bereitstellung des gesamten Ergebnisses benötigt wird.
first_rows
Im Unterschied zu all_rows wird jetzt dahingehend optimiert, erste Ergebnisse möglichst schnell anzuzeigen. Somit eignet sich das Verfahren bei bestimmten Abfragevarianten überhaupt nicht. Ein Beispiel dafür sind gruppierende Abfragen, bei denen zunächst alle Einzelsätze bereitgestellt werden müssen, bevor die Gruppierfunktion überhaupt gestartet werden kann.
rule
führt zur regelbasierten Optimierung.
Tabelle 3.9: Optimierungsarten in einer Oracle-Datenbank
Gerade durch die zuletzt gezeigten Hints können Sie neben der Optimierungsart noch weiteren Einfluss auf die Arbeitsweise des Optimierers nehmen, was sich üblicherweise in einem anderen bzw. entsprechend geänderten Ausführungsplan niederschlägt. Im nächsten Schritt werden wir uns zunächst anschauen, auf welche Art- und Weise Sie sich den vom Optimizer ermittelten Ausführungsplan anschauen können und im Anschluss daran, also im übernächsten Kapitel, finden Sie einige Grundregeln für die Erstellung effizienter Abfragen und erst danach folgt in diesem Buch eine Übersicht ausgewählter Hints. Wenn Sie jetzt von mir die Empfehlung für oder gegen die eine oder andere Optimierungsart erwarten, dann muss ich Sie enttäuschen, da ich mich hier auf Basis meiner bisherigen beruflichen Erfahrung nicht festlegen kann bzw. will. So wie ich bestimmte Dokumentationen bzw. die Hinweise von Oracle verstanden habe, handelt es sich bei der kostenorientierten Variante um das modernere Verfahren, wobei die regelbasierte Variante sogar nur noch aus Gründen der Kompatibilität enthalten ist und einige Objekte und Methoden (z.B. Bitmap-Index oder HaschJoin) überhaupt nicht unterstützt. Trotzdem kenne ich eine Reihe von großen Datenbanken, die auch in der Version 8 immer noch regelbasiert betrieben werden, denn vor allem bei großen Datenbanken ist eine Optimiererumstellung aufwendiger, als mal eben das dbms_utility-Paket einzusetzen. Nach meiner bisherigen Erfahrungen hat es in kleineren Datenbanken zumindest keine negativen Auswirkungen, wenn man mit der Durchführung einfacher analayze-Szenarien die kostenbasierte Optimierung einschaltet.
Tuning von Abfragen
329
Bei großen Datenbanken (viele Tabellen mit über 100.000 bis 3 Mio. Datensätzen) ist das allerdings nicht ganz so einfach, sondern hier muss man schon genauer hinschauen, welche Statistikvarianten benötigt werden bzw. welche Histogramme zusätzlich erstellt werden müssen. Die alleinige Trivialanalyse des Schemas mit dem dbms_utility-Paket hat hier einmal in einem mir bekannten Fall den Abfragegau ausgelöst, indem ohne sonstige Änderungen zuvor im Sekundenbereich laufende Abfragen auf einmal über 30 Minuten für die gleiche Arbeit brauchten. Für unser Schema sollten Sie jetzt allerdings einmal diese Analyse durchführen, damit Ihnen im Folgenden das volle Aktionsspektrum des Optimierers zur Verfügung steht. Hierzu können Sie das dbms_utility-Paket folgendermaßen verwenden. execute dbms_utility.analyze_schema('UBEISPIEL','compute');
3.4.2
Ausführungspläne
Wie schon gesagt, erstellt der Optimierer unabhängig von der gewählten bzw. eingestellten Methode vor der Ausführung einer Abfrage einen zugehörigen Ausführungsplan. Mit Hilfe eines speziellen Befehls können Sie diese Ausführungspläne sichtbar machen, wobei die Analyse des Ausführungsplans oftmals Hinweise über mögliche Verbesserungen Ihrer Abfrage liefert. Planungstabelle erstellen Bevor Sie jedoch erstmalig einen solchen Ausführungsplan erstellen können, müssen Sie dafür sorgen, dass in Ihrer Datenbank bzw. Ihrem Schema eine geeignete Tabelle vorhanden ist, in der die einzelnen Schritte des Plans gespeichert werden. Konkret heißt diese Tabelle plan_table und kann bei Bedarf mit einem standardmäßig vorhandenem Skript erstellt werden. Sie finden das Skript in der Datei UTLXPLAN.SQL, die sich normalerweise im \RDBMSxx\ADMIN-Unterverzeichnis Ihrer Oracleinstallation befindet. SQLWKS> desc plan_table Column Name Null? ------------------------------ -------STATEMENT_ID TIMESTAMP REMARKS OPERATION OPTIONS OBJECT_NODE OBJECT_OWNER OBJECT_NAME OBJECT_INSTANCE OBJECT_TYPE OPTIMIZER SEARCH_COLUMNS ID PARENT_ID
Type ---VARCHAR2(30) DATE VARCHAR2(80) VARCHAR2(30) VARCHAR2(30) VARCHAR2(128) VARCHAR2(30) VARCHAR2(30) NUMBER(38) VARCHAR2(30) VARCHAR2(255) NUMBER NUMBER(38) NUMBER(38)
330
POSITION COST CARDINALITY BYTES OTHER_TAG PARTITION_START PARTITION_STOP PARTITION_ID OTHER
Abfragen
NUMBER(38) NUMBER(38) NUMBER(38) NUMBER(38) VARCHAR2(255) VARCHAR2(255) VARCHAR2(255) NUMBER(38) LONG
Listing 3.31: Struktur der Hilfstabelle zur Visualisierung eines Abfrageplans
Wie Sie dem Listing 3.31 entnehmen können, enthält die mit dem Skript erstellte Ergebnistabelle eine ganze Reihe von Feldern, weshalb von einer manuellen Anlage abzuraten ist. Ausführungsplan erstellen Ist die benötigte Hilfs- bzw. Ergebnistabelle erst einmal angelegt, dann steht dem Erstellen der Ausführungspläne nichts mehr im Wege. Konkret existiert hierzu der Befehl explain plan, der zusammen mit der zu analysierenden Abfrage verwendet und nach folgendem Schema angewendet wird. explain plan set statement_id = 'TEST' for <SQL-Abfrage>
An dem Schema erkennen Sie, dass neben der Vorgabe der zu analysierenden SQLAbfrage noch das Setzen des in die Ergebnistabelle übernommenen Feldes statement_id eine gewisse Rolle spielt. Theoretisch können Sie mehrere Ausführungspläne nebeneinander vorhalten, die spätere bei der Auswertung mit Hilfe des hier vergebenen Wertes auseinandergehalten werden können. Auf der anderen Seite lässt die gerade schematisch dargestellte Verwendungsform Schlimmes befürchten, was meiner Meinung nach auch zutrifft, denn wenn Sie eine Analyse mit immer der gleichen Id durchführen, dann werden die unterschiedlichen Pläne vermischt, was eine nachträgliche Auswertung sicherlich vereitelt. Ich habe mir daher angewöhnt, die Analyse einer Abfrage mit folgendem Skript aufzurufen: truncate table plan_table; explain plan for <SQL-Abfrage>
Wie das Ganze nun wirklich konkret funktioniert, das können sie am Beispiel des nächsten Listings (vgl. 3.32) sehen, indem ein Ausführungsplan für unsere typische Gehaltsabfrage erstellt wird. truncate table plan_table; explain plan for
Tuning von Abfragen
331
select persnr, lfdnr, gab from gehalt a where gab = (select max(gab) from gehalt b where b.persnr = a.persnr and b.lfdnr = a.lfdnr and b.gab <= sysdate ) Listing 3.32: Erstellen eines Ausführungsplans für eine Gehaltsabfrage
Als Ergebnis erhalten Sie, wie das nächste Listing (vgl. 3.33) zeigt, eine Hand voll Zeilen in der Hilfstabelle plan_table, und es geht im nächsten Abschnitt nun darum, diese Weissagungen des Systems zu deuten bzw. zu interpretieren. SQLWKS> select operation, options 2> from plan_table; 3> OPERATION OPTIONS ------------------------------ -----------------------------SELECT STATEMENT FILTER TABLE ACCESS FULL SORT AGGREGATE INDEX RANGE SCAN 5 rows selected. Listing 3.33: Ergebnis des im Listing 3.32 erstellten Ausführungsplans
Ausführungsplan lesen Nach der Erstellung eines Ausführungsplans können Sie ihn mit Hilfe der Ergebnistabelle auswerten, was jedoch einfacher gesagt als getan ist. Zum einen stellt sich die Frage, welche Felder man sich hierbei anschauen soll, denn die Tabelle enthält insgesamt über 20 Werte und zum anderen macht es Sinn, die erstellten Datensätze in einer bestimmten Reihenfolge auszuwerten. Hierzu finden Sie sowohl in der Oracle-Dokumentation als auch in nahezu jedem sonstigen Handbuch ähnliche Beispiele, die sich zumindest in Bezug auf die verwendete Auswahl und Verkettung nicht unterscheiden. Ich habe mich für die folgende (vgl. Listing 3.34) leicht abgewandelte Version aus den Oracle-Tuningunterlagen entschieden. select level, position, lpad(' ',2*(level-1)) || operation || ' '|| options || ' ' || object_name as q_plan from plan_table start with id = 0 connect by prior id = parent_id Listing 3.34: Hierarchische Auswertung des Ausführungsplans
332
Abfragen
Wenn Sie sich noch einmal die in der Planungstabelle enthaltenen Datensätze anschauen, dann werden Sie feststellen, dass die dort gespeicherten Datensätze mit Hilfe der Felder id und parent_id verkettet sind, d.h. Sie finden hier wieder ein klassisches Einsatzgebiet der connect by-Anweisung. Wie Sie schon dem Listing 3.33 entnehmen konnten, werden die wesentlichsten Zugriffsinformationen mit Hilfe der drei Spalten operation, options und object_name beschrieben. Mit Hilfe der dargestellten Abfrage werden diese drei Zeilen zu einer Ausgabezeile zusammengefasst, wobei die entsprechend ihrer hierarchischen Einordnung mit Hilfe des Ausdrucks lpad(' ',2*(level-1))
eingerückt werden. Zur weiteren Orientierungshilfe habe ich mich dafür entschieden, zusätzliche noch die beiden Felder level und position mit auszugeben, wobei die Position die Ausführungsreihenfolge bei ansonsten gleichem Level beschreibt. LEVEL POSITION Q_PLAN ---------- ---------- ------------------------------------------------1 SELECT STATEMENT 2 1 FILTER 3 1 TABLE ACCESS FULL GEHALT 3 2 SORT AGGREGATE 4 1 INDEX RANGE SCAN GEHALT_PK 5 rows selected. Listing 3.35: Auswertung des Ausführungsplans für die Abfrage aus Listing 3.32
Die eigentliche Abarbeitung des im Listing 3.35 gezeigten Plans erfolgt von unten nach oben bzw. bei gleicher Ebene entlang der Positionsnummer. Was hiermit gemeint ist, wird spätestens klarer, wenn man für diesen Plan einen zugehörigen Ausführungsbaum zeichnet, den Sie der Abbildung 3.6 entnehmen können.
FILTER
TABLE ACCESS FULL GEHALT
SORT AGGREGATE
INDEX RANGE SCAN GEHALT_PK
Abbildung 3.6: Baumdarstellung unsers Abfrageplans aus Listing 3.35
Dem Baum entsprechend wird unsere Abfrage von unten nach oben bzw. links nach rechts abgearbeitet, d.h. zunächst wird die Gehaltstabelle wegen fehlender Selektionskriterien vollständig gelesen, was als Eingabe für einen Filterprozess dient. Die andere Eingabe dieses Filters ist das Ergebnis einer Sortieroperation, die wiederum die Ergebnisse aus dem Lesen des Index „gehalt_pk“ verarbeitet.
Tuning von Abfragen
333
Es mag sein, dass diese Pläne bzw. deren Darstellen manchen Vollbluttechnikern zu oberflächlich sind, wobei ich entgegenhalten muss, dass zumindest mir diese Pläne in den meisten Fällen gereicht haben, um die eventuell vorhandenen Schwachstellen in der Abfrage schnell zu finden und für Abhilfe zu sorgen, denn folgende und meist gravierende Einflussfaktoren auf die Abfragegeschwindigkeit können Sie einem solchen Plan entnehmen:
X X X
X X
Verwendet Oracle zur Verarbeitung vorhandene Indices bzw. die richtigen Indices oder werden permanent bzw. oft die gesamten Tabellen gelesen? Werden die Tabellen in einer sinnvollen Reihenfolge gelesen? Werden Views, vor allem welche mit umfangreichen Ausgaben, materialisiert, d.h. es wird nicht das in der View gespeicherten SQL-Statement verwendet, sondern die Ergebnismenge der View wird weiterverarbeitet? Auf welche Weise werden die Tabellen miteinander verknüpft? Bei der kostenbasierten Optimierung werdend die Kosten jedes Verarbeitungsschrittes in der Spalte cost gespeichert, d.h. Sie haben hierdurch ein weiteres Kriterium in der Hand, um nach besonders „teuren“ Arbeitsschritten zu suchen, um diese beispielsweise mit Hilfe eines zusätzlichen Index vielleicht zu verbilligen.
Die Kontrolle bzw. Anpassung der eben aufgezählten Einflussfaktoren reicht in den meisten Fällen aus, damit aus lahmen Krücken ordentlich laufende Abfragen werden. Viel mehr zu tun ist sowieso ein zweischneidiges Schwert, denn zum einen kann sich das Verhalten des Optimierers bei sich ändernden Daten- bzw. Mengenkonstellationen extrem verändern oder Oracle reagiert in einer zukünftigen Version auf Ihre Spezialeinstellung gar nicht mehr oder zumindest in einer anderen Weise. Neben dem Ausführungsplan gibt es gibt im Übrigen noch weitere Möglichkeiten, dem System Informationen über die Ausführung von SQL-Statments abzuringen. Eine dieser Möglichkeiten besteht in der Verwendung von TKPROF, wobei Sie Informationen hierüber beispielsweise in der Oracle-Dokumentation im Kapitel „The SQL Trace Facility and TKPROF“ des Buchs „Oracle8 Tuning“, das sich bei Interesse generell zu lesen lohnt, finden. Wie Sie ansonsten schon an unserem bisherigen kleinen Beispiel sehen konnte, werden im Ausführungsplan neben den gelesenen Tabellen oder Indices auch die Methoden dargestellt, mit denen diese Objekte oder zuvor erstellte Ergebnisse verarbeitet werden. Konkret werden diese Informationen im Ausführungsplan für jeden Einzelschritt mit bis zu drei Angaben dargestellt. Als Erstes finden Sie im Feld operation die durchgeführte Operation. Bei einigen dieser Operationen enthält der Plan nähere Hinweise über den verwendeten Algorithmus, die im Feld options gespeichert werden. Oftmals werden die einzelnen Operationen zusammen mit spezifischen Datenbankobjekten (z.B. Tabellen oder Indexen) ausgeführt, wobei Sie in dem Fall den Namen des verwendeten Objekts in der Spalte object_name vorfinden.
334
Abfragen
Die nachfolgende Tabelle 3.10 zeigt Ihnen eine Übersicht der wichtigsten Operationen und Optionen. Beachten Sie hierbei, dass bestimmte Operationen an vorhandene Datenbankoptionen (z.B. Bitmap-Index) oder an spezielle Optimierverfahren (z.B. keine Hash-Verknüpfung im regelbasierten Betrieb) gebunden sind. Operation
Option
BITMAP
Beschreibung Die Verwendung der besonderen Bitmap-Indexe wird im Ausführungsplan entsprechend gekennzeichnet.
CONVERSION TO ROWIDS CONVERSION FROM ROWIDS
Überführung der Datensatzzeiger rowid in Bitmaps (TO ROWIDS) oder umgekehrt (FROM ROWIDS).
INDEX SINGLE VALUE> INDEX RANGE SCAN INDEX FULL SCAN
Zugriff bzw. Verwenden des Bitmap-Index in der vorgegebenen Art und Weise (vgl. INDEX-Operation).
MERGE
Mischen der Bitmaps aus einem „RANGE SCAN” zu einem neuen Bitmap.
OR
Bitweise oder-Verknüpfung für ein Bitmap.
CONNECT BY
Hierarchische Auflösung einer Datenmenge, was durch die entsprechende SQL-Anweisung ausgelöst wird.
CONCATENATION
Verschmelzen zweier Ergebnismengen, d.h. diese Operation entspricht quasi der Mengenoperation union-all.
COUNT
STOPKEY
führt zum Abbruch der Abfrage, wenn eine bestimmte Anzahl von Ergebnissen ermittelt wurde. Wird durch Verwendung der Pseudospalte rownum in der where-Bedingung ausgelöst.
FILTER
Ausführen eines Datensatzfilters aufgrund diverser Anforderungen, beispielsweise der Verwendung einfacher Auswahlanweisungen oder als Ergebnis durchgeführter Verknüpfungen.
FOR UPDATE
Anzeige, dass die selektierten Datensätze gesperrt werden. Resultiert aus der Verwendung der for update-Klausel in einer Abfrage.
HASH JOIN
Ein Verfahren zur Verknüpfung von Tabellen mit Hilfe einer Hash-Funktion. ANTI
Bei der „Anti-Verknüpfung” muss als Ergebnis das übrig bleiben, was man erhält, wenn man von der gesamten primären Tabelle diejenigen Datensätze herausstreicht, die erfolgreich verknüpft werden konnten. Der Verknüpfungsoperator not in kann zusammen mit einer Unterabfrage zu einer Anti-Join-Verknüpfung führen.
Tuning von Abfragen
Operation
INDEX
335
Option
Beschreibung
SEMI
Durchführung eines Semi-Joins nach der HashMethode. Beim sogenannten Semi-Join werden aus der verknüpften Tabelle keine eigentlichen Daten benötigt, d.h. die Verknüpfung erfolgt nur als Selektionskriterium für die primäre Tabelle. Ein Semi-Join kann durch eine exists-Unterabfrage entstehen.
FULL SCAN
liest die gesamte Tabelle in der durch den Index vorgegebenen Sortierreihenfolge, d.h. das System erspart sich hierdurch einen Sortiervorgang.
UNIQUE SCAN
Auswahl eines einzelnen Datensatzes über den angegebenen eindeutigen Index.
RANGE SCAN
Ermittlung einer oder mehrere Datensätze, indem der spezifizierte Index für einen bestimmten Bereich aufsteigend durchlaufen wird.
RANGE SCAN DESCENDING
Wie oben, nur dass der Index hierbei absteigend durchlaufen wird.
INTERSECTION
Ergebnis der entsprechenden Mengenoperation.
MERGE JOIN
Beim Merge-Join liegen die zu verknüpfenden Mengen entsprechend der Verknüpfungsfelder sortiert vor. Beide Mengen werden sequentiell durchlaufen und dabei entsprechend synchronisiert. OUTER
Durchführung eines Outer-Joins mit der eben beschriebenen Technik.
ANTI
Durchführung eines Merge-Anti-Joins (s.a. HashJoin).
SEMI
Durchführung eines Merge-Semi-Joins (s.a. HashJoin).
MINUS
wird durch die entsprechende Mengenoperation ausgelöst.
NESTED LOOPS
besondere Technik zur Durchführung einer Verknüpfung. Hierbei ist eine der beiden Tabellen die führende und bei deren Verarbeitung werden die passenden Datensätze aus der anderen Tabelle hinzugefügt. OUTER
Durchführen eines Outer-Joins mit dem NestedLoops-Verfahren.
REMOTE
Die Daten werden über einen Link aus einer anderen Datenbank abgerufen.
SEQUENCE
Abrufen einer Sequence-Nummer.
SORT
AGGREGATE
Verfahren zur Lieferung einer einzelnen Datenzeile. Wird durch Verwendung einer Aggregatfunktion ohne group by-Klausel ausgelöst.
336
Abfragen
Operation
TABLE ACCESS
Option
Beschreibung
GROUP BY
Sortierung und Gruppierung der Datensätze.
JOIN
Sortierung der Datensätze als Vorbereitung eines sogenannten Merge-Joins.
UNIQUE
Eliminierung doppelter Datensätze, z.B. wegen Verwendung der distinct-Klausel.
ORDER BY
Sortierung der Datensätze, z.B. wegen vorgegebener order by-Klausel.
FULL
Lesen aller Datensätze einer Tabelle.
CLUSTER
ermöglicht einen besonderen Zugriff bei Tabellen, die auf diese Weise angelegt wurde.
HASH
Besondere Zugriffsform bei gestreut gespeicherten Tabellen.
BY ROWID
Direkter Zugriff auf einzelne Datensätze über den Datensatzzeiger.
UNION-ALL
Ausführung der union-Anweisung, d.h. die Ergebnisse zweier Abfrage werden gemischt. Falls Sie statt union all nur union verwendet haben, so führt das System anschließend noch die Operation sort unique aus, um die doppelten Sätze auszuschließen.
VIEW
Ausführung einer View, d.h. die von der View gelieferten Datensätze werden abgerufen und zwischengepeichert.
Tabelle 3.10: Übersicht der wichtigsten Operationen und Optionen eines Ausführungsplans
3.4.3
Ein paar Grundregeln für die Erstellung von Abfragen
Auch wenn es kein Patentrezept gibt, so gibt es dennoch ein paar Grundregeln, deren Einhaltung dazu führt, dass die erstellten Abfragen ganz gut laufen. Bei der kostenorientierten Optimierung müssen Sie zusätzlich noch darauf achten, dass aktuelle Statistiken und Histogramme für die wichtigen Selektionsbegriffe vorhanden sind und nur im Ausnahmefall sollten Sie davon Gebrauch machen, die Arbeit des Optimierers durch Verwendung der sogenannten Hints zu beeinflussen. Eine Beschreibung von einigen dieser Optimiererschalter, die im Übrigen bei der regelbasierten Optimierung ignoriert werden, finden Sie im nächsten Kapitel, doch nun zunächst einmal zu den von mir angedeuteten Regeln, die Sie nach Möglichkeit beachten sollten. Indices Verwendet Oracle die richtigen Indices bzw. sind für die abgefragten bzw. verknüpften Spalten überhaupt welche bzw. vernünftige Indices definiert. Sicherlich beinhaltet jeder zusätzliche Index einen gewissen Overhead, und trotzdem sollten Sie vor allem bei größeren Tabellen und für die innerhalb von Selektionen häufig
Tuning von Abfragen
337
verwendeten Feldern einen Index anlegen, denn bei großen Tabellen ist jeder „TABLE ACCESS FULL“ ein Zeitrisiko. Bei der Anlage der Indices sollten Sie darauf achten, dass die selektivsten Spalten alle in einem Index zusammenhängend enthalten sind, wobei der Index zusätzlich mit einem der verwendeten Felder beginnt, d.h. die Kette der Indexfelder passt zu den verwendeten Suchbegriffen. Beispielsweise kann ein Index über die Spalte name bei der Namenssuche verwendet werden, wohingegen dies bei einem zusammengesetzten Index mit den Feldern geschl, name nicht möglich ist, wohingegen das bei umgekehrter Feldreihenfolge wiederum denkbar wäre. Anders ausgedrückt kann ein Index so lange bzw. bis zu dem Indexfeld benutzt werden, wie die verwendeten Suchbegriffe lückenlos der gleichen Reihenfolge entsprechen. Vermeiden Sie die Anlage unnötiger Indices. Vor allem manche Standardprogramme generieren für ihre Suchbegriffe Indexe in jeglicher Begriffskombination. Bei der regelbasierten Optimierung geht das meistens ins Auge und führt zur Verwendung einer ungeeigneten Indexdatei. Welcher Index bei der kostenbasierten Optimierung verwendet wird, hängt auch wieder von vorhandenen Statistiken ab und trotzdem glaube ich, dass man mit Einzelindices für die wichtigsten Suchbegriffe oftmals besser fährt. Die beim Zugriff bzw. der Selektion verwendeten Indices können Sie mit Hilfe des Ausführungsplans kontrollieren. Bei der kostenbasierten Optimierung sollten Sie sich dabei allerdings nicht wundern, wenn bei kleinen Tabellen niemals der Zugriff über einen Index erfolgt. Regelbasierte Optimierung Bei der regelbasierten Optimierung können Sie Reihenfolge der verarbeiteten Tabellen mit Hilfe der from-Klausel steuern, wobei die Verarbeitung von unten nach oben bzw. rechts nach links erfolgt. Durch die Umsortierung der from-Klausel können Sie also erheblichen Einfluss auf den Ausführungsplan und damit auch auf die Ausführungsgeschwindigkeit der Abfrage nehmen. Bei der regelbasierten Optimierung müssen sie sich also überlegen, welche Tabelle Ihre Abfrage treiben soll und welche anderen Tabellen selektiv hinzugefügt werden sollen. Was man bei der dieser Optimierung außerdem meistens sehr schön im Ausführungsplan ist, ist die Verwendung vorhandener Indices, da diese – Regel ist eben Regel – nach bestimmten Kriterien verwendet werden, obwohl das aufgrund der Datenmenge oder anderer Einschränkungen in diesem Fall vielleicht gar nicht sinnvoll ist, weshalb sie der kostenbasierte Optimierer im gleichen Fall auch eben nicht benutzt. Joins und Mengenoperationen Vermeiden Sie nach Möglichkeit Outer-Joins und, von der union-Verknüpfung einmal abgesehen, umfangreiche Mengenoperationen. Gerade bei der Anwendungsentwicklung können Sie Outer-Join-Problematiken oftmals durch separate Datenbankzugriffe oder durch das Erstellen und Aufbereiten von Arbeitstabellen umgehen, indem Sie beispielsweise die Personalien und vielleicht vorhandene
338
Abfragen
Qualifikationen separat lesen anstelle die Daten in einem Zug mit Hilfe einer Outer-Join-Verknüpfung zu lesen. Im Übrigen gilt das eben Gesagte auch, wenn eine Abfrage irgendwann einmal aufgrund ihres Umfangs nicht mehr so richtig laufen will. Manchmal ist es besser eine Abfrage in mehrere Einzelschritte zu zerlegen, d.h. mit Hilfe eines Skripts und einzelnen Abfragen oder Cursorn wird zunächst eine Arbeitstabelle gefüllt, die anschließend mit Hilfe einer einfachen Auswahlabfrage ausgelesen wird. Unterabfragen Vermeiden Sie Unterabfragen mit den Verknüpfungsopratoren in oder not in, die sich meistens durch exists bzw. not exists-Abfragen und einer damit verbundenen besseren Performance ersetzen lassen. Beachten Sie dabei auch, dass die Selektion von etwas Nichtvorhandenem im Übrigen meistens aufwendiger ist, als die Existenz eines Datensatzes zu überprüfen. Wenn es dann aber schon sein muss, dann sollten Sie wenigstens darauf achten, dass die in der Unterabfrage erzeugte Datenmenge möglichst klein bleibt. Ebenso sollten Sie darauf achten, dass auch die Schachtelungstiefe von Unterabfragen möglichst gering ist. Manche Unterabfragen können auch durch echte Joins ersetzen werden. Das bringt oftmals Laufzeitvorteile, weshalb ich schon bei den Änderungscursorn gesagt hatte, dass vor allem Änderungsabfragen, die wegen der fehlenden fromKlausel nur mittels Unterabfragen eingeschränkt werden können, als Cursor manchmal besser laufen, da Sie hierbei statt der Unterabfrage einen echten Join verwenden können. Betrachten Sie hierzu einmal folgendes Beispiel, indem alle Personalien ausgewählt werden sollen, die ebenfalls über ein Beschäftigungsverhältnis verfügen. Wenn man diese Aufgabenstellung spontan angeht, so kommt einem vielleicht die nachfolgende Lösung in den Sinn, die Auswahl mit Hilfe einer Unterabfrage durchzuführen, wobei Sie den zugehörigen Ausführungsplan im Listing 3.36 finden. select a.* from personalien a where exists(select 1 from bvs b where b.persnr = a.persnr) LEVEL POSITION Q_PLAN ---------- ---------- ------------------------------------------1 1 SELECT STATEMENT 2 1 FILTER 3 1 TABLE ACCESS FULL PERSONALIEN 3 2 INDEX RANGE SCAN BVS_PK Listing 3.36: Ausführungsplan für eine Unterabfrage
In dem Beispiel werden die Personalien mit Hilfe des Primärschlüssels der Beschäftigungsverhältnisse gefiltert. Besser als ein Filter wäre aber die Verwendung eines echten Join-Algorithmus, den Sie durch folgende Umstellung der Abfrage erreichen
Tuning von Abfragen
339
(vgl. Listing 3.37), wobei Sie hierbei zusätzlich berücksichtigen, dass jeder Mitarbeiter mit einem Beschäftigungsverhältnis auch in jedem Fall einen Datensatz mit der laufenden Nummer 0 besitzt. select a.* from personalien a ,bvs b where b.persnr = a.persnr and b.lfdnr = 0 LEVEL POSITION Q_PLAN ---------- ---------- ------------------------------------------1 1 SELECT STATEMENT 2 1 NESTED LOOPS 3 1 TABLE ACCESS FULL PERSONALIEN 3 2 INDEX UNIQUE SCAN BVS_PK Listing 3.37: Ausführungsplan nach Auflösung der Unterabfrage in einen echten Join
Nach Umstellung der Abfrage sieht auch der Ausführungsplan völlig anders aus. Auch jetzt werden die Personalien vollständig gelesen, wobei nun durch den Join vom Typ „NESTED LOOPS“ bei jedem Datensatz direkt im Primärschlüssel der Beschäftigungsverhältnisse nachgeschaut wird, ob dieser Satz auszuwählen ist oder nicht. Aufbau der Abfrage Es gibt Kollegen, die wissen gar nicht, dass der Auswahlbefehl select und nicht select distinct heißt und mal abgesehen davon, dass die distinct-Klausel durchaus in der Lage ist, einen vorhandenen Abfragefehler zu verschleiern, so führt sie in jedem Fall zu einem vielleicht überflüssigen „SORT UNIQUE“ Arbeitsschritt. Generell gilt, dass gut strukturierte Abfragen oftmals besser laufen, als inhaltlich gleiche Spagettiabfragen. Gut strukturiert beinhaltet hierbei allerdings weniger die äußerliche Formatierung, die führt im Regelfall zu weniger Fehlern, sondern die Verwendung unnötiger bzw. ungünstiger Konstruktionen. So sollten Sie beispielsweise aufpassen, wenn Sie bei Auswahl- oder Verknüpfungsbedingungen auf das Phänomen der impliziten Datentypkonvertierung setzen, da das zum Ausschluss vorhandener Indexdateien führen kann. So führt die Anweisung „where zeichenspalte = 1234“ intern zu der Ausführung „where to_number(zeichenspalte) = 1234“ und damit zur Nichtverwendung eines eventuell vorhandenen Index, wobei Letzteres sogar immer dann gilt, wenn die Felder der Tabelle nicht direkt, sondern erst nach dem Durchschleusen durch eine Funktion bzw. innerhalb eines Ausdrucks verwendet werden. Ich habe hierfür im Folgenden einmal drei exemplarische Beispiele aufgeführt und Sie werden sich wundern, wie viel man mit solchen Kleinigkeiten manchmal bewirken kann. where zeichenspalte = to_char(1234) statt where zeichenspalte = 1234 where wertspalte > 3455 -1
340
Abfragen
statt where wertspalte +1 > 3455 where name like 'ABC%' statt where substr(name,1,3) = 'ABC'
Generell gilt, dass Sie einzelne Felder im Rahmen einer Verknüpfung möglichst direkt und nicht erst nach einer mehr oder weniger umfangreichen Umformatierung verwenden sollten. where a.datum = b.datum statt where to_char(a.datum, 'YYYY.MM.DD') = to_char(b.datum, 'YYYY.MM.DD')
Handelt es sich bei dem Datumsfeld beispielsweise um ein Indexfeld, welches im besonderen Fall nur bis zum enthaltenen Monat abgefragt werden muss, dann können Sie durch die folgende Abfrage bzw. Umformatierung ggf. die Verwendung eines „INDEX RANGE SCAN“ erreichen. where a.datum between to_date('2000.01.01', 'YYYY.MM.DD') and to_date('2000.01.31', 'YYYY.MM.DD') statt where to_char(a.datum, 'YYYY.MM' = '2000.01'
Versuchen Sie auch die Verwendung umfangreicher in-Listen zu vermeiden, wobei es sogar oft so ist, dass der Verwendungszwang solcher Listen auf ein Problem im Datenmodell hinweist. where kst in ('PERS', 'EDV', 'UMT', 'VERT')
Die soeben aufgezählten Kostenstellen gehören vielleicht zu einem Bereich, so dass es bei mehreren Bereichen entsprechend viele unterschiedlichen Kostenstellenlisten gibt. In dem Fall fehlt im Datenmodell wohl eher eine Bereichstabelle, mit der sich Kostenstellen zu einem Bereich zuordnen lassen, so dass die Abfrage anschließend folgendermaßen aussehen würde. from kostenstellen a, bereiche b where b.kst = a.kst and b.bereich = 'VT'
Datenbankübergreifende Abfragen Bei datenbankübergreifenden Abfragen ist es ideal, wenn sich alle an der Abfrage beteiligten Tabellen in der gleichen Datenbank befinden. Bei Antwortszeitproblemen sollten Sie über den Umzug von Tabellen nachdenken und diese anschließend im Dialog mit Hilfe eines Synonyms und eines Datenbanklinks versorgen. Views Wenn im Ausführungsplan die Operation „VIEW“ auftaucht, dann gilt nicht unbedingt Alarmstufe Rot, jedoch sollten Sie dem Ganzen einen Moment Ihrer Aufmerksamkeit widmen, denn wenn die View viele Datensätze materialisiert und mitten im Ausführungsplan auftaucht, dann würde es mich nicht wundern, wenn die gesamte Abfrage nicht besonders schnell terminiert.
Tuning von Abfragen
341
Auf der anderen Seite kann es in Extremsituationen aber auch helfen, die Ausführung einer View durch Änderung der zugrundeliegenden Definition zu erzwingen, indem Sie beispielsweise eine distinct-Klausel hinzufügen. Liefert die View nämlich sehr wenige oder sogar nur einen Datensatz, dann kann das Herauslösung und separate Ausführen dieser Teilabfrage sogar Geschwindigkeitsvorteile bringen. Gruppierende Abfragen Vertauschen Sie bei einer gruppierenden Abfrage nicht die Bedeutung von where und having. Alle Einschränkungen, die schon in der where-Klausel durchgeführt werden können, sollten von Ihnen auch dort spezifiziert werden, d.h. in der having-Klausel sollten Sie nur diejenigen Zusatzbedingungen kodieren, die wirklich erst bei der Gruppierung geprüft werden können. Dies möchte ich mit dem folgenden kleinen Beispiel verdeutlichen, in dem ich die Lohnartensumme für die Lohnart 350 auswerte. SQLWKS> select la, sum(betr07) 2> from lohnarten 3> group by la 4> having la = '350' 5> LA SUM(BETR01 --- ---------350 2000 1 row selected.
Zwar liefert diese Abfrage das richtige Ergebnis und dennoch liegt ihr eine zweifelhafte Verfahrensweise zugrunde, denn in dem Fall würde die gesamte Tabelle ausgewertet und gruppiert und aus diesem vollständigen Zwischenergebnis werden erst zum Schluss alle nicht benötigten Lohnarten herausgefiltert. Dabei ist es sicherlich besser, diese Schritte genau umgekehrt durchzuführen, d.h. es werden wirklich nur die benötigten Datensätze gruppiert, so dass die Abfrage folgendermaßen umgestellt werden muss. SQLWKS> select la, sum(betr07) 2> from lohnarten 3> where la = '350' 4> group by la 5> LA SUM(BETR07 --- ---------350 2000 1 row selected.
3.4.4
Verwenden von Hints
Zumindest derzeitig sind die in den Datenbanksystemen verfügbaren Abfrageoptimierer sicherlich schon sehr leistungsfähig und dennoch gibt es Situationen, in denen sich der Abfrageerfolg in Form kurzer Auswahlzeiten nicht so richtig einstel-
342
Abfragen
len will. In dem Fall haben Sie in vielen Datenbanksystemen die Möglichkeit, einen gewissen Einfluss auf die Arbeitsweise des Optimierers zu nehmen, um hierdurch zu einem anderen und in dem vorliegenden Spezialfall besseren Ausführungsplan zu kommen. Wenn Sie in Oracle regelbasiert optimieren, dann beschränken sich Ihre Einflussmöglichkeiten allerdings auf die Umstellung der Abfrage oder der Anlage zusätzlicher Indexe. Bei der kostenbasierten Optimierung steht Ihnen neben der Anlage von Indexen und der Erstellung zusätzlicher Statistiken vor allem das Werkzeug der sogenannte Hints zur Beeinflussung des Ausführungsplans zur Verfügung. Bei solchen Hints handelt es sich um spezielle Anweisungen, die als Kommentar getarnt in die Abfrage eingebaut werden, so dass sich das Ganze schematisch folgendermaßen darstellt: select /*+ Hint-Liste */ persnr, lfdnr, ... from …
In jeder Abfrage können sie mittels der Begrenzungszeichen „/*“ und „*/“ Kommentare einfügen, wobei diesmal das zusätzlich verwendete Pluszeichen („+“) auf mögliche Hint-Kommandos hinweist. Ich bekomme hierbei stark den Eindruck, dass diese Schreibweise aus einer Not heraus geboren wurde, denn für die Verwendung irgendwelcher Optimierhinweise gibt es zum einen natürlich keine Normierung und zum anderen wollte man seitens von Oracle sicherlich nicht das Format der einzelnen Abfragen auf den Kopf stellen, nur um spezielle Ausführungskennzeichen verarbeiten zu können. Das Problem bei den Hints bzw. dem Verstecken in einem Kommentar ist nämlich, dass Oracle fehlerhaft geschriebene Kürzel einfach als Kommentar betrachtet, d.h. es erfolgt weder die Änderung des Ausführungsplans, noch die Ausgabe einer Fehlermeldung. select /*+ Lidel ist billig aber Aldi ist billiger*/ persnr, ... from …
Dieser sinnlose und nicht bekannte Hint ist in Wirklichkeit nur ein Kommentar, der auf den ersten Blick nur wie ein Hint aussieht. In der nachfolgende Tabelle (vgl. 3.11) finden Sie eine Übersicht einiger wichtiger Hints, wobei Sie die vollständige Liste aller verfügbaren Kommandos in der Dokumentation im Buch „Oracle8 Tuning“ und dort im Kaptitel „Optimization Modes and Hints” finden. Hint
Beschreibung
rule
Dieser Hint führt zur regelbasierten Optimierung für eine spezielle Abfrage. Gerade wenn sich nach einer Umstellung des Optimierungsmodus für die eine oder andere Abfrage Laufzeitprobleme einstellen, dann können Sie darauf mit dem Hint schnell reagieren, in dem Sie die zugehörigen Abfragen immer noch regelbasiert ausführen.
Tuning von Abfragen
343
Hint
Beschreibung
ordered
Bei der regelbasierten Optimierung haben Sie gelernt, dass die Tabellen in Abhängigkeit der from-Klausel ausgewertet werden. Der ordered-Hint führt bei der kostenbasierten Optimierung zu einer ähnlichen Verfahrensweise, wobei die Tabellen diesmal von links nach rechts bzw. oben nach unten ausgewertet werden.
full(a)
führt zu einem „FULL TABLE SCAN“ für die vorgegebene Tabelle mit dem Alialsnamen „a“.
use_nl(a b), use_merge(a b), use_hash(a b)
erzwingt die Verknüpfung der Tabellen „a“ und „b“ unter Verwendung der entsprechenden Join-Operation, beispielsweise führt use_nl zur Anwendung eines „NESTED LOOPS” .
hash_aj, merge_aj
Umformung einer not in-Unterabfrage in einen Hash- bzw. Merge-AntiJoin.
index(a Index)
empfiehlt die Verwendung eines Index für die Tabelle mit dem Aliasnamen „a“. Statt eines speziellen Indexnames können Sie hierbei auch mehrere Indices vorschlagen.
index_desc
entspricht dem Index-Hint, wobei der Index jetzt absteigend durchlaufen werden soll.
Tabelle 3.11: Übersicht einiger ausgewählter Hints
Beispiele Damit bleibt eigentlich nur noch die Frage offen, wie diese Hints genau innerhalb einer Abfrage verwendet werden, was ich zum Abschluss dieses Kapitels anhand einiger Beispiele demonstrieren will. Beginnen wir dabei mit dem Hint index, der dem System die Verwendung desselben nahe legt. select /*+ index(a personalien1)*/ * from personalien a where name like 'R%'; LEVEL POSITION Q_PLAN ---------- ---------- ---------------------------------------------1 2 SELECT STATEMENT 2 1 TABLE ACCESS BY INDEX ROWID PERSONALIEN 3 1 INDEX RANGE SCAN PERSONALIEN1 Listing 3.38: Anwendung des Index-Hints
Ohne unseren Hint würde das System aufgrund der Größe unserer Tabelle einfach die gesamte Tabelle lesen, was sicherlich auch sinnvoll ist. Bei entsprechend großen Tabellen wird der Optimierer die passenden Indices in der Regel ganz von alleine finden, wodurch wir wieder bei einem mit der Hint-Verwendung verbundenen Problem wären. Warum soll man dem System etwas beibringen, was es bei dem Vorliegen der entsprechenden Rahmenbedingungen von ganz alleine tut? Das nächste Beispiel zeigt, wie Sie mit Hilfe eines entsprechenden Hints die Verknüpfung zweiter Tabelle über die nested loops-Mehtode erzwingen können.
344
Abfragen
select /*+ use_nl(a b) */ a.* from personalien a, bvs b where b.persnr = a.persnr; LEVEL POSITION Q_PLAN ---------- ---------- -----------------------------1 25 SELECT STATEMENT 2 1 NESTED LOOPS 3 1 TABLE ACCESS FULL PERSONALIEN 3 2 INDEX RANGE SCAN BVS_PK Listing 3.39: Verwenden des use_nl-Hints
Die anderen Verknüpfungs-Hints werden ganz entsprechend angewendet. Besonders einfach ist natürlich die Verwendung der Hints rule oder ordered, die ganz ohne zusätzliche Parameter angewendet werden. Das nächste Beispiel dieser Thematik zeigt Ihnen einmal die gleichzeitige Verwendung verschiedener Hints (vgl. Listing 3.40). Dabei erstellen wir wieder eine Abfrage, in der wie die Personalien, Beschäftigungsverhältnisse und die Gehälter miteinander verknüpfen. Die ersten beiden Tabellen sollten dabei über die Methode „nested loops“ und die Gehaltstabelle mit Hilfe eines „merge joins“ verknüpft werden. select /*+ ordered use_nl(a b) use_merge(c) */ a.* from personalien a, bvs b, gehalt c where b.persnr = a.persnr and c.persnr = b.persnr and c.lfdnr = b.lfdnr; LEVEL POSITION Q_PLAN ---------- ---------- ---------------------------------------1 33 SELECT STATEMENT 2 1 MERGE JOIN 3 1 SORT JOIN 4 1 NESTED LOOPS 5 1 TABLE ACCESS FULL PERSONALIEN 5 2 INDEX RANGE SCAN BVS_PK 3 2 SORT JOIN 4 1 TABLE ACCESS FULL GEHALT Listing 3.40: Anwenden einer ganzen Hintliste
Wie Sie dem im Listing 3.40 gezeigten Ausführungsplan entnehmen können, werden die Gehaltstabelle und das Ergebnis der Verknüpfung zwischen Personalien und Beschäftigungsverhältnis in der Tat mit Hilfe eines Merge-Joins verknüpft, weshalb Sie auch jedes Mal vorher die Operation „SORT JOIN“ im Ausführungsplan finden. Die anderen beiden Tabellen werden über einen „NESTED LOOPS“ miteinander verbunden.
Tuning von Abfragen
345
Damit das Ganze auch wirklich so wie in der Hint-Liste vorgegeben funktioniert, muss Oracle unsere Tabellen wie in der from-Klausel vorgegeben abarbeiten, weshalb wir zusätzlich auch den ordered-Hint spezifizieren müssen. Damit kommen wir zum letzten Beispiel, indem wir uns einmal die Verwendung von Hints innerhalb einer Unterabfrage anschauen. Im Prinzip können Sie pro Abfrage eine Hintliste spezifizieren, d.h. die verschiedenen Unterabfragen können eigene Hints enthalten. Wir schauen uns das anhand der schon häufig strapazierten Gehaltsabfrage an, wo wir mit Hilfe einer Unterabfrage den letzten Gehaltsdatensatz ermitteln (vgl. Listing 3.41). select /*+ index(c geh_dat) */ c.* from gehalt c where c.gab = (select /*+ use_merge(b c1) */ max(gab) from gehalt c1, bvs b where c1.persnr = c.persnr and c1.lfdnr = c.lfdnr and b.persnr = c1.persnr and b.lfdnr = c1.lfdnr); LEVEL POSITION Q_PLAN ---------- ---------- --------------------------------------1 2 SELECT STATEMENT 2 1 FILTER 3 1 TABLE ACCESS BY INDEX ROWID GEHALT 4 1 INDEX FULL SCAN GEH_DAT 3 2 SORT AGGREGATE 4 1 MERGE JOIN 5 1 INDEX UNIQUE SCAN BVS_PK 5 2 FILTER 6 1 TABLE ACCESS FULL GEHALT Listing 3.41: Anwenden zusätzlicher Hints in einer Unterabfrage
Machen Sie die Gegenprobe und Sie werden feststellen, dass der Ausführungsplan ohne die Vorgabe der Hints ganz anders aussieht. Die Tabellen der Unterabfrage werden dann über die Methode „NESTED LOOPS“ verbunden und der Zugriff auf die Gehaltstabelle erfolgt ohne die Verwendung eines Index. Zusammenfassung Man kann es nicht oft genug sagen, aber ich halte die Verwendung von Hints nur in Ausnahmefällen für gerechtfertigt, auch wenn das Experimentieren mit diesen Kommandos durchaus Spaß machen kann. Wenn Sie schon bei der Entwicklung von Abfragen Hints einsetzen, dann haben Sie das Problem, dass Sie eigentlich nicht wissen, ob die Datenbank in der Produktionsumgebung nicht ganz von alleine auf den gleichen oder sogar bessere Ausführungspläne kommt. Zum anderen kann es passieren, dass der Optimierer nach dem Einspielen des neusten Patches eine bestimmte Abfrage anders bzw. besser analysiert, d.h. durch die Verwendung von Hints berauben Sie sich dieser Option. Bei der Verwendung von Hints ist also ein gewisses Fingerspitzengefühl gefragt.
346
Abfragen
Außerdem sollte man den Ausführungsplan nach dem Einfügen zusätzlicher Hints kontrollieren, da es bei der Ausführung der Abfrage niemals Fehlermeldungen wegen fehlerhafter Hints gibt. Wird der Optimierbefehl nicht im Ausführungsplan sichtbar, dann kann das folgende Gründe haben:
X X X
Der Hint ist falsch kodiert, enthält fehlerhafte Parameter oder wurde fehlerhaft geschrieben. Es handelt sich (z.B. „index“) um einen Vorschlagshint, die vorgegebenen Optionen machen aber alle keinen Sinn und werden deshalb ignoriert. Der Hint wird durch einen übergeordneten Befehl außer Kraft gesetzt oder macht in der verwendeten Kombination keinen Sinn.
3.5
Data Dictionary Views
Der letzte Teil des Kapitels über die Abfragen führt uns zu einem Streifzug durch die vom System bereitgestellten Views zur Abfrage verschiedenster Informationen aus dem Data Dictionary. Mit Hilfe dieser Views können Sie Informationen über die in der Datenbank gespeicherten Objekte, den Zugriffsrechten einzelner Benutzer oder aktuelle Datenbankaktivitäten sammeln. Im Prinzip könnte ich mit diesem letzten Abschnitt das gesamte Buch voll machen, denn die Liste der verfügbaren Views ist so lang, dass die Beschreibung jeder einzelnen View, womöglich noch zusammen mit einem kleinen Anwendungsbeispiel, den gesetzten Rahmen bei weitem sprengen würde. Aus dem Grund werde ich mich auf einige ausgewählte Beispiele beschränken und verweise ansonsten einmal wieder auf die zugehörigen Passagen der OracleDokumentation. Konkret finden Sie eine vollständige Übersicht und Beschreibung aller verfügbaren Data Dicitionary Views im Buch „Oracle8 Reference“. Beim erstmaligen Überfliegen der vorhandenen Views fällt zunächst einmal auf, dass verschiedene dieser Sichten ähnliche Namen aufweisen und sich eigentlich nur durch die Präfixe all, dba und user unterscheiden. In der Tat liefern die entsprechenden Views (z.B. all_tables, dba_tables und user_tables) ganz ähnliche Ergebnisse und unterscheiden sich zum einen nur durch die Menge der selektierten Datensätze und zum anderen durch die standardmäßig installierten Zugriffsrechte auf diese Objekte, d.h. beispielsweise hat jeder Benutzer normalerweise Zugriff auf die user-Views aber nicht automatisch Zugriff auf die dba-Views. In der Tabelle 3.12 finden Sie eine Übersicht, was die einzelnen Präfixe gewöhnlich in Bezug auf die Selektion bewirken. Präfix
Beschreibung
user
führt zur Anzeige aller im eigenen Schema enthaltenden Objekte.
all
enthält alle Objekte der entsprechender user-View plus alle Objekte auf die der aktuelle Benutzer Zugriff hat.
dba
enthält alle in der Datenbank vorhandenen Objekte.
Tabelle 3.12: Bedeutung der Präfixe bei den unterschiedlichen Systemviews
Data Dictionary Views
347
Bevor es nun losgeht, noch ein Hinweis in eigener Sache: Bei dem Arbeiten mit den verschiedenen Views gehe ich im Folgenden nicht auf die Bedeutung der einzelnen gelieferten Spalten ein, denn wie schon gesagt, finden Sie eine ausführliche Beschreibung jeder vorhandenen Systemview in der Oracle-Dokumentation.
3.5.1
Analyse der Datenbankobjekte
Im ersten Schritt möchte ich die Verwendung verschiedener Views demonstrieren, mit deren Hilfe Sie Informationen über die in der Datenbank gespeicherten Objekte erhalten können. Die meisten Abfragen erstelle ich dabei anhand der userViews, wobei Sie natürlich genauso gut eine der anderen Viewkategorieen verwenden können. Eventuell müssen Sie dabei nur berücksichtigen, neben dem Objektnamen auch noch den Namen des zugehörigen Schemas zu spezifizieren. Informationen über Tabellen Informationen über die in der Datenbank vorhandenen Tabellen erhalten Sie in erster Linie durch Abfrage der View user_tables. Neben verschiedenen Informationen zu dem verwendeten Tablespace oder der Speicherbelegung finden Sie hier auch einen Eintrag, ob bzw. wann für die Tabelle zuletzt eine Statistik erstellt wurde. Neben den Informationen zu einer einzelnen Tabelle können Sie mit Hilfe dieser bzw. der entsprechenden dba-View aber auch ganz gut feststellen, welche Tabellen alle in einem speziellen Tablespace gespeichert sind. SQLWKS> select table_name 2> from user_tables 3> where tablespace_name = 'USR'; TABLE_NAME -----------------------------AUSTRITTSGRUENDE BLAENDER BVS GEHALT KOSTENSTELLE ... 12 rows selected. Listing 3.42: Verwendung der View user_tables zur Analyse der Tablespacebelegung
Analyse der Tabellenstruktur Informationen über den konkreten Aufbau einer Tabelle erhalten Sie mit Hilfe der View user_tab_columns. Mit Hilfe einer entsprechenden Abfrage können Sie beispielsweise leicht feststellen, welche Tabellen alle das Feld „persnr“ enthalten. SQLWKS> select table_name 2> from user_tab_columns 3> where column_name = 'PERSNR';
348
Abfragen
TABLE_NAME -----------------------------AKTUELLES_GEHALT BVS GEHALT LOHNARTEN PERSONALIEN 5 rows selected. Listing 3.43: Ermittlung der Verwendung eines bestimmten Feldes mit Hilfe der View user_tab_columns
Außerdem verwende ich diese View gerne, um innerhalb von Programmen dynamische SQL-Anweisungen zu erzeugen. Auch hierzu möchte ich ein kleines Beispiel vorstellen und eine Funktion erstellen, mit deren Hilfe Sie eine Zeichenfolge mit allen in der Tabelle vorhandenen Spalten erstellen können, um diese beispielsweise in einer select oder insert into-Anweisung zu verwenden. create or replace function get_fieldlist(in_table in varchar2) return varchar2 is out_liste varchar2(5000); begin declare cursor get_cols is select column_name, data_type from user_tab_columns where table_name = in_table order by column_id; col_record get_cols%rowtype; begin open get_cols; out_liste := ' '; loop fetch get_cols into col_record; exit when get_cols%notfound; out_liste := out_liste || ', ' || col_record.column_name; end loop; close get_cols; return substr(out_liste, 4); end; end; Listing 3.44: Erstellen einer Feldliste für eine beliebige Datenbanktabelle
Data Dictionary Views
349
Innerhalb der selbsterstellten Funktion get_fieldlist ermitteln wir mit Hilfe des Cursors get_cols und der zugehörigen Abfrage auf die View user_tab_columns die Spalten der beim Funktionsaufruf übergebenen Tabelle. Beim Verarbeiten der vom Cursor gelieferten Datensätze hängen wir die einzelnen Feldnamen mit Hilfe der Ausgabevariable out_liste aneinander. Neben den Feldnamen und der in der Tabelle verwendeten Reihenfolge liefert die View natürlich auch weitere Informationen wie den Datentyp oder die null/not null-Einstellung des Feldes. Das folgende Beispiel (vgl. Listing 3.45) zeigt Ihnen die Verwendung unserer neuen Funktion get_fieldlist zur Erstellung einer Feldliste für eine beliebige Tabelle. SQLWKS> select get_fieldlist('PERSONALIEN') from dual 2> GET_FIELDLIST('PERSONALIEN') -------------------------------------------------------------------------------PERSNR, NAME, STRASSE1, STRASSE2, STRASSE3, STRASSE4, ORT, LAND, BLAND, PLZ, TEL 1 row selected. Listing 3.45: Verwenden der Funktion get_fieldlist
Analyse von Indices Neben den Tabellen können Sie mit Hilfe der Systemviews natürlich auch nach Indices bzw. deren Aufbau forschen. Hierbei ist zunächst einmal die View user_indexes zu nennen, mit deren Hilfe Sie beispielsweise die zu einer Tabelle gehörenden oder in einem Tablespace gespeicherten Indices ermitteln können. SQLWKS> select index_name, tablespace_name 2> from user_indexes 3> where table_name = 'GEHALT'; INDEX_NAME TABLESPACE_NAME ------------------------------ -----------------------------GEHALT_PK INDX 1 row selected. Listing 3.46: Ermitteln der für eine Tabelle vorhandenen Indices
Analog zu den user_tab_columns existiert auch für die Indices eine weitere View mit dem Namen user_ind_columns, mit deren Hilfe Sie die Struktur jedes beliebigen Index ermitteln können. In unserem Beispiel erhalten Sie hierbei für den Index „GEHALT_PK“ folgendes Ergebnis (vgl. Listing 3.47). SQLWKS> 2> 3> 4> 5>
select column_name from user_ind_columns where index_name = 'GEHALT_PK' and table_name = 'GEHALT' order by column_position;
350
Abfragen
COLUMN_NAME -------------------------------------------------------------PERSNR LFDNR GAB 3 rows selected. Listing 3.47: Ermittlung des Aufbaus des Primärschlüssels der Gehaltstabelle
Aufbau einer View ermitteln Eigentlich soll der Zugriff auf Daten und Zusammenhänge mit Hilfe einer View abstrahiert werden und dennoch ist es manchmal notwendig, die sich hinter einer View verbergende Auswahlabfrage anzuzeigen, beispielsweise um die aktuelle Version des Abfrageobjekts zu überprüfen oder sogar um die in der View abstrahierten Zusammenhänge nachzuvollziehen. Hierzu stellt Oracle Ihnen die Systemview user_views zur Verfügung, mit deren Hilfe Sie Zugriff auf die in der View gespeicherten Abfrage haben (vgl. Listing 3.48). SQLWKS> set longwidth 1000; Longwidth 1000 SQLWKS> select text_length, text 2> from user_views 3> where view_name = 'AKTUELLES_GEHALT' 4> TEXT_LENGT TEXT ---------- -----------------------------------------297 select a.persnr, b.lfdnr, a.name, b.kst from personalien a, gehalt b where b.persnr = a.persnr and b.gab = (select max(gab) from gehalt b1 where b1.persnr = b.persnr and b1.lfdnr = b.lfdnr and b1.gab <= sysdate ) 1 row selected. Listing 3.48: Analyse der einer View zugrundeliegenden Abfrage
Beachten Sie bei diesem Beispiel die erste Anweisung, mit deren Hilfe die Ausgabe bzw. Formatierung von Textfeldern (Datentyp long) verändert wurde. Damit der in diesem Datenfeld gespeicherte Text nicht nach einigen Zeichen abgeschnitten wird, müssen Sie den Ausgabebereich entsprechend erhöhen. Die View selbst liefert Ihnen im Zweifelsfall übrigens die benötigte Textlänge, indem Sie hierzu das Feld text_length abfragen.
Data Dictionary Views
351
Vorhandene Trigger analysieren Mit Hilfe der View user_triggers erhalten Sie einen Überblick über die in Ihrem Schema bzw. der Datenbank installierten Trigger. Neben dem im Feld trigger_body gespeicherten PL/SQL-Programm erhalten Sie hierdurch unter anderem auch Informationen über die zugehörige Tabelle oder den installierten Aufrufmechanismus. Da es sich bei dem Feld trigger_body wieder um ein Textfeld vom Datentyp long handelt, müssen Sie eventuell die Textausgabe wieder mit Hilfe des Parameters longwidth geeignet einstellen. SQLWKS> select trigger_body 2> from dba_triggers 3> where trigger_name = 'LOHNARTEN_UPD' 4> TRIGGER_BODY -----------------------------------------------------------begin if to_char(sysdate,'D') not in ('5','7') then raise_application_error(-20001,'Die Tabelle kann nur am Wochenende geändert werden.'); end if; end; 1 row selected. Listing 3.49: Analyse eines Triggerprogramms
Funktionen, Prozeduren und Pakete durchleuchten Informationen über die in der Datenbank bzw. Ihrem Schema gespeicherten Funktionen, Prozeduren oder Pakete erhalten Sie mit Hilfe der View user_source. Im Unterschied zu den letzten beiden Beispielen werden die Programmteile dieser Objekte allerdings nicht in einem Textfeld, sondern entsprechend der Eingabe zeilenweise gespeichert, weshalb Sie im Feld line die Ausgabe einer laufenden Nummer finden. Über die Spalte type erhalten Sie Informationen über den Typ des gespeicherten Objekts. Die möglichen Werte entsprechend den verfügbaren Objekten, d.h. die Spalte liefert die nachfolgenden Ergebnisse: SQLWKS> select distinct type from dba_source; TYPE -----------FUNCTION PACKAGE PACKAGE BODY PROCEDURE TYPE 5 rows selected.
Das nun folgende Beispiel (vgl. Listing 3.50) erstellt ein Listing der soeben erstellten Funktion zur Auswertung unserer Tabellenstruktur.
352
Abfragen
select rtrim(text) from user_source where name = 'GET_FIELDLIST' order by line; Listing 3.50: Erstellen eines Listings für eine selbsterstellte Funktion
Gerade im Zusammenhang mit Funktionen oder Prozeduren ist manchmal auch interessant, welche Argumente diese Objekte beim Aufruf erwarten, weshalb Sie auch diese Informationen mit Hilfe einer entsprechenden View abfragen können. Die heißt konkret user_arguments und im nächsten Listing (vgl. 3.51) finden Sie wieder ein Beispiel für ihren Gebrauch. SQLWKS> select argument_name, position, data_type, in_out 2> from user_arguments 3> where object_name = 'GET_FIELDLIST'; ARGUMENT_NAME POSITION ------------------------------ ---------0 IN_TABLE 1 2 rows selected.
DATA_TYPE -------------VARCHAR2 VARCHAR2
IN_OUT --------OUT IN
Listing 3.51: Abfrage der Argumentstruktur für eine selbsterstellte Funktion
3.5.2
Aktivitäten in der Datenbank abfragen
Die meisten der zu dieser Kategorie passenden Systemviews beginnen mit dem Präfix v$, d.h. diesmal findet in den meisten Fällen keine Unterscheidung nach Schemazugehörigkeit oder Zugriffsrechten statt. Datenbank abfragen Beginnen möchten ich hierbei mit einem ganz einfachen Beispiel. Sofern Sie in einem Skript oder einem Programm überprüfen möchten, in welcher Datenbank Sie sich gerade befinden, dann können Sie das mit Hilfe einer Abfrage der View v$database durchführen (vgl. Listing 3.52). SQLWKS> select dbid, name, created 2> from v$database 3> DBID NAME CREATED ---------- --------- -------------------3206369407 DATENB01 31-JUL-00 1 row selected. Listing 3.52: Abfrage des Datenbanknamens
Data Dictionary Views
353
Datenbankparameter ermitteln Eine beliebte Frage jeder Hotline ist immer die nach den aktuellen Werten verschiedener Datenbankparameter und da der Zugriff auf die Initialisierungsdatei der Datenbankinstanz nicht unbedingt immer gegeben ist, können Sie die aktuellen Parametereinstellungen auch per Abfrage auf die View v$parameter ermitteln. Mit Hilfe des nachfolgenden Beispiels (vgl. Listing 3.53) wird die aktuelle Arbeitseinstellung des Optimierers abgefragt. SQLWKS> select name, value 2> from v$parameter 3> where name = 'optimizer_mode'; NAME VALUE -------------------------------------------------- -----------------optimizer_mode CHOOSE 1 row selected. Listing 3.53: Abfrage von Parametereinstellungen mit Hilfe der View v$parameter
Aktive Sessions ermitteln Mit Hilfe der View v$sessions können Sie alle aktive Sitzungen bzw. die Anmeldungen eines bestimmten Benutzers abfragen. Das folgende Beispiel (vgl. Listing 3.54) zeigt Ihnen von den insgesamt verfügbaren Spalten ein paar ausgewählte Felder, die vor allem für weitergehende Forschungen interessant sein können. SQLWKS> select sid, serial#, sql_address, command, status, 2> osuser, terminal 3> from v$session 4> where username = 'UBEISPIEL'; 5> SID SERIAL# SQL_ADDR COMMAND STATUS OSUSER TERMINAL ----- ---------- -------- ---------- -------- --------------- --------9 18 0213E4E0 0 ACTIVE Administrator RAY_DELL 1 row selected. Listing 3.54: Abfrage der aktiven Sitzungen des Benutzers „UBEISPIEL“
Beispielsweise benötigen Sie die Felder sid und serial#, um eine hängende Session aus dem System zu entfernen, d.h. wenn nichts mehr geht, dann besteht die erste Aktion darin, die zugehörige Session zu finden und die Werte dieser beiden Felder zu ermitteln. Anschließend können Sie die aus dem System herausschmeißen, indem Sie einen entsprechenden alter system-Befehl absetzen. alter system kill session '9,18';
Dieser Befehl benötigt als Parameter eine Zeichenfolge, in der die beiden Werte sid und serial# durch Komma getrennt enthalten sind, d.h. in meinem Beispiel könnte ich meine Session durch das eben gezeigte Beispiel abschießen, wobei die Anwendung dieser Schießübung den Datenbankadministratoren vorbehalten ist.
354
Abfragen
Manchmal ist es nicht ganz so einfach, bei Problemen die zugehörige Session zu ermitteln, was vor allem dann gilt, wenn sich die einzelnen Benutzer nicht direkt sondern stattdessen alle mit der gleichen Benutzer-Id anmelden, wobei diese zentrale Anmeldung vielleicht sogar noch über einen speziellen Anwendungsserver erfolgt. In solchen Fällen kann Ihnen vielleicht die Spalte command weiterhelfen, mit deren Hilfe die momentane Aktivität der Sitzung verschlüsselt wird. Eine vollständige Übersicht der möglichen Schlüssel finden Sie in der Oracle-Dokumentation hinter der Beschreibung v$session-View, d.h. ich habe Ihnen in der Tabelle 3.13 nur die wichtigsten Schlüssel zusammengestellt. Command
Beschreibung
0
Es wird gerade kein Befehl ausgeführt.
2
insert-Anweisung.
3
Ausführung einer Auswahlabfrage.
6
update-Anweisung.
7
delete-Anweisung.
Tabelle 3.13: Ausgewählte Verschlüsselungen der command-Spalte der View v$session
Wenn auch das nicht weiterhilft, dann haben Sie auch noch die Möglichkeit herauszufinden, welche Aktivitäten die einzelnen Sitzungen im Detail gerade durchführen. Hierzu können Sie beispielsweise die aktuellen Sperrungen oder das gerade ausgeführte SQL-Kommando mit Hilfe weiterer Systemviews auslesen. Mehr dazu erfahren Sie in den folgenden Abschnitten. Sperrungen ermitteln Die in einer Session durchgeführten Sperrungen können Sie mit Hilfe der Views v$lock bzw. v$locked_object ermitteln. In beiden Fällen können Sie mit der SessionId (sid) in die Abfrage einsteigen, wobei die zweite der genannten Views die einfachere Verbindung zur zugehörigen Tabelle ermöglicht, d.h. bei Sperrkollisionen ist der Einstieg über v$locked_object meistens einfacher. Um das nachfolgende Beispiel auszuprobieren benötigen Sie wieder zwei Sitzungen Ihres SQL-Editors. Verwenden Sie eine der beiden Sitzungen, um mit Hilfe einer Auswahlabfrage einen oder mehrere Datensätze zu sperren. select * from personalien where persnr = '7000188' for update;
Mit Hilfe der zweiten Sitzung wollen wir nun die in der Datenbank gesperrten Objekte ermitteln, was mit folgender Abfrage (vgl. Listing 3.55) möglich wäre. SQLWKS> select a.session_id, a.oracle_username, a.os_user_name, b.object_name 2> from v$locked_object a, 3> user_objects b 4> where b.object_id = a.object_id;
Data Dictionary Views
355
SESSION_ID ORACLE_USERNAME OS_USER_NAME OBJECT_NAME ---------- ------------------------------ --------------- -----------9 UBEISPIEL Administrator PERSONALIEN 1 row selected. Listing 3.55: Abfrage aktueller Sperrungen
Wie Sie der Abfrage entnehmen können, ermitteln wir mit Hilfe der Objekt-Id den Namen des gesperrten Objekts, indem wir die Id mit Hilfe der View user_objects auflösen. Ist diese Sperrung beispielsweise seit längerem in der Datenbank und führt zur Behinderung andere Aktivitäten, dann könnten Sie jetzt in einem zweiten Schritt mit Hilfe einer weiteren Abfrage auf die View v$session den zugehörigen Wert für das Feld serial# ermitteln und wären damit in der Lage, den Sitzungsabbruch in der oben beschriebenen Weise zu erzwingen, wodurch auch die Sperrungen aufgehoben würden. Aktivität der Sitzung aufstöbern Nicht nur bei langlaufenden Skripten oder Programmen stellt sich manchmal die Frage, was im Rahmen einer Datenbanksitzung gerade so angestellt wird. Auch das kann man mit Hilfe der vorhandenen Systemviews ermitteln, wenn man zunächst einmal die zugehörige Sitzungs-Id festgestellt hat. Die View v$session liefern nämlich mit ihrem Wert sql_address die Möglichkeit, gezielt in eine weitere View mit dem Namen v$sql einzusteigen, mit deren Hilfe Sie das aktuell in der SGA befindliche SQL-Statement abfragen können. Auf meinem Rechner werden die Personalien immer noch durch die oben abgesetzte Abfrage gesperrt, d.h. die zugehörige Abfrage sollte sich noch in der SGA befinden, was im Folgenden (vlg. Listing 3.56) geprüft bzw. festgestellt wird. SQLWKS> select a.sql_text 2> from v$sql a, 3> v$session b 4> where b.sql_address = a.address 5> and b.sid = 9; SQL_TEXT ---------------------------------------------------------------------select * from personalien where persnr = '7000188' for update 1 row selected. Listing 3.56: Ermittlung des aktuell von der Sitzung ausgeführten SQL-Kommandos
3.5.3
Zugriffsrechte entschlüsseln
Auch zur Auswertung der in der Datenbank vorhandenen Benutzer, Rollen und deren zugewiesenen Einzelrechten finden Sie in Ihrer Oracle-Datenbank eine Reihe von Systemviews. Ebenso wie bei den Datenbankobjekten, existieren auch hierbei
356
Abfragen
wieder eine Reihe von ähnlichen Views mit den unterschiedlichen Präfixen all, dba und user, die genau wie damals bei der Analyse der verschiedenen Datenbankobjekte eine voreingestellte Sicht auf die insgesamt verfügbaren Sicherheitsinformationen liefern. So liefert beispielsweise die View user_tab_privs wieder die auf einzelnen Tabellen vergebenen Rechte für den aktuellen Benutzer, wohingegen Sie mit Hilfe der View dba_tab_privs diese Rechte für jeden beliebigen Benutzer abfragen können. Wenn Sie an dieser Stelle allerdings nun wieder das eine oder andere Beispiel erwarten, dann muss ich Sie auf das nächste Kapitel vertrösten, indem die ganze Thematik der Benutzer und Rollen behandelt wird. Ich finde, dass die Beispiele dort besser aufgehoben sind, so dass ich mich hier lediglich für eine Aufstellung (vgl. Tabelle 3.14) der wichtigsten Views entschieden habe. Bei der Erstellen dieser Liste habe ich mich auf die Aufzählung der dba-Views beschränkt, die entsprechend meiner eben gemachten Erklärung ebenfalls als all- und user-Variante vorliegen. View
Beschreibung
dba_users
liefert eine Liste der im System definierten Benutzer.
dba_roles
listet analog zu dba_users die vorhandenen Rollen auf.
dba_sys_privs
zeigt die einem Benutzer bzw. einer Rolle verliehenen System-Privilegien (z.B. create table) an.
dba_tab_privs
liefert die vergebenen Zugriffsrechte für ganze Tabellen.
dba_col_privs
Hierdurch erhalten Sie ggf. die Zugriffsrechte für einzelne Spalten.
dba_role_privs
zeigt die dem Benutzer zugewiesenen Rollen an.
role_role_privs
liefert die einer Rolle zugewiesenen Rollen.
role_tab_privs
listet die mit einer Rolle verknüpften Zugriffsrechte auf Tabellen auf.
Tabelle 3.14: Ausgewählte Systemviews zur Auswertung von Benuzterprofilen und Rollen
4
Benutzer und Rollen
Mit Hilfe des vierten Kapitel werden wir uns im Folgenden ein wenig mit der in Datenbanken üblichen Benutzerverwaltung beschäftigen und uns dabei anschauen, was Oracle Ihnen hierbei als Unterstützung zu bieten hat. Um es gleich vorweg zu nehmen: die zu diesem Thema gehörenden Möglichkeiten stecken zumindest im Vergleich zu den sonstigen Funktionalitäten noch in den Kinderschuhen. Das ist allerdings kein spezifischer Makel der Oracle-Datenbank, sondern in Bezug auf diesen Punkt können sich eigentlich alle derzeitig aktiven Hersteller die Hand geben. Vielleicht liegt das allerdings auch daran, dass wir im Bereich der Zugriffsrechte für einzelne Benutzer, die wir am liebsten benutzerindividuell auf Datensatz und Feldebene vergeben würden, ein wenig übereifrig sind, denn eine gewisse Grundfunktionalität ist in den heute verfügbaren Datenbanksystemen schon vorhanden, nur uns scheint das niemals zu reichen. Damit Sie mich nicht falsch verstehen: Sicherlich ist es notwendig, die in einer Datenbank gespeicherten Informationen vor dem Missbrauch durch Dritte zu schützen. Was in deutschen Unternehmen allerdings zum Teil getrieben wird, das kann schon ziemlich abenteuerlich sein, wenn auf Feldebene, also auch in Abhängigkeit spezieller Feldinhalte, unterschiedliche Rechte im Bezug auf das Lesen, Ändern, Einfügen und Löschen von Datensätzen bzw. einzelner Felder gefordert werden.
4.1
Einführung in die Benutzerverwaltung
Ausgangspunkt bei der Arbeit mit einem Datenbanksystem ist der einzelne Benutzer oder User mit seinem in der Datenbank gespeicherten Profil. Einem solchen Benutzer entspricht in der Realität üblicherweise ein konkreter Anwender, wobei es im Regelfall auch eine Reihe von technischen Benutzern gibt (z.B. „sys“ oder „system“), die zur Administrierung der Datenbank verwendet werden. Ein solcher Benutzer besitzt vor allem eine in der Datenbank eindeutige Kennung, die sogenannte Benutzer- bzw. User-Id und üblicherweise ein nur ihm bekannte Passwort, wobei diese beiden Informationen beim Anmelden an die Datenbank benötigt werden. Daneben steht der Benutzer auch noch für eine Sammlung von zugewiesenen Berechtigungen, die im Einzelnen regeln, welche Datenbankobjekte der Benutzer verwenden oder welche sonstigen Aktivitäten er in der Datenbank ausführen darf. Die einem Benutzer zugewiesenen Berechtigungen kann man in folgende Kategorien unterteilen:
358
X X X
Benutzer und Rollen
objektbezogene Rechte Systemrechte Profile.
Objektbezogene Rechte Wie Sie in den vorherigen Kapiteln gesehen haben, zeichnet sich eine Datenbank üblicherweise durch eine Vielzahl unterschiedlicher Objekte aus, die im Regelfall zusammen bzw. unter der Hoheit eines technischen Users angelegt werden. Standardmäßig haben alle Benutzer lediglich Zugriff auf die im eigenen Schema gespeicherten Datenbankobjekte, so dass der Zugriff auf andere Objekte wie Tabellen oder Funktionen explizit gewährt werden muss. Systemrechte Eigentlich ist ein Benutzer bzw. Anwender jemand, der seine Tagesarbeit mit denen in einer Datenbank vorhandenen Objekte erledigt. Hierbei werden die in den Tabellen gespeicherten Daten abgefragt oder geändert oder es werden in der Datenbank gespeicherte Programme ausgeführt. Dabei gehört die Anlage neuer Tabellen, das Erstellen oder Löschen von Indices oder das Ändern von Prozeduren oder Paketen nicht zum Aufgabengebiet eines gewöhnlichen Endanwenders. Aus diesem Grund beinhaltet das Recht, eine Tabelle zu lesen oder eine Prozedur zu starten, noch lange nicht die Möglichkeit, die Struktur der Tabelle zu ändern oder das in der Prozedur gespeicherte Programm zu verbessern. Um das tun zu dürfen, benötigt man weitere besondere Privilegien, die man insgesamt als Systemrechte bezeichnen kann. Zu diesen Systemrechten gehören auch weitergehende besondere Privilegien, wie zum Beispiel das Recht zur Anlage neuer Benutzer oder die Berechtigung zur Durchführung einer Datensicherung. Profile Eine sogenanntes Profil spielt bei der gesamten Benutzerdefinition nur eine untergeordnete Rolle. Es gibt viele Installationen, bei denen alle angelegten Benutzer das standardmäßig vorhandene Profil default besitzen und wo auch keine weiteren Profile in der Datenbank gespeichert sind. Ein solches Profil kann beispielsweise Limitierungen in Bezug auf die beanspruchbaren Ressourcen beinhalten, indem dort vielleicht die Größe des privaten SGABereichs oder die Anzahl der vom Benutzer parallel zu betreibenden Datenbanksitzungen vorgegeben werden. Auf der anderen Seite regelt ein solches Profil auch den Komplex der Passwortverwaltung, indem Sie dort beispielsweise Regeln und Methoden für den Zyklus oder die Inhalte bei den Passwortänderungen festlegen.
Einführung in die Benutzerverwaltung
4.1.1
359
Das Rollenkonzept
Wie Sie sich sicherlich vorstellen können, kann so ein Benutzerprofil wegen des Umfangs der in der Datenbank gespeicherten Objekte aber auch aufgrund der Vielzahl der möglichen Einzelrechte ziemlich umfangreich sein. Gesellt sich jetzt auch noch der Umstand dazu, dass es in der Datenbank nicht nur ein paar, sondern stattdessen sogar mehrere hundert Anwender gibt, dann geht das schnell zu Lasten der Übersicht oder Wartbarkeit. Die Verwendung von Einzelrechten entbehrt des weiteren jeglicher Vernunft, wenn man sich anschaut, dass sich die einzelnen Benutzer-Ids in der Praxis vor allem nur durch Ihre Kennung und die Passwörter unterscheiden, oder zumindest zu größeren derartigen Gruppen zusammengefasst werden können. Aus diesem Grund bietet Ihnen Oracle das Konzept der Rollen an, mit dem Sie beliebige Einzelrechte zu Gruppen, den sogenannten Rollen, zusammenfassen können. Danach, d.h. in einem zweiten Schritt, können Sie den Benutzern die zuvor geschnürten Bündel von Einzelrechten zuweisen. Auf diese Weise werden nicht nur die einzelnen Benutzerprofile transparenter, sondern die gesamte Profilstruktur der Datenbank ist leichter zu durchschauen und in gewissen Teilen auch wiederverwendbar. Dies kann in der Praxis dadurch erreicht werden, dass zunächst kleine und mehrfach verwendbare Zugriffspakete zu entsprechenden Rollen gepackt werden. Da eine Rolle selbst wieder verschiedene Rollen im Profil enthalten kann, können in einem zweiten oder sogar dritten Schritt diese kleinen „Röllchen“ zur anwendbaren Profilen gemischt werden, bevor der konkrete Benutzer letztendlich eine oder mehrere dieser Rollen zugewiesen bekommt.
4.1.2
Der Oracle Security Manager
Ähnlich wie bei den Datenbankobjekte finden Sie auch zur Bearbeitung der Benutzerprofile ein geeignetes Hilfsmitteln in Ihrem Oracle-Werkzeugkasten. Das gilt zumindest dann, wenn Sie den Enterprise Manager installiert haben, denn im Rahmen dieser Produktpalette finden Sie den sogenannten Security Manager (vgl. Abb. 4.1), mit dessen Hilfe Sie Benutzer, Rollen und Profile definieren bzw. bearbeiten können. Der Aufbau und die Funktionsweise des Security Managers entspricht im Wesentlichen den anderen Programmen aus der Gruppe des Enterprise Managers. Im linken Teil des Arbeitsfensters finden Sie wieder eine Liste, in der die vorhandenen Benutzer, Gruppen und Profile dargestellt werden. Nach der Markierung eines dort gezeigten Eintrags erhalten Sie im rechten Teil des Programmfensters die Möglichkeit, das ausgewählte Objekt zu bearbeiten. Die dabei gezeigten Fenster und Reiter hängen natürlich von dem markierten Objekt ab, d.h. ein Profil besitzt logischerweise andere Einstellparameter als eine Rolle oder ein einfacher Benutzer.
360
Benutzer und Rollen
Abbildung 4.1: Arbeitsfenster des Security Managers
Aber auch besondere andere Eigenschaften, die ich schon beim Schema Manager beschrieben hatte, finden Sie bei diesem Programm wieder. So können Sie das Programm beispielsweise wieder dazu verwenden, die zu einer Aktion zugehörigen SQL-Anweisungen auszuspionieren oder Sie protokollieren alle Ihre Aktivitäten mit Hilfe der Log-Menüeinträge, um das zugehörige Skript anschließend zu archivieren. Wenn Sie mit einer 8i-Version arbeiten, dann finden Sie den Security-Manager wieder im neuen DBA Studio (vgl. Abb. 4.2), wobei die einzelnen verfügbaren Funktionen allerdings die gleichen sind. Im dargestellten Baum des DBA Studios finden Sie in der Kategorie „Sicherheit“ die gleichen Einträge wie beim Security Manager der 8er-Version. Dennoch ist es aus meiner Sicht trotz dieser brauchbaren Programme sinnvoll, die zum Bearbeiten von Benutzern und Rollen einsetzbaren SQL-Anweisungen zu kennen, da bei größeren Anwendungen die Profile üblicherweise nicht per Hand erstellt, sondern mit Hilfe entsprechender Programme oder Skripte generiert werden.
Benutzerverwaltung mit SQL
361
Abbildung 4.2: Benutzerverwaltung im DBA-Studio
4.2
Benutzerverwaltung mit SQL
In Oracle besteht genau wie in vielen andere Datenbanksystemen trotzt des vorhandenen Tools die Möglichkeit, die Benutzer und Rollen mit Hilfe spezieller SQLAnweisungen anzulegen bzw. zu bearbeiten. Das Schöne dabei ist, dass man sich hierfür kaum spezielle Befehle merken muss, denn ähnlich wie die Anlage, das Ändern oder Löschen von sonstigen Datenbankobjekten beginnen die Befehle zur entsprechenden Benutzerbearbeitung auch mit create, alter und drop. Generell handelt es sich bei den hier gezeigten Klauseln zu den einzelnen Befehlen mal wieder um einen Auszug der insgesamt vorhandenen Möglichkeiten, d.h. es lohnt sich unter Umständen, die genaue Bandbreite der Anweisung in der OracleDokumentation nachzulesen, wobei Sie alle verfügbaren Parameter beispielsweise in der SQL-Referenz finden. Sofern nichts anderes vorgegeben ist, finden Sie die einzelnen Listings auch wieder auf der CD im \USER-Verzeichnis. Die Namen entsprechen dabei dem Listingnamen zuzüglich der Namenserweiterung SQL.
4.2.1
Einen Benutzer bearbeiten
Im Listing 4.1 finden Sie die typische Befehlsabfolge zum Löschen und Anlegen eines neuen Datenbankbenutzers, wobei das hier verwendete Beispiel wieder dem im Kapitel 2 verwendeten Skript zur Anlage unseres Schema-Benutzers entspricht.
362
Benutzer und Rollen
drop user ubeispiel1 cascade; commit; / create user ubeispiel1 identified by manager default tablespace usr temporary tablespace temporary profile default account unlock quota unlimited on usr quota unlimited on indx quota unlimited on temporary; commit; Listing 4.1: Typisches Skript zur Anlage eines Datenbankbenutzers
Wie Sie dem Listing 4.1 entnehmen können, wird ein Benutzers mit Hilfe der Anweisung drop user aus der Datenbank gelöscht. Dabei sorgt die cascade-Klausel dafür, dass dies auch dann funktioniert, wenn der Benutzer Eigentümer irgendwelcher Datenbankobjekte, beispielsweise Tabellen ist, wobei die hierbei anschließend ebenfalls aus der Datenbank entfernt werden. Die Anlage eines Benutzers erfolgt mit Hilfe der Anweisung create user, wobei Sie neben der neuen Benutzerkennung vor allem auch die identified by-Klausel zur Vorgabe des zugehörigen Passworts verwenden sollten. Alle anderen Parameter können Sie auch in einem zweiten Schritt durch das Absetzen entsprechender alter user-Anweisungen einstellen (vgl. Listing 4.2), wobei Sie die Bedeutung der einzelnen Klauseln der Tabelle 4.1 entnehmen können. create user ubeispiel1 identified by manager; alter user ubeispiel1 default tablespace usr temporary tablespace temporary profile default account unlock quota unlimited on usr quota unlimited on indx quota unlimited on temporary; Listing 4.2: Alternative Anlage eines neuen Benutzers
Klausel
Beschreibung
default tablespace
legt einen Standard-Tablespace fest (Standard „system“), der immer dann verwendet wird, wenn Sie bei der Anlage neuer Datenbankobjekte keine explizite Spacevorgabe vornehmen.
temporary tablespace
In dem hier spezifizierten Tablespace werden temporäre Objekte (z.B. Zwischenergebnisse) gespeichert.
Benutzerverwaltung mit SQL
Klausel
Beschreibung
363
profile
Name des zugeordneten Profils (z.B. „default“).
account
Freigabe oder Sperren des Benutzerprofils. Der Zusatz lock führt zur Sperrung des in der Datenbank verbleibenden Profils, wohingegen es mit unlock freigegeben wird.
quota
Mit Hilfe dieser Klausel können Sie festlegen, wie viel Platz ein Benutzer mit seinen Objekten in dem spezifizierten Tablespace belegen darf. Diese Vorgabe kann wie in unserem Beispiel uneingeschränkt oder in Form einer festgelegten Anzahl von Kilo- oder Megabytes festlegen.
identified by
Haben wir zwar nicht zusammen mit der alter user-Anweisung verwendet, aber auf diese Weise könnten Sie das Passwort für einen vorhandenen Benutzer ändern.
Tabelle 4.1: Beschreibung einiger Klauseln zur Verwendung in einem Benutzerprofil
4.2.2
Rollen anlegen und bearbeiten
Zum Bearbeiten einer Rolle benötigen Sie zunächst einmal nur die beiden Kommdos drop role und create role, mit denen Sie eine vorhandene Rolle löschen bzw. eine neue Rolle anlegen können. Das Listing 4.3 zeigt das einmal an einem einfachen Beispiel, wobei die Rolle in dem Beispiel ohne spezielles Passwort angelegt wird, so dass sie später auf einfache Weise aktiviert werden kann. drop role personal; create role personal; Listing 4.3: Löschen und Anlegen einer Rolle
4.2.3
Profile anlegen und bearbeiten
Auch Profile können Sie mit Hilfe von SQL-Anweisungen anlegen, ändern oder löschen. Das Listing 4.4 zeigt das einmal anhand eines einfachen Beispiels, in dem das neue Profil „aushilfe“ erstellt wird. drop profile aushilfe cascade; create profile aushilfe limit sessions_per_user 1 private_sga 200K; Listing 4.4: Löschen und Anlegen des Profils „aushilfe“
Das neue Profil erhält einmal abgesehen von zwei Ausnahmen für alle anderen Parameter Standardeinstellung, da diese nicht im Rahmen der Anlage mit speziellen Werten eingestellt werden. Die Anlage des neuen Profils erfolgt durch die Anweisung create profile, wonach der Name des neuen Profils und anschließend das Schlüsselwort limit folgt. Im Anschluss daran erfolgt die Aufzählung der einzu-
364
Benutzer und Rollen
schränkenden Parameter, wobei ich in unserem Beispiel vor allem dafür gesorgt habe, dass sich Benutzer mit diesem Profil nur einmal an der Datenbank anmelden können. Vor der Anlage des neuen Profils erfolgte in unserem Beispielskript die Löschung desselben mit Hilfe einer drop profile-Anweisung. Hierbei können Sie ähnlich wie bei den Benutzern wieder die Klausel cascade verwenden, um sicherzustellen, dass das Profil trotzt eventueller Verwendung gelöscht wird. Hierbei erhalten alle Benutzer, die das gelöschte Profil aktuell besitzen, automatisch „default“ als ihr neues Profil zugewiesen. Genau wie beim Anlegen neuer Benutzer haben Sie auch bei den Profilen die Möglichkeit, dieses zunächst einmal recht nackt anzulegen und die einzelnen Parameter in einem zweiten Schritt durch entsprechender alter profile-Befehle zu verändern (vgl. Listing 4.5). alter profile aushilfe limit failed_login_attempts 5 password_lock_time 1; Listing 4.5: Verändern eines Profils mit Hilfe entsprechender alter-Kommandos
Des letzte Beispiel kontrolliert die Anzahl der fehlerhaften Anmeldeversuche und sperrt den Benutzer nach fünf vergeblichen Versuchen für einen Tag. Diese und weitere interessante Profileinstellungen können Sie der Tabelle 4.2 entnehmen. Profilparameter
Beschreibung
sessions_per_user
Anzahl der Sitzungen, die ein Benutzer parallel in der Datenbank betreiben kann.
connect_time
Maximale Lebensdauer einer Datenbanksitzung in Minuten.
idle_time
Anzahl der Minuten, nach der eine inaktive Sitzung aus dem System geworfen wird.
failed_login_attempts
legt die Anzahl der möglichen vergeblichen Anmeldeversuche fest, bevor die Benutzerkennung gesperrt wird.
password_life_time
gibt die maximale Lebensdauer für ein Passwort in Tagen an, d.h. nach Ablauf dieser Zeit muss das Passwort vom Benutzer geändert werden. In dem Zusammenhang sind vielleicht auch die beiden Parameter password_reuse_time und password_reuse_max interessant, mit denen die Wiederverwendbarkeit von abgelaufenen Passwörtern gesteuert werden kann. Das gleiche gilt für den Parameter password_grace_time, mit dem Sie entsprechend der eingestellten Tage vor dem Ablaufen des Kennworts eine Warnmeldung generieren können.
password_lock_time
Anzahl der Tage, für die eine Benutzerkennung nach einer Sperre wegen fehlerhafter Anmeldeversuche, gesperrt bleibt.
password_verify_function
Mit Hilfe dieses Parameters können Sie in Ihrem System eine Funktion zur Überprüfung vergebener Passwörter installieren.
Tabelle 4.2: Auszug aus den vorhandenen Profilparametern
Benutzerverwaltung mit SQL
365
Vor allem der letzte Parameter dürfte doch so richtig nach dem Geschmack deutscher Revisionsmitarbeiter sein, denn er ermöglicht uns, die Regeln für die Passwortvergabe so komplex zu gestalten, dass wir Techniker anschließend wieder sicher sein können, die meisten Kennwörter auf einem Zettel unter der Schreibtischunterlage oder der Tastatur zu finden. Mit Hilfe einer Funktion, die Sie innerhalb des sys-Schemas anlegen müssen, können Sie in der Datenbank bzw. für die Benutzer des zugehörigen Profils ein beliebiges Programm für die Passwortüberprüfung installieren. Wie das genau geht, das will ich Ihnen anhand einer sehr einfachen Funktion gerne einmal zeigen und daher habe ich im Listing 4.6 eine einfache Prüffunktion kodiert. create or replace function sys.test_kennwort ( benutzer varchar2, password varchar2, altes_password varchar2) return boolean is begin if password = 'HURRA' then raise_application_error(-20002, 'Das Kennwort ist doof!!'); else return true; end if; end; Listing 4.6: Erstellen einer Prüffunktion für Kennwörter
Wie Sie dem Listing 4.6 entnehmen können, heißt meine Passwortprüffunktion sys.test_kennwort und erhält beim Aufruf drei Parameter vom Datentyp varchar2, wobei der Rückgabetyp der Funktion vom Datentyp boolean ist, so dass die Funktion lediglich true (=wahr, Kennwort ist ok) und false (=falsch, Kennwort falsch) zurückliefern kann. Im nächsten Schritt müssen Sie die Prüfroutine in einem Profil, beispielsweise „default“, installieren. Wie das geht, dass können Sie dem nächsten Listing (vgl. 4.7) entnehmen. alter profile default limit password_verify_function test_kennwort; Listing 4.7: Installation der neuen Passwortroutine
Nun gibt es aber kein Halten mehr und wir wollen im nächsten Schritt unsere neue Superroutine ausprobieren, indem wir das Kennwort irgendeines Benutzers ändern. Wie das geht und was dabei logischerweise passiert, das sehen Sie im Listing 4.8, in dem wir mit Hilfe der typischen alter user-Anweisung das Passwort ändern.
366
Benutzer und Rollen
alter user ubeispiel identified by HURRA; ORA-28003: password verification for the specified password failed ORA-20002: Ich finde das Kennwort doof!! Listing 4.8: Knock-Out, unsere neue Funktion mischt sich bei der Passwortvergabe ein
4.3
Rechtevergabe mit SQL
Die erstellten Profile werden bei der Anlage eines Benutzers bzw. mit Hilfe eines entsprechenden alter user-Befehls den Benutzerkennungen zugewiesen oder das System verwendet das default-Standardprofil, sofern keine spezielle Profil-Einstellungen vorgenommen wird. Dennoch ist man nach der Anlage einer Rolle oder eines Benutzers noch weit von einer funktionierenden Kennung entfernt, denn anschließend ist der neue Benutzer zwar im System angelegt, aber dürfen darf er bisher noch gar nichts. Das ändert sich erst, indem einer Rolle spezielle Rechte, die Rolle dem Benutzer oder dem Benutzer direkt spezielle Privilegien zugewiesen werden.
4.3.1
Vergabe von Einzelrechten und Rollen
Die Vergabe von Einzelrechten erfolgt grundsätzlich mit Hilfe der grant-Anweisung, wohingegen für die Entziehung von Privilegien der Befehl revoke zur Verfügung steht. Natürlich darf nicht jeder Benutzer eine weitere Kennung anlegen oder irgendwelche Privilegien an andere Benutzer weitergeben, sondern hierzu muss er entweder die notwendigen Systemrechte besitzen oder er muss Besitzer derjenigen Datenbankobjekte sein, für die er anderen Anwendern Zugriffsrechte erteilt. Zugriffsrechte auf Tabellen Wie Sie wissen, werden die in der Datenbank gespeicherten Tabellen mit Hilfe der DML-Anweisungen (DML = Data Manipulation Language) insert, update und delete geändert und durch ein select-Kommando abgefragt. Die bei den Tabellen vorhandene Rechteverwaltung entspricht dabei genau diesen vier Möglichkeiten, d.h. Sie können die Verwendung jede dieser vier Anweisungen separat freigeben. Die Anwendung der grant-Anweisung entspricht dabei dem folgenden Schema: grant [(Feldliste)] on to
Die in dem Schema gezeigte Feldliste existiert allerdings nur für die beiden Anweisungen insert und update, so dass Sie das Ändern oder Erweitern einer Tabelle auf spezielle Felder einschränken können. Ein solches restriktives Einfügen macht natürlich nur dann Sinn, wenn das aufgrund der Tabellenstruktur überhaupt möglich ist, d.h. die nicht im Zugriffsprofil enthaltenen Spalten müssen entweder den besonderen Wert null vertragen oder über einen Standardwert versorgt werden.
Rechtevergabe mit SQL
367
Innerhalb einer grant-Anweisung können mehrere DML-Zugriffsrechte für eine Tabelle erteilt werden, indem die einzelnen zugelassenen Kommandos durch Komma getrennt aufgezählt werden. Ein Beispiel für die konkrete Anwendung können Sie dem Listing 4.9 entnehmen, indem der Rolle „personal“ verschiedene Rechte für die Tabelle „personalien“ erteilt werden. grant select, update (name, strasse1, strasse2, strasse3, strasse4, ort, plz), delete, insert on personalien to personal; Listing 4.9: Erteilung von Zugriffsrechten auf eine Tabelle
In diesem Beispiel erhält die Rolle „personal“ fast vollen Zugriff auf die Personalstammdaten. Einzige Ausnahme ist die Einschränkung der Änderungsberechtigung auf die Anschriftenfelder. Das hier gezeigte Beispiel wäre in der Praxis natürlich mehr als fragwürdig, denn wenn ich Löschen und Neuanlegen darf, dann kann ich mit entsprechendem Mehraufwand auch jede Änderung durchführen. Sofern das dann also doch zu viele Rechte für den Zugriff auf die gespeicherten Personalien waren, dann können Sie das Zugriffsprofil mit Hilfe der revoke-Anweisung jederzeit wieder einschränken. In unserem Beispiel (vgl. Listing 4.10) entziehen wird der Rolle „personal“ wieder den Lösch- und Einfügezugriff auf diese Tabelle. revoke delete, insert on personalien from personal; Listing 4.10: Entzug von Zugriffsrechten mit Hilfe des revoke-Befehls
An diesem Beispiel sehen Sie, dass sich die Anwendung von revoke eigentlich kaum vom grant unterscheidet, d.h. im Prinzip müssen eigentlich nur das Schlüsselwort to gegen das neue Wörtchen from austauschen. Allerdings müssen Sie beachten, dass sich die Zugriffsrechte auf die Tabellen nicht spaltenweise entziehen lassen, d.h. bei einer Änderung eines spaltenbezogenen update-Rechts, müssen Sie dieses in der neuen Spezifikation erneut freigeben oder vollständig entziehen. Die Verwendung des revoke-Kommandos entspricht somit also folgendem Schema: revoke on from
Die hier beschriebene Anwendung der beiden Befehle grant und revoke gilt natürlich nicht nur explizit für Tabellen, sondern für alle Objekte, die mit den aufgeführten DML-Kommodos geändert oder abgefragt werden können, was vor allem zusammen mit der Verwendung von Synonymen oder Views interessant ist. Außerdem können Sie anstelle der in meinem Beispiel verwendeten Rolle auch die Kennung eines beliebigen Benutzers angeben, ohne dass die Befehle ansonsten in irgendeiner Form geändert werden müssten.
368
Benutzer und Rollen
Die Leseberechtigung auf eine View beinhalten übrigens auch automatisch den impliziten Zugriff auf die dahinterliegenden Tabellen, d.h. die explizite Berücksichtung der Tabellen im Zugriffsprofil ist nicht notwendig. Das gilt sogar prinzipiell für alle aufeinander aufbauenden Objekte, d.h. wenn Sie eine Prozedur ausführen dürfen, in der eine spezielle Tabelle gelesen wird, dann benötigen Sie nicht unbedingt auch einen expliziten Zugriff auf diese Tabelle. Ausführungsrechte von Funktionen und Prozeduren Das Starten der in der Datenbank gespeicherten ausführbaren Objekte wie Funktionen oder Prozeduren wird ebenfalls mit Hilfe der grant-Anweisung in der Variante execute erlaubt, bzw. mit Hilfe der entsprechenden revoke-Anweisung wieder entzogen. Dem folgenden Beispiel in Listing 4.11 können Sie entnehmen, wie der Rolle „personal“ das Recht zur Ausführung der Funktion get_fieldlist erteilt wird. grant execute on get_fieldlist to personal; Listing 4.11: Starten einer Funktion freigeben
Das Ganze funktioniert entsprechend für Prozeduren und Pakete, wobei Sie bei den Paketen berücksichtigen müssen, dass hierbei natürlich immer alle enthaltenen Prozeduren und Funktionen freigegeben werden. Alles erlauben Wie Sie gesehen haben, unterscheiden sich die Freigaben auf Tabellen oder Prozeduren kaum voneinander und funktionieren zumindest ab dem on-Schlüsselwort exakt gleich. Es gibt daher für die Anwendung des grant-Kommondos sogar noch einmal eine Vereinfachung, wenn man für das jeweils spezifizierte Objekte den vollen Zugriff gewähren will. Erlaube für das genannte Objekt alles, was mit ihm geht; in diesem Sinne können Sie die Schlüsselwörter execute, select, update usw. einfach durch das neue Wörtchen all ersetzen, so dass die Rechtevergabe für die in der Datenbank gespeicherten Objekte jetzt immer nach dem gleichen Schema erfolgt: grant all on to
Auf die gleiche Weise können Sie natürlich auch beim Entzug von Einzelrechten vorgehen, was hierbei den zusätzlichen Vorteil hat, dass Sie sich nicht darum kümmern müssen, ob die zu entziehenden Rechte überhaupt vergeben wurden. So führt die Anweisung revoke select on bvs from ubeispiel1; ORA-01927: cannot REVOKE privileges you did not grant
beispielsweise zu einer Fehlermeldung, da das hier spezifizierte Recht bislang noch gar nicht erteilt wurde, wohingegen die Entziehung aller Rechte in jedem Fall einwandfrei funktioniert. revoke all on bvs from ubeispiel1;
Rechtevergabe mit SQL
369
Systemrechte vergeben Die umfangreich vorhandenen Systemrechte werden ebenfalls mit Hilfe der grantAnweisung verliehen und mittels revoke-Befehl wieder entzogen. Allerdings wandelt sich hierbei das Schema der grant-Anweisung ein wenig und entspricht folgendem Aufbau: grant <Systemrecht> to
Der Entzug eines Systemrechts mittels revoke-Befehl entspricht diesem Schema wieder weitgehend, d.h. außer dass Sie grant durch revoke ersetzen, müssen Sie auch noch das Schlüsselwort to durch das Wörtchen from austauschen. Das nachfolgende Beispiel (vgl. Listing 4.12) zeigt Ihnen die Anwendung dieser grant-Variante, indem der Rolle „personal“ das Systemrecht „create session“ zum Anmelden an die Datenbank zugewiesen wird. grant create session to personal; Listing 4.12: Vergabe von Systemrechten
Natürlich können Sie auch mehrere Systemrechte wieder gleichzeitig mit einer grant-Anweisung zuteilen oder mit einem revoke-Befehl entziehen, wobei Sie in beiden Fallen die zu berücksichtigenden Rechte durch Komma getrennt aufzählen müssen. An dieser Stelle könnte man nun die nächsten drei Seiten damit füllen, eine Übersicht der vorhandenen Systemrechte in Form einer entsprechenden Tabelle darzustellen. Das hieße aber abschreiben, denn eine solche Tabelle ist schon in der Oracle-Dokumentation enthalten. Sie finden sie dort im Buch „Oracle8 SQL Reference“ unter der Beschreibung des grant-Kommandos. Schlagen Sie in der OnlineDokumentation im Index dieses Buches einfach den Begriff grant nach und folgenden Sie anschließend dem Verweis „system privilegs and roles to users“. Rollen zuweisen Zur Zuweisung der Rollen gibt es eigentlich nichts Besonderes mehr zu sagen, außer dass sich eine Rolle in Bezug auf die Verwendung der grant- und revokeBefehle wie ein normales Systemrecht verhält, d.h. mit der im Listen 4.13 gezeigten Variante weisen wir einem Benutzer die Rolle „personal“ zu. grant personal to ubeispiel1 Listing 4.13: Zuweisen einer Rolle zu einem Benutzerprofil
Mehr ist hierbei eigentlich nicht zu berücksichtigen, außer vielleicht, dass eine Rolle nicht unbedingt direkt einer Benutzerkennung, sondern auch einer anderen Rolle zugewiesen werden kann. Ebenfalls müssen Sie einem Benutzer als letzten Schritt nicht nur in Rollen gebündelte Profile zuweisen, sondern Sie können hierbei auch alle denkbaren Einzelrechte verwenden.
370
Benutzer und Rollen
Die Berechtigung, selbst Rechte zu vergeben Die Berechtigung, anderen Benutzern oder Rollen spezielle Zugriffsrechte zu erteilen, ist für die im Besitz befindlichen Objekte quasi angeboren. Hat man also das Recht, in der Datenbank eine neue Tabelle anzulegen, dann hat man anschließend auch das Recht darüber zu entscheiden, wer außer einem noch Zugriff auf diese Tabelle erhält. Des weiteren existieren spezielle Systemrechte, die einen Anwender priviligieren können, anderen Benutzern spezielle Rechte zu erteilen bzw. zu entziehen. Ein Beispiel hierfür sind die beiden Systemrechte grant any privilege und grant any role, mit denen Anwender zum Wächter vorhandener Rechte und Rollen wird. Will man nicht ganz so weit gehen, dann besteht als Alternative noch die Möglichkeit, nur die Weitergabe der zugewiesenen Rechte zu erlauben, indem diese mit dem Zusatz with admin option erteilt werden, d.h. die grant-Anweisung wird am Ende durch diese Klausel erweitert. grant select, update (name, strasse1, strasse2, strasse3, strasse4, ort, plz), delete, insert on personalien to personal with admin option;
Öffentliche Objekte Indem Sie den Zugriff auf ein Objekt für den Pseudo-Benutzer public freigeben, wird für dieses der öffentliche Zugriff installiert, d.h. anschließend kann jeder Benutzer in der vorgegebenen Art- und Weise auf das Objekt zugreifen. grant all on gehalt to public;
4.3.2
Zugriffsschutz auf Datensatzebene
Der bisher praktizierte Zugriffsschutz auf die in der Datenbank gespeicherten Objekte folgte meistens dem Motto ganz oder gar nicht. Einzige Ausnahme war vielleicht noch die Möglichkeit, eine Änderungs- oder Einfügeanweisung auf spezielle Felder der Tabelle einzuschränken. Ich beschäftige mich in den letzten zehn Jahren überwiegend mit Personalsystemen (Planung und Abrechnung) und habe in dieser Zeit noch keinen Kunden gesehen, dem das gereicht hätte. Das wenigste, was eigentliche alle benötigten war die Möglichkeit, den Zugriff auf einzelne Datensätze zu steuern, d.h. bestimmte Mitarbeitergruppen sollen nur von speziellen Benutzern gelesen oder bearbeitet werden können. Die Kriterien für diesen Selektionsprozess folgen meistens organisatorischen oder hierarchischen Gegebenheiten, wobei sogar Mischformen möglich sind. Dem ersten Fall liegt der Gedanke zugrunde, dass Mitarbeiter einer bestimmten Abteilung oder eines Bereichs nur von den dort zuständigen Sachbearbeitern gelesen und
Rechtevergabe mit SQL
371
bearbeitet werden können. Den anderen Fall finde ich immer ein wenig belustigend, denn schließlich ist es doch ganz normal, dass man für die Ansicht der besonders bunten Tiere im Zoo eine ganz spezielle Eintrittskarte benötigt. Doch nun wieder zum Ernst der Lage. So lange Sie auch in der Anwenderdokumentation Ihrer Datenbank suchen werden, Sie werden keine speziellen Befehle zur Parametrierung des satzbezogenen Zugriffs finden. Das ist, falls überhaupt, aber kein direkter Mangel der Oracle-Datenbank, sondern entspricht zumindest in den Datenbanken, die ich kenne, scheinbar dem momentanen Stand der Technik. Sofern Sie gerade eine sogenannte Standardsoftware wie beispielsweise SAP oder PeopleSoft einführen, dann merken Sie von diesem Mangel natürlich recht wenig, da solche Produkte üblicherweise ihre eigene Benutzer- und Sicherheitsverwaltung haben und einen datensatzbezogenen Zugriffsschutz innerhalb der zugehörigen Anwendungsprogramme regeln. Diese Beobachtung trifft allerdings nicht nur auf Datenschutzebene zu, sondern solche Produkte benutzten generell ziemlich wenig von den in Oracle vorhandenen Möglichkeiten, d.h. das gesamte Geschäftsmodell ist nicht Bestandteil der Datenbank, sondern wird mit Hilfe der zur Anwendung gehörenden Programmlogik abgebildet. Die Datenbank selbst verkommt dabei zum dummen Datenfriedhof, d.h. Mitdenken und eigene Intelligenz sind unerwünscht. Das hat natürlich zur Folge, dass Sie mit Hilfe von Produkten Dritter zwar sicherlich Abfragen auf die Datenbank aber im Regelfall keine Eingaben durchführen dürfen, da in dem Fall die zu den Daten gehörende Prüf- und Geschäftslogik nicht ausgeführt wird. Außerdem müssen Sie sich im ersten Fall auch wieder mit den Datenschutzproblematiken auseinandersetzen, d.h. es obliegt jetzt Ihnen, die vom Hersteller verdrängten Probleme anzugehen. Dabei ist das hier skizzierte Problem durchaus lösbar. Ich selbst habe mit einem Team einmal eine Anwendung entworfen und realisiert, in der neben den Daten auch die gesamte Geschäftslogik und alle Datenschutzmechanismen enthalten waren, d.h. es war im laufenden Betrieb völlig egal, welche Eingaben mit welchen Produkten in der Datenbank durchgeführt wurden. Was nun den satzbezogenen Zugriffsschutz angeht, so wird in einigen Standardlösungen der Ansatz verfolgt, eine spezielle View zu bauen, die innerhalb der Dialoganwendungen mit der eigentlichen Suchanfrage verknüpft wird. Da man aufgrund der meistens vorhandenen Dialogführung auch nur das Ändern kann, was zuvor gelesen wurde ist auf diese Weise auch sichergestellt, dass keine unberechtigten Änderungen durchgeführt werden können. Mit Hilfe der in der Datenbank verfügbaren Funktionalitäten kann man aber auch noch einen Schritt weitergehen, indem man sich beispielsweise die Verhaltensweisen von Views als aktive Datenschutzkomponente verfügbar macht. Wie Sie wissen ist eine View eigentlich nichts anderes als eine ausführbar gespeicherte Abfrage, wodurch sich die Möglichkeit ergibt, die datensatzbezogenen Zugriffsbestimmungen innerhalb dieser View abzuspeichern.
372
Benutzer und Rollen
Das könnte man zum einen so realisieren, dass jeder Benutzer für den Zugriff auf die Tabellen eine eigene View erhält, in der die erlaubten Datensätze mit Hilfe der gespeicherten Abfrage ermittelt werden. create or replace view ubeispiel1_personalien as select * from personalien where name between 'A' and 'C';
Anschließend erhält der entsprechende Benutzer lediglich die Berichtigung, die in der Datenbank vorhandenen Personalien durch seine individuelle Brille zu betrachten, was Sie mit Hilfe der folgenden grant- bzw. revoke-Anweisung realisieren können: revoke all on personalien from ubeispiel1; grant all on ubeispiel1_personalien to ubeispiel1;
Anschließend kann unser neuer Benutzer nur noch Mitarbeiter entsprechend dem vorgegebenen Namensbereich bearbeiten, was in der Praxis dann folgendermaßen aussieht. SQLWKS> select persnr, name 2> from ubeispiel.ubeispiel1_personalien; PERSNR NAME ----------- -------------------------------------------------7000006 Beckmann,Yonne 7000018 Bruckner,Yonne 7000021 Bätzing,Bärbel 3 rows selected.
Natürlich müssen Sie bei diesem Verfahren die View immer dann neu erstellen, wenn sich die den Zugriffsschutz bestimmenden Regelwerke ändern, jedoch könnte man hierfür ein entsprechendes Programm entwickeln, dass für neue Benutzer oder sich ändernden Zugriffsrechten die benötigten Views automatisch generiert und selbst das eintippen des SQL-Textes einer solchen View ist nicht schwieriger als das, was den Sicherheits-Administratoren in Standardanwendungen teilweise abverlangt wird. Trotzdem kann man ein solches Zugriffssystem sicherlich auch komfortabler und damit aber aus technischer Sicht auch komplexer gestalten, indem man in die Datenbank Methoden implementiert, die den Zugriffsschutz zur Laufzeit interpretieren. Der erste Schritt, so etwas zu realisieren, besteht zunächst einmal darin, die Zugriffsberechtigungen mit Hilfe spezieller Tabellen in der Datenbank zu beschreiben. Entsprechend unserem einfachen Beispiel könnte das folgendermaßen aussehen (vgl. Listing 4.14). drop table pers_sec; create table pers_sec ( userid varchar2(15) not null, name_von varchar2(15) not null,
Rechtevergabe mit SQL
373
name_bis varchar2(15) not null, constraint pers_name_ck check (name_von <= name_bis), constraint pers_sec_pk primary key (userid, name_von) ); commit; Listing 4.14: Anlegen einer Tabellen zum Speichern von Zugriffskriterien
Mit Hilfe der neuen Tabelle pers_sec sollen demnächst die auf Namen basierenden Zugriffsrechte verwaltet werden, so dass wir gleich einmal für unseren Benutzer „ubeispiel1“ zwei Kriterien in die Datenbank einstellen. insert into pers_sec values ('UBEISPIEL1','Aa','De'); insert into pers_sec values ('UBEISPIEL1','Fe','Rf');
Im nächsten Schritt müssen wir eine Funktionen (vgl. Listing 4.15) erstellen, mit deren Hilfe wir die Zugriffskriterien zur Laufzeit auswerten können. create or replace function check_access(in_persnr in varchar2) return integer is raccess integer; begin raccess := 0; select count(*) into raccess from personalien a, pers_sec b where a.persnr = in_persnr and b.userid = user and a.name between b.name_von and b.name_bis; return raccess; end; Listing 4.15: Erstellen unserer Sicherheitsfunktion
Unsere diesbezügliche Funktion heißt check_access und erhält beim Aufruf die zu überprüfende Personalnummer als Parameter. Dabei ist der von der Funktion zurückgelieferte Wert nur dann Null (0), wenn der aktuelle Benutzer keinen Zugriff auf diesen Mitarbeiter hat. Das überprüfen wir innerhalb der Funktion, indem wir die Personalien mit unserer Regeltabelle pers_sec verknüpfen, die Personalien selbst mit Hilfe der übergebenen Personalnummer einschränken und vor allem die Regeltabelle mit Hilfe der Pseudospalte user selektieren. and b.userid = user
Diese Pseudospalte liefert die zur aktuellen Sitzung und damit die zur Abfrage passende Benutzerkennung. Es gäbe sicherlich noch andere Möglichkeiten diesen Wert, beispielsweise über die Session-Informationen, herauszubekommen, aber die Verwendung der Variablen ist der allereinfachste Weg.
374
Benutzer und Rollen
Nachdem wir nun die benötigten Kontrollfunktion für den Lesezugriff programmiert haben, erstellen wir als Nächstes eine neue Leseview, die im Unterschied zur vorhergehenden Lösung von allen Datenbankbenutzern verwendet werden kann. create or replace view read_personalien as select * from personalien where check_access(persnr) > 0; Listing 4.16: Zweite Variante unserer Leseview mit satzbezogenem Zugriffsschutz
Jetzt müssen wir unserem Anwender „ubeispiel1“ geschwind noch Zugriff auf diese neue View erteilen, was wieder mit Hilfe einer gewöhnlichen grant-Anweisung passiert: grant select on read_personalien to ubeispiel1
Die Spannung steigt, denn jetzt probieren wir unsere Konstruktion aus und verwenden die neue Abfrage, nachdem wir uns als „ubeispiel1“ an der Datenbank angemeldet haben. SQLWKS> select persnr, name 2> from ubeispiel.read_personalien; PERSNR NAME ----------- -------------------------------------7000002 Karlo,Armin-Helgo 7000003 Heiden,Magareta 7000004 Hardt,Andreas 7000005 Nibelungen,Ellen ... 7000010 Müller,Frida 7000013 Heler,Sascha 17 rows selected. Listing 4.17: Ausprobieren des Zugriffsschutzes auf Datensatzebene
Wie Sie sehen, funktioniert unsere neue Variante des satzbezogenen Zugriffsschutzes prima, wobei das Verfahren in der Praxis zumindest in Dialogsystemen auch hinreichend schnell ist. Das liegt natürlich auch daran, dass normalerweise niemand im Dialog hunderte von Datensätzen auf einmal abruft, sondern aufgrund vorgegebener Suchbegriffe eine Handvoll Mitarbeiter vorselektiert werden, von denen anschließend mit Hilfe unserer Funktion noch einmal verschiedene Datensätze herausgestrichen werden, weil die Funktion wegen des fehlenden Zugriffsrechts für sie den Wert 0 zurückliefert. Für den Batchbetrieb oder zur Erstellung besonderer Auswertungen benötigt man im Regelfall sowieso spezielle Benutzerkennungen, die meistens jenseits des üblichen Zugriffschutzes funktionieren, so dass dieses bei großen Datenmengen etwas kopflastige Verfahren hierbei keine Rolle spielt.
Auswertung der Benutzerprofile
375
Ich habe einmal im Rahmen eines Projektes so etwas Ähnliches realisiert und die Reaktionszeit des Dialogs liegt bei allen Masken unter einer Sekunde, wobei über 100.000 Mitarbeiter in der Datenbank gespeichert sind. Schreibschutz installieren Die Realisierung eines datensatzabhängigen Schreibschutzes könnte man im Übrigen auf ganz ähnliche Weise realisieren, indem man entweder änderungsfähige Views baut, die in ihrer where-Bedingung die änderbaren Datensätze selektieren oder man installiert die benötigten Berechtigungsprüfungen in entsprechenden Vorab-Triggern, so dass die Änderungstransaktion zurückgerollt wird, wenn der Benutzer keine Berechtigung zu deren Durchführung hat. Ich persönlich würde das zuletzt skizzierte Verfahren in jedem Fall vorziehen, werde hierfür in diesem Workshop allerdings kein Beispiel anbringen. Zum einen würde das den Rahmen des Buches so langsam sprengen und zum anderen muss ja auch noch etwas für meine Tagesarbeit als Anwendungsberater übrigbleiben.
4.3.3
Benutzerverwaltung in der Praxis
Im Rahmen der letzten Kapitel haben Sie gesehen, wie Sie durch Verwendung eines Skriptes oder dem Security Manager bzw. dem DBA Studio von Oracle neue Benutzer in der Datenbank anlegen und mit Hilfe von Einzelrechten oder Rollen die zugehörigen Profile verwalten können. Logischerweise kann man auch für diese Aufgabe zunächst entsprechende Anwendungstabellen in der Datenbank definieren, in denen die für das Profil benötigten Daten mit Hilfe eines einfachen Anwendungsprogramms gespeichert werden. Die eigentliche Anlage der Benutzer bzw. die Zuweisung der einzelnen Objektrechte erfolgt dann im zweiten Schritt mit Hilfe eines entsprechenden PL/SQL-Programms. Im Rahmen des nächsten Buchabschnitts, wo wir uns ein wenig intensiver mit der PL/SQL-Programmierung beschäftigen werden, habe ich hierzu ein passendes Beispiel geplant, wo Sie mit Hilfe einer Tabelle und einer Prozedur die benötigten Profile quasi auf Knopfdruck erstellen können.
4.4
Auswertung der Benutzerprofile
Im Kapitel 3.5.3 hatte ich schon die wichtigsten Systemviews aufgezählt (vgl. Tabelle 3.14), mit denen Sie Auswertungen über vorhandene Benutzer, Rollen und die jeweilig vergebenen Rechte durchführen können. Ebenso hatte ich Ihnen damals noch das ein oder andere Beispiel für die Anwendung dieser Views versprochen, was nun im Folgenden passieren soll. Zur Erinnerung möchte ich hier noch einmal kurz erwähnen, dass genau wie bei den Datenbankobjekten auch diese Views meistens wieder in drei Varianten zur Verfügung stehen, die sich namentlich durch die drei Präfixe dba, all und user und inhaltlich durch die von der View gelieferten Datenmengen unterscheiden. So eignen sich die user-Views lediglich zur Analyse des eigenen, aktuell angemeldeten
376
Benutzer und Rollen
Benutzers, wohingegen Sie mit Hilfe der dba-Abfragen jeden angelegten Benutzer betrachten können. Definierte Benutzer und Rollen Wie schon gesagt, eignen sich die beiden Views dba_users und dba_roles, um einen Überblick über die in der Datenbank angelegten Benutzer und Rollen zu erhalten. Im Prinzip erhalten Sie mit Hilfe dieser Sichten alle Felder zurück, die Sie bei der Anlage mit Hilfe der entsprechenden create-Befehle vorgeben konnten, und da die Anwendung auch ansonsten nichts interessantes mehr zu Tage fördert bzw. nicht weiter schwierig ist, möchte ich an dieser Stelle auf eine Demonstration verzichten. Entsprechend einfach ist auch die Auswertung eines Profils mit Hilfe der View dba_profiles. Sie liefert für jeden spezifizierten Parameter (ressource_name) einen eigenen Datensatz, wobei Sie die vorhandene Einstellung dem Wert limit entnehmen können. select ressource_name, limit from dba_profiles where profile = 'DEFAULT'
Vergebene Systemrechte ermitteln Um zu ermitteln, welche direkt zugewiesenen Systemrechte ein Benutzer hat, können Sie eine Abfrage auf die View dba_sys_privs erstellen (vgl. Listing 4.18). Allerdings müssen Sie dabei beachten, dass man Systemrechte auch indirekt über eine Rolle erhalten kann. Dieses Problem wird uns im Folgenden bei der Auflösung von Benutzerprofilen permanent begleiten, d.h. wir haben bestimmte Einzelrechte und beliebig viele zugewiesene Rollen, die wiederum beliebige Einzelrechte oder weitere Rollen beinhalten. SQLWKS> select * from dba_sys_privs where grantee = 'SYSTEM' 2> GRANTEE PRIVILEGE ADM ------------------------------ ------------------------------------ --SYSTEM UNLIMITED TABLESPACE YES 1 row selected. Listing 4.18: Ermittlung direkt zugewiesener Systemrechte
Die zugewiesenen Rollen ermitteln Die in einem Benutzerprofil enthaltenen Rollen erhalten Sie durch einen Zugriff auf die View dba_role_privs, wie Sie dem folgenden Beispiel (vgl. Listing 4.19) entnehmen können. SQLWKS> select grantee, granted_role, admin_option 2> from dba_role_privs 3> where grantee = 'SYSTEM';
Auswertung der Benutzerprofile
GRANTEE -----------------------------SYSTEM SYSTEM 2 rows selected.
377
GRANTED_ROLE -----------------------------DBA RBEISPIEL
ADM --YES YES
Listing 4.19: Auswertung der direkt zugewiesenen Rollen
Das Rollengeflecht zerlegen Wie Sie wissen, muss eine Rolle nicht nur bzw. gar nicht aus Einzelrechten bestehen, sondern kann wiederum beliebige Rollen enthalten. Die in einer Rolle enthaltenen Rollen erhalten Sie durch eine Abfrage der View role_role_privs, wofür Sie im Listing 4.20 ein entsprechendes Beispiel finden. SQLWKS> select * 2> from role_role_privs where role = 'RBEISPIEL'; 3> ROLE GRANTED_ROLE ------------------------------ -----------------------------RBEISPIEL EXP_FULL_DATABASE RBEISPIEL IMP_FULL_DATABASE RBEISPIEL RESOURCE 3 rows selected.
ADM --YES YES YES
Listing 4.20: Ermitteln der einer Rolle zugewiesenen Unterrollen
Systemrechte einer Rolle ermitteln Zur Ermittlung der einer Rolle zugewiesenen Systemrechte werden Sie keine spezielle View im Datenhaushalt von Oracle finden, denn die können Sie wieder mit Hilfe der schon verwendeten Abfrage auf die Sicht dba_sys_privs ermitteln, indem Sie anstelle einer Benutzerkennung jetzt einfach den Namen der Rolle als Selektionskriterium verwenden. ... where grantee = 'DBA'
Zugriffsrechte auf Datenbankobjekte Neben verschiedenen Systemrechten werden im Rahmen eines Benutzerprofils vor allem Zugriffsrechte auf die in der Datenbank gespeicherten Objekte vorgehalten. Diese zugeteilten Objektrechte können Sie dem System mit Hilfe einer Abfrage auf die View dba_tab_privs entlocken, wofür Sie im Folgenden wieder ein kleines Beispiel finden. SQLWKS> select table_name, privilege 2> from dba_tab_privs 3> where grantee = 'UBEISPIEL1'; 4>
378
TABLE_NAME -----------------------------UBEISPIEL1_PERSONALIEN UBEISPIEL1_PERSONALIEN UBEISPIEL1_PERSONALIEN UBEISPIEL1_PERSONALIEN READ_PERSONALIEN 5 rows selected.
Benutzer und Rollen
PRIVILEGE ---------------------------------DELETE INSERT SELECT UPDATE SELECT
Listing 4.21: Auswerten der für einen Benutzer vorhandenen Objektberechtigungen
Die von dieser Abfrage gelieferte Spalte table_name sollten Sie nicht ganz so wörtlich nehmen, denn dort werden neben Tabellen auch Views, Synonyme oder Funktionen und Prozeduren angezeigt, wobei Sie in den beiden letzten Fällen dann als eingestelltes Privileg natürlich den Wert „EXECUTE“ vorfinden. Anstelle eines Benutzernamens können Sie die Abfrage auch zusammen mit einer Rolle verwenden und erhalten in dem Fall die der Rolle zugewiesenen Objektberechtigungen. Spaltenbezogene Rechte Sofern die vergebenen Rechte nicht für das ganze Objekt, sondern stattdessen für einzelne Spalten zugewiesen wurden, dann finden Sie die Zuweisungne nicht in der View dba_tab_privs, sondern stattdessen in der Sicht dba_col_privs, wobei jetzt alle zugewiesenen Spalten mit den jeweiligen Rechten aufgelistet werden. Genau wie in dem letzten Beispiel besteht auch hierbei wieder die Möglichkeit, die View zusammen mit einer Benutzerkennung oder einer Rolle zu verwenden. SQLWKS> select table_name, privilege 2> from dba_col_privs 3> where grantee = 'PERSONAL'; 4> TABLE_NAME PRIVILEGE ------------------------------ -----------------------------PERSONALIEN UPDATE PERSONALIEN UPDATE PERSONALIEN UPDATE PERSONALIEN UPDATE PERSONALIEN UPDATE PERSONALIEN UPDATE PERSONALIEN UPDATE 7 rows selected. Listing 4.22: Auswerten der erteilten Zugriffsrechte auf Spaltenebene
Anzeigen aller Berechtigten für eine Tabelle Wie Sie den bisherigen Beispielen entnehmen konnten, ist die Abfrage einzelner Teilinformationen der Benutzerprofile nicht sonderlich schwierig, da die diesbe-
Auswertung der Benutzerprofile
379
züglich vorhandenen Views nicht sonderlich komplex sind. Das eigentliche Problem entsteht eigentlich erst dadurch, dass aufgrund der möglichen Schachtelungen die gesamten Profildefinitionen nicht in einem Rutsch gelesen werden können. So zeigt die folgende Abfrage (vgl. Listing 4.23) zwar, alle eingetragenen Zugriffsberechtigungen für die Personalientabelle, jedoch werden in der Spalte grantee sowohl Benutzerkennungen als auch Rollen gezeigt. SQLWKS> select grantor, grantee, privilege, column_name 2> from dba_col_privs 3> where owner = 'UBEISPIEL' and table_name = 'PERSONALIEN' 4> 5> union 6> 7> select grantor, grantee, privilege, '-' 8> from dba_tab_privs 9> where owner = 'UBEISPIEL' and table_name = 'PERSONALIEN'; 10> GRANTOR GRANTEE PRIVILEGE COLUMN_NAME ---------------------- --------------------- -------------- ----------UBEISPIEL PERSONAL SELECT UBEISPIEL PERSONAL UPDATE NAME UBEISPIEL PERSONAL UPDATE ORT UBEISPIEL PERSONAL UPDATE PLZ UBEISPIEL PERSONAL UPDATE STRASSE1 UBEISPIEL PERSONAL UPDATE STRASSE2 UBEISPIEL PERSONAL UPDATE STRASSE3 UBEISPIEL PERSONAL UPDATE STRASSE4 UBEISPIEL SYSTEM SELECT 9 rows selected. Listing 4.23: Überprüfung der Zugriffsberechtigung auf die Personalien
In einem zweiten Schritt müssten jetzt noch die Rollen dahingehend aufgelöst werden, dass jeder Benutzer ermittelt wird, der irgendwie im Besitz einer solchen Rolle ist, wobei das aufgrund der möglichen Schachtelungen noch einmal ein gutes Stück Arbeit sein kann.
5
PL/SQL-Programmierung
In den vorhergehenden Kapiteln haben Sie schon einen ersten Kontakt mit den PL/ SQL-Sprachelementen erhalten. Bei der Vorstellung verschiedener Datenbankobjekte wie Funktionen oder Trigger ließ es sich gar nicht vermeiden, verschiedene PL/SQL-Routinen zu verwenden und damit diesem Kapitel vorzugreifen. Jetzt wollen wir die Verwendung dieser in der Datenbank enthaltenen Programmiersprache etwas genauer unter die Lupe nehmen und uns der PL/SQL-Programmierung ein wenig systematischer aber dennoch schnell nähern. Wenn Sie noch nie programmiert haben, dann sollten Sie damit allerdings nicht unbedingt jetzt beginnen, d.h. ich halte PL/SQL nicht für die beste Wahl, um neben einer neuen Sprache auch das Programmieren in seinen Grundzügen zu erlernen. Das bedeutet aber auch nicht unbedingt, dass Sie jetzt zwangsläufig noch ein weiteres Programmiersystem zum Üben kaufen müssen. Zumindest sofern Sie mit NT arbeiten, können Sie ja mal spaßeshalber über das Menü „Start – Ausführen“ oder in einem MS-DOS-Fenster den Befehl qbasic eingeben. Hierdurch starten Sie den immer noch vorhandenen Basic-Interpreter, der sich aus meiner Sicht immer noch prima eignet, um zum einen erste Gehversuche im Programmierhandwerk zu unternehmen und zum anderen vielleicht ein weiteres Buch von mir zu kaufen. Alle in diesem Kapitel gezeigten Beispiele finden Sie wieder auf Ihrer Buch-CD, und zwar im Verzeichnis \PLSQL, wobei Sie in gewohnter Weise wieder für jedes Beispiel (Listing) eine entsprechende Datei finden.
5.1
Einführung in PL/SQL
5.1.1
Allgemeines
Die meisten der aktuell verfügbaren Datenbanksysteme besitzen eine Spracherweiterung, mit deren Hilfe spezielle Kommandos und Funktionen ermöglicht werden, die über den reinen SQL-Umfang hinaus gehen. Zum Teil werden diese speziellen Befehle oder Funktionen auch gebraucht, um bestimmte Systemroutinen auszuführen und sind damit zwangsläufig eng mit dem konkreten Datenbanksystem verbunden. Darüber hinausgehend wird der Sprachumfang aber auch in allgemeinerer Form erweitert, so dass neben den reinen SQL-Operationen eine richtige Programmiersprache entsteht, was auch bei dem hier beschriebenen PL/SQL der Fall ist. Dennoch gibt es natürlich einen gravierenden Unterschied zu einer klassischen Programmiersprache, die üblicherweise produktunabhängig für alle möglichen Aufgabenstellungen verwendet werden kann. Das ist bei PL/SQL natürlich anders, denn die Verwendung dieser Sprache ist an die Oracle-Datenbank gebunden, d.h.
382
PL/SQL-Programmierung
neben einem speziellen Teil der Datenbank, der in der Lage ist SQL-Anweisungen zu interpretieren und auszuführen, gibt es auch noch einen anderen Teil, der ganze SQL-Programme ausführen kann. Diesen Teil, die sogenannte PL/SQL-Engine, gibt es sogar in zwei Varianten, denn Sie haben die Möglichkeit PL/SQL-Programme auf dem Server, also innerhalb der Datenbankumgebung, oder auf dem Client auszuführen. In der Praxis werden PL/SQL-Programme eigentlich immer auf dem Server, d.h. im Datenbanksystem ausgeführt, da ja gerade die Nähe zu den benötigten Daten den großen Vorteil dieser Programmiersprache ausmacht. Zusammen mit einigen Oracle-Tools erhält man unter Umständen aber auch eine PL/SQL-Engine für den Client, wodurch sich PL/SQL-Programme etwas einfacher testen lassen und vor allem unabhängig vom Server entwickelt werden können. Datenbankskript versus Wirtstechnologie Eine oft verwendete Alternative zur Verwendung vollständiger PL/SQL-Programme besteht in der Benutzung gewöhnlicher Programmiersprachen (dem Wirt), in der die benötigten SQL-Routinen eingebettet werden. Das ist heute in nahezu allen gängigen Programmiersprachen möglich (z.B. C, Visual Basic oder Cobol), wobei es sogar Sprachen wie beispielsweise SQR gibt, die speziell auf diese Aufgabenstellung hin ausgerichtet sind. Die Entscheidung darüber, welche dieser beiden Techniken konkret eingesetzt wird, hängt aus meiner Sicht von vielen verschiedenen Details der konkreten Frage- bzw. Aufgabenstellung ab und kann nicht pauschal beantwortet werden. Ich denke, dass man die folgenden Kriterien prüfen bzw. bedenken sollte.
X
X
X
Wird das Programm hauptsächlich dazu verwendet, um mit Hilfe von Konditionalbedingungen oder Schleifen die Ausführung bestimmter SQL-Abfragen zu steuern, so ist die eingebettete Verwendung in anderen Programmiersprachen durchaus sinnvoll. Das gilt vor allem dann, wenn für die konkret zu verwendende Sprache entsprechendes Know How vorhanden ist. Schließlich kann die Einbettung ja sogar auch so weit gehen, dass ganze PL/SQL-Routinen wieder vom Wirtsprogramm aus an den Server gesendet werden, d.h. man verbaut sich durch diese Methode nicht die Verwendung komplexer PL/SQL-Anweisungsfolgen. Der große Vorteil von reinen PL/SQL-Programme ist natürlich, dass diese automatisch zusammen mit der Datenbank in jede verfügbare Betriebsumgebung umziehen. Wechseln Sie also die Betriebsplattform, beispielsweise von NT-Server auf Unix, dann sind Ihre PL/SQL-Programme sofort verfügbar, was für die selbsterstellten NT-Serverprogramme sicherlich nicht immer garantiert werden kann. Müssen im Rahmen der Programmierung häufig interdisziplinäre Aufgabenstellungen angegangen werden (z.B. Lesen oder Erstellen von sequentiellen Dateien, Erstellen von Reports, Verbinden mit anderen Datenbanken oder Dialogsystemen), dann fällt die Verwendung reiner PL/SQL-Anwendungen aus meiner Sicht sowieso aus, da manche dieser Aufgabenstellungen nur umständlich bzw. gar nicht lösbar sind.
Einführung in PL/SQL
X
383
Sofern bestimmte Abfragen häufig satzweise verarbeitet werden, dann sind reine PL/SQL-Programme bzw. zumindest entsprechende Routinen natürlich wieder im Vorteil, denn da Verarbeitung in dem Fall auf dem Server abläuft, müssen keine Datensätze über das Netzwerk übertragen werden. Außerdem besitzen Sie mit Ihrer Oracle-Datenbank auch alle notwendigen Hilfsmittel, regelmäßig wiederkehrende Jobs automatisch ablaufen zu lassen.
Grundsätzliches zur Datenbankprogrammierung Wie Sie meinen Kriterien entnehmen können, habe ich überhaupt nichts gegen reine PL/SQL-Programme, aber ich finde herkömmliche Programme, in denen SQL-Anweisungen oder ganze PL/SQL-Routinen eingebettet sind allerdings genauso hübsch. Was mich vielmehr stört sind Anwendungen, die in den neuen Datenbankumgebungen nach althergebrachter Weise realisiert werden, d.h. im Rahmen des Programms wird eine Abfrage abgesetzt, jeder Datensatz empfangen und irgendwie verarbeitet, wobei diese Verarbeitung dann ebenfalls satzbezogen abläuft, d.h. jeder gelesene Datensatz wird innerhalb einer externen Programmschleife per abgeschickter update-Anweisung verändert. Bei solchen Programmen merkt man auf den ersten Blick gar nicht, dass eine SQL-Datenbank angebunden ist, sondern stattdessen könnten auch sequentielle Dateien verarbeitet werden. Außerdem kann man ohne weitere Prüfung behaupten, dass solche Programme meistens nicht laufzeitoptimal realisiert sind, d.h. würde man die Aufgabenstellung anders angehen, dann kämen dabei schnellere Anwendungen heraus. Beim ersten Kontakt mit einem relationalen Datenbanksystem besteht die größte Herausforderung eines Anwendungsentwicklers nämlich darin, sich bei der Gestaltung seines Programms an eine mengenorientierte Verarbeitung zu gewöhnen und die satzweise Abarbeitung von Ergebnissen nur noch im Ausnahmefall anzuwenden. Verwenden von PL/SQL-Kommandos Nach den vorhergehenden Worten zur Einstimmung, und bevor wir uns nun im Folgenden systematisch mit den verschiedenen Sprachkonstruktionen beschäftigen, möchte ich jetzt noch einmal kurz zusammenstellen, wo Sie überall PL/SQLErweiterungen finden bzw. verwenden können.
X
X
Innerhalb eines Skripts können Sie entsprechende Befehle verwenden, um beispielsweise einen komplexen Änderungscursor zu programmieren oder in Abhängigkeit verschiedener Bedingungen unterschiedliche SQL-Abfragen abzusetzen. Solche Skripte werden üblicherweise entweder bei Bedarf per Hand gestartet oder in anderen Programmen eingebettet. Das Innenleben von benutzerdefinierten Funktionen besteht üblicherweise auch aus reichlichen PL/SQL-Routinen, selbst wenn diese Funktionen anschließend nur im Rahmen gewöhnlicher Abfragen verwendet werden, so ist deren Erstellung also eigentlich nicht ohne PL/SQL-Programmierung denkbar.
384
X X
PL/SQL-Programmierung
Die Funktionalität von Datenbanktriggern beinhaltet eigentlich auch fast immer mehr, als die unveränderte Weitergabe geänderter Datensätze an irgendwelche Protokolltabellen, so dass bei deren Erstellung üblicherweise PL/SQLKommandos zur Anwendung kommen. Letztendlich bleiben dann noch die schon bekannten Objekte der Prozeduren oder Pakete übrig, wobei es sich herbei um die PL/SQL-Vertreter schlechthin handelt. Die mögliche Bandbreite reicht von der Erstellungen kleiner Tools, die wiederum vielleicht aus klassischen Programmen heraus gestartet werden, bis hin zur Programmierung ganzer Anwendungen, beispielsweise einer Gehaltsabrechnung.
5.1.2
Blockstruktur
PL/SQL ist wie viele andere Programmiersprachen auch blockorientiert, wobei diese Blockstruktur aus den Schlüsselwörtern begin und end gebildet werden, was irgendwie an die Programmiersprache Pascal erinnert. Diese Blöcke besitzen in einem PL/SQL-Programm zwei Aufgabenstellungen. Zum einen werden Sie dazu gebraucht, ein solches Programm oder Skript insgesamt zu begrenzen und zum anderen können Sie die Blöcke dazu benutzen, logisch zusammenhängende Programmteile zu markieren. Neben kosmetischen Gründen gibt es hierfür in der Praxis allerdings auch einen interessanten Anwendungsfall, denn die einzelnen PL/SQL-Blöcke können verschiedene Fehlerbehandlungsroutinen beinhalten, wobei Sie weitere Informationen hierzu erst später in diesem Workshop erhalten. Ein PL/SQL-Programm hat damit etwas vereinfacht dargestellt zunächst folgenden schematischen Aufbau: begin Anweisungen des Block 1 begin Anweisungen des Block 1-1 begin ... end; end; begin ... end; end;
Außerdem gilt, dass alle PL/SQL-Anweisungen, die im Übrigen formatfrei kodiert werden können, mit einem Semikolon („;“) abgeschlossen werden. Das gilt, wie Sie ebenfalls dem Schema entnehmen können, mit Ausnahme der begin-Anweisung
Einführung in PL/SQL
385
auch für das Schlüsselwort end, d.h. neben den einzelnen Anweisungen wird auch ein gesamter Block mit einem Semikolon beendet. Neben einer individuellen Fehlerbehandlung kann jeder PL/SQL-Block ebenfalls einen separaten Deklarationsteil besitzen, mit dessen Hilfe Sie alle benötigten Variablen oder Cursor definieren müssen, so dass sich die Struktur des PL/SQL-Programms bei genauerer Betrachtung ein wenig erweitert (vgl. Listing. 5.1). declare i integer; begin i := 1; declare ii integer; begin ii:= 2; i := 3; begin ii := 21; end; end; begin i:=33; end; end; Listing 5.1: Blockstruktur eines PL/SQL-Programms mit verschiedenen Deklarationsteilen
Wie Sie dem Listing entnehmen können, wird der jeweilige Deklarationsteile mit Hilfe des Schlüsselwortes declare eingeleitet, dem anschließend eine Aufzählung aller benötigten Objekte folgt, wobei die Deklaration jedes einzelnen Objekts genau wie eine normale Anweisung wieder mittels eines Semikolons beendet wird. Was Sie dem letzten Beispiel auch entnehmen können ist die Sichtbarkeit der einzelnen Variablen in den verschiedenen Programmblöcken. Prinzipiell vererbt sich diese Sichtbarkeit entlang der Blockschachtelung von außen nach innen, d.h. die für den aktuellen Block definierten Variablen können auch in allen weiter innenliegenden Blöcken verwendet werden. Wenn Sie sich das letzte Beispiel anschauen, dann sehen Sie dort auch schon, wie in PL/SQL die Wertzuweisung zu einer Variablen passiert. Genau wie in einigen anderen Programmiersprachen erfolgt das nicht mit einem einfachen Gleichheitszeichen, sondern durch die Kombination mit einem Doppelpunkt („:“). Mehr über die Deklaration und Verwendung von Variablen und Datentypen erfahren Sie direkt im Anschluss im nächsten Kapitel. Ansonsten ist vielleicht noch wichtig, weil beliebte Fehlerquelle, dass PL/SQL keine leeren Programmblöcke verträgt, d.h. der folgende Block führt während der Kompilierung zu einem Fehler:
386
PL/SQL-Programmierung
begin end;
Kommentare In Ihren PL/SQL-Programmen können Sie an jeder beliebigen Stelle Kommentare einfügen, wobei diese wie üblich mit Hilfe spezieller Sonderzeichen markiert werden. Konkret stehen Ihnen hierbei zwei unterschiedliche Varianten zur Verfügen:
X
Verwenden Sie ein doppeltes Minuszeichen, um den Rest der aktuellen Zeile als Kommentar zu markieren. -- ich bin ein Kommentar begin -- geht auch hinter einem Befehl
X
Verwenden Sie die Zeichen „/*“ und „*“, um den dazwischenliegenden Text als Kommentar zu markieren. Ein solcher Kommentar kann ebenfalls hinter einer beendeten Anweisung beginnen. /* ------------------------Mehrzeiliger Kommentar ------------------------ */
5.1.3
Datentypen
Bei einem ersten Blick auf die in Oracle vorhandenen bzw. vordefinierten Datentypen kommt man sich vor wie in einem Supermarkt. Da steht schließlich auch nicht nur ein Erdbeerjogurt im Regal, sondern man hat gleich zehn verschiedene zur Auswahl. Doch genau wie bei den Jogurts verhält es sich auch mit den Datentypen; in beiden Fällen habe ich noch nicht alle vorhandenen Möglichkeiten ausprobiert. Aus diesem Grund finden Sie in diesem Abschnitt zunächst einmal in der Abbildung 5.1 eine Übersicht der verfügbaren Typen, wobei ich eine Kurzbeschreibung der aus meiner Sicht wichtigsten Typen in der Tabelle 5.1 zusammengestellt habe. Sofern Sie mehr bzw. weitergehende Informationen benötigen, dann empfehle ich Ihnen das Buch „PL/SQL User's Guide and Reference“ aus der Oracle-Programmdokumentation, in dem Sie nicht nur eine Beschreibung aller vorhandenen Typen, sondern auch ansonsten noch weitergehenden interessante Informationen finden. Wenn Sie ein wenig Programmiererfahrung in einer moderneren Programmiersprache haben, dann werden Ihnen die meisten der in Abbildung 5.1 aufgeführten Typen bekannt sein, oder Sie können sich zumindest vorstellen, welche Daten sich mit ihrer Hilfe speichern lassen. Ansonsten kann ich Sie dahingehend beruhigen, dass auch ich die meisten der hier aufgeführten Varianten bisher noch nie verwendet habe. Eine Übersicht und kurze Beschreibung besonders wichtiger Typen können Sie der folgenden Tabelle entnehmen.
Einführung in PL/SQL
387
Kompositionen
Skalare Typen binary_integer dec decimal double precision float int integer natural naturaln pls_integer positive positiven real signtype smallint
char character long long raw nchar nvarchar2 raw rowid string varchar varchar2 boolean
record table varray
Referenzierung ref cursor ref object_type
Lobs ffile blob clog nclob
date
Abbildung 5.1: Übersicht der vorhandenen Datentypen Datentyp
Beschreibung
binary_integer
dient zur Speicherung Integerzahlen im Wertebereich on -2147483647 .. 2147483647. Bei der normalen Anwendung können Sie statt dieses Datentyps die Werte auch mit number, integer oder pls_integer definieren, jedoch benötigen Sie den Typ binary_integer, wenn Sie in Ihren Programmen noch Tabellen (Datenfelder) vom Typ table verwenden.
boolean
dient zur Speicherung der Wahrheitswerte true bzw. false, wobei im Ausnahmefall auch „ich weiß es nicht“, also null gespeichert werden kann.
char
Mit diesem Typ werden einzelne Zeichen oder Zeichenketten mit fester Länge gespeichert, wobei im letzteren Fall eine Längenvorgabe in der Form char(x) im Wertebereich von 1..32767 erfolgen muss.
date
speichert Datums- und/oder Zeitwerte.
number
Das ist der eigentliche Datentyp, um in Oracle numerische Typen zu definieren. Ohne weitere Vorgeben erzeugen Sie hiermit eine Variable zur Speichern von Fließkommazahlen. Ansonsten haben Sie die Möglichkeit, auch die benötigten Vor- und Nachkommastellen anzugeben, indem Sie die Größe und Genauigkeit der Zahl im Klammern bzw. im Format number(Größe, Genauigkeit) angeben. Bei den anderen numerischen Datentypen (z.B. integer oder smallint) handelt es sich um Spezialfälle, die auch alle mit Hilfe einer number-Anweisung deklariert werden könnten, und zum Teil ist die Existenz dieser Spezialtypen auch nur damit zu begründen, dass die Migration anderer Datenbanken hierdurch vereinfacht wird.
record
dient zur Anlage einer Datenstruktur und ist besonders im Zusammenhang mit Cursorn interessant.
rowid
Spezielles internes Format zur Aufnahme des internen Datensatzzeigers.
388
PL/SQL-Programmierung
Datentyp
Beschreibung
table
dient zur Anlage einer indizierten Tabelle bzw. einem Datenfeld mit variabler Ausdehnung. Als Index wird eine fortlaufende Nummer vom Typ binary_integer verwendet oder das Datenfeld wird als Kollektion verwendet und muss dann mit Hilfe der entsprechenden Methoden selbst dimensioiniert werden.
varchar2
Hiermit können Sie Zeichenfolgen mit einer variablen Länge speichern, wobei Sie die Maximallänge in der Form varchar(x) mit einem Wertebereich von 1..32767 angeben müssen.
varray
Hiermit können Sie Datenfelder fester Länge, also mit einer maximalen Anzahl von Elementen definieren.
Tabelle 5.1: Beschreibung einiger ausgewählter Datentypen
Bevor ich nun im nächsten Schritt versuche, die Verwendung der einzelnen Datentypen irgendwie verbal zu beschreiben habe ich mir gedacht, es ist einfacher und ich glaube auch verständlicher, ich demonstriere die Verwendung an verschiedenen kleinen Beispielen, in dem nichts weiter passiert, als dass die oben beschriebenen Typen verwendet und mit Werten versorgt bzw. abgefragt werden. Benutzen einfacher Datentypen Bei der Verwendung der einfachen Datentypen (vgl. Listing 5.2) ist eigentlich nichts weiter zu beachten, außer vielleicht dass bei der Wertzuweisung bzw. bei Vergleichsoperationen die verwendeten Datentypen zueinander passen sollten. In einigen Fällen klappt auch die gemischte Verendung unterschiedlicher Typen, da Oracle in einem gewissen Umfang die implizite Typkonvertierung beherrscht, wozu ich allerdings später noch etwas sagen werde. declare -i x d d1 v b
Einfache Datentypen integer; varchar2(30); date; date; number(7,2); boolean;
begin -- Verwenden der einfachen Variablen i:=1; x:= 'Hallo'; v:= -33.22; d:= sysdate; d1 := d; b := true; end; Listing 5.2: Verwenden einfacher Datentypen
Einführung in PL/SQL
389
Strukturen und einfache Felder Die Definition einer Struktur (vgl. Listing 5.3) erfolgt mit Hilfe der Schlüsselwörter type und is record. Dazwischen steht der von Ihnen vergebene Name für die zu definierende Struktur und was danach folgt erinnert irgendwie and die Definition einer Tabelle. Im Prinzip können Sie bei der Strukturdefinition wieder weitere Strukturen als Elemente verwenden, wobei die Struktur dann allerdings nicht mehr in einem indizierten Datenfeld benutzt werden kann. Außerdem finden Sie im Listing 5.3 auch zwei Beispiele für die Definition und Verwendung von indizierten Datenfeldern. Das erste Datenfeld t_ti enthält als Element eine einfache Zeichenkette mir variabler Länge, wohingegen die Elemente des zweiten Feldes der definierten Struktur r_xr entsprechen. Damit wir innerhalb des eigentlichen Programmblocks alle diese definierten Typen verwenden können, deklarieren wir des weiteren die zugehörigen Variablen v_xr, v_ti und v_pe. declare -- Definition eines Records ( type r_xr is record persnr varchar2(10), name varchar2(40) ); -- Definition einfacher Datenfelder type t_ti is table of varchar2(10) index by binary_integer; type t_pe is table of r_xr index by binary_integer; -- Einfache Recordvariable v_xr r_xr; -- Variablen für die Datenfelder definieren v_ti t_ti; v_pe t_pe; begin -- Bestücken des Records v_xr.persnr := '199'; v_xr.name := 'Raymans'; -- Verwenden der einfachen Datenfelder v_ti(22) := v_xr.name; v_pe(33) := v_xr; v_pe(34).persnr := v_xr.persnr; v_pe(34).name := 'Raymans'; end; Listing 5.3: Beispiel zur Benutzung von Strukturen und einfachen Datenfeldern
390
PL/SQL-Programmierung
Das Schöne und Einfache an den indizierten Datenfeldern ist, dass sie ohne Initialisierung und Dimensionierung verwendet werden können, d.h. die Wertzuweisung v_ti(99) := 'Mustermann';
führt zum Speichern des vorgegebenen Textes im Element mit der laufenden Nummer bzw. Index 99, ohne dass wir uns zuvor darum kümmern mussten, dass das Datenfeld hinreichend groß ist. Die Verwendung einer Struktur, bzw. der Zugriff der darin enthaltenen Elemente erfolgt in der mittlerweile eigentlich üblichen Form, d.h. der Bezeichner der Struktur bzw. die Strukturvariable und der Name des Elements werden gemeinsam verwendet und durch einen Punkt getrennt. Handelt es sich links und rechts vom Gleichheitszeichen um die gleiche Struktur, dann können Sie auch eine gesamte Struktur zuweisen oder vergleichen; ansonsten müssen Sie alle entsprechenden Elemente einzeln übertragen oder abprüfen. Durch unseren Typ t_pe bzw. der zugehörigen Variable v_pe finden Sie ebenfalls ein Muster, wie eine Struktur innerhalb eines indizierten Datenfelds verwendet werden kann. Varrays und Kollektionen Auch bei den Varrays und Kollektionen handelt es sich im Prinzip um Datenfelder, die innerhalb eines PL/SQL-Programms allerdings anders behandelt werden und die vor allem auch innerhalb der objektorientierten Tabellen verwendet werden können. In meinem Beispiel (vgl. Listing 5.4) habe ich zunächst wieder die Struktur r_xr mit zwei Elementen angelegt. Anschließend definiere ich noch das Varray t_dt, das maximal 22 Datumswerte aufnehmen kann und die Kollektion t_py, die eine beliebige Anzahl von Elementen des Typs r_xr, also unserer Struktur, enthält. Damit wir im Programmblock wieder mit den erstellten Datentypen arbeiten können, deklariere ich zusätzlich die entsprechenden Variabeln v_py, v_dt und v_xr. declare -- Definition eines Records type r_xr is record ( persnr varchar2(10), name varchar2(40) ); -- Definition der Tabellentypen bzw. Datenfelder type t_py is table of r_xr; type t_dt is varray(22) of date; -- Einfache Recordvariable d date; v_xr r_xr; -- Variablen für die Datenfelder definieren v_py t_py;
Einführung in PL/SQL
391
v_dt t_dt; begin -- Verwenden des Varrays v_dt:= t_dt(); v_dt:= t_dt(null, null, null, null); v_dt:= t_dt(sysdate, sysdate+1, sysdate+2, d); d := v_dt(1); v_dt(1) := d; -- Verwenden der Kollektion v_py := t_py(); v_py.extend(1); v_py(v_py.count).name := 'Hugo'; v_xr := v_py(1); end; Listing 5.4: Beispiele zur Verwendung von Varrays und Kollektionen
Einer der wesentlichen Unterschiede zu den vorhergehenden Beispielen ist, dass die jetzt verwendeten Datenfelder initialisiert bzw. verwaltet werden wollen. Bei den Varrays erfolgt die Initialisierung mit Hilfe einer Konstruktion aus dem zugrundeliegenden Datentyp. Verwechseln sie dabei allerdings nicht die beiden folgenden Anweisungen, die beide als eine Form der Initialisierung verstanden werden können: v_dt:= t_dt(); v_dt:= t_dt(null, null, null, null);
Die erste Anweisung führt nämlich dazu, dass das Datenfeld anschließend leer ist, also keine Elemente mehr enthält und damit im Programm auch nicht zum Speichern konkreter Werte benutzt werden kann. Im Unterschied dazu verschafft die zweite Anweisung im Varray vier leere Plätze zum Speichern eines beliebigen Datumswertes. In beiden Fällen wird der definierte Datentyp t_dt als Konstruktor für die typkonforme Wertzuweisung benutzt. Ist so ein Varray erst einmal initialisiert bzw. dimensioniert, dann können Sie es wie ein indiziertes Datenfeld verwenden, wobei der größte verwendbare Index von der konkreten Initialisierung und der entsprechend der Deklaration maximalen Größe abhängt. Die aktuelle Größe eines Varrays können Sie übrigens mit Hilfe der Methode count abfragen, die Ihnen die aktuelle Größe des Datenfeldes liefert: if v_dt.count = 4 then ... i := v_dt.count
Neben dem Varray finden Sie in dem Beispiel 5.4 auch ein Muster für die Anwendung einer Kollektion. Konkret geht es um den Typ t_py bzw. die zugehörige Variable v_py, mit denen Sie in Ihrem Programm ein beliebiges Datenfeld mit der definierten r_xr-Struktur benutzen können. Genau wie beim Varray und im Unter-
392
PL/SQL-Programmierung
schied zu den indizierten Datenfeldern müssen Sie jetzt allerdings wieder ein Stück Extraarbeit leisten und die Verwaltung des Feldes übernehmen. Diese beginnt genau wie beim Varray mit der Initialisierung des Feldes, beispielsweise durch Zuweisung eines leeren Feldes, das wiederum mit Hilfe des zugehörigen Typs als Konstruktor gebildet wird. v_py := t_py()
Anschließend ist unsere Kollektion zwar leer, aber gebrauchsfertig. Um nun konkret einen Datensatz in der Kollektion speichern zu können, müssen Sie dafür zunächst den benötigten Platz schaffen, wobei Oracle zur Verwaltung der Kollektionen eine Reihe von Methoden zur Verfügung stellt. Die Methode zur Aufnahme neuer Elemente in die Kollektion heißt extend und wird im einfachsten Fall einfach mit der Anzahl der neuen Elemente verwendet. v_py.extend(1); v_py.extend(5, 2);
Die erste Anweisung erweitert die Kollektion um ein leeres Element, wohingegen die zweite fünf Kopien des zweiten Elements ans Ende anfügt. Auch für die Kollektionen gilt, das die sonstige Anwendung den indizierten Datenfeldern eigentlich sehr ähnlich ist, d.h. Sie können beispielsweise auch wieder mit Hilfe einer Indexnummer direkt auf ein spezielles Element zugreifen. Eine Beschreibung der insgesamt vorhandenen Methoden exists, count, limit, first, last, prior, next, extend, trim und delete finden Sie in der PL/SQL-Dokumentation „PL/SQL User's Guide and Reference“. Gehen Sie dort in das Kapitel „Using Collection Methods“ bzw. den dortigen Abschnitt „Collection and Records“. Ausdrücke Vor ein paar Seiten hatte ich die Wertzuweisung so nach dem Motto links die Variable und rechts etwas passendes und dazwischen Doppelpunkt und Gleichheitszeichen in einem Nebensatz erwähnt und damit die einfachste Form eines Ausdrucks beschrieben, der nur aus einer Konstanten oder Variablen besteht. In der Praxis können rechts vom Gleichheitszeichen natürlich komplexere Konstruktionen stehen, die üblicherweise als Ausdruck oder Formel bezeichnet werden. Ein solcher Ausdruck enthält Variablen, Konstante, Funktionen, die mit Hilfe geeigneter Operatoren verknüpft werden, so dass das Ergebnis dieser gesamten Verknüpfung der links vom Doppelpunkt spezifizierten Variablen zugewiesen wird. Das Schöne an der hier verwendeten Schreibweise ist, dass Sie beispielsweise innerhalb eines Seminars bei vielen Teilnehmern weniger Stirnrunzeln hervorruft, denn folgender Ausdruck x=x+1 ist, und soviel Schulmathematik kennt wohl jeder, eben nicht gleich. Nicht unbedingt einleuchtend ist, dass hiermit ja auch gemeint ist, das das Ergebnis der Formel x+1 der alten Variablen x wieder zugewiesen werden soll. Gut, dass wir in Oracle daher x:=x+1 schreiben und damit deutlich machen, dass wir der in der Formel verwendeten Variablen auch das Ergebnis wieder speichern möchten.
Einführung in PL/SQL
393
Achten Sie bei komplexeren Formeln darauf, dass alle beteiligten Variabeln, Konstanten und Funktionen einen kompatiblen Datentyp besitzen, damit nicht die implizite Konvertierungsmaschinerie in Gang gesetzt wird, deren Ergebnis manchmal passen, manchmal aber auch dubios sein können. Ansonsten haben Sie bei der Verknüpfungen der am Ausdruck beteiligten Elemente die in der Tabelle 5.2 beschriebenen Operatoren zur Auswahl. Operator
Beschreibung
+-
führt zur Addition bzw. Subtraktion zweier numerischer Werte. Das einzelne vorangestellte Minuszeichen führt zur Vorzeichenumkehr „x := - (2 + 3)“ bzw. kennzeichnet eine negative Zahl „x := 3 * -2“.
*/
Mit Hilfe dieser Operatoren können Sie zwei numerische Werte multiplizieren bzw. dividieren.
||
Dies ist ein Operator, mit dem Sie zwei Zeichenketten aneinanderhängen können.
Tabelle 5.2: Verknüpfungsoperatoren für Ausdrücke
Bei gleichwertigen Operatoren (z.B. nur Additionen) wird die Berechnung eines Ausdrucks von rechts nach links durchgeführt. Ansonsten richtet sich die Berechnung nach der Rangfolge eines Operators, wobei auch in Oracle grundsätzlich Punkt vor Strichrechnung gilt. Eine gewisse Ausnahme dieser Regel bildet das Minuszeichen in der Verwendung als Negationsoperator „a := b * -c“, denn in dem Fall hat es die höchste Priorität. Ist diese normale Reihenfolge nicht sinnvoll, dann können Sie diese mit Hilfe von gewöhnlichen Klammern beliebig verändern, wobei in Fall von geschachtelten Klammern, die innersten zuerst ausgewertet werden. Initialisierung und „ not null“ Im Rahmen der letzten Beispiele haben Sie Situationen kennen gelernt, bei denen besondere Datentypen erst nach einer durchgeführten Initialisierung verwendbar waren. Diese Initialisierung erfolgte mit Hilfe einer geeigneten Wertzuweisung und ist streng genommen nicht nur eine Anforderung komplizierter Datentypen, sondern tut eigentlich allen definierten Variabeln gut. Standardmäßig enthalten nämlich auch die einfachen Variabeln beim Programmstart den besonderen Wert null, also nichts, was zu Problemen bei der Verwendung in einem Ausdruck führen kann. Betrachten Sie hierzu das folgende kleine Beispiel: declare i integer; b integer; begin b := 2 + i; end;
Das Problem an dem markierten Ausdruck ist, dass die Variable b nach der Berechnung nicht den Wert 2, sondern null enthält, weil in dem Berechnungsausdruck eine nicht initialisierte Variable enthalten war. Wegen dieser möglichen Probleme sollte man sich in einem Programm entweder für einen speziellen Initialisierungs-
394
PL/SQL-Programmierung
teil entscheiden, oder diese Aufgabe schon bei der Deklaration der Variablen erledigen. Hierfür gibt es im PL/SQL-Sprachumfang zwei verschiedene Möglichkeiten, deren Bewertung ich mal wieder der Kategorie Geschmacksache zuordnen würde. Zum einen können Sie der Variablendeklaration eine Art Wertzuweisung anhängen, so dass die Variablen beim Starten des Programms bzw. des zugehörigen Blocks schon die dort spezifizierten Werte enthalten. declare i x d d1 v b
integer := 1; varchar2(30) := 'Hallo'; date := sysdate; date := d+1; number(7,2) := -33.22; boolean := true;
Zum anderen besteht ebenfalls die Möglichkeit, die Initialisierung mit Hilfe des Schlüsselworts default vorzunehmen, so dass das Ganze genauso funktioniert und nur etwas anders aussieht: declare i x d d1 v b
integer default 1; varchar2(30) default 'Hallo'; date default sysdate; date default d+1; number(7,2) default -33.22; boolean default true;
Wofür Sie sich auch immer entscheiden mögen, besonders interessant ist die in dem Beispiel markierte Stelle, denn die zeigt Ihnen, dass Sie im Rahmen der Initialisierung auch wieder Berechnungen, also komplexere Ausdrücke einsetzen können. Hierbei müssen Sie allerdings ein wenig auf die Reihenfolge der im Ausdruck verwendeten Variablen achten, denn erst am Ende des Deklarationsteils sind alle definierten Objekte bekannt, d.h. erst dann könnten alle denkbaren Verknüpfungen aufgelöst werden. Auch kompliziertere Datentypen wie Strukturen, Varrays oder Kollektionen können schon während der Deklaration mit Werten ausgestattet werden. Bei Strukturen passiert das in der Form, dass Strukturdefinition und nicht die eigentliche Variable initialisiert wird. declare type r_xr is record ( persnr varchar2(10) := '475837', name varchar2(40) := 'Raymans'); v_xr r_xr;
Wie Sie dem Beispiel entnehmen können, erfolgt die Initialisierung einer Struktur schon während der Typdeklaration, so dass auch die zugehörige Variable v_xr automatisch die definierten Standardwerte in ihren Elementen enthält. Betrachten wir nun noch, wie die Initialisierung bei den Varrays und Kollektionen funktioniert.
Einführung in PL/SQL
395
declare type t_py is table of r_xr; type t_dt is varray(22) of date; v_py t_py := t_py(v_xr, null); v_dt t_dt := t_dt(null, null, null, null);
Beginnen wir auch diesmal wieder mit der Betrachtung des Varrays. Genau wie bei einer der im Beispiel 5.4 verwendeten Initialisierungsvariante dimensionieren wir das Datenfeld v_dt mit insgesamt vier leeren Elementen. Auch die Erstausstattung der Kollektion erfolgt wieder mit Hilfe einer Konstruktion aus dem Datentyp. Im Unterschied zu dem Beispiel 5.4 legen wir diesmal allerdings keine leere Kollektion an, sondern statten diese mit zwei Elementen aus. Das erste Element entspricht dabei einer Kopie der Strukturvariablen v_xr und das zweite Element der Kollektion ist leer. Damit wären wir in Bezug auf die Initialisierung eigentlich fertig, jedoch beinhaltet die zu diesem Abschnitt gehörende Überschrift noch den Hinweis auf eine weitere Aufgabenstellung, die sich allerdings kurz und knapp oder schnell und schmerzlos beschreiben lässt. Genau wie bei der Anlage einer Datenbanktabelle, wo Sie bei jedem Datenfeld die Fähigkeit zur Aufnahme eines null-Wertes durch eine besondere Klausel verhindern konnten, so besteht auch innerhalb eines PL/SQL-Programms eine entsprechende Verfahrensweise, um den Variablen die Aufnahme dieses Wertes zu verbieten. Wie Sie dem folgenden Beispiel entnehmen können, geschieht das übrigens wieder mit Hilfe der gleichen not null-Klausel, wobei diese auch direkt wieder hinter dem spezifizierten Datentype angeordnet wird. declare i integer not null default 1; x varchar2(30) not null := 'Hallo';
Typableitung In einem Programm werden meistens Daten verarbeitet, die in der Datenbank mit Hilfe irgendwelcher Tabellen gespeichert werden. Auch wenn sich dieser Satz zunächst noch so anhört, als würde ich sagen „Wasser ist nass“, so ergibt sich daraus doch eine entscheidende Konsequenz. Werden diese Daten bzw. die zugehörigen Felder während der Verarbeitung nämlich in entsprechenden Variablen oder Strukturen zwischengespeichert, so entsteht schon auf Feldebene eine enge Abhängigkeit zwischen dem Datenhaushalt und den zugehörigen Anwendungsprogrammen. Felder können sich im Laufe der Zeit ändern, indem sie beispielsweise länger werden oder in einen anderen Datentyp überführt werden, so dass in dem Fall auch alle zugehörigen PL/SQL-Programme überprüfen müssten. Natürlich sind Programme meistens irgendwie an die zugehörigen Daten gebunden und dennoch ist es lästig und eigentlich unnötig, über Programmänderungen nachzudenken, nur weil ein Textfeld um zehn Stellen verlängert wurde.
396
PL/SQL-Programmierung
Damit so etwas nicht passiert, können Sie die im Programm verwendeten Datentypen aus den zugehörigen Daten ableiten, d.h. Sie beziehen sich bei der Anlage des Feldes auf die entsprechende Tabelle bzw. das dort enthaltene Feld und sagen dem PL/SQL-Programm, dass Ihre Variable den gleichen Typ besitzen soll. Wird der morgen geändert bzw. erweitert, dann funktioniert auch Ihr Programm ab morgen automatisch in entsprechender Weise. declare ge gehalt.gehalt%type; ps personalien%rowtype;
Die Zauberwörter für diese Technik heißen type und rowtype und werden wie in dem eben gezeigten Beispiel zusammen mit einem Prozentzeichen („%“) angewendet. Dabei müssen Sie die type-Klausel verwenden, wenn Sie eine einfache Variable entsprechend eines in der Datenbank vorhandenen Feldes definieren möchten, d.h. Sie müssen hierbei neben dem konkreten Feld auch noch die zugehörige Tabelle vorgeben. Im Gegensatz dazu hilft die rowtype-Klausel bei der Anlage von Strukturvariablen, die beispielsweise dem Aufbau einer in der Datenbank vorhandenen Tabelle entsprechen sollen und wird daher auch nur zusammen mit einem Tabellennamen verwendet. Implizite Konvertierung Immer wenn man innerhalb von Ausdrücken mehrere Felder oder Variablen miteinander verknüpft dann läuft man Gefahr, dass die vorhandene implizite Datentypkonvertierung zuschlägt. Beim bisherigen Lesen dieses Buches haben Sie sicherlich schon gemerkt, dass ich nicht unbedingt ein Freund dieser automatischen Wandelung bin.
Ja
Ja
Ja
X
Ja
CHAR
Ja
X
DATE
Ja
LONG
Ja
X
Ja
Ja
NUMBER
Ja
DATE
Ja
VARCHAR2
Ja
X
ROWID
PLS_INT Ja
BIN_INT
RAW
NUMBER Ja
CHAR
Ja
BIN_INT
LONG
Sicherlich ist das Wandeln bzw. Anpassen des Datentyps kein Problem, wenn die beteiligten Variablen zumindest der gleichen Typenklasse entspringen oder die Umwandlung in einen größeren, übergeordneten Typ erfolgt. Ab es wird auch an Stellen umgeformt, wo das Ergebnis manchmal dubios ist, beispielsweise von Zeichenketten in Datumswerte oder umgekehrt. Die insgesamt möglichen implizierten Konvertierungen habe ich Ihnen einmal mit Hilfe der Oracle-Dokumentation in der Tabelle 5.3 dargestellt, wobei ich die Stellen fett markiert habe, wo die implizierte Konvertierung keine Probleme bereitet; an den anderen gekennzeichneten Stellen sollten Sie aber zumindest vorsichtig sein.
Ja Ja
Ja
Ja Ja
Ja X
Ja
Ja Ja
Ja
VARCHAR2
Ja
Ja
Ja
Ja
Ja X
Ja
Ja
VARCHAR2
ROWID
ROWID
X
RAW
PLS_INT
Ja
Ja
Ja
DATE
Ja
Ja
PLS_INT
CHAR Ja
RAW
BIN_INT
NUMBER
397
LONG
Einführung in PL/SQL
Ja
Ja X
Ja
Ja
X
Tabelle 5.3: Übersicht der möglichen impliziten Datenkonvertierung
Wie schon gesagt, sollten Sie an den Stellen, wo kein fettes „Ja” steht ein wenig wachsam sein, wohingegen Sie in allen anderen Fällen explizit, also mit Hilfe spezieller Funktionen, konvertieren müssen. Falls Sie sich nun fragen, warum ich diesen automatischen Wandelmechanismus nicht einfach nur als Feature hinstelle, so möchte ich darauf kurz antworten. Wenn Datentypen eigentlich nicht zueinander passen (z.B. Texte und Zahlen oder Texte und Datumswerte), dann erfolgt eine implizite Umwandlung auf der Basis von Einstellungen und Regeln. Sowohl Einstellung als auch Regeln können sich allerdings ändern, so dass Ihr Programm vielleicht von heute auf morgen anders als sonst reagiert. So können Sie beispielsweise mit Hilfe der Anweisung alter session set nls_date_format = 'DD.MM.YYYY';
die standardmäßige Datumsaufbereitung für Ihre Session einstellen. Ist die so wie in dem obigen Beispiel gesetzt, dann funktioniert das folgende Programm einwandfrei: declare d date; begin d:= '22.11.2000'; end;
Aber eben nur dann, d.h. bei einer anderen Einstellung wie zum Beispiel dem Standardwert führt die Ausführung des Skripts zu einem Fehler. Ähnliches gilt für die zugrundeliegenden Regeln. Diese können sich theoretisch schon innerhalb eines Mini-Updates ändern und so in bestimmten Grenzfällen bei der impliziten Konvertierung zu anderen Ergebnissen führen. Der dritte Grund warum ich so skeptisch bin ist, dass der fahrlässige Umgang mit Datentypen auch eine beliebte und schwierig zu findende Fehlerquelle ist. Werden während einer Berechnung bestimmte Zwischenergebnisse „aus Versehen“ in einen Integertyp konvertiert, dann sind am Ende alle Nachkommastellen weg.
398
5.1.4
PL/SQL-Programmierung
Funktionen
Eine Programmiersprache ohne Funktionen kann ich mir gar nicht mehr vorstellen, wobei die verschiedenen Funktionen in Oracle nicht nur innerhalb eines PL/ SQL-Programms, sondern auch für die ganz normalen SQL-Abfragen verfügbar sind. Allerdings übertrifft Oracle das eine oder andere bzw. die meisten anderen Datenbanksysteme dahingehend, dass Sie in der Lage sind, auch selbst Funktionen zu erstellen, die anschließend auch innerhalb der Programmierung oder bei der Abfrageerstellung eingesetzt werden können. Wie eigene Funktionen erstellt werden können ist in diesem Kapitel nicht das primäre Thema, sondern ich möchte Ihnen hier im Wesentlichen eine Übersicht der standardmäßig verfügbaren Miniprogramme geben. Dennoch finden Sie in diesem Buch auch hierüber einige Informationen. Da wäre zunächst einmal die Beschreibung der einzelnen Schema-Objekte im Kapitel 2, wo sich ein separates Kapitel mit den Funktionen beschäftigt. Außerdem finden Sie auch weiter unten bei den Anwendungsbeispielen weitere Anmerkungen zur Funktionserstellung. Zur generellen Verwendung einer Funktion möchte ich eigentlich nicht viel sagen. Wie in allen Programmiersprachen werden die Funktion mit Hilfe Ihres Namens aufgerufen und erhalten dabei keine oder mehrere Parameter, die in Klammern und durch Komma getrennt und in der richtigen Reihenfolge zu übergeben sind. Als Ergebnis bekommt man genau einen Wert in Form eines bestimmten Datentyps zurück und genau wegen dieser Verhaltensweisen können die Funktionen so elegant und sogar beliebig geschachtelt in Ausdrücken verwendet werden. Oftmals werden Funktionen bei der Dokumentation einer Programmiersprache in Gruppen eingeteilt. So finden man dort beispielsweise die Gruppe der numerischen Funktionen, die sich dadurch auszeichnen, dass die Funktion als Ergebnis einen numerischen Wert zurückliefert und vielleicht sogar eine Zahl als Parameter erwartet. Auf ganz ähnliche Weise entstehen so auch die Gruppen der String- oder Datumsfunktionen. Auf der anderen Seite gibt es Funktionen, die man nicht so gerne der einen oder anderen Gruppe zuordnen möchte bzw. gar nicht zuordnen kann. Das sind beispielsweise Funktionen, die zur Datumskonvertierung benutzt werden oder spezielle Systemfunktionen. Ich werde eine solche Gruppierung im Folgenden auch durchführen, dabei allerdings nur die wichtigsten Funktionen in diesem Workshop beschreiben. Dabei beginne ich ab Tabelle 5.4 mit der Übersicht numerischer Funktionen. In jedem Fall finden Sie da, wo es sich lohnt hinter den Tabellen noch die eine oder andere Ergänzung oder ein vertiefendes Beispiel. Numerische Funktionen Die Gruppe von Funktionen liefert als Ergebnis eine Zahl und erwartet als Parameter ebenfalls numerische Werte. Funktion
Beschreibung
abs
ermittelt den absoluten Betrag des übergebenen Wertes, d.h. mit anderen Worten erhalten Sie als Ergebnis den übergebenen Wert mit positivem Vorzeichen.
Einführung in PL/SQL
399
Funktion
Beschreibung
ceil
Diese Funktion liefert die kleinste ganze Zahl, die gleich oder größer dem übergebenen Wert ist. Der Ausdruck ceil(14.3) liefert als Ergebnis also 15, wohingegen ceil(-14.3) zwar immer noch die nächstgrößere ganze Zahl und deshalb – 14 liefert. Die ceil-Funktion eignet sich unter anderem prima zum Runden. Möchten Sie einen berechneten Betrag n beispielsweise auf volle 25,- _ runden, dann können Sie das folgendermaßen realisieren: ceil(n / 25) * 25.
floor
Hiermit berechnen Sie für den übergebenen Wert die größte ganze Zahl, die kleiner oder gleich diesem Wert ist. Damit ergibt der Ausdruck floor(14.3) jetzt 14 und floor(-14.3) bewegt sich jetzt in die andere Richtung und liefert – 15.
mod
Die sogenannte Modulo-Funktion berechnet den Rest einer ganzzahligen Division, d.h. die Funktion erhält diesmal zwei Werte. Konkret liefert mod(m, n) den Rest der Ganzzahldivision von m durch n, z.B. gilt: mod (11, 3) = 2. Wenn Sie also einmal den Rest einer Division von m durch n benötigen, dann können Sie anstelle der Formel m – n * (floor(m / n)) auf einfach mod benutzen.
power
Wenn Sie bei den Operatoren einen Potenzoperator vermisst haben, dann liegt das daran, dass es in PL/SQL hierfür eine Funktion gibt. Die power-Funktion erwartet zwei numerische Werte und potenziert den ersten mit dem zweiten übergebenen Parameter, z.B. liefert power(3, 2) als Ergebnis die Zahl neun.
round
Mit Hilfe der round-Funktion können Sie den übergebenen numerischen Wert runden. Die Funktion erwartet normalerweise zwei Parameter, wobei der erste den zu rundenden Wert und der zweite die zu rundende Stelle, also quasi die verbleibenden Nachkommastellen, vorgibt. Sofern Sie den zweiten Parameter weglassen, dann nimmt Oracle hierfür automatisch den Wert 0 an, d.h. es wird auf die erste Stelle vor dem Komma gerundet. Sie können mit dieser Funktion übrigens auch runden, indem Sie bei der Genauigkeit negative Zahlen verwenden, was zur Rundung vor dem Komma führt.
sign
Vorzeichenfunktion, die als Ergebnis 0, 1 oder – 1 liefert, wenn der übergebene Wert 0, positiv oder negativ ist.
sqrt
berechnet die Quadratwurzel für den übergebenen Wert. Falls es bei Ihnen auch schon so lange her ist: Eine beliebige Wurzel können Sie berechnen, indem Sie zusammen mit der power-Funktion anstelle der Wurzel den Kehrwert als Exponent verwenden. Falls sich das jetzt wie die automatische Übersetzung aus dem Chinesischen anhörte, so möchte ich es noch einmal mit einem Beispiel versuchen. Die vierte Wurzel aus 16 berechnen Sie folgendermaßen: power(16, 1/4).
trunc
Die trunc-Funktion scheidet dem übergebenen Wert die Nachkommastellen ab. Sofern nicht alle abgeschnitten werden sollen, dann können Sie die Zahl der übrigbleibenden als zweiten Parameter vorgeben.
Tabelle 5.4: Tabelle 5.4: Übersicht wichtiger vordefinierter Funktionen
Funktionen für Zeichenketten Diese Funktionsgruppe verwendet als wesentliches Argument eine Zeichenfolge und liefert im Normalfall auch einen Text zurück. Auch hier gilt das keine Regel ohne Ausnahme ist, weshalb manche Funktion eine Zahl als Argument erhält.
400
PL/SQL-Programmierung
Funktion
Beschreibung
chr
liefert das dem übergebenen Wert entsprechende Zeichen entsprechend dem für die Datenbank eingestellten Zeichensatz.
concat
Falls Sie den Operator „||“ nicht mögen, dann können Sie zwei Zeichenfolgen auch mit dieser Funktion aneinanderhängen.
initcap
Diese Funktion versucht den übergebenen String nach einfachem Strickmuster in Groß-/Kleinschreibweise umzuwandeln. Allerdings ist das Ergebnis aus meiner Sicht meistens zweifelhaft, denn die Funktion liefert beim Aufruf initcap('herman van veen') als Ergebnis „Herman Van Veen“ zurück.
lower
Mit dieser Funktion wandeln Sie die übergebene Zeichenfolge in Kleinbuchstaben um.
lpad
Die lpad-Funktion erwartet insgesamt drei Parameter, von denen der erste und letzte Zeichenketten und der mittlere eine Ganzzahl ist. Konkret geht es darum, den als ersten Parameter übergebenen Text auf die im zweiten Parameter spezifizierte Länge zu bringen. Dazu wird der als dritte Parameter spezifizierte Text, der beim Weglassen als einzelnes Leerzeichen gedeutet wird, so lange aneinanderhängt, bis diese Kette zusammen mit der übergebenen Zeichenfolge die gewünschte Textlänge erreicht. Diese Kette wird anschließend vor der übergebenen Zeichenfolge gestellt und zusammen mit dieser zurückgegeben.
ltrim
Diese Funktion erwartet üblicherweise auch zwei Zeichenfolgen, wobei beim Fehlen der zweiten ein einzelnes Leerzeichen angenommen wird. Ansonsten wird der linke Teil der im ersten Parameter übergeben Zeichenfolge abgeschnitten, so lange die Zeichen dem zweiten Parameter entsprechen. In der Praxis wird die ltrim-Funktion nur zusammen mit einer Zeichenkette verwendet, bei der vorangestellte Leerzeichen abgeschnitten werden sollen.
replace
Mit Hilfe dieser Funktion können Sie für eine übergebene Zeichenfolge eine Suchen- und Ersetzen-Funktion ausführen. Die Funktion benötigt also insgesamt drei Textparameter. Spezifizieren Sie als Erstes die zu durchsuchende Zeichenkette, danach den Such- und als Letztes den Ersetzungsbegriff. Konkret wird aus replace(’JACK AND JUE’,’J’,’B’) somit „BLACK AND BLUE“.
rpad
funktioniert analog zur lpad, nur dass sich das Geschehen jetzt auf der rechten Seite der zu verlängernden Zeichenkette abspielt.
rtrim
Genau wie bei ltrim wird die übergebene Zeichenfolge gekürzt, diesmal allerdings auf der rechten Seite. Meistens wird mit dieser Funktion anhängende Leerzeichen abgeschnitten.
substr
Mit dieser Funktion können Sie aus einer Zeichenfolge eine beliebige andere Zeichenkette herausschneiden, wobei Sie die Anfangsposition und die Schnittlänge beliebig vorgeben können. Konkret besitzt die Funktion also einen ersten Textund ein bis zwei numerische Parameter. Dabei gibt der erste Parameter die Zeichenfolge vor, aus der ein gewünschter Text herausgeschnitten werden soll. Der zweite Parameter bestimmt die Anfangsposition und der letzte übergebene Wert bestimmt die Anzahl der herauszuschneidenden Zeichen. Sofern Sie ihn weglassen, schneidet die Funktion immer von der Anfangsposition bis zum Ende des Textes aus. Sofern Sie als zweiten Parameter eine negative Zahl verwenden, wird die Anfangsposition vom rechten Ende an ermittelt.
Einführung in PL/SQL
401
Funktion
Beschreibung
translate
Mit dieser Funktion können Sie einen Text zeichenweise übersetzen. Hierzu werden drei Parameter benötigt. Als Erstes müssen Sie die zu übersetzende Zeichenfolge vorgeben und mit Hilfe der beiden nächsten Textparameter legen Sie die zu übersetzenden Zeichen in korrespondierender Reihenfolge fest, d.h. das n-te Zeichen im Parameter 2 wird durch das entsprechende Zeichen im dritten Parameter ersetzt. Betrachten wir zur Verdeutlichung das folgende Beispiel translate('Raymans','am','12'), indem in der übergebenen Zeichenfolge jedes „a“ in eine „1“ und jedes „y“ in eine „2“ übersetzt werden soll. Alle anderen Zeichen kommen nicht in den Übersetzungstabellen vor und bleiben daher unverändert, so dass dieses Beispiel den Text „R1m21ns“ als Ergebnis liefert.
upper
wandelt ähnlich wie die lower-Funktion die übergebene Zeichenkette in Großbuchstaben um.
Tabelle 5.5: Übersicht wichtiger Zeichenkettenfunktionen
Zeichenkettenfunktionen mit numerischer Rückgabe Diese jetzt folgende Funktionsgruppe erwartet als wesentliches Argument wieder eine Zeichenfolge, liefert jedoch ein numerisches Ergebnis zurück. Funktion
Beschreibung
ascii
liefert für ein übergebenes Zeichen die Position, an der es in dem Zeichensatz der Datenbank kodiert ist. Ist ascii-Funktion ist also das Gegenstück zur chrFunktion.
instr
Diese Funktion wird zum Finden eines Zeichens oder eines Textes in einer anderen Zeichenfolge verwendet und üblicherweise mit drei Parametern verwendet. Der dritte Parameter legt dabei fest, ab welcher Stelle die Funktion mit der Suche beginnen soll. Der Ausdruck instr(’Raymans’, ’a’, 1) liefert als Ergebnis also den Wert 2, wohingegen man bei instr(’Raymans’, ’a’, 6) als Ergebnis den Wert 0 erhält, da der gesuchte Buchstabe nach der sechsten Stelle nicht mehr auftaucht.
length
Mit dieser Funktion können Sie die Länge, als die Anzahl der vorhandenen Zeichen, des übergebenen Textausdrucks ermitteln.
nlssort
Das ist eine ganz besondere Funktion, deren Verwendung ich im Anschluss an diese Tabelle noch einmal zeigen werde. Gewöhnliche Sortieranweisungen führen immer zur binären Sortierung der Daten, d.h. die Daten werden entsprechend der Positionen der Zeichen im Zeichensatz sortiert. Das führt vor allem bei uns zu Problemen, wenn nach bestimmten Feldern (z.B. Name) sortiert wird und diese Felder Sonderzeichen bzw. Umlaute enthalten können, denn die Sonderzeichen bzw. Umlaute werden bei der binären Sortierung ans Ende des Alphabets gerückt. Mit Hilfe dieser Funktion können Sie diesem Problem entgegnen und einen Begriff erzeugen, der sich lexikalisch sortieren lässt.
Tabelle 5.6: Wichtige Funktionen für Zeichenfolgen mit numerischem Ergebnis
402
PL/SQL-Programmierung
Ein besonderes Augenmerk möchte ich noch einmal auf die Funktion nlssort lenken, mit der Sie, wie gesagt, eine Zeichenfolge in ein lexikalisches Äquivalent überführen können. Gerade wir haben wegen unserer Sonderzeichen ein Problem, wenn es darum geht Datensätze nach Feldern mit Umlauten zu sortieren oder zu selektieren. So führt die Auswahl where name between 'A' and 'Z'
beispielsweise nicht zur Anzeige des Namens „Üglo“ oder „Ötztürk“, da diese wegen des Umlauts als ersten Buchstaben in der binären Sortierreihenfolge erst hinter „Z“ folgen. Gleiches gilt im Übrigen auch für die Sortierung, d.h. Sie würden den Namen „Änderson“ nicht am Anfang, sondern erst ganz am Ende der Liste finden. Mit Hilfe der nlssort-Funktion können Sie dieses Dilemma lösen, indem Sie die entsprechenden Strings geeigent umformatieren, so dass anschließend sowohl eine korrekte Selektion wie auch eine ordentliche Sortierung möglich ist. Konkret benötigt die Funktion hierzu neben der umzuformatierenden Zeichenfolge auch die Angabe des dabei zu verwendenden Regelwerkes, das bei uns quasi konstant in der Form 'NLS_SORT = German'
verwendet werden kann. Das nun folgende Beispiel (vgl. Listing 5.5) zeigt Ihnen die konkrete Anwendung. SQLWKS> select name, nlssort(name, 'NLS_SORT = German') 2> from personalien 3> where nlssort(name, 'NLS_SORT = German') 4> between nlssort('A','NLS_SORT = German') 5> and nlssort('Z', 'NLS_SORT = German') 6> order by nlssort(name, 'NLS_SORT = German') 7> NAME NLSSORT(NAME,'NLS_SORT=GERMAN' Bätzing,Bärbel 19146E873C553219146419284B0002 Beckmann,Yonne 19281E4650145555825A5555280000 Bruckner,Yonne 1964731E46552864825A5555280002 Calvo,Ulrike 1E144B785A734B643C462800020100 ... Üglo, Heinz 73324B5A0137283C5587000A010101 Underglo, Heinz 7355232864324B5A0137283C558700 Voltair,Samuel 785A4B6E143C6469145073284B0002 Voltair,Samuel 785A4B6E143C6469145073284B0002 Voltair,Samuel 785A4B6E143C6469145073284B0002 25 rows selected. Listing 5.5: Verwenden der nlssort-Funktion
Einführung in PL/SQL
403
Ein bisschen Arbeit ist da natürlich schon, denn Sie müssen alle verwendeten Felder und Texte mit Hilfe der Funktion umwandeln und das nicht nur einmal, sondern mehrfach an unterschiedlichen Stellen. Aus diesem Grund bestünde gerade für so ein Namensfeld natürlich auch die Möglichkeit, das nlssort-Ergebnis mit Hilfe einer zusätzlichen Spalte in der Tabelle zu speichern und diese bei einer Namensänderung mit Hilfe eines Triggers zu aktualisieren. In dem Fall würden nicht nur zwei nlssort-Anweisungen wegfallen, sondern das System könnte bei der nls-Namenssuche wieder einen Index verwenden, sofern für diese Spalte einer angelegt wurde. In dem Fall müssten Sie die Tabelle mit einem neuen Feld (z.B. nls_name) erweitern, dass mindestens viermal so lang wie das zugehörige Klartextfeld sein muss. Die Erweiterung der Tabelle Personalien in unserer Musterdatenbank könnte beispielsweise folgendermaßen aussehen: alter table personalien add nls_name varchar2(300); update personalien set nls_name = nlssort(name, 'NLS_SORT = German');
Anschließend können Sie dann die im Beispiel 5.5 gezeigte Abfrage folgendermaßen verändern. select name from personalien where nls_name between nlssort('A','NLS_SORT = German') and nlssort('Z','NLS_SORT = German') order by nls_name
Datumsfunktionen In dieser Gruppe finden Sie vor allem Funktionen, die ein Datum als Argument erwarten und als Ergebnis wiederum ein Datum oder einen Teil daraus zurückgeben. Ohne spezielle Funktionen können Sie mit Datumswerten nur wenige Operationen Berechnungen direkt ausführen. Zum einen können Sie zwei Datumswerte miteinander vergleichen, und das innerhalb einer Auswahl- oder Konditionalbedingung verwenden. Zum anderen können Sie eine ganze Zahl zu einem Datum addieren bzw. von diesem abziehen, wobei diese Zahl dabei als eine Anzahl von Tagen interpretiert wird, so dass als Ergebnis das entsprechende neue Datum dabei herauskommt. Außerdem können Sie zwei Datumswerte voneinander abziehen, wobei Sie als Ergebnis in dem Fall die Anzahl der dazwischenliegenden Tagen erhalten. Je nachdem, ob Sie dabei das jüngere vom älteren Datum abziehen oder umgekehrt, ist das Ergebnis positiv bzw. negativ. Weitergehende Berechnungen sind auch möglich, bedürfen jedoch den Einsatz spezieller Funktionen, für die Sie in der nun folgenden Tabelle 5.7 eine Übersicht finden.
404
PL/SQL-Programmierung
Funktion
Beschreibung
add_months
Diese Funktion erwartet zwei Parameter. Beim ersten handelt es sich um einen Datumswert und beim zweiten um eine ganze Zahl. Als Ergebnis liefert sie wiederum ein Datum, das einer der Zahl entsprechenden Anzahl von Monaten weitergeschoben wurde. Positive Zahlen schieben das Datum dabei in die Zukunft, wohingegen negative Werte zu einer Reise in die Vergangenheit führen. Beispiel: add_months(sysdate, 12) = „heute in einem Jahr“.
last_day
ermittelt für das übergebene Datum den letzten Tag des darin enthaltenen Monats und gibt für diesen Tag wieder ein Volldatum aus. Erhält die Funktion beispielsweise als Wert den 03.10.2000, so liefert sie als Ergebnis den 31.10.2000 zurück.
months_ between
berechnet für zwei Datumswerte die dazwischenliegende Anzahl von Monaten. Enthalten beide Daten die gleiche Tageszahl (z.B. 02.) oder beschreiben beide Datumswerte den jeweiligen Monatsletzten (z.B. 31.05 und 30.04), dann erhalten Sie als Ergebnis eine ganze Zahl. Ansonsten wird der in der Differenz enthaltene Monatsteil auf der Basis von 31 Tagen umgerechnet.
next_day
Wann ist wieder Montag, diese Frage können Sie mit Hilfe der next_dayFunktion leicht beantworten. Hierzu übergeben Sie ihr als ersten Parameter ein Datum und danach als zweiten den gesuchten Wochentag in Form eines Textes. Als Ergebnis erhalten Sie das Datum für diesen gesuchten Tag. Die Verwendung von next_day(sysdate, ’Mon’) liefert Ihnen also das Datum des nächsten Montags. Als Kürzel für die gesuchten Wochentage können Sie entsprechend der Tage von Sonntag bis Samstag die Kürzel „Sun, Mon, Tue, Wed, Thu, Fri, Sat“ verwenden.
sysdate
Diese Funktion wird manchmal auch als Pseudospalte bezeichnet. Das ist insofern verständlich, da sysdate ohne jeden Parameter verwendet wird und Funktionen ohne Parameter jedoch trotzdem üblicherweise wenigstens mit einer leeren Klammer kodiert werden, was bei selbstdefinierten und parameterlosen Funktionen im übrigen auch möglich ist, obwohl Sie auch diese ohne Klammern verwenden können. Aber mal abgesehen von diesen Formalien, so liefert Ihnen diese Funktion oder Pseudospalte in jedem Fall das aktuelle Tagesdatum auf Ihrem Datenbankserver, wobei der gelieferte Wert auch die aktuelle Tageszeit enthält. Das ist eine beliebte Fehlerquelle, weshalb ich hierzu am Ende der Tabelle noch etwas ergänzen werde.
trunc
Mit dieser Funktion können Sie ein Datum abschneiden. Hierzu müssen Sie neben dem Datum auch vorgeben, wo die Funktion die Schere ansetzen soll. Hierzu erhält die Funktion einen zweiten Textparameter, mit dem diese Stelle des Datums markiert wird. Da diese Textkürzel auch noch an vielen anderen Stellen, beispielsweise bei der Datumskonvertierung, verwendet werden, möchte ich hier und jetzt lediglich auf die Tabelle 5.9 verweisen, in der Sie eine Übersicht der wichtigsten Formatcodes finden. Außerdem finden Sie im Anschluss an diese Tabelle, also zwei Sätze weiter, noch ein Anwendungsbeispiel zusammen mit der sysdate-Funktion.
Tabelle 5.7: Tabelle 5.7: Übersicht wichtiger Datumsfunktionen
Kommen wir noch einmal zurück auf die Funktion sysdate, die wie gesagt, neben dem aktuellen Serverdatum auch dessen Zeit zurückliefert. Wenn Sie nun sysdate verwenden, um den Wert in einem Datumsfeld zu speichern oder damit zu verglei-
Einführung in PL/SQL
405
chen, dann sollten Sie schon einen Moment darüber nachdenken, ob in dem Datumsfeld eine Zeitkomponente sinnvoll ist. Betrachten wir beispielsweise einmal das Datumsfeld gab in unseren Gehaltsdaten. Aufgrund unseres Datenimports enthalten alle unsere Gültigkeitsdaten lediglich ein Datum bzw. besitzen als Zeitanteil alle die Konstante 00:00:00. Ist es nun sinnvoll, wenn sich in einem solchen Datum irgendwann einmal ein konkreter Zeitanteil einschleicht, und wir hierdurch beispielsweise einen Gehaltsdatensatz erhalten, der am 01.12.2000 um 17:43:22 gültig wird. Ich denke, die Antwort auf diese Frage ist eindeutig nein und im anderen Fall müssen Sie sich allerdings auch damit abfinden, dass der Datensatz bei der folgenden Historienabfrage nicht selektiert wird: where gab = (select max(gab) from gehalt b where b.persnr = a.persnr and b.lfdnr = a.lfdnr and b.gab <= '01.12.2000')
Das gespeicherte Datum ist nämlich nicht kleiner als der Vergleichswert „01.12.2000 00:00:00“, sondern um über 17 Stunden größer. Und auch bei der Verwendung von sysdate sieht wie purer Zufall aus, dass der Datensatz ab 17:44 plötzlich ausgewählt wird. Im umgekehrten Fall, wo alle gespeicherten Daten den Zeitpunkt 00:00:00 enthalten, ist es natürlich egal, wenn wir die kleiner oder gleichRelation mit oder ohne Zeitanteil ausführen. Wenn Sie also mit sysdate arbeiten und die von der Funktion gelieferten Ergebnisse dauerhaft speichern wollen, dann müssen Sie den darin enthaltenen Zeitwert eventuell loswerden, was Sie mit Hilfe der trunc-Funktion erledigen können. SQLWKS> select to_char(sysdate, 'DD.MM.YYYY HH24:MI:SS'), 2> to_char(trunc(sysdate,'DD'), 'DD.MM.YYYY HH24:MI:SS') 3> from dual 4> TO_CHAR(SYSDATE,'DD TO_CHAR(TRUNC(SYSDA ------------------------------------29.09.2000 09:30:30 29.09.2000 00:00:00 1 row selected.
Wie schon gesagt, ist die trunc-Funktion in der Lage ein Datumswert abzuschneiden, wobei die übergebene Zeichenfolge „DD“ dazu führt, dass die Funktion das Datum hinter dem Tag abschneidet, wobei man wissen muss, dass Oracle die Datumswerte in der Reihenfolge Jahr – Monat – Tag – Stunden – Minuten – Sekunden speichert. Konvertierungsfunktionen Mit der nächsten Gruppe erhalten Sie einen Überblick über die wichtigsten Funktionen, mit denen Sie einen Wert explizit von einem Datentyp in einen anderen Typ umwandeln können.
406
PL/SQL-Programmierung
Funktion
Beschreibung
chartorowid
Wie schon bei der Behandlung der Pseudospalte rowid gesagt, handelt es sich hierbei um einen hexadezimalen Wert, normalerweise ohne Probleme in eine Zeichenfolge oder von dieser in den Typ rowid überführt werden kann. Dennoch empfehle ich Ihnen in solchen Fällen die Verwendung der Funktion chartorowid, die den übergebenen String in einen Datensatzzeiger vom Typ rowid umwandelt, denn in dem Fall ist Ihr Programm resistent gegen eventuellen Änderungen der zugehörigen Regelwerke in zukünftigen Versionen.
rowidtochar
Hierbei handelt es sich um das Gegenstück zu chartorowid, d.h. diese Funktion wandelt den hexadezimalen Wert eines Datensatzzeigers in eine Zeichenfolge um.
to_char
Mit dieser Funktion wandeln Sie ein übergebenes Datum bzw. eine übergebene Zahl in eine Zeichenfolge um. Hierzu müssen Sie als zweiten Parameter einen Formatcode spezifizieren, mit dem Sie die Regeln für die Umwandlung festlegen. Eine Übersicht der möglichen Formate finden Sie in der Tabelle 5.9.
to_date
Als Gegenstück zur Funktion to_char wandelt to_date eine Zeichenfolge in einen Datumswert um. Auch hierbei gilt, dass Sie das zu verwendende Format als zweiten Parameter vorgeben müssen, wobei es sich auch diesmal um die gleichen Formatcodes der Tabelle 5.9 handelt.
to_number
Diese Funktion wandelt eine Zeichenfolge in einen numerischen Wert um, wobei Sie auch diesmal wieder die Formatcodes der Tabelle 5.9 als Umwandlungshilfe verwenden können.
Tabelle 5.8: Wichtige Funktionen zur Typkonvertierung
Zumindest in meinem Einsatzgebiet befinden sich die Konvertierfunktionen to_char, to_date und auch to_number, wenn auch nicht ganz so oft, im permanenten Einsatz. In der Regel sollten Sie alle diese Funktionen immer mit einem zweiten Parameter verwenden, mit dem Sie das bei der Konvertierung zu beachtende Format festlegen, denn ohne diesen zweiten Parameter können Sie die Funktionen auch ganz weglassen, da in dem Fall sowieso implizit konvertiert wird. Auf der anderen Seite muss man von der Vielzahl der verfügbaren Formatkürzel nicht unbedingt alle benutzen, so dass ich Ihnen in der Tabelle 5.9 einmal die wichtigsten Kürzel vorgestellt habe. Außerdem vertragen diese drei Funktionen auch noch einen dritten Parameter, mit dessen Hilfe Sie sogenannte nls-Parameter (nls = national language support) also Anweisungen zur nationalen Sprachunterstützung vorgeben können, was bei der Formatierung von Zahlen manchmal ganz hilfreich ist. Bei Datumswerten lautet die in unseren Breitengeraden sinnvolle nls-Anweisung 'NLS_DATE_LANGUAGE = German'
wohingegen man bei numerischen Werten wohl vor allem auf die korrekte Darstellung von Tausernder- und Dezimaltrennzeichen Wert legen dürfte, was Sie durch Verwendung des folgenden nls-Parameters erreichen: 'NLS_NUMERIC_CHARACTERS = '',.'''
Einführung in PL/SQL
407
Formatcode
Beschreibung
DD
Platzhalter für den Tag im Datum in zweistelliger Form also eventuell mit führender Null.
MM
Entsprechend „DD“ dient dieser Code als Platzhalter für den Monat im Wertebereich von 01 bis 12.
YYYY
Mittlerweile sollte es wohl üblich sein, vierstellige Jahreszahlen zu verwenden, weshalb der Y-Formatcode entsprechend häufig verwendet wird.
HH24
Code für die Darstellung der Stunden im 24-Stunden-Format.
MI
Platzhalter für die Minuten im Wertebereich von 00 bis 59.
SS
Platzhalter für die Sekunden ebenfalls im Wertebereich von 00 bis 59.
DDD
liefert bzw. steht für den Tag in Relation zum 01.01 des Jahres.
Day
Dieser Code repräsentiert den im Datum enthaltenen Tag in Klarschrift. So liefert „day“ für den 29.09.2000 den Wert „friday“, „Day“ dahingegen „Friday“ und „DAY“ letztendlich „FRIDAY“.
Dy
entspricht im Prinzip der Kodierung „Day“, wobei diesmal Kurztexte (ersten drei Buchstaben) geliefert bzw. erwartet werden. Ähnlich wie bei dem vorhergehenden Formatcode unterscheidet Oracle auch diesmal zwischen Großund Kleinschreibweise.
Month
liefert ähnlich wie „Day“ den im Datum enthaltenen Monat im Klartext. Genau wie bei der Tagesbezeichnung gilt auch hier wieder das Gesagte in Bezug auf die Groß- und Kleinschreibweise.
Mon
Dieser Code entspricht praktisch der Verwendung von „Dy“ auf Monatsbasis, d.h. hierdurch erhalten Sie ein dreistelliges Monatskürzel. Entsprechend der groß- bzw. kleingeschriebenen Anwendung erhalten Sie als Ergebnis klein, groß oder gemischt geschriebene Kürzel (jan, feb, mar, ..., dec).
0
Platzhalter für eine Ziffer.
9
Ebenfalls Platzhalter für eine Ziffer, wobei hierbei allerdings führende Nullen unterdrückt werden.
.
Platzhalter für den Dezimalpunkt.
D
Platzhalter für das Dezimaltrennzeichen entsprechend der nls-Einstellungen.
MI
Platzhalter für das Vorzeichen.
G
Platzhalter für ein Tausendertrennzeichen entsprechend der nls-Einstellung.
Tabelle 5.9: Übersicht wichtiger Formatcodes
Neben diesen Formatcodes können Sie innerhalb des zu verwendenden Formatstrings weitere beliebige Sonder- und Trennzeichen verwenden. Das ist zumindest so lange kein Problem, wie das von ihnen verwendete Zeichen nicht als Formatcode interpretiert wird. In dem Fall müssen Sie die verwendeten Zeichen in doppelten Anführungszeichen setzen, d.h. sofern Sie die Buchstabenfolge „dd“ nicht als Formatcode interpretiert wissen möchten, dann müssen Sie sie folgendermaßen verwenden: to_char(sysdate, '"dd"dd.mm.yyyy')
408
PL/SQL-Programmierung
Ich finde, mit diesen Formatcodes muss man einfach einmal spielen, was bei den Datumswerten unter zu Hilfenahme von sysdate auch prima möglich ist. Ein paar Anregungen zum Experimentieren mit der Formatierung von Datumswerten finden Sie im Listing 5.6 und ein Analoges Beispiel für numerische Werte steht Ihnen mit dem Beispiel 5.7 zur Verfügung. select to_char(sysdate, 'DD.MM.YYYY') Datum from dual; select to_char(sysdate, 'DD.MM.YYYY HH24:MM:SS') Datum from dual; select to_char(sysdate, '"DD"DD.MM.YYYY') Datum from dual; select to_char(sysdate, '"... heute ist "Day" der "dd." "Month" also der "ddd."Tag im Jahr "yyyy', 'NLS_DATE_LANGUAGE = German') Datum from dual; DATUM ---------29.09.2000 29.09.2000 10:09:00 DD29.09.2000 ... heute ist Freitag der 29. September also der 273.Tag im Jahr 2000 Listing 5.6: Beispiele für den Gebrauch der Formatcodes für Datumswerte
select to_char(-232323.22, '9999G990D99MI', 'NLS_NUMERIC_CHARACTERS = '',.''' ) from dual; select to_char(0.22, '9999G990D99MI', 'NLS_NUMERIC_CHARACTERS = '',.''' ) from dual; select to_char(0.22, '9999G990D99MI') from dual; select to_char(2345, '000000MI') from dual; select to_char(2345, '000000') from dual; TO_CHAR(.....
-----------232.323,220,22 0.22 002345 002345 Listing 5.7: Beispiele für die Formatierung von Zahlen
Gerade die letzten beiden Beispiele liefern noch einmal einen interessanten Randaspekt, wenn es beispielsweise darum geht, Ganzzahlen in entsprechende Zeichenfolgen umzuwandeln. Ohne weitere Formatierung erhalten Sie als erstes Zeichen immer einen Platzhalter für das Vorzeichen. Somit müssen Sie dieses entweder mit Hilfe des mi-Formatcodes ans Ende der Zahl verlagern oder die umgewandelte Zahl in einem zweiten Schritt mit Hilfe der ltrim-Funktion trimmen.
Einführung in PL/SQL
409
Sonstige Funktionen Damit wären bei der derjenigen Funktionsgruppe angekommen, die bei dem Versuch irgendetwas nach Kategorien zu ordnen meistens entsteht und alles aufnimmt, was in den anderen Gruppen nicht so richtig reinpasst. Funktion
Beschreibung
decode
ermöglicht die Vertextung bzw. Umsetzung eines Feldes mit Hilfe der in der Funktion verwendeten Schlüssel/Werteliste: decode(, wert1, text1, wert2, text2...)
greatest
liefert den größten der übergebenen Argumente zurück. Das erste Argument regelt dabei die Show, d.h. es legt fest in welches Datenformat alle anderen Argumente bei Bedarf konvertiert werden und welche Regeln bei der Ermittlung des größten Wertes gelten.
least
Hierbei handelt es sich quasi um das Gegenstück von greatest, d.h. mit Hilfe dieser Funktion erhalten Sie den kleinsten der übergebenen Argumente. Ansonsten gilt das zu greatest Gesagte auch hier ganz analog.
nvl
Mit Hilfe dieser Funktion können Sie den Wert null substituieren. Die Funktion benötigt hierzu zwei Parameter. Mit Hilfe des ersten übergeben Sie die zu überprüfende Spalte und als zweiten spezifizieren Sie den zu verwendenden Ersatzwert, sofern der erste Parameter den Wert null enthält. Am Ende der Tabelle finden Sie noch ein paar Hinweise zur Verwendung der nvl-Funktion.
uid
Diese Funktion liefert eine Ganzzahl, die den aktuellen Benutzer eindeutig identifiziert. Mit Hilfe dieses Wertes können Sie in vielen anderen Tabellen bzw. Views (z.B. dba_users.user_id oder v$session.user#) einsteigen.
user
Ähnlich zu uid liefert die user-Funktion die Kennung des aktuellen Benutzers.
userenv
Mit Hilfe dieser Funktion können Sie für den aktuellen Benutzer verschieden Einstellungen der Datenbanksitzung abfragen. Hierzu erhält die Funktion einen Textparameter, der die gewünschte Information spezifiziert. Bei diesen Codes ist aus meiner Sicht allerdings nur der Schlüssel „SESSIONID“ erwähnenswert, mit dem Sie in anderen Tabellen bzw. Views (z.B. v$session.audsid) Abfragen durchführen können.
Tabelle 5.10: Tabelle 5.10: Übersicht weiterer wichtiger Funktionen
Nun noch ein paar Worte zur nvl-Funktion. Deren Verwendung erspart einem oftmals die Konstruktion aufwendiger Berechnungen oder Abfragen. Sollen wir zum Beispiel alle aktiven Mitarbeiter selektieren, dann sind das diejenigen, deren Austritt entweder leer oder größer dem aktuellen Tagesdatum sind, was Sie folgendermaßen abfragen könnten: select persnr, lfdnr from bvs where austrt is null or austrt > sysdate;
410
PL/SQL-Programmierung
Hierdurch schleicht sich allerdings eine or-Verknüpfung in unsere Auswahlabfrage ein, was bei umfangreicheren Selektionskriterien entsprechend zu berücksichtigen ist. Die Verwendung der nvl-Funktion bietet einen eleganteren Lösungsansatz und führt zu folgender where-Klausel: where nvl(austrt, sysdate+1) > sysdate
In dem Fall werden nicht gefüllte Austritte einfach durch einen Wert ersetzt, der bei der anschließenden Selektion in der gewünschten Weise berücksichtigt wird.
5.1.5
Ausgabe von Meldungen
Mit Hilfe spezieller Funktionen sind Sie in der Lage, während der Ausführung Ihres PL/SQL-Programms spezielle Meldungen auszugeben, um beispielsweise auf besondere Situationen oder Fehler hinzuweisen. Dabei gibt es zur Ausgabe von Fehlernachrichten spezielle Kommandos, die neben der Meldung auch automatisch dafür sorgen, dass das aktuell laufende Programm abgebrochen wird, wohingegen die ausgegebenen Hinweise gesammelt und erst nach Beendigung des Programms bereitgestellt werden. Oft werden die Hinweismeldungen auch dazu benutzt, um eine gewisse Ablaufverfolgung des Programms durchzuführen. Zwar existieren hierzu auch spezielle Tools, die eventuell im Zusammenspiel mit der Client-Engine mehr Möglichkeiten bieten, jedoch sind solche Tools zum einen nicht immer verfügbar und zum anderen kennt man sich mit deren Umgang vielleicht auch nicht gut genug aus. Hinweise ausgeben Zur Aufbereitung und Ausgabe von Hinweisen bietet Oracle verschiedene Funktionen an, die Ihnen durch das Paket dbms_output bereitgestellt werden. Mit Hilfe dieses Pakets wird ein interner Puffer verwaltet, der am Ende Ihres Programms oder Skripts am Bildschirm ausgegeben werden kann. Das passiert allerdings nur, wenn Sie den Parameter serveroutput auf on gestellt haben, was Sie mit Hilfe der folgenden set-Anweisung durchführen können: set serveroutput on;
Diese Einstellung gilt für die aktive Datenbanksitzung oder solange, bis Sie diese Einstellung wieder auf den Ausgangswert off setzen. Die für die Erstellung von Meldungen wesentlichste Prozedur im dbms_output-Paket heißt put_line und kopiert die als Parameter übergebene Zeichenkette in diesen eben beschriebenen internen Puffer. begin dbms_output.put_line('Hallo, hier bin ich'); end;
Dabei fügt diese Prozedur genauer gesagt eine komplette Zeile in diesen Puffer, d.h. man kann sich diesen internen Puffer wie ein mehrzeiliges Textfeld vorstellen, das mit Hilfe der put_line-Funktion vollgeschrieben wird und wobei jede dieser Zeile
Einführung in PL/SQL
411
maximal 255 Zeichen lang ist. Dieses Textfeld bzw. der gesamte Puffer ist im Übrigen standardmäßig 2000 Byte groß und kann bei Bedarf mit Hilfe der Paket-Prozedur dbms_output.enable vergrößert werden. begin dbms_output.enable(20000); end;
Außerdem wird die Pufferung der erzeugten Hinweise hierdurch erneut eingeschaltet, sofern Sie dieses Verfahren zuvor durch Anwenden der dbms_output.disable-Prozedur abgeschaltet haben. Ein solches Abschalten hat allerdings auch zur Konsequenz, dass die bisher gepufferten Daten dabei verloren gehen, da der interne Puffer hierbei gelöscht wird. Soll keine ganze Zeile, sondern nur ein einzelner Text bzw. ein einzelnes Feld in den Ausgabepuffer kopiert werden, dann können Sie hierzu die put-Prozedur des Pakets verwenden und einen Zeilenwechsel vielleicht irgendwann durch das Aufrufen der new_line-Prozedur veranlassen. Das folgende Beispiel entspricht also der soeben durchgeführten Textausgabe, wobei die Ausgabe diesmal in zwei Schritten erfolgt und durch einen manuellen Zeilenwechsel abgeschlossen wird. Begin dbms_output.put('Hallo, '); dbms_output.put('hier bin ich'); dbms_output.new_line; end;
Das ist auch schon fast alles, was in dem dbms_output-Paket enthalten ist. Der Vollständigkeit halber möchte ich noch erwähnen, dass Sie in dem Paket neben den eben beschriebenen Prozeduren noch die beiden Objekte get_line und get_lines finden, mit denen Sie die in den Puffer geschriebenen Daten wieder auslesen können. Die Verwendung dieser beiden Prozeduren ist übrigens im Kopf des Pakets beschrieben, d.h. sofern Sie die beiden wirklich einmal verwenden müssen, dann finden Sie dort weiterhelfende Informationen. Ansonsten finden Sie eine Beschreibung des gesamten Pakets auch im Kapitel 12 des Buchs „Oracle8 Application Developer's Guide“ bzw. schlagen Sie im dortigen Index einfach „dbms_output“ nach, um die entsprechenden Textstellen zu finden. Meistens wird das dbms_output-Paket dazu verwendet, den Ablauf eines PL/SQLProgramms zu kontrollieren oder während der Ausführung den Wert bestimmter Variablen zu protokollieren. Dabei kann das Paket in allen PL/SQL-Objekten, also auch in Triggern, angewendet werden. Wenn Sie hierbei anschließend eine Transaktion auf die Tabelle ausführen, für die ein Trigger mit Hinweismeldungen angelegt wurde, dann erhalten Sie die erstellten Hinweise nach der Durchführung der Transaktion auf dem Bildschirm. Fehlermeldungen generieren Wenn Sie während der Programmausführung eine Fehlermeldung und einen damit verbundenen Programmabbruch erzeugen möchten, dann können Sie dies mit
412
PL/SQL-Programmierung
Hilfe der Prozedur raise_application_error erreichen. Diese Prozedur ist übrigens Bestandteil des Pakets dbms_standard, weshalb sie ohne Nennung des Paketnamens verwendet werden kann. Die Prozedur erhält üblicherweise zwei bis drei Parameter. Mit Hilfe des ersten Parameters müssen Sie eine Fehlernummer vorgeben, wobei hierfür der Nummernkreis von –20000 bis –20999 für anwendungsspezifische Fehler reserviert wurde. Der zweite Parameter enthält die bis zu 2048 Zeichen lange Fehlermeldung. Der dritte Parameter wird standardmäßig mit dem Wert false (falsch) vorbelegt und führt dazu, dass nur die von Ihnen ausgegebene Fehlernachricht ausgegeben wird. Verwenden Sie stattdessen den Wert true (wahr), dann werden auch eventuell vorhergehende Fehler angezeigt, was bei der Programmierung einer Fehlerbehandlungsroutine interessant sein kann. In diesem Workshop finden Sie bei der Beschreibung solcher Fehlerbehandlungsroutinen auch noch weitergehende Informationen zu raise_application_error. Das nun folgende kleine Beispiel (vgl. Listing 5.8) zeigt Ihnen die Anwendung der raise_application_error-Routine mitsamt den Ausgaben, die hierbei am Bildschirm des SQL-Editors erscheinen. Mit Hilfe des dbms_out-Pakets dokumentieren wir dabei, dass unser Programm nach Ausgabe der Meldung wirklich abbricht, denn die kurz vor Programmende erzeugte Hinweismeldung wird nicht ausgegeben. Begin dbms_output.put_line('Anfang'); raise_application_error(-20000,'Keine Lust zum Arbeiten'); dbms_output.put_line('Ende'); end; ORA-20000: Keine Lust zum Arbeiten ORA-06512: at line 3 Anfang Listing 5.8: Verwenden von raise_application_error
5.1.6
Konditionalbedingungen
Nur selten läuft ein Programm geradlinig von oben nach unten bzw. von seinem Anfang bis zu seinem Ende durch. In der Regel sind bestimmte Programmbefehle an besondere Bedingungen gebunden, so dass zur Laufzeit entschieden wird, ob spezielle Befehle oder vielleicht sogar Unterprogramme aufgerufen werden oder nicht. Die Programmierung solcher Bedingungen erfolgt in PL/SQL genau wie in den meisten anderen Programmiersprachen mit Hilfe der if-Anweisung, dabei entspricht die einfache Form dieser Anweisung dem folgenden Schema: if then [else ] end if;
Einführung in PL/SQL
413
Ist die hinter dem if-Befehl spezifizierte Bedingung erfüllt, so werden die hinter dem Schlüsselwort then kodierten Anweisungen ausgeführt und anschließend wird die Programmausführung mit den Anweisungen fortgesetzt, die der end if-Anweisung folgen. Ist die Bedingung nicht erfüllt, dann verzweigt das Programm zu den Anweisungen, die der else-Klausel folgen, sofern solche Anweisungen überhaupt vorhanden sind, denn die Kodierung eines else-Zweigs ist optional. Wie Sie dem Schema allerdings richtig entnehmen können, ist die Vorgabe der then-Anweisungen im Unterschied zu einigen anderen Programmiersprachen allerdings nicht wahlfrei. Das erkennt man im Übrigen schon daran, dass hinter dem then genau wie hinter dem else kein Semikolon steht, d.h. sowohl if als auch else alleine bilden keine vollständige Anweisung, denn diese werden ja bekanntlich mit einem Semikolon beendet. Ansonsten gibt es allerdings kaum besondere Restriktionen. Die if-Anweisungen können im Programm beispielsweise beliebig geschachtelt werden. Das geht prinzipiell so lange, bis Sie die Übersicht verlieren, d.h. hier bilden wir und nicht das System die Grenzen des Möglichen. Formal müssen Sie dabei nur beachten, dass jedem if irgendwann einmal das zugehörige end if folgen muss. Was die mit der if-Anweisung verbundene Ausführungsbedingung angeht, so gilt hier im Prinzip das Gleiche, was Sie schon bei den Abfragen bei der Beschreibung der where-Klausel gelesen haben bzw. dort nachlesen können. So eine Bedingung besteht also wieder aus einem oder mehreren Ausdrücken, die jeweils mit Hilfe der verfügbaren Vergleichsoperatoren (vgl. Kapitel 3.1.2) verglichen und wo mehrere solcher Vergleichsbedingungen mit Hilfe der vorhandenen Verknüpfungsoperatoren (vgl. Tabelle 3.1) verbunden werden. if a > b then; dbms_output.put_line('then-Zweig'); else dbms_output.put_line('else-Zweig'); if b > c then dbms_output.put_line('then-2-Zweig'); end if; end if;
Oft hat man beim Programmdesign das Problem, dass der else-Teil einer if-Bedingung nicht unbedingt immer ausgeführt werden soll, sondern selbst wiederum von einer weiteren, also geschachtelten Unterbedingung abhängig ist. Für solche Fälle hat sich in den letzten Jahren in vielen Programmiersprachen eine besondere Unterform der if-Abfrage entwickelt, die dem folgenden Schema entspricht: if then elsif then elsif then [else
414
PL/SQL-Programmierung
] end if;
Ist die erste if-Bedingung nicht erfüllt, dann wird die nächstfolgende elsif-Bedingung untersucht, der ggf. die dritte und vierte usw. Bedingung folgt. Wenn keine der vorhandenen Bedingungen erfülltl ist und damit zur Ausführung der zugehörigen Anweisungen führt, dann kommen die im else-Zweig kodierten Programmschritte zur ausführen, was natürlich nur geht, wenn eine solcher Zweig überhaupt vorhanden ist. Sprungmarken Zusammen mit den if-Anweisungen kommen zumindest mir immer noch sofort die Sprungmarken in den Sinn, was nicht daran liegt, dass ich sie so oft verwende, sondern früher, so vor ungefähr 60 Jahren, da bildeten if-Befehl und Sprungbefehl irgendwie eine Einheit, was heutzutage wegen der vielen anderen und mächtigen Konstruktionsmöglichkeiten sicherlich nicht mehr gilt. Dennoch gibt es den Befehl immer noch in nahezu allen Programmiersprachen und diejenigen Sprachen, die von sich behaupten, dass sie frei von Sprüngen wären, haben das nur dadurch erreicht, indem sie spezielle Sprungbefehle einfach verschleiern, denn ein exit ist im Prinzip auch nichts anderes als der Sprung zu einer vordefinierten Marke hinter der zugehörigen Schleife. Doch lassen wir diesen Glaubenskrieg links liegen und wenden uns wieder dem eigentlichen Thema zu. In Ihren PL/SQL-Programmen haben Sie die Möglichkeit, Sprungmarken zu setzen. Eine solche Sprungmarke besteht im Prinzip aus einem beliebigen Wort, einer Art Überschrift, mit der die zugehörige Programmstelle gekennzeichnet wird. Eine solche Sprungmarke wird in PL/SQL von doppelten Kleiner- und Größerzeichen eingeschlossen und besteht ansonsten im Regelfall nur aus Buchstaben und Ziffern. <<Sprungmarke>>
Wenn Sie außerdem noch andere besondere Zeichen (Minus, Leerzeichen usw.) innerhalb einer Sprungmarke verwenden wollen, dann müssen Sie diese insgesamt mit doppelten Anführungszeichen einschließen. <<"Sprungmarke 2-23">>
Eine solche Sprungmarke können Sie in Ihrem Programm mit Hilfe der goto-Anweisung ansteuern, indem Sie den Befehl zusammen mit der gewünschten Sprungmarke verwenden. Hierbei müssen Sie allerdings folgende Einschränkungen beachten: 1. Sie dürfen nicht in eine if-then-else-Anweisung hineinspringen, was im Übrigen auch für Sprünge zwischen den einzelnen Zweigen gilt, denn die sind ebenfalls verboten. 2. Sie dürfen nicht in tieferliegende Programmblöcke springen. 3. Sie dürfen eine Prozedur oder Funktion nicht per goto-Anweisung verlassen. 4. Sie dürfen aus einer Fehlerbehandlungsroutine nicht in den zugehörigen Block zurückspringen.
Einführung in PL/SQL
415
Ich weiß nicht, wie es Ihnen geht, aber ich empfinde keine der vier genannten Einschränkungen in irgendeiner Weise restriktiv. Wie schon gesagt, benötigt man den Befehl in der heutigen Zeit aufgrund der vielen anderen Möglichkeiten sowieso nur noch sehr sehr selten. Trotzdem habe ich Ihnen im Beispiel 5.9 eines meiner Lieblingsbeispiele serviert, indem Sie die „sinnvolle“ Anwendung des gotoBefehls demonstriert bekommen. begin goto anfang; <<"schritt-4">> dbms_output.put_line('Schritt-4'); goto ende; <<"schritt-3">> dbms_output.put_line('Schritt-3'); goto "schritt-4"; <<"schritt-2">> dbms_output.put_line('Schritt-2'); goto "schritt-3"; <> dbms_output.put_line('Anfang'); goto "schritt-2"; <<ende>> dbms_output.put_line('Ende'); end; Statement processed. Anfang Schritt-2 Schritt-3 Schritt-4 Ende Listing 5.9: Anwendung des goto-Befehls
5.1.7
Schleifen
Wie schon bei der Einleitung zu den Konditionalbedingungen gesagt, läuft ein Programm nur selten sequentiell vom Start zum Programmende, sondern manche Programmteile werden nur unter bestimmten Bedingungen ausgeführt, wohingegen andere Teile häufiger wiederholt werden müssen. Solche Wiederholungen bzw. Schleifen können auch in PL/SQL mit Hilfe entsprechender Anweisungen programmiert werden.
416
PL/SQL-Programmierung
Je nach verwendeter Programmiersprache gibt es immer verschiedene Varianten, um diese Schleifen zu programmieren. Unter PL/SQL stehen Ihnen die im Folgenden beschriebenen Schleifen-Konstruktionen zur Verfügung, wobei Sie ein kleines Anwendungsbeispiel ganz am Ende des Kapitels im Listing 5.10 finden:
X
while loop-Schleife Bei der while loop-Schleife werden die in der Schleife programmierten Anweisung so lange ausgeführt, wie die hinter der while-Anweisung kodierte Bedingung erfüllt ist. Ist diese Bedingung schon beim Erreichen der Schleife nicht mehr erfüllt, dann wird die Schleife folglich gar nicht durchlaufen. Die Programmierung einer while loop-Schleife entspricht dem folgenden Schema: while loop end loop;
X
loop-Schleife Bei der loop-Schleife handelt es sich um eine Sonderform der soeben beschriebenen while loop-Schleife. Wie Sie schon an der Begriffsführung erkennen können, fehlt hierbei die while-Klausel und damit die Abbruchbedingung der Schleife, d.h. loop-Schleifen laufen zunächst einmal endlos lange. Damit das in der Praxis dann doch nicht passiert, denn Endlosschleifen gelten nach neusten wissenschaftlichen Erkenntnissen als Fehler, gibt es die exit-Anweisung, mit der Sie die Schleife verlassen können, so dass die Verwendung der loop-Schleife nach etwa folgendem Schema erfolgt: loop if then exit; end if; end loop;
X
for loop-Schleife Bei der for loop-Schleife, die manchmal auch einfach Zählschleife genannt wird, legen Sie von vornherein explizit fest, wie oft die Schleife durchlaufen wird, indem eine Laufvariable entsprechend eines vorgegebenen Intervalls variiert wird, bis die hintere Intervallgrenze erreicht wird. Die Verwendung einer for loopSchleife sieht in etwa folgendermaßen aus: for in [reverse] loop end loop;
Normalerweise wird das vorgegebene Intervall in aufsteigender Reihenfolge durchlaufen, d.h. die Laufvariable nimmt mit jedem Schleifendurchlauf den nächsten Wert des Intervalls an.
Einführung in PL/SQL
417
exit-Anweisung Zum vorzeitigen Verlassen einer Schleife existiert in PL/SQL die exit-Anweisung. Es dürfte nur wenig Anforderungen geben die dazu führen, dass der exit-Befehl direkt verwendet wird, d.h. in aller Regel wird er innerhalb einer if-Anweisung verwendet, mit der die besondere Ausstiegsbedingung für die Schleife festgelegt wird. Aus diesem Grund gibt es für die exit-Anweisung auch eine weitere Variante, in der Sie die Abbruchbedingung mit Hilfe des Schlüsselwortes when direkt zusammen mit dem exit-Befehl Programmieren können. exit when
Schleifen benennen In bestimmten Ausnahmesituationen kann es sinnvoll sein, den einzelnen Schleifen eine Markierung in Form einer gewöhnlichen Sprungmarke zu geben, die direkt vor dem Anfang der Schleife gesetzt werden muss. Optional können Sie den verwendeten Sprungnamen auch noch einmal an das Ende der Schleife direkt hinter der end loop-Anweisung stellen: <<Schleife1>> loop ... end loop Schleife1;
Aus meiner Sicht sind solche Benennungen nur in bestimmten Ausnahmesituationen sinnvoll, denn hierdurch haben Sie bei geschachtelten Schleifen die Möglichkeit, mit Hilfe einer weiteren exit-Variante gezielt aus mehreren Schleifen herauszuspringen. Das folgende Beispiel demonstriert diese Möglichkeit, indem mit Hilfe der exit-Anweisung als allen Schleifen herausgesprungen wird: <<Schleife1>> loop loop exit schleife1 when … end loop; end loop;
Anwendungsbeispiel declare i integer := 0; j integer := 0; k integer := 0; begin dbms_output.enable(50000); loop i:=i+1;
418
PL/SQL-Programmierung
j:= 1; while j < 10 loop for k in reverse 1..10 loop dbms_output.put('Schleife=> '); dbms_output.put(to_char(i, '99.')); dbms_output.put(to_char(j, '99.')); dbms_output.put(to_char(k, '99')); dbms_output.new_line; end loop; j:=j+1; end loop; exit when i=10; end loop; end; Listing 5.10: Anwendungsbeispiel zur Schleifenverwendung
5.1.8
Datenbankabfragen
Innerhalb eines PL/SQL-Programms können Sie zu jedem beliebigen Zeitpunkt irgendwelche Datenbankaktionen ausführen. Dabei können Sie beispielsweise innerhalb der where-Bedingung auf alle verfügbaren Variablen Bezug nehmen, so dass auf diese Weise dynamische SQL-Kommandos entstehen: update personalien set name = xname where persnr = xpnr;
In diesem Beispiel wird der Name für einen speziellen Mitarbeiter geändert, wobei der neue Name bzw. die zu ändernde Personalnummer erst zur Laufzeit durch die Werte in den Variablen xname bzw. xpnr feststehen. Wesentlich interessanter als die Beschreibung solcher parametrisierten Änderungsabfragen ist jedoch die Fragestellung, wie innerhalb eines PL/SQL-Programms irgendwelche Tabellen der Datenbank gelesen werden können. Die einfachste Variante, bestimmte Felder aus der Datenbank in einem PL/SQL-Programm zu lesen besteht darin, eine gewöhnliche Auswahlabfrage zu kodieren, und die selektierten Felder mit Hilfe der into-Klausel in entsprechende Variablen oder eine geeignete Struktur zu kopieren. declare xpersnr personalien.persnr%type; xname personalien.name%type; begin select persnr, name into xpersnr, xname
Einführung in PL/SQL
419
from personalien where persnr = '7000188'; dbms_output.put_line(xname); end;
Allerdings führt diese Abfragetechnik zu einem Laufzeitfehler, wenn die eingesetzte Abfrage nicht genau einen Datensatz zurückliefert. Positiv ausgedrückt gibt es also Probleme, wenn Ihre Abfrage keinen oder mehrere Datensätze liefert. Für dieses Problem gibt es genau genommen drei verschiedene Lösungsansätze: 1. Sie verwenden anstelle einer solchen Abfrage einen Cursor. Das ist natürlich wesentlich aufwändiger, denn wie Sie schon gesehen haben bzw. gleich noch einmal sehen werden, müssen Sie den Cursor nicht nur definieren, sondern auch mit Hilfe spezieller Befehle verarbeiten. Dafür ist die ganze Anwendung dann allerdings fehlertolerant, denn sofern der Cursor keinen Datensatz liefert erkennen Sie das mit Hilfe der entsprechenden Statusanzeige %notfound und ansonsten wird der Cursor nach dem Lesen des ersten Satzes wieder geschlossen, so dass auch die Anlieferung mehrerer Datensätze kein Problem mehr darstellt. 2. Die zweite Möglichkeit besteht darin, die Abfrage in einem speziellen Block zu kapseln und den Block mit einer geeigneten Fehlerbehandlungsroutine auszustatten. Natürlich führt auch das zu einem gewissen Mehraufwand in Form entsprechender Programmlogik, so dass auch diese Lösung eigentlich nicht so recht überzeugen kann. 3. Die einfachste Variante zur Lösung des Problems besteht in der Anwendung eines kleinen Tricks. Wie Sie sich vielleicht erinnern, hatte die Verwendung von Aggregatfunktionen in einer Abfrage die Nebenwirkung, dass die Abfrage hierdurch immer Ergebnisse zurückliefert. Wenn wir die benötigten Spalten also mit Hilfe der max-Funktion abfragen, dann ist sichergestellt, dass wir immer genau einen Wert erhalten und wenn die Abfrage gar keinen Datensatz selektiert, dann erhalten wir aufgrund der max-Funktion immerhin noch den Wert null. Die dritte Variante, scheint auf den ersten Blick die einfachste Lösung zu bieten, was sich bestätigt, wenn man sich das geänderte Programm einmal anschaut: declare xpersnr personalien.persnr%type; xname personalien.name%type; begin select max(persnr), max(name) into xpersnr, xname from personalien where persnr = '7000188'; dbms_output.put_line(xname); end;
420
PL/SQL-Programmierung
Verwenden von Cursorn Sofern Sie in Ihrem Programm nicht nur einen einzelnen Datensatz benötigen, sondern die aus einer Abfrage resultierenden Datenmenge verarbeiten müssen, dann ist es notwendig hierfür einen entsprechenden Cursor zu definieren. In diesem Workshop (vgl. Kapitel 3.3.5) haben Sie schon den Cursor als Instrument zum Durcharbeiten einer Datenmenge kennen gelernt. Im Prinzip handelt es sich bei einem solchen Cursor um ein Objekt, das es Ihnen ermöglicht in einem Programm eine spezielle Abfrage auszuführen und die zugehörigen Datensatz im Rahmen einer Schleife einzeln zu empfangen bzw. zu verarbeiten. Hierzu müssen Sie den Cursor im Deklarationsteil des Programms bzw. des zugehörigen Blocks zunächst einmal definieren. Hierbei wird neben dem Namen des Cursors auch die damit verbundene Abfrage festgelegt. Das folgende Beispiel erzeugt den Cursor read_pers, mit dem zwei Felder der Tabelle personalien gelesen werden. declare cursor read_pers is select persnr, name from personalien order by name;
Innerhalb des Programms können Sie den Cursor bei Bedarf öffnen, so dass die damit verbundene Abfrage ausgeführt wird. Anschließend haben Sie die Möglichkeit, die einzelnen Datensätze der Reihe nach abzufordern. Wird der Cursor nicht mehr benötigt, bzw. wurden alle Datensätze gelesen, dann sollte er innerhalb des Programms wieder geschlossen werden, was auch zu einer Freigabe der an den Cursor gebundenen Ressourcen führt. Dabei erfolgt das Öffnen eines Cursors erfolgt mit Hilfe der open-Anweisung. Für das Abrufen der einzelnen Datensätze existiert die Anweisung fetch und geschlossen wird der Cursor mit Hilfe eines close-Befehls. In dem folgenden Beispiel 5.11 finden Sie ein kleines Beispiel für die Verwendung eines Cursors, indem die Personalien der Reihe nach gelesen und die jeweiligen Namen ausgegeben werden. declare cursor read_pers is select persnr, name from personalien; xpersnr personalien.persnr%type; xname personalien.name%type not null := ' '; begin open read_pers; loop fetch read_pers into xpersnr, xname; exit when read_pers%notfound; dbms_output.put_line(xname); end loop; end; Listing 5.11: Datensätze mit Hilfe eines Cursors verarbeiten
Einführung in PL/SQL
421
Wie Sie an dem Beispiel 5.11 sehen können, erfolgt die Verarbeitung der selektierten Datensätze mit Hilfe einer loop-Schleife. Damit diese Endlosschleife nicht bis zum jüngsten Tag läuft, programmieren wir mit Hilfe der exit-Anweisung eine geeignete Abbruchbedingung, indem wir die Statusvariable notfound des Cursors abfragen. Diese enthält nämlich genau dann den Wert true, wenn der vorherige fetch-Befehl wegen des erreichten Cursorendes keinen Datensatz mehr geliefert hat. Insgesamt stehen Ihnen bei einem Cursor die in der Tabelle 5.11 gezeigten Statusvariablen zur Verfügung. Statusvariable
Beschreibung
%found
ist true, wenn der vorherige fetch einen Datensatz geliefert hat.
%isopen
enthält den Wert true, wenn der Cursor geöffnet wurde.
%notfound
entspricht „not %found“ und ist damit nur dann wahr, wenn der vorherige fetch-Versuch nicht erfolgreich war.
%rowcount
zählt alle gelesenen Datensätze, d.h. der Wert dieser Statusvariablen erhöht sich mit jedem fetch-Befehl.
Tabelle 5.11: Beschreibung der Statusvariablen eines Cursors
for-Schleifen für Cursor Zum Abarbeiten der vom Cursor gelieferten Datensätze existiert im PL/SQL-Sprachumfang eine spezielle Variante der for-Schleife, mit der Sie die gelieferten Datensatz vollständig durchlaufen können. Dabei führt diese spezielle Schleife auch implizit die open- und close-Befehle für den Cursor aus, so dass sich unser letzten Beispiel noch einmal deutlich verkürzen lässt (vgl. Listing 5.12). declare cursor read_pers is select persnr, name from personalien; begin for xrec in read_pers loop dbms_output.put_line(xrec.name); end loop; end; Listing 5.12: Anwenden eines Cursors zusammen mit der speziellen for-Schleife
Wenn Sie dieses Beispiel mit der vorhergehenden Definition der for loop-Schleife vergleichen, dann werden Sie vor allem folgende Unterschiede erkennen:
X
X
Anstelle des vorgegebenen Schleifenintervalls tritt jetzt einfach der Name des Cursors, d.h. das Intervall heißt ganz einfach vom ersten bis zum letzten Datensatz. Die Laufvariable der Schleife, die ich in unserem Beispiel xrec genannt habe, wird vom Programm automatisch angelegt. Hierbei handelt es sich um eine Struktur, die Sie auch manuell folgendermaßen definieren könnten:
422
PL/SQL-Programmierung
xrec read_pers%rowtype;
Um das noch einmal deutlich zu sagen: Die for-Schleife verwendet beim Cursor immer eine Laufvariable, die einer Struktur entspricht und die bei Bedarf automatisch angelegt wird. Letzteres können Sie aber auch durch eine eigene Deklaration vorwegnehmen. Ansonsten müssen Sie bei dieser Art der Verarbeitung eigentlich nur noch beachten, dass der Cursor beim Einstieg in die Schleife nicht geöffnet sein darf, da das ansonsten zu einem Laufzeitfehler führt. Parameter in Cursorn verwenden Die bisher in unserem Cursor verwendete Abfrage war stets statisch und besaß keine laufzeitabhängige Komponente. Eine solche können Sie natürlich ähnlich wie bei der zuvor gezeigten einfachen Abfrage erzeugen, indem Sie innerhalb der Abfrage einfach andere Variablen verwenden. declare xpersnr personalien.persnr%type; cursor read_pers is select name from personalien where persnr = xpersnr;
Wird dieser Cursor während der Programmausführung geöffnet, dann richtet sich das Abfrageergebnis nach dem aktuellen Wert der Variablen xpersnr. So weit so gut, und trotzdem hat das soeben beschriebene Verfahren entscheidende Nachteile. Zum einen müssen die beim Cursor verwendeten Variablen alle vorab definiert werden, d.h. schon im Deklarationsteil Ihres Programms entstehen nicht immer angenehme Abhängigkeiten. Zum anderen verdient dieses Verfahren auch nicht gerade einen Übersichtspreis. Wird der Cursor beispielsweise einige hundert Zeilen später verwendet, dann ist nirgendwo ersichtlich, ob und mit welchen Parametern die zugehörige Abfrage ausgeführt wird. Besser wäre es, die benötigten Variablen beim Starten des Cursors als sichtbare Parameter zu übergeben, wozu es in PL/SQL ein entsprechendes Verfahren (vgl. Listing 5.13) gibt. declare cursor read_pers(xpersnr varchar2) is select name from personalien where persnr = xpersnr; begin for xrec in read_pers('7000188') loop dbms_output.put_line(xrec.name); end loop; end; Listing 5.13: Parameterübergabe an einen Cursor
Einführung in PL/SQL
423
Ich finde, dass der ganze Aufbau jetzt stark an eine Funktion erinnert. Bei der Definition des Cursors werden alle benötigten Variablen zusammen mit ihrem Datentyp hinter dem Cursornamen in Klammern aufgelistet und beim Starten bzw. Öffnen des Cursors werden dann alle konkreten Werte wie bei einem Funktionsaufruf in der entsprechenden Reihenfolge übergeben. Dynamisches SQL Die zuletzt verwendeten Abfragen waren zum Schluss zwar alle in einem gewissen Umfang variabel, doch verdient keine der bisherigen Beispiele die Bezeichnung „dynamisches SQL“. Hierunter versteht man im Allgemeinen etwas ganz anderes, nämlich die Möglichkeit die gesamte SQL-Abfrage erst zur Laufzeit zusammenzubauen und anschließend auszuführen. Nehmen wir als Aufgabenstellung einmal an, wir möchten von Zeit zu Zeit alle Indices unseres Schemas reorganisieren. Grundsätzlich ist so eine Indexreorganisation mit Hilfe einer Anweisung gemäß dem folgenden Schema möglich: alter index rebuild;
Außerdem erhalten Sie alle im Schema enthaltenen Indices durch eine Abfrage der View user_indexes, doch wie kann man diese Informationen zusammen mit der jeweils entsprechenden alter index-Anweisung dynamisch kombinieren. Wenn man es nicht besser weiß, dann kann man sich natürlich mit Hilfe einer entsprechenden Anweisung die zur Reorganisation benötigten Statements erzeugen und das hierdurch ausgegebene Skript in einem zweiten Schritt ausführen. select 'alter index ' || index_name || ' rebuild;' from user_indexes
Da PL/SQL Ihnen aber die Möglichkeit bietet, zur Laufzeit generierte SQL-Kommandos auszuführen, können wir diese beiden Schritte auch in einem verschmelzen und uns hierfür ein entsprechendes Skript basteln. Die Basis für die Verwendung dynamischer SQL-Abfragen finden Sie im Paket dbms_sql, das in der Oracle-Dokumentation wieder im Buch „Oracle8 Application Developer's Guide“ beschrieben ist. Wenn wir zunächst einmal von Auswahlabfragen absehen, dann folgt die Verwendung dynamischer SQL-Statements dem folgenden Schema:
X X X X
Öffnen eines Ausführungskanals mit Hilfe der Funktion open_cursor. Prüfen des auszuführenden Statements mit Hilfe der Prozedur parse. Beachten Sie, dass DDL-Statements hierbei auch sofort ausgeführt werden. Auf der anderen Seite schadet es aber auch nicht, wenn Sie jedes Mal eine execute-Funktion verwenden, da das geparste DDL-Statement nicht noch einmal ausgeführt wird. Ausführen der Abfrage durch einen Aufruf der execute-Funktion. Schließen des Ausführungskanals durch einen Aufruf der Prozedur close_cursor.
424
PL/SQL-Programmierung
Die vollständige Lösung der oben beschriebenen Aufgabe finden Sie im Listing 5.14. Zunächst einmal definieren wir in diesem Programm den Cursor meine_indexe entsprechend der eben dargestellten Abfrage auf die View user_indexes. Des weiteren benötigen wir im Programm noch zwei weitere Integervariablen sql_id und ret. Zentralstelle des Programms ist die for loop-Schleife, in der alle vom Cursor gelieferten Indexnamen der Reihe nach geliefert und mit Hilfe der Variablen mi_rec. index_name zur Verfügung gestellt werden. declare cursor meine_indexe is select index_name from user_indexes; sql_id integer; ret integer; begin sql_id := dbms_sql.open_cursor; for mi_rec in meine_indexe loop dbms_sql.parse(sql_id, 'alter index ' || mi_rec.index_name || ' rebuild', dbms_sql.native); -- wegen des DDL-Befehls ist ein execute -- nicht unbedingt notwendig -- ret := dbms_sql.execute(sql_id); end loop; dbms_sql.close_cursor(sql_id); end; Listing 5.14: Reorganisation aller Indices mit Hilfe dynamischer SQL-Anweisungen
Vor dem Einstieg in die Schleife eröffnen wir einen dynamischen SQL-Cursor, indem wir die Funktion open_cursor aufrufen. sql_id := dbms_sql.open_cursor;
Den von dieser Funktion zurückgegebenen Wert speichern wir in einer Variablen, da er für alle weiteren dynamischen Aktivitäten benötigt wird. Innerhalb der Schleife wird das auszuführende Statement zunächst geprüft, indem es der parseProzedur übergeben wird, was prinzipiell nach folgendem Schema geschieht: dbms_sql.parse(, <Statement>, <Spracheinstellung>)
Wie Sie sehen, benötigt diese Prozedur insgesamt drei Parameter. Der erste dieser drei Parameter spezifiziert dabei den zuvor geöffneten Cursor, d.h. hier müssen Sie den von der open_cursor-Funktion gelieferten Wert übergeben. Der zweite Parame-
Einführung in PL/SQL
425
ter enthält die auszuführende Abfrage und mit dem letzten Parameter legen Sie die bei der Prüfung zu verwendende PL/SQL-Sprachversion fest. Hierzu existieren im dbms_sql-Paket drei vordefinierte Werte, die Sie der Tabelle 5.12 entnehmen können. Konstante
Wert
Bedeutung
V6
0
Interpretation des Statements entsprechend der Version 6.
V7
2
Verwenden der Version 7.
Native
1
Aktuelle Version zur Statementanalyse benutzen.
Tabelle 5.12: Sprachoptionen der dbms_sql.parse
Nach der erfolgreichen Analyse des auszuführenden Statements kann dieses mit Hilfe der execute-Funktion ausgeführt werden, die hierzu lediglich die Kennung des zugehörigen Cursors benötigt. Der von dieser Funktion zurückgegebene Wert enthält bei Änderungsabfragen die Anzahl der von der Abfrage betroffenen Datensätze und bei Auswahlabfragen ist dieser Wert ohne Bedeutung. ret := dbms_sql.execute(sql_id);
Zum Schluss müssen Sie den Cursor wieder schließen, was diesmal mit Hilfe der close_cursor-Prozedur passiert, der Sie zu diesem Zwecke wieder die entsprechende Cursorkennung übergeben müssen. dbms_sql.close_cursor(sql_id);
Wie Sie an dem letzten Beispiel gesehen haben, war es gar nicht so schwer, mit Hilfe des dbms_sql-Pakets ein intelligentes Skript zu erstellen, das bestimmte Wartungsarbeiten durchführt. Solche und ähnliche Arbeiten lassen sich in der Praxis wirklich prima mit Hilfe dynamischer SQL-Statements erledigen, weshalb Routinen in der eben beschriebenen Form häufig anzutreffen sind. Allerdings ist der Leistungsumfang des dbms_sql-Pakets noch wesentlich größer, denn neben dem Ausführen von DDL- oder DML-Anweisungen lassen sich auch Auswahlabfragen dynamisch erzeugen und ausführen. Dynamische Auswahlabfragen erstellen Wie Sie gleich sehen werden, ist die Erstellung dynamischer Auswahlabfragen ein ganz schönes Stück Tipparbeit. Mit Hilfe einer dynamischen Auswahlabfrage wollen wir verschiedene Felder aus den Personalien und den zugehörigen Gehältern aus der Datenbank lesen und diese am Bildschirm ausgeben. Die dabei verwendete Abfrage beinhaltet also eine Verknüpfung zwischen den Tabellen personalien und gehalt, wobei die Gehaltsdaten zusätzlich auf den aktuellsten Datensatz eingeschränkt werden sollen. Wir haben es in unserem Beispiel konkret also mit folgender Abfrage zu tun: select a.persnr, a.name, a.gebdatum, b.gehalt from personalien a, gehalt b where b.persnr = a.persnr and b.gab = (select max(gab)
426
PL/SQL-Programmierung
from gehalt b1 where b1.persnr = b.persnr ) order by a.name
Das komplette Programm finden Sie im Listing 5.15, das wir im Anschluss mal wieder Stück für Stück auseinandernehmen werden und selbst wenn Sie dynamische Auswahlabfragen in der Regel nur sehr selten benötigen, so ist das Beispiel doch wenigstens dazu geeignet aufzuzeigen, wie einfach doch die Verwendung eines deklarierten Cursors ist. declare dyn_sql integer; sql_st varchar2(500); rc integer; xpersnr xname xgeburt xgehalt
varchar2(10); varchar2(30); date; number;
begin dbms_output.enable(50000); dyn_sql := dbms_sql.open_cursor; sql_st := 'select a.persnr, a.name, a.gebdatum, b.gehalt from personalien a, gehalt b where b.persnr = a.persnr and b.gab = (select max(gab) from gehalt b1 where b1.persnr = b.persnr ) order by a.name'; dbms_sql.parse(dyn_sql, sql_st, dbms_sql.native); dbms_sql.define_column(dyn_sql, dbms_sql.define_column(dyn_sql, dbms_sql.define_column(dyn_sql, dbms_sql.define_column(dyn_sql,
1, 2, 3, 4,
xpersnr, 10); xname, 30); xgeburt); xgehalt);
rc := dbms_sql.execute_and_fetch(dyn_sql); while rc > 0 loop dbms_sql.column_value(dyn_sql, 1, xpersnr); dbms_sql.column_value(dyn_sql, 2, xname); dbms_sql.column_value(dyn_sql, 3, xgeburt); dbms_sql.column_value(dyn_sql, 4, xgehalt);
Einführung in PL/SQL
427
dbms_output.put(rpad(xpersnr,10,' ') || ' '); dbms_output.put(rpad(xname,30,' ') || ' '); dbms_output.put(to_char(xgeburt, 'DD.MM.YYYY') || ' '); dbms_output.put(to_char(xgehalt,'999990.00' )); dbms_output.new_line; rc := dbms_sql.fetch_rows(dyn_sql); end loop; dbms_output.put_line('Anzahl Sätze: ' || to_char(dbms_sql.last_row_count,'9999')); dbms_sql.close_cursor(dyn_sql); end; Listing 5.15: Beispiel einer dynamischen Auswahlabfrage
Eigentlich beginnt alles genau wie bei den dynamischen Änderungsabfragen, d.h. zunächst erzeugen wir uns wieder einen dynamischen Cursor, dessen Kennung wir in der Variablen dyn_sql speichern. Die auszuführende Abfrage konstruieren wir in der Variablen sql_st und übergeben sie anschließend der Prozedur parse zur Überprüfung und Würdigung. Erst was danach kommt ist im Vergleich zum vorhergehenden Beispiel wirklich neu. dbms_sql.define_column(dyn_sql, 1, xpersnr, 10); dbms_sql.define_column(dyn_sql, 3, xgeburt);
Mit Hilfe der define_column-Prozedur müssen Sie diejenigen Spalten der Abfrage definieren, die Sie nach dem Lesen der einzelnen Datensätze in entsprechende Variablen übernehmen wollen. Dank der Überlagerungstechnik können dabei nahezu alle Datentypen mit Hilfe der gleichen Prozedur definiert werden, die dafür drei bis vier Parameter erwartet. Durch den ersten Parameter wird der Bezug zum definierten Cursor hergestellt, indem die zugehörige Kennung übergeben wird. Der zweite Parameter bezieht sich auf die in der select-Anweisung aufgezählten Spalten und Ausdrücke und beschreibt die entsprechende Position in dieser Liste. Mit Hilfe des dritten Parameters legen Sie die Variable fest, in der die selektierten Werte später bereitgestellt werden sollen. Bei numerischen Werten oder Datumsfeldern sind Sie damit auch schon fertig und nur bei Textfeldern müssen Sie mit Hilfe des vierten Parameters die gewünschte Länge des Textpuffers vorgeben. Die define_column-Prozedur ist also das Bindeglied zwischen der Abfrage auf der einen und den entsprechenden Programmvariablen auf der anderen Seite. Nachdem Sie nun die Verbindung zwischen dem Abfrageobjekt und Ihren Programmvariablen hergestellt haben, könnten Sie die in dem Cursor gespeicherte Abfrage ausführen, was wiederum mit Hilfe der execute-Funktion möglich wäre. Der nächste Schritt bestünde dann darin, denn ersten Datensatz aus der selektierten Datenmenge anzufordern.
428
PL/SQL-Programmierung
Da diese beiden Schritte in Auswahlabfragen eigentlich immer zusammengehören, gibt es dafür im dbms_sql-Paket sogar eine eigene Funktion, die wir in unserem Beispiel auch verwendet haben. rc := dbms_sql.execute_and_fetch(dyn_sql);
Mit der Funktion execute_and_fetch wird also die Abfrage ausgeführt und der erste Datensatz, sofern vorhanden, wird zur Abholung bereitgestellt. Hierzu benötigt die Funktion wiederum die Kennung des geöffneten Cursors und liefert als Ergebnis die Zahl der gelesenen Datensätze zurück, d.h. sofern Ihre Abfrage gar keine Sätze ermitteln konnte, erhalten Sie hier als Wert die Zahl 0. Alle weiteren Datensätze werden mit Hilfe der fetch_rows-Funktion angefordert, die wiederum die Zahl der gelesenen Sätze zurückliefert und die Cursorkennung als Parameter übergeben bekommt. rc := dbms_sql.fetch_rows(dyn_sql);
Aufgrund der Verhaltensweise dieser beiden Funktionen können Sie das Abarbeiten der selektierten Datenmenge ganz gut mit einer while loop-Schleife programmieren, die insgesamt so lange durchlaufen wird, wie der von der fetch_rows-Funktion zurückgegebene Wert größer 0 ist. rc := dbms_sql.execute_and_fetch(dyn_sql); while rc > 0 loop ... rc := dbms_sql.fetch_rows(dyn_sql); end loop;
Innerhalb der Schleife müssen wir nun bei jedem Durchlauf und damit für jeden selektierten Datensatz die einzelnen gewünschten Spalten anfordern, was mit Hilfe der column_value-Prozedur passiert, die hierzu neben der Cursorkennung wieder die Position der Select-Liste und die zugehörige Programmvariable benötigt. dbms_sql.column_value(dyn_sql, 1, xpersnr);
Die restlichen Befehle innerhalb der Schleife dienen zur Aufbereitung bzw. Formatierung der gelesenen Werte, beispielweise werden die Textfelder mit Hilfe der rpadFunktion auf eine einheitliche Länge ausgerichtet, so dass Endergebnis der normalen Ausgabe im SQL-Editor in fast nichts nachsteht. Am Ende der ausgegebenen Datensätze drucken wir sogar noch die Zahl der gelesenen Datensätze aus, die wir mit Hilfe der last_row_count-Funktion ermitteln können. Diese Funktion entspricht praktisch der rowcount-Statusvariablen, die Sie bei den statischen Cursorn kennen gelernt haben, d.h. der von der Funktion last_row_count gelieferte Wert erhöht sich wieder mit jedem erfolgreichen Aufruf von fetch_rows. 7000006 7000018 7000021 ...
Beckmann,Yonne Bruckner,Yonne Bätzing,Bärbel
04.01.1957 13.03.1968 17.01.1963
5899.00 5300.00 624.00
Einführung in PL/SQL
7000012 Sistermann,Willy 7000016 Voltair,Samuel 7000008 Voltair,Samuel 7000009 Zola,Wolf Anzahl Sätze: 23
429
16.08.1975 23.01.1959 19.11.1965 23.07.1966
5122.00 6300.00 5200.00 6200.00
Listing 5.16: Ausführung der dynamsichen Auswahlabfrage aus Beispiel 5.15
Dynamische Abfragen parametrieren In diesem nun folgenden letzten Abschnitt zum Thema der Abfrageverwendung in einem PL/SQL-Programm werden wir die bisherigen Verfahrensweisen krönen und unsere dynamischen Abfragen parametrieren. Wenn Sie sich nun fragen, warum bzw. aus welchem Grund wir irgendwelche Mühen unternehmen, die dynamischen Abfragen in einem nun folgenden Schritt noch flexibler zu gestalten, dann gibt es hierfür eine ganz einfache Antwort, nämlich Zeit. Wird innerhalb eines Programms eine dynamische Abfrage zum Beispiel innerhalb einer Schleife mehrfach mit lediglich anderen Parametern ausgeführt, so können Sie sich den mehrfachen Aufruf der parse-Prozedur und die damit verbundenen Prüfungen und Übersetzungsaktivitäten sparen, indem Sie die Abfrage parametergesteuert erstellen. Dabei müssen Sie für jeden benötigten Parameter in der dynamischen SQL-Anweisung einen entsprechenden Platzhalter definieren. dbms_sql.parse(sql_id, 'delete lohnarten where persnr = :X', dbms_sql.native);
Für diese Platzhalter können Sie ähnliche wie bei den Variablen einem beliebigen Namen verwenden, wobei die Kennzeichnung des Platzhalters durch einen vorangestellten Doppelpunkt erfolgt. Vor dem Ausführen der Abfrage, d.h. vor dem Ausführen der execute- bzw. execute_and_fetch-Funktion müssen Sie den Platzhalter natürlich durch einen konkreten Wert ersetzen, wofür die bind_variable-Prozedur im dbms_sql-Paket existiert. dbms_sql.bind_variable(sql_id, ':X', xpersnr);
Mit Hilfe dieser Prozedur wird der in der Abfrage vorhandene Platzhalter „:X“, dessen Name als zweiter Parameter spezifiziert werden muss durch den als dritten Parameter übergebenen Wert ersetzt. In meinem Beispiel würde also der in der Variablen xpersnr gespeicherte Wert in die Löschabfrage eingesetzt, so dass das anschließende Ausführen der execute-Funktion zur Löschung der entsprechenden Mitarbeiterdaten führt.
5.1.9
Fehlerbehandlung
Tritt während der Ausführung eines PL/SQL-Programms ein Fehler auf, so wird das Programm mitsamt der aktuellen Transaktion abgebrochen. Dabei ist es relativ gleichgültig, wie es zu der Fehlersituation kam bzw. was den Laufzeitfehler ausge-
430
PL/SQL-Programmierung
löst hat. Im Folgenden finden Sie eine bei weitem nicht vollständige Liste von möglichen Ursachen solcher Laufzeitfehler:
X X
X
X
Es liegt eine technische Störung vor. Beispielsweise ist eine an der Abfrage beteiligte Datenbank nicht verfügbar, das verwendete Rollback-Segment ist zu klein, oder Tablespace oder die zugehörige Festplatte ist voll. Manchmal liegen die Ursachen eines Laufzeitfehlers auch im Betriebsablauf, beispielweise weil bestimmte Datenbankobjekte aufgrund von Zugriffskonflikten nicht gesperrt werden konnten oder weil ein Administrator die zum Programm gehörende Session wegen anlaufender Wartungsarbeiten aus dem System geworfen hat. Ein anderer Pulk von Quellen für Laufzeitfehler ist eher im zugehörigen Programm zu suchen. Hierzu zählen beispielsweise Situationen, in denen nicht definierte (z.B. Division durch 0) oder verbotene Anweisungen (Zuweisung von null in eine not null-Variable) ausgeführt werden. Ebenfalls in diese Kategorie gehören Probleme, die beim Einsatz dynamischer SQL-Anweisungen entstehen, weil die zur Laufzeit konstruierten SQL-Abfragen syntaktisch fehlerhaft sind. Die letzte und wesentlichste Gruppe von Fehlerursachen ist allerdings in dem ganz gewöhnlichen Betrieb einer relationalen Datenbank und der damit verbundenen Methoden und Konzepte zu suchen. Was kann der arme Programmierer dafür, wenn seine eigentlich korrekte SQL-Anweisung von irgendeinem Constraint oder Trigger zurückgerollt wird oder wenn ein Datensatz aufgrund der Mehrbenutzerumgebung auf einmal schon da ist und wegen des vorhandenen Primärschlüssel ein Laufzeitfehler des Typs „doppelter Wert bei einem eindeutigen Index“ entsteht?
Die Frage, die Sie sich bei jedem Programm oder Skript nun stellen müssen ist, welcher dieser eben genannten Laufzeitfehler wollen Sie in Ihrem Programm explizit abfangen und bei welchen Fehler lassen Sie es einfach drauf ankommen und nehmen somit im Falle eines Falles eben den Programmabsturz in Kauf. Nach meiner Ansicht kann man die aus den beiden ersten Gruppen resultierenden Laufzeitfehler oftmals durchaus unbeachtet lassen, d.h. beim Eintreten einer solchen Situation erfolgt ein entsprechender Programmabsturz. Eigentlich ist es ja egal, ob die Meldung eines technischen Problems durch eine Systemmeldung oder mit Hilfe einer im Programm generierten Meldung erfolgt. Außerdem gibt es gerade im technischen Umfeld Fehlersituationen, die keine Fehlerbehandlungsroutine dieser Welt erfolgreich behandeln kann, beispielsweise wenn der Datenbankserver einfach ausgeschaltet wurde. Für die in der dritten Gruppe beschriebenen Fehlerquellen finde ich auch nicht unbedingt, dass sie in einer speziellen Fehlerbehandlungsroutine gut aufgehoben sind, denn die meisten solcher Probleme können schon im Vorfeld durch entsprechende Programmpassagen abgefangen bzw. ausgeschlossen werden. Besteht beispielsweise die Möglichkeit, dass ein Divisor den Wert 0 annehmen kann, dann sollte das im Vorfeld der Division durch entsprechende Programmlogik abgefangen werden. Besteht diese Möglichkeit aufgrund der vorhandenen Rahmenbedingungen eigentlich nicht, so kann man in vielen Fällen auch ohne Prüfung und speziel-
Einführung in PL/SQL
431
ler Fehlerbehandlungsroutine in die Divisionsaufgabe einsteigen. Steht dann doch einmal eine 0 unter dem Bruchstrich, so führt das halt zum vorzeitigen Ableben Ihres Programms und der Erkenntnis, dass ein Fehler im Gesamtkonzept vorliegt bzw. die „nicht 0 Garantie“ nicht viel Wert war. Die in der letzten Gruppe beschriebenen Fehlerursachen sollten im Regelfall sicherlich nicht zum Absturz eines Programms, sondern höchstens zum Abbruch der aktuellen Transaktion und zur Ausgabe entsprechender Meldungen führen, wobei es natürlich auch hier Situationen gibt, wo das Versagen einer Operationen den geordneten Rückzug des gesamten Programms nahe legt. Behandlung im Programm Wie ich schon ganz am Anfang des Kapitels angedeutet habe, besteht innerhalb von PL/SQL die Möglichkeit, innerhalb eines Programmblocks eine spezielle Routine zur Behandlung von Laufzeitfehlern zu programmieren. Eine solche Fehlerbehandlungsroutine beginnt mit dem Schlüsselwort exception und bildet meistens den Abschluss eines solchen Programmblocks, so dass sich für die Blockstruktur unserer Programme das folgende neue Schema ergibt: begin ... Anweisungen des Blocks A begin ... Anweisungen des Blocks B exception ... Fehlerbehandlung für Block B end; exception ... Fehlerbehandlung für Block A end;
Diesem Schema können Sie auch entnehmen, dass durch die geeignete Konstruktion von Programmblöcken die Möglichkeit besteht, für spezielle Anweisungen entsprechende Fehlerbehandlungsroutinen zu programmieren. Das ist gerade bei der Erstellung von Schleifen besonders interessant, wenn bei einem Fehler innerhalb der Schleife zwar der aktuelle Datensatz nicht weiter bearbeitet werden soll, die noch fehlenden Durchläufe bzw. die restlichen Datensätze jedoch verarbeitet werden können. Wie gesagt, beginnt die Programmierung einer solchen Ausnahmeroutine mit der exception-Anweisung, der mit Hilfe des Schlüsselwortes when eine Aufzählung der zu berücksichtigenden Ausnahmezustände folgt, so dass sich folgendes Programmschema ergibt: exception when Laufzeitfehler-A then ... Problembehandlung für A
432
PL/SQL-Programmierung
when Laufzeitfehler-B then ... Problembehandlung für B ... when others then
Die zu verwendenden Codes sind im Paket dbms_standard vordefiniert, so dass Sie in Ihrem Programm die dort definierten Konstanten und damit sprechende Namen verwenden können. Eine vollständige Aufstellung der verfügbaren Konstanten müssen Sie jetzt allerdings nicht dem dbms_standard-Paket entlocken, sondern Sie finden diese in der Oracle-Dokumentation im Buch „PL/SQL User's Guide and Reference“ im Kapitel „Error Handling“ und dort im Abschnitt „Predefined Exceptions“. Ist der aufgetretene Fehler in Ihrer selbstgeschriebenen Fehlerbehandlungsroutine nicht enthalten, dann tritt wieder das dafür im System standardmäßig verfügbare Verfahren in Kraft, d.h. es erfolgt die Ausgabe der zugehörigen Hinweis- bzw. Fehlermeldungen und anschließend kommt es zum erzwungenen Programmabbruch. Betrachten Sie nun das zunächst einmal sehr einfache Beispiel 5.17, mit dem ich die grundsätzliche Funktionsweise einmal am Beispiel der Division durch 0 zeigen möchte. declare i integer not null:=0; j integer; begin i:=10/i; --i:= j; exception when zero_divide then raise_application_error(-20000, 'Fehlerhafte Division', false); when others then raise_application_error(-20000,'Unbekannter Fehler',true); end; Listing 5.17: Programmierung einer exception-Routine für Laufzeitfehler
Wie Sie dem Beispiel 5.17 entnehmen können, heißt die Konstante für den bei der Division durch 0 entstehenden Fehler zero_divide. Beim Eintreten dieses Fehlers geben wir mit Hilfe von raise_application_error eine entsprechende Meldung aus und leiten den Programmabbruch ein. ORA-20000: Fehlerhafte Division ORA-06512: at line 9
Einführung in PL/SQL
433
Als dritten Parameter für diese Prozedur habe ich dieses Mal explizit den Wert false übergeben, so dass vorhergehende Fehlermeldungen nicht mit ausgegeben werden. Im Unterschied hierzu verwende ich bei der Behandlung der sonstigen Fehler als dritten Parameter den Wert true, um bei der Verarbeitung des eigentlich unbekannten Problems wenigstens die zugehörige Fehlermeldung zu erhalten. Sie können diese unterschiedliche Verhaltensweise leicht ausprobieren, indem Sie die fehlerauslösende Programmstelle ändern bzw. die Kommentierung im Programm austauschen: -- i:=10/i; i:= j;
Anschließend entsteht bei der Programmausführung wegen der verbotenen Zuweisung von null der Laufzeitfehler value_error, den wir aber nicht explizit abgefangen haben, sondern im Rahmen der sonstigen Fehler bearbeiten und aufgrund des anderen Parameterwertes von raise_application_error sieht das Ganze jetzt folgendermaßen aus: ORA-20000: Unbekannter Fehler ORA-06512: at line 11 ORA-06502: PL/SQL: numeric or value error
Selbst Laufzeitfehler auslösen Weiter oben in diesem Workshop hatte ich gesagt, dass Sie mit Hilfe des raise_application_errors-Befehls die Ausgabe des beim Aufruf verwendeten Hinweises und das Beenden des Programms bewirken. Betrachtet man alleine Aktion und Wirkung dieses Befehls, dann sieht das auch durchaus so aus, wobei die genaue Verfahrensweise eine andere ist, denn mit Hilfe dieser Prozedur lösen Sie in Ihrem Programm einen Laufzeitfehler aus, wobei Sie die im ersten Parameter verwendete Nummer zur Unterscheidung der jeweiligen Fehler verwendet werden können. Und wenn für diesen ausgelösten Fehler keine spezielle Fehlerbehandlungsroutine verfügbar ist, dann führt das eben zu dem beschriebenen Programmabbruch. begin raise_application_error(-20980,'Bitte abbrechen...'); exception when zero_divide then raise_application_error(-20000, 'Fehlerhafte Division', false); when others then raise_application_error(-20000,'Unbekannter Fehler',true); end; ORA-20000: Unbekannter Fehler ORA-06512: at line 9 ORA-20980: Bitte abbrechen... Listing 5.18: Erzeugen eines Laufzeitfehlers mit raise_application_error
434
PL/SQL-Programmierung
Das Beispiel 5.18 demonstriert diesen Zusammenhang noch einmal. Gleich zu Beginn wird mit Hilfe der Prozedur raise_application_error der Laufzeitfehler 20980 ausgelöst, der natürlich zur Abarbeitung der in unserem Block programmierten Fehlerbehandlungsroutine führt. Da der generierte Fehler natürlich nicht in der exception-Liste aufgeführt ist wird der when others-Zweig ausgeführt, was zu den im Listing gezeigten Meldungen und zum Programmende führt. Sofern Sie in Ihrem Programm auf solche selbstausgelösten Laufzeitfehler in besonderer Art und Weise reagieren möchten, dann besteht eine Möglichkeit darin, innerhalb des when others-Zweigs die Statusvariablen sqlcode oder sqlerrm abzufragen und entsprechend der dort gespeicherten Werte zu reagieren. Wie sich anhand der Bezeichnungen der beiden Variablen sicher schon denken können, enthält sqlcode den zum Laufzeitfehler zugehörigen Fehlerocde und sqlerrm die zugehörige Fehlermeldung. Eine andere Möglichkeit besteht darin, für die selbstdefinierten Fehlernummern, die im Übrigen im Wertebereich von –20000 bis –20999 liegen müssen, benannte Laufzeitfehler zu katalogisieren. Hierzu müssen Sie zunächst im Deklarationsteil des Blocks den gewünschten Laufzeitfehlernamen deklarieren und diesen Namen anschließend in einem zweiten Schritt mit der zugehörigen Fehlernummer verknüpfen: declare sonstiges exception; pragma exception_init(sonstiges, -20980);
Mit diesem Beispiel definieren wir den Laufzeitfehler sonstiges und verknüpfen ihn anschließend mit der Anweisung pragma exception_init mit der gewünschten Fehlernummer. Anschließend können Sie diesen Laufzeitfehler in Ihrer Fehlerbehandlungsroutine mit Hilfe eines eigenen when-Zweigs abfangen, was mit Hilfe des nächsten Beispiels 5.19 demonstriert wird. declare sonstiges exception; pragma exception_init(sonstiges, -20980); begin raise_application_error(-20980,'Bitte abbrechen...'); exception when zero_divide then raise_application_error(-20000, 'Fehlerhafte Division', false); when sonstiges then raise_application_error(-20000,'Sonstiger Fehler',true); when others then raise_application_error(-20000,'Unbekannter Fehler',true); end;
Einführung in PL/SQL
435
ORA-20000: Sonstiger Fehler ORA-06512: at line 14 ORA-20980: Bitte abbrechen... Listing 5.19: Abfangen selbsterstellter Laufzeitfehler
Die raise-Anweisung Wenn Sie in Ihrem Programm selbst einen Laufzeitfehler auslösen, für den eine geeignete Fehlerbehandlungsroutine vorhanden ist, dann wirkt die im Beispiel 5.19 gezeigte Verfahrensweise wegen der zweimaligen Verwendung der raise_application_error-Prozedur irgendwie ein wenig umständlich, und in der Tat existiert noch eine weitere Variante, einen Laufzeitfehler auszulösen. Diese zweite Variante hört auf den Namen raise und kann zum Auslösen eines für den Block definierten Laufzeitfehlers verwendet werden, wobei Sie die genaue Verwendung dem folgenden Schema entnehmen können: declare exception; begin ... raise ; exception ... when then end;
Ich möchte auch den raise-Befehl mit Hilfe eines kurzen Beispiels demonstrieren und dabei vor allem auch einmal zeigen, dass Fehlerbehandlungsroutinen natürlich nicht nur zum Programmieren eines schöneren Programmabbruchs gedacht sind, denn wenn Sie innerhalb der exception-Programmierung keinen weiteren unbehandelten Fehler generieren, was aber oftmals und auch in allen unseren bisherigen Beispielen passierte, dann wird das Programm eben nicht abgebrochen, sondern kann hinter der exception-Routine fortgesetzt werden. In solchen Fällen ist es allerdings oftmals zweckmäßig, auf die Verwendung separater Programmblöcke zurückzugreifen. In unserem nächsten Beispiel sollen alle Datensätze der Tabelle personalien verarbeitet werden. Zu Demonstrationszwecken denken wir uns dabei, dass bei allen männlichen Mitarbeitern im Programm der Ausnahmezustand eintritt, was zu einem Laufzeitfehler führt, den wir im Programm natürlich mit Hilfe der raise-Anweisung selbst auslösen. declare cursor xpers is select persnr, name, geschlecht from personalien; falsches_geschlecht exception;
436
PL/SQL-Programmierung
begin for xpers_rec in xpers loop begin if xpers_rec.geschlecht in ('M','2') then -- Simulation eines Fehlers raise falsches_geschlecht; end if; dbms_output.put_line(xpers_rec.persnr || ' ok!'); exception when falsches_geschlecht then dbms_output.put_line(xpers_rec.persnr || ' falsch!'); end; end loop; end; Listing 5.20: Behandeln eines Laufzeitfehlers in einer Schleife
Wie Sie dem Beispiel 5.20 entnehmen können, deklarieren wir in unserem Programm neben dem Cursor xpers auch noch den Laufzeitfehler falsches_geschlecht. Die einzelnen Datensätze des Cursors verarbeiten wir wieder mit Hilfe einer for loop-Schleife, wobei die eigentliche Verarbeitung der Personalien mit Hilfe eines separaten Blocks erfolgt. begin if xpers_rec.geschlecht in ('M','2') then -- Simulation eines Fehlers raise falsches_geschlecht; end if; dbms_output.put_line(xpers_rec.persnr || ' ok!'); exception when falsches_geschlecht then dbms_output.put_line(xpers_rec.persnr || ' falsch!'); end;
In diesem Block lösen wir bei den männlichen Kollegen aus unserer Testdatenbank den zuvor deklarierten Fehler falsches_geschlecht aus, der aber aufgrund der im Block enthaltenen exception-Routine abgefangen wird, und das Einzige, was wir in unserer Fehlerroutine diesmal tun besteht in der Ausgabe einer Meldung in dem von dbms_output verwalteten Protokollpuffers. Der entstandene Laufzeitfehler wurde also abgefangen und es erfolgt demzufolge kein Programmabbruch. Stattdessen fährt unser Programm mit der Verarbeitung des nächsten Datensatzes fort.
Einführung in PL/SQL
7000002 7000003 7000004 7000006 7000009 7000011 7000012 7000014 7000016 7000018
437
falsch! ok! falsch! ok! falsch! falsch! falsch! falsch! falsch! ok!
Listing 5.21: Auszug aus dem zum Beispiel 5.20 zugehörigen Protokoll
Protokollieren von Laufzeitfehlern Das zuletzt gezeigte Beispiel funktioniert zwar ganz prima, ist insofern aber etwas praxisfremd, da die vom Programm mit Hilfe des dbms_output-Pakets erzeugten Meldungen nur angezeigt werden, wenn man das Skript oder Programm mit Hilfe eines SQL-Editors startet. Ist das Skript aber beispielsweise in einem gewöhnlichen Programm eingebettet oder das PL/SQL-Programm wird mit irgendwelchen Mechanismen automatisch gestartet, dann ist die Verwendung des dbms_ouput-Pakets zur Dokumentation von Laufzeitfehlern nicht sonderlich gut geeignet. In der Praxis verwendet man zur Protokollierung solcher Meldung oftmals gewöhnliche Datenbanktabellen, die anschließend nach jedem Programmlauf ausgewertet werden können. Mit Hilfe des nächsten Beispiels (vgl. Listing 5.22) erzeugen wir zunächst einmal eine solche Protokolltabelle, in der wir gleich die personenbezogenen Fehlermeldungen speichern wollen. drop table meldungen; / create table meldungen (persnr varchar2(11), meldung varchar2(255)); Listing 5.22: Anlage einer einfachen Protokolltabelle
Unser nächstes Beispiel verwendet als Ausgangsbasis wieder das im Listing 5.20 gezeigte Programm. Allerdings wollen wir im Rahmen der Verarbeitung diesmal eine Spalte ändern, bzw. diese Änderung beim Auftreten eines Laufzeitfehlers rückgängig machen und stattdessen einen Hinweis in unserer Protokolltabelle einstellen. declare cursor xpers is select persnr, name, geschlecht from personalien; falsches_geschlecht exception; begin delete meldungen;
438
PL/SQL-Programmierung
update personalien set strasse1 = null; commit; for xpers_rec in xpers loop begin update personalien set strasse1 = strasse4 where persnr = xpers_rec.persnr; if xpers_rec.geschlecht in ('M','2') then -- Simulation eines Fehlers raise falsches_geschlecht; end if; commit; exception when falsches_geschlecht then rollback; insert into meldungen values (xpers_rec.persnr, 'Irgend ein fehler '); commit; end; end loop; end; Listing 5.23: Verwenden einer Datenbanktabelle zum Speichern von Laufzeitfehlern
Die ersten Anweisungen in unserer neuen Programmvariante dienen eigentlich nur dazu, das Programm mehrfach wiederholen zu können, indem zum einen die Protokolltabelle gelöscht wird und zum anderen die durchgeführten Änderungen rückgängig gemacht werden. delete meldungen; update personalien set strasse1 = null; commit;
Innerhalb der Programmschleife soll für jeden Mitarbeiter das Feld strasse4 in das Feld strasse1 kopiert werden, was mit Hilfe der folgenden update-Anweisung passiert: update personalien set strasse1 = strasse4 where persnr = xpers_rec.persnr;
Die für gespeicherten männlichen Mitarbeiter generieren wir nach der Durchführung der Änderung nun wieder mit Hilfe des raise-Kommandos den simulierten Laufzeitfehler und gelangen somit in die zugehörige Fehlerbehandlungsroutine.
Einführung in PL/SQL
439
Dort fügen wir mit Hilfe einer einfachen insert-Anweisung einen Datensatz in die soeben angelegte Protokolltabelle ein. insert into meldungen values (xpers_rec.persnr, 'Irgend ein fehler ');
Das eigentliche Problem bei der ganzen Angelegenheit besteht nun darin, dass im Fehlerfall die schon durchgeführten Änderungen für diesen Satz zurückgerollt werden müssen und auf der anderen Seite darauf geachtet werden muss, dass ins Protokoll eingefügte Meldungen in jedem Fall erhalten bleiben. Genau genommen ist das aber gar nicht schwierig und die Lösung besteht einzig und alleine in dem Einfügen geeigneter commit- bzw. rollback-Anweisungen. Wie Sie dem Listing 5.23 entnehmen können, habe ich mich dafür entschieden, die innerhalb der Schleife durchgeführten Transaktionen für jeden Datensatz sofort zu beenden, was durch die vor der Fehlerbehandlungsroutine platzierte commit-Anweisung passiert. Entsteht nun innerhalb des Blocks ein Fehler, dann rollen wir diese aktuelle Transaktion zunächst zurück und fügen anschließend, d.h. in einer neuen Transaktion, die Protokolldatensatz in die entsprechende Tabelle ein, wobei wir auch diese Transaktion sofort durch eine commit-Anweisung abschließen. Wenn Sie dieses Programm laufen lassen, dann erhalten Sie für alle männlichen Mitarbeiter einen Hinweis in der Protokolltabelle und für alle weiblichen Mitarbeiter wird die Straße ins Datenfeld strasse1 kopiert. SQLWKS> select persnr, strasse1 from personalien 2> PERSNR STRASSE1 ----------- ----------------------------------7000002 7000003 Im Schlosspark 3 7000004 7000006 Franz-Haniel-Gasse 3 7000009 ... 26 rows selected. SQLWKS> select * from meldungen 2> PERSNR MELDUNG ----------- -----------------------7000002 Irgend ein fehler 7000004 Irgend ein fehler 7000009 Irgend ein fehler 7000011 Irgend ein fehler ... 12 rows selected. Listing 5.24: Ergebnis der Änderungsabfrage aus dem Beispiel 5.23
440
PL/SQL-Programmierung
Obwohl das Programm einwandfrei funktioniert, habe ich dennoch schon wieder etwas zu meckern, denn Sie sollten sich zumindest darüber im Klaren sein, dass Sie in dem Fall einen undefinierten Zustand bekommen, wenn das zuletzt gezeigte Programm während der Verarbeitung doch noch unkontrolliert abstürzt. Da wir jede Transaktion sofort abschließen bleibt die Datenbank in einem halbfertigen Zustand, wenn das Programm nicht bis zum Ende läuft. Das wäre in unserem und in ähnlich gelagerten Beispielen, wo die Programme jederzeit wiederholt werden können, zwar kein Problem, doch es gibt in der Praxis anders gelagerte Fälle, wo es besser ist, wenn das Programm entweder ganz oder gar nicht läuft. Verwenden von Sicherungspunkten In solchen Fällen hilft der Einsatz von Sicherungspunkten. Im Rahmen der Transaktionsbeschreibung hatten Sie die zugehörige savepoint-Anweisung zwar schon kennen gelernt, jedoch wurde Ihnen ein vernünftiges und einleuchtendes Anwendungsbeispiel bislang vorenthalten. Das möchte ich mit der nächsten und letzten Variante des vorhergehenden Beispiels jetzt nachholen. Um es noch einmal zusammenzufassen: Wir haben es jetzt mit den zwei konkurrierenden Problemen zu tun. Zum einen müssen wir das Wechselspiel zwischen dem Zurückrollen einzelner Datensatzänderungen und dem Einfügen der Protokolldatensätze realisieren und zum anderen soll keine der gesamten Änderungen sichtbar werden, wenn das Programm nicht normal beendet wird. declare cursor xpers is select persnr, name, geschlecht from personalien; falsches_geschlecht exception; begin delete meldungen; update personalien set strasse1 = null; commit; for xpers_rec in xpers loop begin savepoint sicher; update personalien set strasse1 = strasse4 where persnr = xpers_rec.persnr; if xpers_rec.geschlecht in ('M','2') then -- Simulation eines Fehlers raise falsches_geschlecht; end if;
Einführung in PL/SQL
441
exception when falsches_geschlecht then rollback to savepoint sicher; insert into meldungen values (xpers_rec.persnr, 'Irgend ein fehler '); end; end loop; commit; end; Listing 5.25: Verwenden von Sicherungspunkten
Im Unterschied zur letzten Varianten definieren wir diesmal am Anfang des Schleifenblocks den Sicherungspunkt sicher, d.h. wir gehen davon aus, dass am Anfang unserer Schleife die Welt noch bzw. wieder in Ordnung ist. Anschließend führen wir wieder unsere Datenänderung durch, dem die Generierung des Laufzeitfehlers folgt. Bei der Behandlung des Laufzeitfehlers müssen wir als Erstes wieder die zuletzt durchgeführten Änderungen rückgängig machen, was diesmal allerdings mit Hilfe einer speziellen rollback-Anweisung passiert, mit der wir alle Änderungen bis zum letzten Sicherungspunkt zurückrollen. Anschließend fügen wir unsere Meldung in die Protokolltabelle ein. Da wir diesen Sicherungspunkt jedes Mal erneut am Anfang eines Schleifendurchlaufs setzen, führt die Anweisung rollback to savepoint sicher zur Rückgängigmachung aller im aktuellen Durchlauf durchgeführten Änderungen, wohingegen die Einfügeoperation, die hinter dem rollback-Kommando bzw. vor dem Sicherungspunkt ausgeführt wird, dabei erhalten bleibt. Ebenfalls unterschiedlich zur vorhergehenden Programmvariante ist, dass wir diesmal erst ganz am Ende des Programms, d.h. nach dem Verarbeitung aller Datensätze, eine commit-Anweisung absetzen und damit alle durchgeführten Änderungen endgültig bestätigen. Kommt es zwischendurch also doch noch zu einem Programmabbruch, so finden Sie in der Datenbank überhaupt keine Änderungen und Protokolldatensätze. Wenn Sie diesen Effekt einmal ausprobieren wollen, dann können Sie den commitBefehl beispielsweise durch eine rollback- oder raise_application_error-Anweisung ersetzen.
5.1.10 Dateiverarbeitung Zwar dient eine SQL-Datenbank mitsamt der darin enthaltenen Werkzeuge in erster Linie der Verarbeitung der in der Datenbank gespeicherten Tabellen und dennoch bietet Ihnen PL/SQL auch eine Möglichkeit, gewöhnliche Dateien in Ihren Programmablauf mit einzubinden. Auf diese Weise sind Sie in der Lage, beispiels-
442
PL/SQL-Programmierung
weise Schnittstellendateien direkt innerhalb eines PL/SQL-Programms zu verarbeiten, d.h. Sie benötigen nicht unbedingt zusätzliche Arbeitsschritte um solche Dateien in einem vor- bzw. nachgelagerten Schritt zu importieren bzw. zu exportieren. Konkret werden Ihnen diese Möglichkeiten durch das Paket utl_file beschert, d.h. mit Hilfe der dort vorhandenen Funktionen und Prozeduren können Sie gewöhnliche Textdateien erstellen oder verarbeiten. Wie an vielen anderen Stellen auch, so erhalten Sie in diesem Abschnitt natürlich mal wieder nur einen ersten Eindruck von den insgesamt zur Verfügung stehenden Möglichkeiten. Mehr Informationen bzw. die vollständige Beschreibung des utl_file-Pakets finden Sie wieder in der Oracle-Dokumentation im Buch „Oracle8 Application Developer's Guide“ und dort im Kapitel „PL/SQL Input/Output“. Die Verarbeitung bzw. Erstellung von Textdateien erfolgt mittlerweile in fast jeder Programmiersprache mit ähnlichen Befehlen bzw. die generelle Vorgehensweise folgt immer dem gleichen Schema. Zunächst müssen Sie die zu verarbeitende Datei mit Hilfe einer speziellen Anweisung öffnen, wobei Sie üblicherweise eine Kennbzw. Kanalnummer erhalten, die anschließend bei allen anderen Dateioperationen verwendet wird. Mit Hilfe spezieller Befehle können Sie die geöffnete Datei lesen oder beschreiben und nach erledigter Arbeit wird die Datei mit Hilfe einer speziellen Anweisung wieder geschlossen. Verzeichnisse freigeben Gewöhnlich haben die Instanzen der Datenbank uneingeschränkten Zugriff auf die Ressourcen des eingesetzten Servers. Werden nun im Rahmen von Serverprogrammen Dateien erstellt oder geändert und können diese Programme von beliebigen Datenbankbenutzern gestartet werden, dann haben diese dadurch vielleicht auch indirekt Zugriff auf Dateien und Verzeichnisse, für die Sie normalerweise keine Berechtigungen besitzen. Aus diesem Grund ist die Verwendung des utl_file-Pakets an besondere Einstellungen gebunden, d.h. standardmäßig ist das Paket zwar vorhanden, aber es können mit ihm keinerlei Dateien verarbeitet werden. Damit die im Paket enthaltenen Funktionen benutzt werden können, müssen Sie innerhalb der Instanzkonfiguration (INITxxxx.ORA) diejenigen Verzeichnisse explizit freigeben, in denen das Paket Dateien verarbeiten darf. Hierzu müssen Sie in der Konfigurationsdatei der Instanz den Parameter utl_file_dir mit dem freizugebenden Verzeichnis verwenden, wobei Sie für jedes Verzeichnis einen eigenen Parametereintrag spezifizieren müssen. utl_file_dir = c:\temp utl_file_dir = c:\output\data utl_file_dir = c:\daten
Dabei müssen Sie natürlich die vom Betriebssystem des Servers abhängige Schreibweise für Verzeichnisse beachten. Bei dem von mir gezeigten Beispiel handelt es sich offensichtlich um eine Einstellung für NT-Server. Auf einem Unix-Server kippt dann der die Unterverzeichnisse trennende Strich in die andere Richtung. Außer-
Einführung in PL/SQL
443
dem fehlen üblicherweise die Laufwerksbuchstaben und Sie müssen die exakte Schreibweise (Groß-/Kleinschreibweise) beachten: utl_file_dir = /PSI/applications
Wenn Sie einen unbeschränkten Zugriff ermöglichen wollen, dann müssen Sie allerdings nicht jedes vorhandenes Verzeichnis vorgeben, sondern können sich die Sache deutlich vereinfachen, indem Sie folgende Kurzform verwenden. utl_file_dir = *
Datei öffnen In PL/SQL erfolgt das Öffnen einer Datei mit der Funktion utl_file.fopen, die hierzu insgesamt drei Parameter erhält und für die geöffnete Datei eine Kennung vom Typ utl_file.file_type zurückliefert. := utl_file.fopen(, , <Modus>)
Mit Hilfe der ersten beiden Parameter müssen Sie der Funktion das Verzeichnis und den Dateinamen der zu öffnenden Datei vorgeben. Der dritte Parameter legt fest, in welchem Modus die Datei geöffnet wird und bestimmt damit, welche Operationen anschließend mit der geöffneten Datei möglich sind. Modus
Beschreibung
r
öffnet die Datei zum Lesen und ermöglicht damit anschließend die Verwendung der Funktion get_line.
w
Verwenden Sie diesen Parameter, um die Datei im Schreibmodus zu öffnen, wobei eine schon vorhandene Datei hierbei überschrieben wird.
a
Dieser Modus dient zum Fortschreiben der Datei, d.h. eine bereits vorhandene Datei wird hierdurch nicht überschrieben, sondern verlängert. Ist die Datei noch gar nicht vorhanden, dann öffnet die Funktion die Datei automatisch im Modus „w“.
Tabelle 5.13: Verschiedene Modi zum Öffnen einer Datei
Wenn in Ihrem Programm Verzeichnis- oder Dateinamen variabel vorgegeben werden können, dann sollten Sie wenigstens den Laufzeitfehler utl_file.invalid_operation und utl_file.invalid_path abfangen, die bei Verwendung eines fehlerhaften Datei- bzw. Verzeichnisnamens ausgelöst werden. Datei schließen Eine im Programm geöffnete Datei sollten Sie ordentlich schließen, wenn Sie diese nicht mehr benötigen. Ordentlich bedeutet hierbei, dass Sie die geöffnete Datei mit Hilfe der Prozedur utl_file.fclose schließen, der Sie dafür die zugehörige Kennung übergeben müssen. utl_file.fclose()
444
PL/SQL-Programmierung
Auch diese Prozedur kann verschiedene Laufzeitfehler auslösen. Beispielsweise erhalten Sie den Fehler utl_file.invalid_filehandle, wenn Sie die Prozedur mit einer ungültigen Kennung aufrufen. Als Alternative zum Schließen der einzelnen Dateien können Sie am Ende des Programms beispielsweise auch die Prozedur utl_file.fclose_all verwenden, die automatisch alle geöffneten Dateien schließt. Datensätze lesen Gelesen werden die in der Textdatei vorhandenen Datensätze mit Hilfe der Prozedur utl_file.get_line, der Sie hierzu neben der Kennung vor allem einen Puffer zur Aufnahme der gelesenen Datenzeile übergeben müssen. utl_file.get_line(, )
Bei der Verwendung dieser Prozedur sollten Sie vor allem dem Laufzeitfehler not_data_found ein wenig Beachtung schenken, denn dieser Fehler wird ausgelöst, wenn Sie versuchen, über das Ende der Datei hinauszulesen, d.h. mit Hilfe dieses Fehlers können Sie das Dateiende erkennen. Damit sind wir in der Lage, das Ganze anhand eines einfachen Beispiels zu demonstrieren. Hierzu erstellen wir ein Programm (vgl. Listing 5.26), mit dem wir eine beliebige Textdatei wie zum Beispiel die Konfigurationsdatei der Datenbankinstanz lesen und die einzelnen Datensätze am Bildschirm ausgeben. declare f utl_file.file_type; p varchar2(255); begin f := utl_file.fopen('e:\orant\database\db01','initdb01.ora','r'); loop begin utl_file.get_line(f, p); dbms_output.put_line(p); exception when no_data_found then exit; end; end loop; utl_file.fclose(f); end; Listing 5.26: Lesen und Verarbeiten einer Textdatei
Wie Sie dem Beispiel entnehmen können, startet unser Programm mit dem Öffnen der Textdatei und schließt diese mit Hilfe der letzten Programmanweisung wieder. Im Inneren des Programms programmieren wir eine loop-Schleife, mit deren Hilfe
Einführung in PL/SQL
445
die vorhandenen Datensätze der Datei gelesen und ausgegeben werden sollen. Innerhalb der Schleife verwenden wir einen eigenen Programmblock in dem wir eine Fehlerbehandlungsroutine programmieren, in der wir das erreichte Dateiende abfangen und anschließend die loop-Schleife verlassen. begin utl_file.get_line(f, p); dbms_output.put_line(p); exception when no_data_found then exit; end;
Datensätze schreiben Das Erstellen von Textdateien ist unter anderem mit Hilfe der Prozeduren utl_file.put und utl_file.new_line möglich. Dabei wird die erste der beiden genannten Prozeduren dazu benutzt, den Inhalt einer Puffervariablen an die aktuelle Dateiposition zu schreiben. Mit Hilfe der zweiten Prozedur erzeugen Sie in der Ausgabedatei einen Zeilenwechsel, d.h. ein nachfolgender Ausgabebefehl beginnt wieder mit einem neuen Datensatz. Wenn Sie eine Textdatei satzweise wegschreiben, dann müssen Sie die beiden Prozeduren also jedes Mal im Wechsel einsetzen, weshalb es im Paket auch eine Kombination der beiden Prozeduren gibt, die unter dem Namen utl_file.put_line angelegt ist und das Wegschreiben des Datensatzes mit dem anschließenden Zeilenwechsel in einem Rutsch erledigt. utl_file.put(, ); utl_file.new_line(); utl_file.put_line(, );
Zur Demonstration der Dateiausgabe werden wir gleich eine kleine Routine schreiben, mit der Sie den Inhalt einer Tabelle in eine Textdatei exportieren können. Als Tabelle wählen wir die Personalien aus und damit das ganze Beispiel (vgl. Listing 5.27) klein und übersichtlich bleibt, begnügen wir uns damit, anstelle der gesamten Tabelle nur drei verschiedene Datenfelder zu exportieren. Zum Lesen der Tabelle personalien definieren wir in unserem Skript den Cursor xpers, der innerhalb des Programmblocks mit Hilfe einer for loop-Schleife durchgelesen wird. Zuvor öffnen wir mit Hilfe der Prozedur utl_file.fopen die Ausgabedatei C:\TEMP\EXPORT.TXT im Ausgabemodus. declare cursor xpers is select * from personalien; f utl_file.file_type; begin
446
PL/SQL-Programmierung
f := utl_file.fopen('c:\temp','export.txt','w'); for xpers_rec in xpers loop utl_file.put(f, xpers_rec.persnr); utl_file.put(f, chr(9)); utl_file.put(f, xpers_rec.name); utl_file.put(f, chr(9)); utl_file.put(f, to_char(xpers_rec.gebdatum,'YYYY.MM.DD')); utl_file.new_line(f); end loop; utl_file.fclose(f); end; Listing 5.27: Erstellen eines Beispiels für den Export einer Tabelle in eine Textdatei
Zur Ausgabe der einzelnen Datenfelder verwenden wir die Prozedur utl_file.put. Da diese Prozedur wie gesagt die geöffnete Datei mit Hilfe des übergebenen Puffer an der aktuellen Position fortschreibt, können wir jedes benötigte Datenfeld mit Hilfe eines eigenen Prozeduraufrufs ausgeben und nach Ausgabe des letzten Datenfeldes verwenden wir die Prozedur utl_file.new_line, damit die nächste Ausgabeoperation mit einem neuen Datensatz beginnt. Als Alternative hierzu hätten wir den gesamten Ausgabesatz auch zunächst mit Hilfe einer entsprechenden Puffervariablen aufbereiten können und hätten diese Puffervariable anschließend mit Hilfe der Prozedur utl_file.put_line als ganzen Datensatz in die Datei kopiert. Zwischen den einzelnen Datenfelder erzeugen wir die Ausgabe eines speziellen Trennzeichens, wobei wir durch die Ausgabe des Ausdrucks chr(9) einen Tabulator als Trennzeichen in die Textdatei einfügen.
5.1.11 Pipes So langsam stoßen wir mehr und mehr in ganz spezielle Randgebiete der PL/SQLProgrammierung vor, weshalb die Ausführlichkeit der hier gezeigten Beispiele mitsamt der zugehörigen Erläuterungen entsprechend abnimmt. Konkret geht es in diesem Kapitel um die sogenannten Datenbank-Pipes, die Ihnen aufgrund der Funktionalitäten des Pakets dbms_pipe zur Verfügung stehen. Sollten die im Folgenden kredenzten Häppchen Ihren Appetit wecken, dann sollten Sie sich die entsprechenden Seiten in der Oracle-Dokumentation gönnen. Sie finden dort eine ausführliche Beschreibung des dbms_pipe-Pakets im Buch „Oracle8 Application Developer's Guide“ und dort konkret wieder im Kapitel „PL/SQL Input/Output“. Sie erinnern sich sicherlich an die Pakete, mit denen Sie in der Lage waren sitzungsglobale Variablen zu definieren. Oftmals werden solche globalen Variablen auch dazu benutzt, um die Kommunikation zwischen in sich abgeschlossenen aber der aktuellen Sitzung zugeordneten Prozeduren oder Prozessen zu steuern. Ein konkretes Anwendungsbeispiel hierfür liegt in der Weiterleitung von Informationen von einem Vorab-Trigger an einen Danach-Trigger.
Einführung in PL/SQL
447
Bei den hier beschriebenen Pipes handelt es sich so ähnlich wie bei den Variablen im Prinzip auch um globale Bereiche, die allerdings datenbankglobal zur Verfügung stehen, d.h. auf die in der Pipe gespeicherten Informationen kann theoretisch jede beliebige Datenbanksitzung zugreifen. Zur Verwendung solcher Pipes gibt es auch aus meiner Sicht die folgenden beiden Einsatzgebiete:
X X
Sitzungsübergreifende Bereitstellung bestimmter Informationen, beispielsweise um spezielle Zwischenergebnisse oder den Stand der Verarbeitung ohne spezielle Arbeitstabellen zu dokumentieren. Steuerung und Kommunikation zwischen verschiedenen Prozessen, ggf. sogar zwischen PL/SQL-Programmen und sonstigen Serveranwendungen. Mit Hilfe solcher Pipes könnten Sie beispielsweise verschiedene Jobs synchronisieren, indem Sie die zugehörigen Programme so erstellen, dass Sie erst nach dem Empfang spezieller Nachrichten mit der eigentlichen Arbeit beginnen.
Die maximale Lebensdauer einer solchen Pipe entspricht der einer Datenbankinstanz, d.h. mit dem Runterfahren der Datenbank gehen die in der Pipe gepufferten Informationen verloren und werden beim Wiederhochfahren der Instanz nicht automatisch restauriert. Konkret hängt die Lebensdauer einer Pipe davon ab, ob sie implizit oder explizit erstellt wurde. Implizite Pipes werden vom System automatisch erstellt und auch wieder gelöscht, wenn in Ihr keine Informationen mehr vorhanden sind. Im Gegensatz dazu werden die expliziten Pipes mit Hilfe spezieller Befehle erstellt und gelöscht und bleiben daher auch dann erhalten, wenn in dem zugehörigen Bereich aktuell keine Daten gespeichert werden. Übrigens werden die impliziten Pipes häufig auch als öffentliche und die expliziten Varianten als private Pipes bezeichnet. Einstellen von Daten in die Pipe Wie eben gesagt, werden implizite Pipes automatisch vom DBMS angelegt. Das passiert genau dann, wenn eine entsprechende Routine versucht, Informationen in diese Pipe einzustellen. Das nachfolgende Beispiel zeigt dies an einem sehr einfachen Beispiel, indem eine einfache Zeichenfolge in die Pipe mit dem Namen „Hallo“ eingeleitet wird. declare i integer; begin dbms_pipe.pack_message('Ich bin da'); i:=dbms_pipe.send_message('Hallo'); end; Listing 5.28: Erzeugen einer impliziten Pipe
Wie Sie dem Beispiel 5.28 entnehmen können, benötigt man zum Einstellen von Information in die Pipe genau zwei verschiedene Anweisungen. Mit Hilfe der ersten Prozedur dbms_pipe.pack_message wird die zu übermittelnde Information in einen lokalen Pufferbereich übertragen. Anschließend wird dieser Bereich mit Hilfe der Funktion dbms_pipe.send_message in die benannte Pipe eingestellt, wobei der zu
448
PL/SQL-Programmierung
verwendende Name als Parameter übergeben werden muss. In unserem Beispiel stellen wir durch den verwendeten Aufruf also einen kleinen Text in die Pipe mit dem Namen „Hallo“, die, sofern sie noch nicht vorhanden ist, dabei automatisch angelegt wird. Die Prozedur pack_message verträgt aufgrund der mehrfach überlagernden Deklaration im Paket nahezu jeden gängigen Datentyp. Lediglich für die Datentypen raw und rowid gibt es im Paket entsprechende Prozedurvarianten (z.B. pack_message_ rowid). Die send_message-Funktion kann mit ein bis drei Parametern aufgerufen werden: := dbms_pipe.send_message(, <Timeout>, <Max.Länge>);
Wie Sie schon wissen, beschreibt der erste Parameter den Namen der zu verwendenden bzw. erstellenden Pipe. Der zweite Parameter legt fest, wie die Funktion auf den erfolgreichen Einstellvorgang wartet, bevor der Einstellversuch abgebrochen wird. Ohne Vorgabe dieses Parameters wartet send_message der in der Konstanten maxwait festgelegten Anzahl von Sekunden was zur Zeit etwa 1000 Tage entspricht. Mit Hilfe des dritten Parameters können Sie die maximale Größe der Pipe festlegen, wobei die Pipe ohne diesen Parameter maxpipesize Byte groß ist, wobei der aktuelle Wert der maxpipesize-Konstante 8192 Byte ist. Der Rückgabewert der send_message-Funktion ist vom Datentyp Integer und entspricht der Zahl 0, wenn der Einstellvorgang erfolgreich verlief. Im Falle eines Timeouts erhalten Sie den Wert 1 und die Beschreibung weiterer möglicher Rückgabewerte können Sie der Oracle-Beschreibung entnehmen. Auslesen der Pipe Die Entnahme von Informationen aus einer solchen Pipeline ist genauso einfach wie deren Einfüllung und entspricht im Wesentlichen dem umgekehrten Einstellvorgang. Zum Ausprobieren des nun folgenden Beispiels 5.29 sollten Sie mal wieder ein zweite Sitzung Ihres SQL-Editors starten und das im Listing 5.28 gezeigte Einstellbeispiel mit der einen und das nun folgende Lesebeispiel mit der anderen Sitzung ausführen. declare i integer; b varchar2(255); begin i:=dbms_pipe.receive_message('Hallo'); dbms_pipe.unpack_message(b); dbms_output.put_line(b); end; Listing 5.29: Auslesen von Informationen aus einer Pipe
Die Entnahme von Informationen erfolgt mit Hilfe der Funktion dbms_pipe. receive_message und führt zum Kopieren der in der Pipe befindlichen Daten in einen lokalen Zwischenpuffer. Von dort aus können Sie die gepufferten Daten mit
Einführung in PL/SQL
449
Hilfe der Prozedur dbms_pipe.unpack_message in entsprechende Variablen übertragen. Ähnlich wie beim Einstellen von Informationen in die Pipe, kann auch die receive_message-Funktion mit mehr als einem Parameter verwendet werden. := dbms_pipe.receive_message(, <Timeout>);
Der erste Parameter beschreibt wieder den Namen der zu verwendenden Pipe, wobei der zweite Parameter festlegt, wie lange die Funktion auf das Eintreffen von Nachrichten warten soll. Standardmäßig entspricht das wieder der maxwait-Konstante, d.h. ohne konkrete Vorgabe der maximalen Wartezeit warten wir bis zu 1000 Tage, bevor die Funktion erfolglos abbricht. Ebenfalls ähnlich wie beim Senden können Sie auch beim Empfang mit Hilfe des zurückgelieferten integerwertes Feststellen, ob der Vorgang erfolgreich war oder nicht. Die Zahl 0 steht auch hier wieder für den Erfolg und alle anderen Ergebnisse spezifizieren einen aufgetretenen Fehler, wobei der wichtigste wieder die Zeitüberschreitung mit dem Wert 1 ist. Die Kopie der Pipe-Information in eine lokale Arbeitsvariable erfolgt wie schon gesagt mit Hilfe der unpack_message-Prozedur. Genau wie beim Packen der Information liegt auch diese Prozedur im Paket wieder in mehrfacher Deklaration vor, so dass Sie sie mit allen gängigen Datentypen verwenden können. Dennoch müssen Sie natürlich wissen, welche Variable Sie beim Prozeduraufruf verwenden müssen, wenn als Information Texte, Zahlen oder Datumswerte möglich sind. Aus diesem Grund enthält das dbms_pipe-Paket die Funktion next_item_type, mit deren Hilfe Sie den Datentyp der empfangenen Information ermitteln können, wobei Sie die Bedeutung der von dieser Funktion zurückgelieferten Ergebnisse der Tabelle 5.14 entnehmen können: := dbms_pipe.next_item_type; next_item_type
Beschreibung
0
Keine Informationen vorhanden, Pipe ist leer.
6
Ergebnis ist vom Datentyp number.
9
Ergebnis ist vom Datentyp varchar2.
12
Ergebnis ist vom Datentyp date.
Tabelle 5.14: Bedeutung der von nest_item_type gelieferten Ergebnisse
Arbeitsweise der öffentlichen Pipes Wenn Sie nun mit der Arbeitsweise der Pipes ein wenig experimentieren möchten und die beiden Beispiele 5.28 und 5.29 in der vorliegenden Weise verwenden wollen, dann sollten Sie zunächst einmal das Skript 5.28 mit Hilfe einer entsprechenden Sitzung (einmal) ausführen. Anschließend müssen Sie das Skript 5.29 mit Hilfe einer anderen Sitzung ausführen und erhalten die entsprechende Bildschirmausgabe:
450
PL/SQL-Programmierung
Statement processed. Ich bin da
Wenn Sie das Skript 5.29 nun noch einmal ausführen, dann hängt Ihr SQL-Editor anschließend scheinbar, wobei das daran liegt, dass Ihr Skript die nächsten 1000 Tage auf Nachrichten in der Pipe „Hallo“ wartet. Also lassen Sie es nicht so lange warten, und starten das Skript 5.28 noch einmal, wodurch auch das in der anderen Sitzung laufende Programm sofort beendet wird. Neben der Erkenntnis, dass also auch ein wartender Prozess die für die Kommunikation verwendete Pipe eröffnen kann, bestätigt dieses Beispiel die grundsätzliche Arbeitsweise einer Pipe. Man kann sich das Ganze also wirklich als eine Art Pipeline vorstellen, in der auf der einen Seite jemand etwas einfüllt, was auf der anderen Seite entnommen wird, denn mit dem Abrufen von Informationen aus der Pipe werden diese dort gelöscht. Sie können dies noch einmal überprüfen, indem Sie das Beispiel 5.28 mehrmals hintereinander starten. Anschließend können Sie auch das Skript 5.29 entsprechend häufig aufrufen und erst nach einem nochmaligen Aufruf hängt die zugehörige Sitzung wieder, weil das Programm auf Nachrichten wartet. Verwenden privater Pipes Die Verwendung der privaten bzw. expliziten Pipes unterscheidet sich im eigentlichen Gebrauch nicht von der eben beschriebenen öffentlichen Varianten. Der wesentliche Unterschied besteht eigentlich darin, dass Sie die Pipe mit Hilfe einer speziellen Funktion anlegen müssen: := dbms_pipe.create_pipe( [, Größe]);
Der hierbei verwendete Name muss in der Datenbankinstanz eindeutig sein, d.h. der Anlageversuch schlägt fehl, wenn der Name schon für eine andere private oder öffentliche Pipe verwendet wird. Optional können Sie bei der Anlage auch wieder die maximale Größe des Meldungspools festlegen, wobei das Paket standardmäßig die in der Konstante maxpipesize gespeicherte Anzahl von Bytes (z.Zt. 8192) verwendet. Sofern die Anlage der neuen Pipe erfolgreich funktioniert entspricht der zurückgelieferte Wert der Zahl 0. Alle anderen Ergebnisse stehen für einen entsprechenden Fehler. Was für die Anlage gilt, ist entsprechend auf für die Löschung der Pipe anzuwenden, d.h. auch hier müssen Sie die Pipe wieder manuell aus dem System entfernen: := dbms_pipe.remove_pipe();
Hierzu übergeben Sie der remove_pipe-Funktion den Namen der zu löschenden Pipe und erhalten als Ergebnis die Zahl 0, wenn der Löschvorgang erfolgreich ausgeführt werden konnte. Verwalten der Pipes Zur Verwaltung der Pipes gibt es zwei interessante Funktionen im dbms_pipe-Paket. Die erste dieser beiden Funktionen heißt purge und führt zum Löschen aller in der
Einführung in PL/SQL
451
Pipe enthaltenen Nachrichten. Hierzu benötigt die Prozedur als Parameter den Namen der Pipe und, sofern es sich bei dieser um eine öffentliche Pipe handelt, wird sie nach dem Löschen aller Inhalte in einem zweiten Schritt ebenfalls automatisch vom System gelöscht. dbms_pipe.purge();
Die zweite Prozedur dient zum Löschen des lokalen Zwischenpuffers und heißt reset_buffer. dbms_pipe.reset_buffer;
Das Problem ist nämlich, dass wenn Sie den Puffer einmal mit Hilfe der Prozedur dbms_pipe.pack_message bestücken, dann können Sie dies nicht so ohne weiteres rückgängig machen, denn das nochmalige Ausführen der Prozedur packt eine weitere Nachricht in Puffer und führt nicht zum Überschreiben der dort bereits gespeicherten Informationen.
5.1.12 Verwenden von PL/SQL Zum Abschluss dieser PL/SQL-Einführung möchte ich noch einmal zusammenfassen, an welchen Stellen bzw. in welchen Objekten üblicherweise PL/SQL-Elemente verwendet werden und wie diese Objekte erstellt werden. Skripte Eine einfache Möglichkeit zur Verwendung von PL/SQL-Befehlen besteht in der Erstellung eines Skripts, das in der einfachsten Form aus genau einem Programmblock besteht, wobei auch die meisten der in diesem Buch gezeigten Beispiele auf einem solchen Skript basierten. In der Praxis können solche Skripte natürlich mit Hilfe einer der vorhandenen SQL-Editoren abgespielt werden. Aber auch in allen anderen Anwendungen, mit denen Sie gewöhnliche SQL-Abfragen an die Datenbank schicken können, besteht meistens die Möglichkeit ein Skript in Form eines PL/SQL-Blocks zu verwenden, wobei Sie in dem folgenden Listing 5.30 noch einmal dessen grundsätzliche Struktur finden. declare begin end; Listing 5.30: Struktur eines einfachen PL/SQL-Skripts
Funktionen Die selbsterstellten Funktionen enthalten üblicherweise PL/SQL-Sprachelemente, mit denen innerhalb der Funktion komplexe Berechnungen oder, in Abhängigkeit verschiedener Bedingungen, unterschiedliche Datenbankabfragen durchgeführt werden. Das in einer Funktion gekapselte Miniprogramm kann anschließend bei
452
PL/SQL-Programmierung
entsprechenden Zugriffsrechten innerhalb gewöhnlicher SQL-Abfragen oder bei der Erstellung andere PL/SQL-Programme verwendet werden. In diesem Workshop finden Sie im Kapitel 2.2.4 eine ausführliche Einführung in die Erstellung und Verwendung von selbsterstellten Funktionen. Daher ist das Listing 5.31 auch nur als Gedächtnisstütze zu verstehen, wie eine derartige Funktion für die meisten Aufgabenstellungen realisiert werden kann. create or replace function (<Parameterliste>) return is ; begin end; Listing 5.31: Grundsätzlicher Aufbau einer selbsterstellten Funktion
Prozeduren Wenn das erstellte Miniprogramm keinen speziellen Rückgabewert zurückliefern soll, dann wird aus der Funktion eine Prozedur. Damit beschränkt sich die Verwendbarkeit des Prozedurprogramms auf die direkte Ausführung oder die Verwendung in anderen PL/SQL-Programmen. In diesem Workshop finden Sie eine ausführlich Beschreibung zur Erstellung oder Verwendung von Prozeduren im Kapitel 2.2.9; hier soll mit Hilfe des Beispiels 5.32 nur noch einmal das grundsätzliche Schema der Prozedurerstellung gezeigt werden. create or replace procedure (<Parameterliste>) is
; begin end; Listing 5.32: Schema der Prozedurerstellung
Packages Bei den sogenannten Packages handelt es sich genau genommen schon um kleinere in der Datenbank gespeicherte Anwendungen. Ein Package entsteht durch die
Einführung in PL/SQL
453
Verschmelzung von Funktionen und Prozeduren zu einer Einheit, was aus folgenden Gründen sinnvoll sein kann:
X X
Im Falle einer gesamten Anwendung, die intern aus vielen einzelnen Funktionen und Prozeduren besteht, soll nur die Startfunktion nach außen sichtbar sein, d.h. alle anderen Routinen sollen nicht außerhalb des Pakets aufgerufen werden. Zusammenfassung von Funktionen und Prozeduren zu einer logischen Einheit, wie zum Beispiel dem Paket dbms_sql, in dem alle Funktionen und Prozeduren zur Erstellung dynamischer SQL-Anweisungen zusammengefasst sind. Das fördert in der Datenbank nicht nur die Übersicht über die insgesamt vorhandenen Programmobjekte, sondern vereinfacht auch die Erstellung wiederverwendbarer Programmteile, und auch deren Verteilung in andere Datenbanken.
Ein Package besteht aus einer Art Schnittstelle, in dem alle nach außen sichtbaren Funktionen und Prozeduren deklariert werden und dem eigentlichen Programmteil, der sich in einem zweiten Objekt, dem Package body befindet, d.h. hier findet die eigentliche Programmierung der im Package deklarierten Objekte statt. Werden im Package body zusätzliche Funktionen oder Prozeduren erstellt, so sind die nach außen unsichtbar, können also nur innerhalb des Pakets aufgerufen werden. In diesem Workshop haben wir die Pakete im Kapitel 2.2.8 behandelt und dabei auch entsprechende Beispiele erstellt. Das nun folgende Beispiel 5.33 soll noch einmal an das Schema erinnern, wie solche Packages grundsätzlich angelegt werden. create or replace package <Paketname> as -- Deklaration aller sichtbaren Funktionen, -- Prozeduren und globalen Variablen function (<Parameterliste>) return ; procedure (<Parameterliste>); end; / create or replace package body <Paketname> as -- Deklarieren lokaler Funktionen und Prozeduren function (<Parameterliste>) return ); procedure (<Parameterliste>); -- Erstellen der öffentlichen Objekte function (<Parameterliste>) return ) is ... übliches Schema zum erstellen einer Funktion end; procedure (<Parameterliste>) is ... übliches Schema zum erstellen einer Prozedur end; -Programmieren der lokalen Funktionen und Prozeduren
454
PL/SQL-Programmierung
begin -- Programmieren eines Initialisierungsteils, der beim -- Laden des Pakets ausgeführt wird. end; Listing 5.33: Schema zum Programmieren eines Packages
Trigger Bei den Triggern handelt es sich um die letzte Kategorie von Datenbankobjekten, bei denen üblicherweise PL/SQL-Sprachelemente zum Einsatz kommen. Bei den Triggern handelt es sich um Programme, die beim Ausführen bestimmter Transaktionen automatisch gestartet werden. Hierzu wird ein solches Programm einer Datenbanktabelle und einer oder mehreren Änderungstransaktionen zugeordnet. Auf diese Weise können Sie für eine bestimmte Tabelle beispielsweise ein Programm erstellen, dass immer dann ausgeführt wird, wenn in dieser Tabelle eine neuer Datensatz eingefügt wird. In unserem Workshop werden die Trigger und ihre Funktionsweise im Kapitel 2.2.18 ausführlich behandelt, so dass hier (vgl. Listing 5.34) nur noch einmal an das Schema zu ihrer Erstellung erinnert werden soll. create or replace trigger before|after on begin end; Listing 5.34: Schema zur Erstellung von Datenbanktriggern
5.2
Anwendungsbeispiele
Nach dieser Einführung in die PL/SQL-Programmierung möchte ich dieses Thema noch mit Hilfe einiger Anwendungsbeispiele abrunden. Die dabei gezeigten Beispiele sind natürlich lauffähig, müssen in Produktivbedingungen jedoch sicherlich erweitert bzw. angepasst werden, d.h. es handelt sich hierbei nicht um fertige wiederverwendbare Tools, sondern um Anregungen für Ihre eigene Entwicklungsarbeit.
5.2.1
Benutzerverwaltung
Im Rahmen des vierten Kapitels haben Sie gesehen, wie Sie neue Datenbankbenutzer anlegen und deren Zugriffsrechte verwalten können. Zwar können diese Arbeiten mit Hilfe der vorhandenen Werkzeuge vollständig erledigt werden, jedoch ist
Anwendungsbeispiele
455
das gigantische Arbeit, sofern die Datenbank über entsprechend viele Tabellen besitzt und die Fachabteilung viele Benutzerkennungen mit vor allem unterschiedlichen Zugriffsprofilen fordert. Dabei kann man die Benutzer und die zugehörige Rechteverwaltung oftmals schon mit einfachen Maßnahmen dramatisch unterstützen, was im Rahmen dieses Beispiels gezeigt werden soll. Konkret geht es hier um die Verwaltung von Zugriffsrechten, die mit Hilfe einer entsprechenden Tabelle definiert und anschließend durch den Aufruf einer Prozedur erstellt bzw. aktualisiert werden können. Diese Tabelle nennen wir in unserem Beispiel benutzer_rechte und kann mit Hilfe des Listings 5.35 erzeugt werden; die Bedeutung der einzelnen Spalten können Sie der Tabelle 5.15 entnehmen und das Listing 5.36 dient zum Einfügen eines Zugriffsprofils für den Benutzer „ubeipsiel1“. drop table benutzer_rechte; / create table benutzer_rechte ( benutzer varchar2(30) not null, record varchar2(30) not null, typ char(1) default 'E' not null, aendern char(1) default 'N' not null, constraint b_r_typ check (typ in ('E','G')), constraint b_r_aendern check (aendern in ('N','Y')), constraint benutzer_rechte_pk primary key (benutzer, record) using index tablespace indx storage (initial 10K next 10K) ) tablespace usr storage (initial 10K next 10K); / commit; Listing 5.35: Tabelle zum Speichern der gewünschten Zugriffsrechte
Spalte
Bedeutung
benutzer
Benutzer oder Gruppe, für die der jeweilige Zugriff eingestellt werden soll.
record
Datensatz oder Datensatzgruppen (z.B. „pers%“), für die Zugriffsrechte erteilt werden sollen.
typ
legt fest, ob es sich um einen einzelnen Datensatz „E“ oder eine Satzgruppe „G“ handelt.
aendern
bestimmt, ob die jeweiligen Datensätze auch geändert „Y“ werden dürfen.
Tabelle 5.15: Aufbau der Tabelle benutzer_rechte
456
PL/SQL-Programmierung
insert into benutzer_rechte values ('UBEISPIEL1', 'PERSONALIEN', 'E', 'Y'); insert into benutzer_rechte values ('UBEISPIEL1','L%','G','N'); insert into benutzer_rechte values('UBEISPIEL1','B%','G','N'); commit; Listing 5.36: Einfügen des Zugriffsprofils für den Benutzer „ubeispiel1“
Das Programm Die Programmierung der benötigten Funktionen erfolgt mit Hilfe eines Packages, das Sie mit Hilfe des Listings 5.37 anlegen können und das insgesamt zwei sichtbare Prozeduren enthält. Die erste Prozedur heißt create_user und dient zur Anlage bzw. Aktualisierung einer einzelnen Benutzerkennung, wohingegen die zweite Prozedur zur Aktualisierung aller Benutzer dient, was beispielsweise nach umfangreichen Datenbankwartungen hilfreich ist. Die Ausprogrammierung der einzelnen Prozeduren können Sie dem Listing 5.38 entnehmen. create or replace package benutzer as procedure create_user(userid in varchar2); procedure create_all_user; end; Listing 5.37: Definition des Pakets zur Benutzerverwaltung
create or replace package body benutzer as /* -----------------------------------------------------------------Löschen vorhandener Zugriffsberechtigungen -------------------------------------------------------------------*/ procedure revoke_objects(userid in varchar2) as -- 'gedachtes' declare cursor revoke_objects is select distinct table_name from all_tab_privs where grantee = userid; cid integer; rc integer; begin cid := dbms_sql.open_cursor; for revoke_objects_rec in revoke_objects loop dbms_sql.parse(cid, 'revoke all on ' || revoke_objects_rec.table_name || ' from ' || userid, dbms_sql.native);
Anwendungsbeispiele
rc := dbms_sql.execute(cid); end loop; dbms_sql.close_cursor(cid); end revoke_objects; /* -----------------------------------------------------------------Ausführen eines grant-SQL-Befehls -------------------------------------------------------------------*/ procedure grant_sql(sql_txt in varchar2) as -- 'gedachtes' declare cid integer; rc integer; begin cid := dbms_sql.open_cursor; dbms_sql.parse(cid, sql_txt, dbms_sql.native); rc := dbms_sql.execute(cid); dbms_sql.close_cursor(cid); dbms_output.put_line(sql_txt || ' -> ok'); exception when others then dbms_output.put_line('Fehler bei ' || sql_txt); dbms_output.put_line(sqlerrm); end grant_sql; /* -----------------------------------------------------------------Anlage bzw. Aktualisieren eines einzelnen Benutzers oder einer einzelnen Gruppe. -------------------------------------------------------------------*/ procedure create_user(userid in varchar2) as -- 'gedachtes' declare cursor aktuelle_rechte is select * from benutzer_rechte where benutzer = userid order by typ desc; cursor records (in_record varchar2) is select table_name from all_tables where table_name like in_record union
457
458
PL/SQL-Programmierung
select view_name from all_views where view_name like in_record; grant_txt varchar2(255):=' '; begin -- Löschen aller vorhandenen Zugriffsrechte revoke_objects(userid); -- Alle Datensätze des Profils bearbeiten for aktuelle_rechte_rec in aktuelle_rechte loop if aktuelle_rechte_rec.aendern = 'Y' then grant_txt := 'grant all on '; else grant_txt := 'grant select on '; end if; if aktuelle_rechte_rec.typ = 'E' then grant_sql(grant_txt || aktuelle_rechte_rec.record || ' to ' || userid); else for records_rec in records(aktuelle_rechte_rec.record) loop grant_sql(grant_txt || records_rec.table_name || ' to ' || userid); end loop; end if; end loop; commit; end create_user; /* -----------------------------------------------------------------Aktualisieren aller Benutzer und Rollen -------------------------------------------------------------------*/ procedure create_all_user as -- 'gedachtes' declare cursor alle_benutzer is select distinct benutzer from benutzer_rechte; begin for alle_benutzer_rec in alle_benutzer loop create_user(alle_benutzer_rec.benutzer); end loop;
Anwendungsbeispiele
459
end create_all_user; /* -----------------------------------------------------------------Initialisierungsteil -------------------------------------------------------------------*/ begin dbms_output.enable(50000); end benutzer; Listing 5.38: Programm zur automatischen Generierung von Benutzerrechten
Je nachdem, ob Sie die Rechte für einen einzelnen Benutzer oder ob Sie alle gespeicherten Benutzerrechte aktualisieren möchten, müssen Sie die Prozedur create_user oder create_all_users starten, was durch folgende execute-Anweisung möglich ist: execute benutzer.create_user('UBEISPIEL1'); execute benutzer.create_all_users;
Programmbeschreibung Beginnen wir bei der Beschreibung mit der Prozedur create_all_users. Diese Prozedur verwendet den Cursor alle_benutzer, mit dem die Namen aller in der Tabelle benutzer_rechte gespeicherten Benutzer- bzw. Rollennamen selektiert werden. Innerhalb der Prozedur wird dieser Cursor mit Hilfe einer for loop-Schleife durchlaufen und für jeden gefundenen Namen wird die Prozedur create_user aufgerufen. Die Prozedur create_user erhält beim Aufruf die Kennung des zu aktualisierenden Benutzers, egal ob sie direkt oder aus der create_all_users-Prozedur heraus aufgerufen wird. Innerhalb dieser Prozedur rufen wir im ersten Schritt die Prozedur revoke_objects auf, mit der dem übergebenen Benutzer zunächst einmal alle eventuell zugeteilten Zugriffsrechte entzogen werden. Anschließend durchläuft die Prozedur mit Hilfe einer Schleife den Cursor aktuelle_rechte, der für den aktuellen Benutzer eine Selektion der für ihn in der Tabelle benutzer_rechte gespeicherten Datensätze liefert. Innerhalb dieser Schleife wird zunächst in Abhängigkeit des Feldes benutzer_rechte. aendern der Anfang einer typischen grant-Anweisung generiert und in der Variablen grant_txt gespeichert. Sofern es sich bei dem aktuellen Datensatz um eine Zugriffssteuerung vom Typ „E“ handelt, dann rufen wir die Prozedur grant_sql auf, innerhalb derer der entsprechende grant-Befehl ausgeführt wird. Hierzu geben wir der grant_sql-Funktion als Argument die vollständige Anweisung mit, die beim Aufruf entsprechend zusammengestellt wird. Handelt es sich bei dem aktuellen Datensatz um eine Gruppe, dann starten wir den anderen Cursor records, der die Namen aller dem Suchmuster entsprechenden Tabellen oder Views liefert. Dabei führt auch hier jeder gefundenen Datensatz zu einem Aufruf der grant_sql-Prozedur, um die entsprechende grant-Anweisung abzusetzen.
460
PL/SQL-Programmierung
Innerhalb der grant_sql-Prozedur wird der übergebene grant-Befehl mit Hilfe einer dynamsichen SQL-Anweisung ausgeführt. Tritt dabei ein Fehler auf, beispielsweise weil der eingetragene Benutzer nicht angelegt ist, die aufgeführte Tabelle nicht existiert oder der ausführende Benutzer selbst nicht das Recht hat, die Zugriffsrechte zu erteilen, dann führt das aufgrund der programmierten exception-Routine zur Ausgabe einer entsprechenden Fehlermeldung. Mit Hilfe der Prozedur revoke_objects werden dem aktuellen Benutzer zunächst alle früher erteilten Zugriffsrechte entzogen, wobei diese mit Hilfe einer Abfrage auf die View all_tab_privs ermittelt werden. Diese Abfrage wird in der Prozedur mit Hilfe des Cursors revoke_objects ausgeführt, der innerhalb einer for loop-Schleife abgearbeitet wird, wobei für jeden gefundenen Datensatz eine entsprechende revokeAnweisung generiert und abgesetzt wird.
5.2.2
Änderungsprotokollierung
Manchmal ist es notwendig, alle in der Datenbank durchgeführten Änderungen festzuhalten, wobei neben dem zugehörigen Verursacher (Benutzerkennung, Datum und Zeit) auch der alte Zustand des Datensatzes festgehalten werden soll. Zu diesem Zwecke benötigen wir natürlich eine spezielle Protokolltabelle, in der die geänderten Datensätze festgehalten werden. Solche Protokolldateien können aus Gründen der Nachvollziehbarkeit oder auch für andere Prozesse (z.B. Schnittstellen) angelegt werden. Da mit Hilfe des Protokolls quasi ein Abbild der Zustände vor bzw. nach der Änderung entsteht, besteht die Möglichkeit, die reinen Änderungen herauszufiltern und entsprechend zu reagieren. Wir werden die Protokollierung am Beispiel der Gehaltstabelle durchführen, d.h. wir benötigen eine Tabelle, die dem Aufbau der Tabelle gehalt entspricht und zusätzlich noch verschiedene Statusfelder enthält. Statusfeld
Beschreibung
audit_userid
enthält die Benutzerkennung, die für die Datenänderung verantwortlich ist.
audit_zeit
Zeitstempel, an der die Änderung durchgeführt wurde.
audit_typ
beschreibt die durchgeführte Änderungstransaktion. A: Es wurde ein neuer Datensatz angelegt, wobei der im Protokoll befindliche Datensatz diesem neuen Satz entspricht. L: Ein Datensatz wurde gelöscht. Dabei wird der gelöschte Satz ins Protokoll gestellt. U: Ein vorhandener Satz wurde geändert, das Protokoll beinhaltet jetzt den Datensatz vor der Änderung. K: Es wurde eine Schlüsseländerung durchgeführt. Eine Schlüsseländerung wird wie eine Löschung und Neuanlage protokolliert, wobei der K-Satz den gelöschten Datensatz enthält. N: Das ist bei einer Schlüsseländerung der zweite Eintrag im Protokoll, wobei diesmal der neue bzw. geänderte Datensatz festgehalten wird.
Tabelle 5.16: Bedeutung der einzelnen Statusfelder in unserer Protokolltabelle
Anwendungsbeispiele
461
Die Protokolltabelle selbst legen wir, wie Sie im Listing 5.39 sehen, mit Hilfe eines kleinen Tricks an, indem wir die Struktur der Gehaltstabelle durch eine Abfrage und die benötigten Statusfelder mit Hilfe geeigneter Ausdrücke erzeugen. Die Abfrage gestalten wir dabei so, dass keine Datensätze selektiert werden, so dass nur die Struktur der Tabelle kopiert wird. drop table audit_gehalt; / create table audit_gehalt tablespace usr storage (initial 10K next 10K) as select user as audit_userid, sysdate as audit_zeit, 'E' as audit_typ, a.* from gehalt a where persnr = 'xx'; / alter table audit_gehalt modify ( audit_userid not null, audit_zeit not null, audit_typ not null); / commit; Listing 5.39: Anlage der Protokolltabelle
Das Programm Die Programmierung dieser Aufgabenstellung erfolgt mit Hilfe eines Triggers. Dieser Trigger soll nach Durchführung der Änderung für jeden Datensatz gestartet werden, so dass wir mit Hilfe recht einfacher SQL-Anweisungen die benötigten Protokollsätze erzeugen können. Das Muster für einen solchen Trigger finden Sie in dem nachfolgenden Beispiel 5.40. create or replace trigger audit_gehalt after update or insert or delete on gehalt for each row declare i_flag char(1); d_flag char(1); begin if updating('PERSNR') or updating('LFDNR') or updating('GAB') then i_flag := 'N'; d_flag := 'K'; else
462
PL/SQL-Programmierung
i_flag := 'A'; if deleting then d_flag := 'L'; else d_flag := 'U'; end if; end if; -- Einfügetransaktion oder Ändern des Primärschlüssels if inserting or i_flag = 'N' then insert into audit_gehalt values (user, sysdate, i_flag, :new.persnr, :new.lfdnr, :new.gab, :new.kst, :new.gehalt, :new.zulage); end if; -- Bei allen Änderungen werden die alten (:old) Werte protokolliert if deleting or updating then insert into audit_gehalt values (user, sysdate, d_flag, :old.persnr, :old.lfdnr, :old.gab, :old.kst, :old.gehalt, :old.zulage); end if; end; Listing 5.40: Verwenden eines Triggers zur Änderungsprotokollierung
Programmbeschreibung Bei einer solchen Aufgabenstellung handelt es sich um ein Paradebeispiel für einen Trigger, der nach Durchführung der Datenänderungen ausgeführt werden soll. Kommt es vor oder während der Änderungen, beispielsweise wegen eines VorabTriggers oder einer Constraint-Verletzung zum Abbruch der Transaktion, so wird dieser Trigger erst gar nicht mehr gestartet, d.h. es kommt auch nicht zum Erzeugen der Protokollsätze. Der Trigger, der mit dem Namen audit_gehalt für die Tabelle gehalt erstellt wird, wird aufgrund der verwendeten Klauseln after update or insert or delete on gehalt for each row
von der Datenbank automatisch nach jeder Änderungstransaktion für jeden betroffenen Datensatz gestartet. Da wir nichts anderes vorgeben stehen uns somit die alten und neuen Werte des geänderten Datensatzes mit Hilfe der Bindungsvariablen :old und :new zur Verfügung. Normalerweise führt jede Datensatzänderung zu einem entsprechendem Eintrag in der Protokolltabelle. Dabei wird mit Ausnahme der neu eingefügten Datensätze immer der Wert vor der Änderung im Protokoll gespeichert. Ebenfalls eine Ausnahme stellt die Durchführung einer Schlüsseländerung dar, da in dem Fall zwei Datensätze in das Protokoll geschrieben werden.
Anwendungsbeispiele
463
Das ist der einzige Grund, warum wir innerhalb des Triggerprogramms überhaupt Programmlogik benötigen und die vom Trigger bereitgestellten Änderungsdaten nicht einfach an die Protokolltabelle weiterleiten können. Konkret überprüfen wir als Erstes, ob eines der drei Primärschlüsselfelder geändert wurde und setzen in dem Fall die beiden Protokollcodes „N“ und „K“ in die entsprechenden Variablen i_flag bzw. d_flag. if updating('PERSNR') or updating('LFDNR') or updating('GAB') then i_flag := 'N'; d_flag := 'K'; else i_flag := 'A'; if deleting then d_flag := 'L'; else d_flag := 'U'; end if; end if;
In den anderen Fällen speichern wir je nachdem ob Datensätze angelegt, gelöscht oder geändert wurden die Codes „A“, „L“ oder „U“ in den Variablen. Im nächsten Schritt erfolgt das Anlegen der Protokollsätze. Dabei müssen die neuen Daten der Gehaltstabelle ins Protokoll kopiert werden, wenn entweder neue Datensätze eingefügt wurden oder eine Primärschlüsseländerung vorliegt, was man jetzt mit Hilfe der Variablen i_flag erkennen kann. -- Einfügetransaktion oder Ändern des Primärschlüssels if inserting or i_flag = 'N' then
In allen anderen Fällen müssen die :old-Werte in das Protokoll übertragen werden, wobei aufgrund der Programmstruktur bei einer Schlüsseländerung beide if-Zweige durchlaufen werden. -- Bei allen Änderungen werden die alten (:old) Werte protokolliert if deleting or updating then
In allen Fällen erfolgt die Bestückung der zusätzlichen Protokollspalten audit_userid und audit_zeit mit Hilfe der Systemfunktionen user und sysdate. Wenn Sie nun einmal nacheinander die im Listing 5.41 gezeigten Änderungstransaktionen ausführen, dann können Sie anschließend die Funktionsweise des Triggers mit Hilfe der Protokolleinträge kontrollieren. update gehalt set gehalt = 7455 where persnr = '7000188'; insert into gehalt values ('7000188', 0, to_date('01.01.2001','DD.MM.YYYY'), 'MARK', 6400, 300);
464
PL/SQL-Programmierung
update gehalt set gab = to_date('01.04.2001','DD.MM.YYYY') where persnr = '7000188' and gab < to_date('01.01.2002','DD.MM.YYYY'); delete gehalt where persnr = '7000188' and gab < to_date('01.01.2002','DD.MM.YYYY'); Listing 5.41: Ausführen verschiedener Änderungstransaktionen
Wie Sie nun der Protokolltabelle bzw. dem Listing 5.42 entnehmen können, werden ab jetzt alle Änderungen der Gehaltstabelle einwandfrei aufgezeichnet. SQLWKS> 2> 3> 4> 5> 6>
select audit_userid, to_char(audit_zeit,'DD.MM.YYYY HH24:MI:SS'), audit_typ, persnr, lfdnr, gab, kst, gehalt, zulage from audit_gehalt where persnr = '7000188' order by 2
AUDIT_USERID TO_CHAR(AUDIT_ZEIT, A PERSNR LFDNR GAB KST GEHALT ZULAGE ------------------- ------------------- ----------- ---------- ---------------- ---------- ---------- --------UBEISPIEL 04.10.2000 18:46:41 U 7000188 0 01-JAN-07 MARK 6240 250 UBEISPIEL 7000188
04.10.2000 19:18:04 A 0 01-JAN-01 MARK
6400
300
UBEISPIEL 7000188
04.10.2000 19:18:04 N 0 01-APR-01 MARK
6400
300
UBEISPIEL 7000188
04.10.2000 19:18:04 K 0 01-JAN-01 MARK
6400
300
UBEISPIEL 7000188 5 rows selected.
04.10.2000 19:18:04 L 0 01-APR-01 MARK
6400
300
Listing 5.42: Auswertung des Änderungsprotokolls
Wie Sie dem Protokoll entnehmen können, wurde zunächst um 18:46 eine Datensatzänderung durchgeführt, wobei das Protokoll die alten Tabellenwerte anzeigt. Anschließend wurde um 19:18 ein neuer Datensatz zum 01.01.2001 angelegt, der anschließend auf den 01.04.2001 verschoben und danach gelöscht wurde. Dabei kann man die letzten Transaktion natürlich nicht mehr anhand des Zeitstempels unterscheiden, wenn sie, so wie bei mir, innerhalb einer Abfrage ausgeführt werden. Sofern das bei der konkreten Aufgabenstellung ein Problem darstellt, dann müssen Sie die Protokolltabelle noch um ein weiteres Feld erweitern, in dem Sie
Anwendungsbeispiele
465
beispielsweise eine laufende Nummer mit Hilfe einer Sequence (vgl. Kapitel 2.2.12) speichern. Werden solche Protokolltrigger im größeren Umfang, d.h. für viele Tabellen, gebraucht, dann würde ich mir wiederum eine Prozedur oder ein Paket erstellen, dass die Trigger oder zumindest den benötigten Triggercode automatisch generiert. Hierzu könnte man sich wieder eine Arbeitstabelle anlegen, in der man die Tabellen nebst zugehöriger Protokolltabelle speichert. Wie Sie gesehen haben, sind die meisten Anweisungen im Triggerprogramm konstant, d.h. die einzelnen Trigger unterscheiden sich eigentlich nur durch die abgeprüften Primärschlüsselfelder und durch die konkret verwendete insert-Anweisung zum Erstellen des Änderungsprotokolls. Im Kapitel 3.5.1 dieses Workshops haben Sie ein Muster gesehen (vgl. Listing 3.44), wie Sie mit Hilfe einer speziellen Abfrage die Struktur einer Tabelle und damit den String für die values-Liste erzeugen könnten. Im Beispiel 3.47 (gleiches Kapitel) finden Sie ein Muster dafür, wie Sie die im Primärschlüssel vorhandenen Felder ermitteln können, so dass Sie ebenfalls in der Lage sind, die benötigten updating-Prüfungen zu generieren.
5.2.3
Komplexe Integritätsbedingungen
Wie Sie schon wissen, können Integritätsbedingungen manchmal mit Hilfe der sogenannten Constraints in der Datenbank angelegt werden. Das gilt auch dann, wenn die Entscheidung über die Ordnungsmäßigkeit eines Datensatzes von der Existenz eines Satzes aus einer anderen Tabelle (vgl. Abb. 2.13) abhängt. Auch solche referentiellen Integritätsbedingungen können im Rahmen der Tabellendefinition mit Hilfe der foreign key-Klausel angelegt werden, weshalb man in dem Zusammenhang auf von der deklarativen referentiellen Integrität spricht. Manchmal sind die Datenstrukturen allerdings ungeeignet oder die Prüfroutinen so komplex, dass die zugehörigen Regeln nicht mit Hilfe gewöhnlicher Constraints abgebildet werden können. In solchen und anderen Fällen können Ihnen Trigger weiterhelfen, da Sie hierbei als Prüfroutine komplexe PL/SQL-Programme erstellen können. Damit sind nicht mehr die enge formale Struktur einer Constraint-Klausel, sondern nur noch die Komplexität und eventuell die Laufzeit der erstellten Prüfregel Ihre Gegner. Automatisches Löschen Als erstes Beispiel zu diesem unerschöpflichen Themenkomplex wollen wir uns mit dem kaskadierenden Löschen voneinander abhängiger Datensätze beschäftigen. Betrachen Sie hierzu zunächst einmal die Abbildung 5.2, in der Sie noch einmal einen Ausschnitt unserer Datenbankstruktur finden.
466
PL/SQL-Programmierung
personalien (persnr)
bvs (persnr, lfdnr)
lohnarten
gehalt
(persnr, lfdnr, gab)
(persnr, lfdnr, gab)
Abbildung 5.2: Auszug der Musterdatenbank mit der Struktur der Stammdatentabellen
Die nun folgende erste Aufgabe besteht also darin, beim Löschen eines Datensatzes auch die jeweils abhängigen Sätze automatisch mit Hilfe geeigneter Trigger zu löschen. Als Erstes programmieren wir nun den benötigten Löschtrigger für die Tabelle personalien (vgl. Beispiel 5.43), in dem wir nichts weiter zu tun brauchen, als für die gelöschte Personalnummer eine Löschanweisung auf die bvs-Tabelle abzusetzen. create or replace trigger personalien_delete after delete on personalien for each row begin delete bvs where persnr = :old.persnr; end; Listing 5.43: Löschweitergabe an abhängige Personalstamm-Tabellen
Das zweite Beispiel 5.44 ist natürlich genauso einfach, denn beim Löschen eines Datensatzes der bvs-Tabelle geben wir den Löschbefehl an die Tabellen lohnarten und gehalt weiter. create or replace trigger bvs_delete after delete on bvs for each row begin delete lohnarten where persnr = :old.persnr and lfdnr = :old.lfdnr; delete gehalt where persnr = :old.persnr and lfdnr = :old.lfdnr; end; Listing 5.44: Löschweitergabe an bvs-abhängige Tabellen
Anwendungsbeispiele
467
Prüfbedingungen Nachdem wir nun die Löschabhängigkeiten mit Hilfe eines Triggers realisiert haben, geht es im zweiten Schritt darum, mit Hilfe dieses Features eine Prüfbedingung zu erstellen. Konkret möchte ich das am Beispiel der Kostenstellen- und Gehaltstabelle demonstrieren, die durch das gemeinsame Feld kst miteinander verbunden sind. Bei solchen Prüfbedingungen tut man nach meiner Erfahrung gut daran, die gewünschten Regelwerke nach den Kategorien falsch und unerwünscht zu unterscheiden, wobei die folgenden Bedingungen sicherlich zur ersten Kategorie gehören:
X
X X
Verwenden einer Kostenstelle zu einem Gültigkeitstermin, an dem diese noch gar nicht definiert ist, d.h. es gilt gehalt.gab < min(kostenstelle.gab). Dieser Effekt muss auch verhindert werden, wenn das Gültigkeitsdatum der Kostenstelle im Nachhinein verändert wird. Löschen einer Kostenstelle, wenn diese noch in irgendeinem Gehaltsdatensatz verwendet wird. Dahingegen ist es lediglich unerwünscht, inaktive (status = „I“) Kostenstellen in aktuellen Gehaltsdatensätzen zu verwenden. Dies unterstreicht man in einer konkreten Anwendung sicherlich dadurch, dass der Dialog in Abhängigkeit der Gültigkeit des Gehaltsdatensatzes nur aktive Kostenstellen anzeigt oder beim Speichern einer Gehaltsänderung eine Hinweismeldung generiert, dass die aktuelle Kostenstelle zur Zeit eigentlich inaktiv ist.
Werden solche Regeln, die im Normalfall zwar beachtet und im Ausnahmefall aber genauso gut umgangen werden dürfen, entsprechend restriktiv in der Datenbank abgelegt, dann erhält man schnell Systeme, die eigentlich nur noch dann funktionieren, wenn niemand mehr Änderungen eingibt. Würde man in unserem Beispiel versuchen, die Benutzung inaktiver Kostenstellen restriktiv zu verhindern, dann müssten Sie sich mit folgenden Herausforderungen beschäftigen:
X
X
Unsere bisherigen Abfragen, in denen wir abhängige Datensätze mit Hilfe von max-Unterabfragen selektiert haben, spiegeln genau genommen immer nur eine Zeitpunktsbetrachtung wieder. Die Prüfung solcher Bedingungen muss aber immer über ganze Zeiträume erfolgen, denn obwohl die Kostenstelle zum aktuellen Gehaltstermin noch gültig ist, kann sie ja schon ab morgen auf inaktiv gesetzt sein. Prüfungen müssten also immer vom Gültigkeitstermin des geänderten Datensatzes bis zum implizit vorgegebenen Gültig-bis-Datum, das sich durch eine eventuell folgende Historie ergibt, durchgeführt werden. Soll die Verwendung inaktiver Sätze also wirklich restriktiv verhindert werden, so muss dies schon beim Design des Datenmodells berücksichtigt werden, wobei unser Datenmodell hierfür eigentlich nicht geeignet ist. Alternativ kann natürlich auch die Möglichkeit zur Durchführung von Datenänderungen mit Hilfe von Änderungsabfragen verzichtet werden und stattdessen werden alle Änderungen mit Hilfe von Prozeduren durchgeführt, die alle neuen Werte als Parameter erhalten. Mit Hilfe solcher Prozeduren wäre es auch in unserem Datenmodell möglich, die Verwendung inaktiver Sätze zu verhindern.
468
X
PL/SQL-Programmierung
Scheinbare Verstöße gegen die Verwendung inaktiver Kostenstellen sind bei genauerer Betrachtung manchmal gar keine. Betrachtet man nur die Gehaltsdaten, so wird dort wegen einer fehlenden Historie noch eine inaktive Kostenstelle verwendet. Da der Mitarbeiter aber vielleicht schon seit langem ausgetreten ist, bedeutet dies, dass auch die Gehaltshistorie eigentlich inaktiv ist, was aber erst durch Hinzuziehung weiterer Datensätze (z.B. bvs-Tabelle) festgestellt werden kann. Somit müssten auf einmal auch die Beschäftigungsdaten die Kostenstellenintegrität überwachen, obwohl diese Tabelle eigentlich gar nichts mit den Kostenstellen zu tun hat. In der Praxis sind solche Abhängigkeiten häufig unvorstellbar komplex. Man hat es praktisch mit einer Art Spinnennetz zu tun, wobei die Regel in der Mitte sitzt und von allen Seiten am Netz gezogen und gerüttelt wird.
Aus den soeben geschilderten Gründen sollen die hier vorgestellten Trigger natürlich nur dazu dienen, echte Fehler zu verhindern. Dabei beginnen wir im Beispiel 5.45 mit der Gehaltstabelle und erstellen einen Trigger, der die Verwendung ungültiger Kostenstellen verhindert. create or replace trigger gehalt_kst after insert or update of gab on gehalt for each row declare ct integer; begin select count(*) into ct from bvs b, kostenstelle a where b.persnr = :new.persnr and b.lfdnr = :new.lfdnr and a.unternehmen = b.unternehmen and a.kst = :new.kst and gab = (select max(gab) from kostenstelle a1 where a1.unternehmen = a.unternehmen and a1.kst = a.kst and a1.gab <= :new.gab ); if ct = 0 then raise_application_error(-20000,'Kostenstelle ungültig'); end if; end; Listing 5.45: Trigger zur Überwachung der in den Gehaltsdaten verwendeten Kostenstellen
Der Trigger wird nach der Durchführung bestimmter Änderungsabfragen gestartet und kann somit als letzte Instanz noch den Abschluss der zugehörigen Transaktion verhindern. Konkret wird er beim Einfügen neuer Datensätze und beim Ändern des Gültigkeitsdatums gestartet.
Anwendungsbeispiele
469
Der Zugriff auf die Kostenstellentabelle kann wegen des Unternehmens nur durch Einbeziehung der Beschäftigungsdaten erfolgen, da dort das Unternehmen des Mitarbeiters gespeichert ist. Konkret zählt die verwendete Abfrage die Anzahl der Kostenstellenhistorien, die kleiner oder gleich dem angelegten bzw. geänderten Gehaltsdatensatz sind. Entspricht diese Anzahl dem Wert 0, d.h. es sind keine Historien vorhanden, so ist die im Gehaltsdatensatz verwendete Kostenstelle ungültig. Das Gegenstück der benötigten Prüfung findet innerhalb der Kostenstellen statt. Wird eine Kostenstelle gelöscht oder ihr Gültigkeitsdatum geändert, dann ist das nur dann möglich, wenn auch im Anschluss daran noch jeder Datensatz der die geänderte Kostenstelle verwendet, diese bei einer umgekehrten Abfrage mit seinem Gültigkeitsdatum wiederfindet. Sofern Ihnen nicht ganz klar ist, was ich damit gemeint habe, dann wird das mit Hilfe der folgenden Abbildung 5.3 sicherlich etwas klarer.
Kostenstelle k1
k2 g1
g2
k3
Gehalt g3
k4 g4
Abbildung 5.3: Darstellung des Zusammenhangs zwischen Gehältern und Kostenstellen
Die Abbildung 5.3 zeigt Ihnen einen konsistenten Zustand zwischen einer Gehaltsund Kostenstellenhistorie, denn aus Sicht des Gehaltsdatensatzes findet jede Historie zu ihren Gültigkeitsdaten g1 bis g4 einen dazu passenden Satz in der Kostenstellentabelle (g1 -> k1, g2 -> k1, g3 -> k2 usw.). Würde nun die erste Kostenstellenhistorie gelöscht oder das Gültigkeitsdatum so geändert, dass es hinter dem Datum g1 liegt, dann entstünde hierdurch eine inkonsistente Situation, denn anschließend fände der erste Gehaltsdatensatz keine Kostenstelle mehr. create or replace trigger kostenstelle_kst before delete or update of unternehmen, kst, gab on kostenstelle for each row declare ct integer; begin select count(*) into ct from gehalt a, bvs b where b.persnr = a.persnr and b.lfdnr = a.lfdnr and b.unternehmen = :old.unternehmen and a.kst = :old.kst and not exists(select 1 from kostenstelle c where c.unternehmen = :old.unternehmen
470
PL/SQL-Programmierung
and c.kst = :old.kst and c.gab <= a.gab ); if ct > 0 then raise_application_error(-20000,'Kostenstellenänderungen wegen vorhandener Verknüpfungen nicht möglich.'); end if; end; Listing 5.46: Überwachung der Konsistenz beim Löschen einer Kostenstelle
Im Rahmen des Triggerprogramms analysieren wir einfach den Datenbestand dahingehend, ob es Gehaltsdatensätze gibt, für die es keine passende Kostenstelle gibt, was wir mit Hilfe einer entsprechenden not exists-Unterabfrage ermitteln. Was beim Test des Programms allerdings dabei herauskommt, dass können Sie dem Listing 5.47 entnehmen. delete kostenstelle where kst = 'PSOFT'; ORA-04091: table SYSTEM.KOSTENSTELLE is mutating, trigger/function may not see it Listing 5.47: Das mutating-Syndrom
Das Problem besteht nämlich darin, dass Sie innerhalb eines satzweise arbeitenden Triggers die zugrundeliegende Tabelle selbst nicht lesen dürfen. Falls Sie es bei der Programmierung doch einmal tun, dann erhalten Sie bei der Ausführung des Triggers die eben gezeigte Fehlermeldung, wobei dabei das weitere Dilemma darin besteht, dass Sie nur mit erheblichen Programmieraufwand aus dieser Zwickmühle herauskommen. Bevor ich nun zu einem konkreten Lösungsvorschlag komme, möchte ich noch einmal auf das Ausführungsschema der verschiedenen Triggertypen zurückkommen. Wie Sie wissen, unterscheiden sich die einzelnen Trigger zum einen durch den Zeitpunkt ihrer Ausführung und zum anderen dadurch, ob Sie für die zugehörige Transaktion oder für jeden betroffenen Datensatz ausgeführt werden, was insgesamt zu folgendem Ausführungsschema führt: 1. Ausführung aller Vorab-Trigger, die nicht für jeden einzelnen Datensatz gestartet werden sollen. 2. Durchlaufen aller im Rahmen der Transaktion selektierten Datensätze. –
Ausführung aller datensatzbezogenen Vorab-Trigger.
–
Ändern, Löschen, Einfügen des Datensatzes entsprechend des SQL-Befehls. Durchführen alle vorhandenen Constraint-Prüfungen.
–
Ausführung aller datensatzbezogenen Danach-Trigger.
3. Ausführen aller nicht datensatzbezogenen Danach-Trigger.
Anwendungsbeispiele
471
Dieses Schema und den Umstand, das wir mit Hilfe eines Packages sitzungsglobale Variablen speichern können, werden wir uns bei der Lösung des Kostenstellenproblems zu Nutze machen, indem wir diese Variablen in einem Transaktions-VorabTrigger initialisieren, uns in einem satzweise arbeitenden Danach-Trigger die geänderten Kostenstellen merken und diese im Transaktions-Danach-Trigger überprüfen. Betrachten Sie nun noch einmal die Abbildung 5.3. Augrund unseres Datenmodells besitzten die historischen Daten zwar einen echten Anfang, der vom Gültigkeitsdatum der ersten Historie bestimmt wird, gelten aus technischer Sicht anschließend aber für immer. Zwar kann eine Kostenstelle als inaktiv markiert werden, aber in dem Fall ist sie trotzdem noch vorhanden. Aus diesen Gründen müssen wir bei Kostenstellenänderungen eigentlich nur in folgenden Fällen Prüfungen durchführen:
X X
Der erste Datensatz mit dem Beginndatum „k1“ (vgl. Abb. 5.3) wird gelöscht. Das Beginndatum des ersten Datensatzes wird so verändert, dass das neue Beginndatum hinter dem alten liegt, d.h. die Kostenstelle wird insgesamt erst zu einem späteren Zeitpunkt gültig.
create or replace package kostenstelle_paket is type kst_aend is record (unternehmen kostenstelle.unternehmen%type, kst kostenstelle.kst%type, gab date ); type t_kst_aend is table of kst_aend; v_kst_aend t_kst_aend; end; Listing 5.48: Erstellen des für die Kostenstellenprüfung benötigten Pakets
Das Listing 5.48 zeigt Ihnen die Anlage des benötigten Packages. Hier definieren wir zunächst die Struktur kst_aend, die alle Primärschlüsselfelder der Kostenstellentabelle enthält. Im nächsten Schritt definieren wir für diese Struktur die dynamische Tabelle t_kst_aend, und damit wir diese neuen Typen auch benutzen können, benötigen wir zusätzlich die Variable v_kst_aend, mit deren Hilfe wir diese Tabelle in den folgenden Programmen ansprechen können. create or replace trigger kostenstelle_kst1 before delete or update of unternehmen, kst, gab on kostenstelle begin kostenstelle_paket.v_kst_aend := kostenstelle_paket.t_kst_aend(); end; Listing 5.49: Initialisieren des Packages beim Starten der Änderungstransaktion
472
PL/SQL-Programmierung
Mit Hilfe des im Listing 5.49 gezeigten Vorab-Triggers wird unsere Arbeitstabelle v_kst_aend beim Start einer entsprechenden Änderungstransaktion gelöscht, indem wir der Variablen mit Hilfe des Typenkonstruktors eine leere Tabelle zuweisen. Im nächsten Schritt folgt der satzweise arbeitende Trigger, in dem wir die Schlüssel aller geänderten Datensätze speichern. create or replace trigger kostenstelle_kst2 after delete or update of unternehmen, kst, gab on kostenstelle for each row declare i integer; begin if deleting or :old.gab < :new.gab or updating('UNTERNEHMEN') or updating('KST') then kostenstelle_paket.v_kst_aend.extend(1); i:=kostenstelle_paket.v_kst_aend.count; kostenstelle_paket.v_kst_aend(i).unternehmen := :old.unternehmen; kostenstelle_paket.v_kst_aend(i).kst := :old.kst; kostenstelle_paket.v_kst_aend(i).gab := :old.gab; end if; end; Listing 5.50: Speichern der geänderten Datensätze in einem dynamischen Datenfeld
Mit Hilfe des satzweise arbeitenden Triggers speichern wir alle relevanten Datensätze in dem globalen Datenfeld. Entsprechend der gemachten Vorbemerkungen muss das beim Ändern eines Historiendatums grundsätzlich nur dann passieren, wenn das neue Datum hinter dem alten liegt. Für jeden Datensatz wird das Datenfeld v_kst_aend mit Hilfe der extend-Methode um ein Element erweitert. Den aktuellen Index können Sie jeweils mit Hilfe der count-Methode ermitteln, wobei die Verwendung der Hilfsvariablen i nur aus Gründen der besseren Lesbarkeit erfolgt. create or replace trigger kostenstelle_kst3 after delete or update of unternehmen, kst, gab on kostenstelle declare i integer; ct integer; pgab date; begin for i in 1..kostenstelle_paket.v_kst_aend.count loop dbms_output.put(kostenstelle_paket.v_kst_aend(i).unternehmen); dbms_output.put(kostenstelle_paket.v_kst_aend(i).kst); dbms_output.put(','); dbms_output.put(kostenstelle_paket.v_kst_aend(i).gab); -- Prüfen, ob die Kostenstelle geprüft werden muss
Anwendungsbeispiele
select count(*) into ct from kostenstelle where unternehmen = kostenstelle_paket.v_kst_aend(i).unternehmen and kst = kostenstelle_paket.v_kst_aend(i).kst and gab < kostenstelle_paket.v_kst_aend(i).gab; if ct > 0 then dbms_output.put(' => keine Prüfung erforderlich!'); else -------------------------------------------------------------- Die 1. Historie wurde gelöscht oder verändert. -- Das Feld gab der neuen ersten Historie ermitteln ------------------------------------------------------------select min(gab) into pgab from kostenstelle where unternehmen = kostenstelle_paket.v_kst_aend(i).unternehmen and kst = kostenstelle_paket.v_kst_aend(i).kst and gab > kostenstelle_paket.v_kst_aend(i).gab; ct := 0; if pgab is null then ---------------------------------------------------------- Die Kostenstelle wurde vollständig gelöscht. -- Prüfen, ob die Kostenstelle noch irgendwo verwendet -- wird --------------------------------------------------------dbms_output.put(' => Kostenstelle gelöscht'); select count(*) into ct from gehalt a, bvs b where b.persnr = a.persnr and b.lfdnr = a.lfdnr and b.unternehmen = kostenstelle_paket.v_kst_aend(i).unternehmen and a.kst = kostenstelle_paket.v_kst_aend(i).kst and rownum = 1; else ---------------------------------------------------------- Die erste Kostenstellenhistorie wurde gelöscht bzw. -- deren Beginndatum wurde verküzt. Das neue Beginndatum -- der ersten Historiesteht jetzt im Feld pgab. Prüfen ob -- es Gehaltsdatensätze gibt, -- die die geänderte Kostenstelle verwenden und vor pgab -- gültig sind. --------------------------------------------------------dbms_output.put(' => Neue Gültigkeit ab ' || pgab);
473
474
PL/SQL-Programmierung
select count(*) into ct from gehalt a, bvs b where b.persnr = a.persnr and b.lfdnr = a.lfdnr and b.unternehmen = kostenstelle_paket.v_kst_aend(i).unternehmen and a.kst = kostenstelle_paket.v_kst_aend(i).kst and a.gab < pgab and rownum = 1; end if; if ct > 0 then dbms_output.put(' (Fehler)'); dbms_output.new_line; raise_application_error(-20000, 'Inkonsistente Kostenstelle: ' || kostenstelle_paket.v_kst_aend(i).unternehmen || kostenstelle_paket.v_kst_aend(i).kst || ', ' || kostenstelle_paket.v_kst_aend(i).gab); else dbms_output.put(' (OK)'); dbms_output.new_line; end if; end if; end loop; end; Listing 5.51: Prüfung der im Datenfeld gespeicherten Kostenstellen
Zunächst einmal werden die im Datenfeld gesammelten Kostenstellen mit Hilfe einer for loop-Schleife verarbeitet. Hierbei wird im Inneren der Schleife für jedes Element als Erstes ermittelt, ob zu der geänderten Kostenstelle noch eine Vorgängerhistorie existiert. Falls ja, dann gibt es aufgrund unserer Vereinbarungen keinen Grund mehr, in den Gehaltsdaten nach Inkonsistenzen zu suchen. Ansonsten gibt es prinzipiell zwei Möglichkeiten: Entweder die Kostenstelle wurde vollständig gelöscht oder die erste Historie wurde geändert, d.h. die Historische Linie der Kostenstelle beginnt jetzt mit einem anderen Datum. Im ersten Fall schauen wir mit Hilfe einer geeigneten Abfrage in der Datenbank nach, ob die Kostenstelle noch von irgendeinem Gehaltsdatensatz verwendet wird. Im zweiten Fall wird geprüft, ob es Gehaltsdatensätze mit der geänderten Kostenstelle gibt, die vor deren Historienbeginn liegen. In beiden Fällen wird die Transaktion mit Hilfe eines Aufrufs von raise_application_error abgebrochen, wenn entsprechende Datensätze in der Datenbank gefunden werden.
Anwendungsbeispiele
475
Wie Sie dem Listing 5.51 entnehmen können, habe ich an verschiedenen Stellen die Ausgabe von Hinweismeldungen (dbms_output) eingebaut. Diese Meldungen können Ihnen beim Test oder beim Spielen mit diesem Trigger helfen. Hierzu ist es vielleicht auch sinnvoll, wenn Sie die Abbruchbedingung im letzten Trigger zunächst einmal auskommentieren und Ihre Transaktionen selbständig durch ein rollback-Kommando zurückrollen. delete kostenstelle where kst = 'PSOFT'; rollback 001PSOFT,01-JAN-92 => Kostenstelle gelöscht (Fehler)
Zusammenfassung Ich glaube, durch das letzte Beispiel bekommt man schon ein Gefühl dafür, dass irgendwie eigentlich alles möglich ist. In Analogie zum letzten Beispiel könnten Sie zum einen auch die Prüfung der Gehaltsdatensätze noch effizienter gestalten und zum anderen wäre ebenfalls eine Überwachung des Inaktiv-Status denkbar, was in unserem Beispiel wegen der getroffenen Vereinbarung nicht notwendig war, zum anderen aber wegen der wesentlich umfangreicheren Programmierung den Rahmen aber auch bei weitem gesprengt hätte. Schließlich will ich Ihnen mit diesem Workshop keine fertigen Lösungen verkaufen, sondern Ideen generieren, wie Sie Ihre konkreten Aufgabenstellungen vielleicht lösen bzw. angehen könnten. In diesem Sinne ist auch das letzte Anwendungsbeispiel zu verstehen, das sicherlich schon ganz gut funktioniert, dem für einen echten Produktiveinsatz aber noch das eine oder andere Feature fehlt.
5.2.4
Exporthilfe
Als letztes Anwendungsbeispiel soll ein Programm erstellt werden, mit in Analogie zum Kapitel 5.1.10 eine Tabelle exportieren können. Allerdings soll das Programm so flexibel sein, dass Sie mit seiner Hilfe zum einen jede beliebige Tabelle exportieren und dabei auch das Ausgabeformat in einem gewissen Umfang beeinflussen können. Das Programm Die Programmierung erfolgt wieder mit Hilfe eines Packages, da neben der eigentlichen Startprozedur weitere private Prozeduren benötigt werden. Die Anlage des Pakets können Sie dem Beispiel 5.52 entnehmen und das zugehörige Programm finden Sie im Listing 5.53. create or replace package export as procedure export_table(owner in varchar2, table_name in varchar2, path in varchar2, file in varchar2, dtformat in varchar2, ft in varchar2, st in varchar2, kopf boolean); end; Listing 5.52: Anlage des Exportpakets
476
PL/SQL-Programmierung
create or replace package body export as sql_buff varchar2(5000); sql_head varchar2(1000); /*-------------------------------------------------------------------Ermitteln der Struktur für die Exporttabelle. Das Ergebnis wird in den Variablen sql_buff und sql_head zwischengespeichert --------------------------------------------------------------------*/ procedure get_sql(iowner in varchar2, itable_name in varchar2, dtformat in varchar2, ft in varchar2, st in varchar2) is -- Abfrage der Tabellenstruktur cursor sel_cols is select column_name, data_type from all_tab_columns where owner = iowner and table_name = itable_name order by column_id; lbuff feldt satzt first
varchar2(50); varchar2(5); varchar2(15); boolean := true;
begin sql_buff := 'select '; -- Aufbereiten der Textbegrenzung if ft > ' ' then feldt := '''' || ft || ''''; else feldt := ''; end if; -- Aufbereiten des Feldtrennzeichens satzt := ' || ' || '''' || st || '''' || ' || '; -- Abarbeiten aller Feldnamen der gewählten Tabelle for sel_cols_rec in sel_cols loop -- Sonderbehandlung für das erste Datenfeld if first then first := false; lbuff := ''; sql_head := ''; else lbuff := satzt; sql_head := sql_head || st; end if;
Anwendungsbeispiele
-- Konstruktion eines select-Strings -- für Textfelder if sel_cols_rec.data_type in ('VARCHAR2','VARCHAR','CHAR') then lbuff := lbuff || feldt || ' || ' || sel_cols_rec.column_name || ' || ' || feldt; sql_buff := sql_buff || lbuff; sql_head := sql_head || sel_cols_rec.column_name; -- für Datumsfelder elsif sel_cols_rec.data_type = 'DATE' then lbuff := lbuff || 'nvl(to_char(' || sel_cols_rec.column_name || ',' || '''' || dtformat || '''' || '),' || '''' || '''' ||')'; sql_buff := sql_buff || lbuff; sql_head := sql_head || sel_cols_rec.column_name; -- für numerische Felder elsif sel_cols_rec.data_type = 'NUMBER' then lbuff := lbuff || 'nvl(to_char(' || sel_cols_rec.column_name || '),' || '''' || '''' ||')'; sql_buff := sql_buff || lbuff; sql_head := sql_head || sel_cols_rec.column_name; end if; end loop; end; /*--------------------------------------------------------------------Ausführen des dynamischen SQL-Buffers und Ausgabe der erhaltenen Daten --------------------------------------------------------------------*/ procedure export_records(path in varchar2, file in varchar2, kopf boolean) is ct rc textbuff rec_ct
integer; integer; varchar2(1500); integer:=0;
f utl_file.file_type; begin -- Öffnen und Parsen des dynamsichen Cursors ct := dbms_sql.open_cursor; dbms_sql.parse(ct, sql_buff, dbms_sql.native); dbms_sql.define_column(ct, 1, textbuff, 1500); -- Öffnen der Exportdatei f := utl_file.fopen(path, file, 'w');
477
478
PL/SQL-Programmierung
-- Ausgabe der Feldüberschriften, falls gewünscht if kopf then utl_file.put_line(f, sql_head); end if; -- Ausführen des Cursors und holen des ersten Satzes rc := dbms_sql.execute_and_fetch(ct); while rc > 0 loop dbms_sql.column_value(ct, 1, textbuff); -- Satz wegschreiben utl_file.put_line(f, textbuff); -- Nächsten Record holen rc := dbms_sql.fetch_rows(ct); rec_ct := rec_ct + 1; end loop; -- Cursor und Datei schließen dbms_sql.close_cursor(ct); utl_file.fclose(f); dbms_output.put_line('Datensätze ' || to_char(rec_ct,'9999999')); end; /*-------------------------------------------------------------------Hauptprogramm --------------------------------------------------------------------*/ procedure export_table(owner in varchar2, table_name in varchar2, path in varchar2, file in varchar2, dtformat in varchar2, ft in varchar2, st in varchar2, kopf boolean) is begin -- SQL-String konstruieren get_sql(owner, table_name, dtformat, ft, st); -- from-Klausel ergänzen sql_buff := sql_buff || ' from ' || owner || '.' || table_name; dbms_output.put('Export von '); dbms_output.put(owner || '.' || table_name); dbms_output.put(' in '); dbms_output.put(path || ' -> ' || file); dbms_output.new_line; -- Export starten export_records(path, file, kopf); end; end; Listing 5.53: Programm für den universellen Dateiexport
Anwendungsbeispiele
479
Programmbeschreibung Das Exportprogramm wird mit Hilfe der Prozedur export_table gestartet, der neben dem Namen der Tabelle vor allem auch der Name und das Verzeichnis der Exportdatei übergeben werden müssen. Mit Hilfe der nächsten drei Parameter wird das bei Datumsfeldern zu verwendende Format, das zu verwendende Textbegrenzungszeichen und das Feldtrennzeichen vorgegeben und mit Hilfe des letzten Parameters können Sie festlegen, ob in der Datei die Feldnamen als erste Zeile ausgegeben werden sollen. execute export.export_table('UBEISPIEL','GEHALT', 'c:\temp','gehalt.txt', 'DD.MM.YYYY','"',';',true); Statement processed. Export von UBEISPIEL.GEHALT in c:\temp -> gehalt.txt Datensätze 49
Bei diesem Aufruf exportieren wir also die Tabelle gehalt aus dem Schema ubeispiel in die Datei C:\TEMP\GEHALT.TXT. Dabei werden Datumswerte im Format DD.MM.YYYY ausgegeben, Textfelder werden in Anführungszeichen gesetzt und alle Datenfelder werden durch ein Semikolon getrennt, wobei die Feldnamen als erste Zeile ausgegeben werden. PERSNR;LFDNR;GAB;KST;GEHALT;ZULAGE "7000001";0;01.01.1990;"PERS";4500;500 "7000002";0;01.04.1998;"PERS";4750;300 "7000002";0;01.04.1999;"PERS";4980;290 "7000003";0;01.08.1999;"EDV";6500;0 "7000004";0;01.04.1998;"EDV";7250;455 "7000004";0;01.07.1998;"PSOFT";7250;455 "7000004";0;01.04.1999;"PSOFT";7850;512 "7000005";0;01.04.1998;"PSOFT";6200;120 .... Listing 5.54: Auszug aus der Exportdatei
Innerhalb der Hauptprozedur export_table des Programms wird zunächst die Prozedur get_sql gestartet, mit der die Struktur der zu exportierenden Tabelle ausgelesen werden soll. Das passiert dort mit Hilfe des Cursors sel_cols, der mit Hilfe einer Abfrage auf die View all_tab_columns alle in der übergebenen Tabelle vorhandenen Spalten und die zugehörigen Datentypen selektiert. Das Ziel dieser Prozedur ist es, für jede vorhandene Datenspalte die in der Variabel sql_buff gespeicherte SQL-Abfrage zu erweitern. Diese Abfrage wird so konstruiert, dass alle in der Tabelle vorhandenen Felder aneinandergehängt werden, d.h. die später für den Export abgesetzte Abfrage selektiert genau eine Spalte. Für unsere Gehaltsdaten hat diese select-Anweisung beispielsweise folgenden Aufbau:
480
PL/SQL-Programmierung
select '"' || PERSNR || '"' || ';' || nvl(to_char(LFDNR),'') || ';' || nvl(to_char(GAB,'DD.MM.YYYY'),'') || ';' || '"' || KST || '"' || ';' || nvl(to_char(GEHALT),'') || ';' || nvl(to_char(ZULAGE),'')
Die benötigten Exportsätze werden also schon durch Abfrage vollständig aufbereitet, d.h. unser select-Ausdruck enthält ebenfalls alle benötigten Trenn- und Begrenzungszeichen. Aus diesem Grund wird zunächst einmal das Textbegrenzungszeichen in der Variablen feldt durch folgende Anweisungen aufbereitet: if ft > ' ' then feldt := '''' || ft || ''''; else feldt := ''; end if;
Aufgrund der Anweisung im if-Zweig enthält die Variable feldt das beim Prozeduraufruf übergebene Textbegrenzungszeichen, das von je einem Anführungszeichen eingeschlossen ist. In unserem Fall enthält die Variable also folgenden Wert '“' . Das zu verwendende Feldtrennzeichen enthält ebenfalls wird ebenfalls aufbereitet und in der Variablen satzt gespeichert. Dabei enthält diese Variable neben diesem Trennzeichen auch den speziellen Konkatinierungsoperator, der bei der späteren Abfrage für das Aneinanderhängen der einzelnen Spalten sorgt. Im nächsten Schritt wird der Cursor mit Hilfe einer for loop-Schleife abgearbeitet. Dabei müssen alle exportfähigen Datentypen mit Hilfe spezieller Anweisungen in den SQL-Puffer eingearbeitet werden, weshalb Sie in dem Beispiel für die einzelnen Datentypen unterschiedliche if-Zweige finden. Sofern Sie das Programm in der Praxis verwenden wollen, dann müssen Sie den inneren Teil der Schleife eventuell erweitern oder anpassen, wenn Sie die weitere Datentypen ausgeben oder die vorhandenen Möglichkeiten ausbauen wollen. Neben dem Fortschreiben des SQL-Puffers in der Variablen sql_buff wird innerhalb der Schleife auch die Variable sql_head gefüllt, in der die Feldüberschriften aufbereitet werden. Nach dem Durchlaufen der Prozedur get_sql befindet sich in der Variablen sql_buff, also ein abfragefähiger Ausdruck und in der Variablen sql_head stehen alle zugehörigen Feldnamen. Was dem SQL-Statement allerdings noch fehlt ist eine geeignete from-Klausel, die der sql_buff-Variablen im Anschluss an die Prozedur angefügt wird. sql_buff := sql_buff || ' from ' || owner || '.' || table_name;
Danach verzweigt das Programm in die Prozedur export_records, in der die eigentliche Tabellenausgabe stattfindet. Eigentlich handelt es sich hierbei um die Kombination von zwei schon gezeigten Beispielen, denn zum einen wird mit Hilfe des Pakets dbms_sql eine Auswahlabfrage ausgeführt, wobei konkret das in der Variablen sql_buff gespeicherte Statement verwendet wird und zum anderen werden die selektierten Datensätze mit Hilfe des Paketes utl_file in eine Textdatei geschrieben.
6
Anwendungsentwicklung
Mit diesem Kapitel nähern wir uns dem Ende des Workshops, wobei ich das Buch mit einigen Hinweisen zur Anwendungsentwicklung bzw. der Einbeziehung der Oracle-Datenbank bei der Erstellung von Front-Ends abschließen möchte. Immer wenn Sie ein konkretes Anwendungsprogramm, beispielsweise eine WindowsAnwendung, entwickeln, in der Sie auf Daten aus einer Oracle-Datenbank zugreifen wollen, dann besteht das erste zu überwindende Problem darin, aus Ihrem Anwendungsprogramm eine Verbindung zur Datenbank herzustellen.
6.1
Grundlagen
Genau genommen müssen Sie dabei eine Verbindung zum Net8-Protokoll aufnehmen, denn hierüber können Sie sich anschließend an eine Oracle-Instanz anmelden und danach SQL-Befehle absenden und Ergebnisse oder Fehlermeldungen empfangen. Für die Kommunikation eines Clients mit einer Datenbankinstanz über das Net8-Protokoll existiert ein standardisiertes Verfahren, das oftmals mit dem Kürzel „OCI“ beschrieben wird. Diese Abkürzung steht für „Oracle Call Interface“ und beschreibt eine Funktionsbibliothek die alle Routinen bereitstellt, die für die Kommunikation mit einer Oracle-Instanz benötigt werden. Hierbei handelt es sich beispielsweise um Funktionen, um
X X X
eine Verbindung mit einer Datenbankinstanz aufzubauen bzw. zu beenden. eine SQL-Abfrage oder einen PL/SQL-Block an den Server zu schicken. Abfrageergebnisse oder Fehlermeldungen zu empfangen.
Ein solches OCI steht für alle Betriebssysteme zur Verfügung, für die ein Oracle-Client existiert. Allerdings besitzt diese Funktionsbibliothek ein Format, das in der ursprünglichen Form eigentlich nur C/C++ Programmierer anspricht, dabei auch gewisse Anforderungen an diesen stellt und in anderen Programmiersprachen eigentlich nicht direkt verwendet werden kann. Wenn Sie beispielsweise ein Windows-Programm entwickeln und hierbei nicht C bzw. C++ als Programmiersprache verwenden, dann besteht eine Möglichkeit zur direkten Verwendung der OCIBibliothek darin, zunächst eine DLL (also doch wieder C) zu entwerfen, in der Sie die benötigten OCI-Funktionen einbetten. Bei den einzelnen DLL-Funktionen müssen Sie dann darauf achten, dass diese innerhalb der konkret eingesetzten Umgebung verwendbar sind, was in der Regel bedeutet, dass Sie bei der Parametergestaltung möglichst einfache Datentypen verwenden sollten. Hinweise zur Verwendung der OCI-Bibliothek finden Sie in der Oracle-Dokumentation im Buch „Application Development“. Dort finden Sie verschiedene Verweise auf detaillierte Beschreibungen des Oracle Call Interfaces.
482
Anwendungsentwicklung
Auf der anderen Seite wäre es aber unverständlich, wenn nicht sogar unsinnig, die Arbeit, die andere schon vor uns erledigt haben und die uns des weiteren sogar zur Verfügung steht selbst noch einmal zu wiederholen, d.h. es gibt auch andere Möglichkeiten ohne den Umweg über den C-Compiler Programme mit Oracle-Datenbankzugriffen zu erstellen. Teilweise sind die benötigten Funktionalitäten sogar schon in den Anwendungen oder Programmiersprachen integriert. Wenn Sie beispielsweise Front-Ends mit Hilfe von Oracle-Forms entwerfen, dann brauchen Sie sich über die Verbindung zur Datenbankinstanz eigentlich kein Kopfzerbrechen zu machen. Gleiches gilt im Übrigen auch, wenn Sie Anwendungen mit SQR entwickeln und selbst den CobolProgrammierern steht aufgrund verfügbarer Zusatzoptionen ein problemloser Datenbankzugang zur Verfügung. Neben solchen produktspezifischen Lösungen existieren aber auch noch weitere allgemeinverwendbare Hilfen, um mit einer Oracle-Instanz in Kontakt zu treten. Unter Windows handelt es sich dabei im Prinzip um besondere DLLs oder OCXSteuerelemente, die eine einfacherer Verwendung der OCI-Funktionen ermöglichen, wobei diese meistens sogar mit weiteren Zusatzfunktionen ausgestattet wurden, indem Sie den Empfang einer Datenmenge beispielsweise nicht mit Hilfe unzähliger systemnaher Funktionen programmieren müssen, sondern hierfür geschaffene Objekte mit komfortablen und leistungsfähigen Methoden finden. Im weiteren Verlauf möchte ich Ihnen zwei Vertreter dieser verfügbaren Helferlein kurz vorstellen und deren Verwendung ebenfalls anhand eines Beispiels demonstrieren, wobei ich mir als Vertreter eine ODBC-Verbingung und die Anwendung der Oracle-Objekte ausgesucht habe.
6.1.1
ODBC-Verbindung
Wie schon gesagt, besteht eine Variante zum Einbinden einer Oracle-Datenbank in die eigene Programmierung oder in verschiedene Anwendungsprogramme darin, die Verwendung mit Hilfe eines ODBC-Treibers (ODBC = Open Database Connectivity) herzustellen. Um über ODBC mit einer Oracle-Datenbank arbeiten zu können, müssen insgesamt zwei Voraussetzungen erfüllt sein:
X
X
Der zur Oracle- Datenbank passende ODBC-Treiber muss installiert sein, was im Rahmen einer standardmäßigen Client-Installation auf Windows-Arbeitsplätzen automatisch erfolgt. Bei manueller Auswahl der zu installierenden Komponenten müssen Sie gegebenenfalls darauf achten, dass auch das Produkt „Oracle8 ODBC Driver“ mit zur Installation ausgewählt wird. Der ODBC-Treiber muss für die Arbeit mit der gewünschten Datenbank eingerichtet werden, was Sie mit Hilfe eines speziellen ODBC-Managers durchführen können, den Sie üblicherweise in der Systemsteuerung Ihres Windows-Systems finden.
Grundlagen
483
Aufbau einer ODBC-Verbindung Nach dem Start des ODBC-Managers, der sich heutzutage in seinem Programmfenster ODBC-Datenquellen-Administrator nennt, erhalten Sie ein kleines Arbeitsfenster mit verschiedenen Registerkärtchen. Mit Hilfe des Registerkärtchens „Treiber“ (vgl. Abb. 6.1). erhalten Sie eine Liste der bereites installierten ODBC-Treiber. Wie Sie der Abbildung auch entnehmen können, gibt es für einige Datenbanksysteme durchaus passende ODBC-Treiber von verschiedenen Herstellern.
Abbildung 6.1: ODBC-Datenquellen-Administrator mit einer Liste der installierten Treiber
Nach meinen Erfahrungen funktioniert sowohl der von Microsoft als auch der von Oracle gelieferte Treiber sehr gut. Dennoch gibt es aus meiner Sicht kein wirkliches Entscheidungsproblem, denn ich habe in der Vergangenheit immer den vom Datenbankhersteller gelieferten Treiber verwendet, sofern er verfügbar war und funktionierte. Um den ODBC-Treiber zusammen mit einer Datenbank verwenden zu können, müssen Sie ihn für die gewünschte Datenquelle einrichten. Dies hängt damit zusammen, dass gemäß dem eigentlichen Konzept von ODBC eine Unabhängigkeit zwischen Anwendung und verwendeter Datenbank entstehen soll. Aus dem Grund verbinden sich die einzelnen Anwendungen nicht direkt mit einer Datenbank, sondern zunächst mit einer abstrakten Datenquelle (vgl. Abb. 6.2). Was sich konkret hinter dieser Datenquelle verbirgt, das wird im Rahmen der ODBC-Datenquellenkonfiguration festgelegt.
484
Anwendungsentwicklung
Anwendungsprogramm
ODBC-Datenquelle
Treiber und Datenbank
(MeineDaten)
(Oracle-Treiber, db01)
Net8
Datenbank
Abbildung 6.2: Schematische Struktur einer ODBC-Datenquelle
Wie Sie der Abbildung 6.2 entnehmen können, verbindet sich eine ODBC-Anwendung mit Hilfe eines logischen Namens an eine Datenbank bzw. genauer gesagt an eine Datenquelle. Welche Datenbank (Name und Hersteller) sich konkret dahinter verbirgt wurde im Rahmen der Konfiguration dieser Datenquelle festgelegt, d.h. auf diese Weise können zumindest theoretisch datenbankunabhängige Anwendungen erstellt werden. Echte Datenbankunabhängigkeit erreicht man natürlich nicht nur dadurch, dass man Verbindungsinformationen flexibel bzw. abstrakt verwaltet. Ich habe im Rahmen dieses Workshops ja schon mehrfach angedeutet, dass die verschiedenen verfügbaren Datenbanksysteme ein durchaus unterschiedliches Funktionsspektrum besitzen, wobei sich selbst die Formulierung bestimmter Abfragen unterschiedlich gestaltet. Setzt man in seiner Anwendung also irgendwelche speziellen Funktionalitäten ein, dann ist es mit dieser Unabhängigkeit sowieso vorbei. Damit dieses Konzept aber wenigstens im Rahmen gewöhnlicher Auswahl- bzw. Änderungsabfragen funktioniert, bietet ODBC neben der Verschlüsselung von Datenquellen natürlich noch weitere Funktionalitäten (vgl. Abb. 6.3). Ich habe in der Abbildung 6.3 einmal den Aufbau eines ODBC-Treibers stark vereinfacht dargestellt. Im Prinzip besteht so ein ODBC-Treiber aus drei Schichten. Mit Hilfe der ersten Schicht erfolgt die Kontaktaufnahme und Kommunikation zum Treiber, d.h. diese Schicht fungiert quasi als Schnittstelle zum Anwendungsprogramm. In der mittleren Schicht werden die an den Treiber übermittelten Abfragen für die konkret eingesetzte Datenbank übersetzt. Die hierdurch entstehenden Abfragen werden mit Hilfe der ebenfalls datenbankspezifischen Verbindungsschicht an die Datenbank weitergeleitet. Zurück geht der Weg genau umgekehrt, d.h. die in der Verbindungsschicht empfangenen Daten werden durch den Treiber dem Anwendungsprogramm über die Schnittstellenschicht zur Verfügung gestellt.
Grundlagen
485
Anwendungsprogramm
Interface PassThrough
Universalübersetzer
Datenbankverbindung
Datenbank
Abbildung 6.3: Verwenden eines ODBC-Treibers
Technisch betrachtet, besteht ein solcher ODBC-Treiber aus einem Geflecht verschiedenster DLLs, die jeweils spezifische Einzelaufgaben in diesem komplexen Funktionsnetzwerk übernehmen. Dabei liegt auf der Hand, dass der Gebrauch der in Abbildung 6.3 skizzierten „Universalübersetzers“ einen gewissen Overhead mit sich bringt. Allerdings können Sie der Abbildung auch entnehmen, dass es eine Möglichkeit gibt, diesen Übersetzer zu umgehen, indem Sie sogenannte Passthrough-Abfragen erstellen. Bei diesem Abfragetyp kommunizieren der ODBC-Einund Ausgang quasi direkt miteinander, was zwei Vorteile mit sich bringt. Zum einen steht Ihnen jetzt wieder der nahezu vollständige Funktionsumfang Ihrer Datenbank zu Verfügung und zum anderen entstehen Ihnen hierbei keine nennenswerten Geschwindigkeitseinbußen mehr. Die Verwendung von ODBC zusammen mit der Option PathThrough stellt also durchaus eine Alternative zur direkten Verwendung des Oracle Call Interfaces dar. Aus meiner Erfahrung gibt es zumindest nichts was dagegen spricht, denn es funktioniert stabil, ist schnell genug, relativ einfach zu programmieren und eröffnet einem den direkten Zugang zu allen wichtigen Datenbankfunktionalitäten. Datenquelle einrichten Bevor wir uns mal an einem Beispiel anschauen, wie Sie mit Hilfe von ODBC Daten aus Ihrer Datenbank empfangen können, müssen wir zunächst eine Datenquelle für unsere Datenbankinstanz „db01“ einrichten. Öffnen Sie hierzu das Fenster Ihrer Windows-Systemsteuerung und starten Sie dort den ODBC-Manager bzw. Datenquellen-Administrator. Wählen Sie anschließend innerhalb des Registrierkärtchens „Benutzer-DSN“ die Schaltfläche „Hinzufügen“, um die Anlage einer
486
Anwendungsentwicklung
neuen Datenquelle einzuleiten. Hierdurch erhalten Sie einen Dialog (vgl. Abb. 6.4), der Ihnen eine Liste aller installierten ODBC-Treiber anzeigt.
Abbildung 6.4: Auswahl des Treibers für die neue Datenquelle
Aus dieser Liste (vgl. Abb. 6.4) wählen Sie den ODBC-Treiber von Oracle aus. Anschließend können Sie mit der Schaltfläche „Fertig stellen“ mit der Verarbeitung fortfahren, wodurch Sie einen Dialog zur Konfiguration der neuen Datenquelle erhalten.
Abbildung 6.5: Neue Datenquelle konfigurieren
Mit Hilfe des zur Einrichtung der neuen Datenquelle angezeigten Setup-Dialogs (vgl. Abb. 6.5) müssen Sie der zunächst einmal einen Namen geben, unter dem die Datenquelle in den späteren ODBC-Programmen angesprochen wird. Zusätzlich können Sie in dem nächsten Feld eine optionale Beschreibung für diese Datenquelle erfassen.
Grundlagen
487
Das nächste Datenfeld ist wieder wichtig, denn hier legen Sie fest, mit welcher Datenbank die Verbindungsschicht Kontakt aufnehmen soll. Da dieser Kontakt über das Net8-Netzwerk hergestellt wird, müssen Sie hier einen gültigen Servicenamen verwenden. Im darauffolgenden Feld haben Sie die Möglichkeit, die BenutzerId, mit der die Anmeldung erfolgen soll, als eine Art Standardwert vorzugeben, wobei Sie innerhalb Ihrer Anwendung die Benutzer-Id zusammen mit dem zugehörigen Passwort beim Starten der ODBC-Verbindung vorgeben können. Speichern Sie Ihre neue Datenquellendefinition mit Hilfe der OK-Schaltfläche. Anschließend können Sie den ODBC-Administrator beenden, d.h. das war schon alles, um eine neue ODBC-Datenquelle einzurichten. Ein Anwendungsbeispiel Nach der Installation bzw. der Konfiguration einer ODBC-Datenquelle steht Ihnen die Oracle-Datenbank in vielen Anwendungen quasi auf Knopfdruck zur Verfügung. Beispielsweise können Sie von Excel aus mit Hilfe von Microsoft Query direkt Abfragen in der Oracle-Datenbank ausführen und die Ergebnisse in Ihr Excel-Arbeitsblatt importieren. Das Gleiche gilt im Übrigen auch für Winword, denn auch hier existiert zumindest in den aktuelleren Versionen eine Datenschnittstelle, mit der Sie beispielsweise Serienbriefe durch direktes Anzapfen bzw. Verwenden einer Datenbankabfrage mischen können. Ein weiteres Beispiel ergibt sich in der Verwendung von Oracle-Tabellen innerhalb einer Access-Datenbank. Wählen Sie hierzu im Datei-Menü den Eintrag „Externe Daten“ und dort „Tabelle Verknüpfen“. Anschließend erscheint ein Dialog, mit dessen Hilfe Sie die Datenquelle, in der die zu verknüpfende Tabelle liegt auswählen können. Wählen Sie in diesem Fenster in dem Dialogfeld „Dateityp“ den Eintrag „ODBC-Datenquelle“ aus, wodurch Sie automatisch ein neues Dialogfenster (vgl. Abb. 6.6) erhalten, in dem Sie das Registerkärtchen „Computerdatenquelle“ finden.
Abbildung 6.6: Verknüpfung zu einer Oracle-Tabelle herstellen
488
Anwendungsentwicklung
Wählen Sie in der angezeigten Liste der verfügbaren Datenquellen den soeben angelegten Eintrag zur Verbindung mit der db01-Instanz aus und beenden Sie den Dialog danach mit Hilfe der OK-Schaltfläche. Anschließend werden Sie zur Eingabe einer Benutzerkennung nebst zugehörigem Passwort aufgefordert und wenn Sie auch diese letzte „Hürde“ überwunden haben, dann stehen Ihnen die in der Oracle-Datenbank vorhandenen Tabellen zur Auswahl.
Abbildung 6.7: Verknüpfung zur Tabelle ubeispiel.personalien herstellen
Wählen Sie in der gezeigten Liste (vgl. Abb. 6.7) der vorhandenen Tabellen diejenigen oder alle aus, für die Sie in Ihrer Access-Datenbank eine Verknüpfung wünschen, die nach Beendigung dieses Dialogs mit OK hergestellt werden. Innerhalb der Access-Anwendung können Sie diese eingebundenen Tabellen anschließend genaue wie gewöhnliche Access-Objekte benutzen, d.h. Sie können darauf basierende Abfragen, Formulare oder Reports erstellen. Verwenden von Visual oder Access Basic Genauso gut wie die eben beschriebene Verfahrensweise funktioniert auch die Verwendung einer ODBC-Datenquelle in einem Programm, beispielsweise unter Verwendung von Visual Basis oder Access Basic. Beachten Sie bei den folgenden Beispielen, dass Sie unter Verwendung von DAO 3.6 erstellt wurden, d.h. Sie müssen die entsprechende Library zur Verwendung in Ihrem Projekt freischalten. Hierzu finden Sie üblicherweise den Menüeintrag „Verweise“ unter dem Extrasoder Projektmenü. Die beiden gezeigten Beispiele finden Sie auf Ihrer CD übrigens im Verzeichnis \ANWENDUNG. Das erste Beispiel 6.1 könnte beispielsweise in einem Access-Modul verwendet werden, wobei Sie die erstellte Funktion start beispielsweise mit Hilfe eines Makros starten können. Function start() Dim db As Database Dim qu As QueryDef
Set db = CurrentDb()
Grundlagen
489
On Error Resume Next db.QueryDefs.Delete "Read" On Error GoTo 0 Set qu = db.CreateQueryDef("Read") qu.Connect = "ODBC;DSN=MeineDB01;UID=UBEISPIEL;PWD=MANAGER;DATABASE=DB01" qu.ReturnsRecords = False qu.SQL = "delete from ubeispiel.personalien where persnr = '4711'" qu.Execute MsgBox "Gelöscht " & Format$(qu.RecordsAffected) qu.Close db.Close End Function Listing 6.1: Verwenden von ODBC innerhalb einer Access-Funktion
Im Beispiel 6.1 erstellen wir innerhalb der aktuellen Access-Datenbank zunächst eine neues Abfrageobjekt mit dem Namen „Read“. Damit das in jedem Falle funktioniert, wird das Objekt vorab mit Hilfe der delete-Methode aus der aktuellen QueryDefs-Liste gelöscht. Nachdem wir nun das neue bzw. alte Abfrageobjekt wieder neu erstellt haben, beginnen wir in der Funktion die einzelnen Eigenschaften zu besetzen. Im Rahmen der ODBC-Programmierung ist vor allem die connect-Eigenschaft besonders wichtig, da mit ihr die Verbindung zur der zu verwendenden Datenquelle hergestellt wird. ODBC;DSN=MeineDB01;UID=UBEISPIEL;PWD=MANAGER;DATABASE=DB01
Eine solche Verbindungszeichenfolge beginnt in unserem Fall immer mit der Konstanten „ODBC“, denen weitere, zum Teil treiberspezifische Einträge folgen, die alle durch ein Semikolon getrennt werden. In unserem Beispiel müssen Sie mit Hilfe der Einträge UID, PWD und DATABASE die Benutzerkennung, das zugehörige Passwort und den Servicenamen für das Net8-Protokoll vorgeben. In den folgenden Programmzeilen belegen wir weitere Eigenschaften des Abfrageobjekts mit den gewünschten Eigenschaften und führen die im Objekt gespeicherte Löschabfrage anschließend mit Hilfe der execute-Methode aus. Als Variante können Sie auch ein Abfrageobjekt erstellen, indem Sie eine beliebige Datenbankabfrage auf die Oracle-Datenbank speichern, das Sie anschließend innerhalb von Access für die Berichtserstellung oder beliebige andere Aktivitäten verwenden können. Set qu = db.CreateQueryDef("Read") qu.Connect = "ODBC;DSN=MeineDB01;UID=UBEISPIEL;PWD=MANAGER;DATABASE=DB01" qu.ReturnsRecords = True qu.SQL = "select * from ubeispiel.personalien" qu.Close
490
Anwendungsentwicklung
Das nächste und letzte Beispiel (vgl. Listing 6.2) zeigt, wie Sie beispielsweise innerhalb eines Visual Basic-Programms die Verbindung zu einer Oracle-Datenbank aufnehmen, anschließend eine Abfrage auf die Personalstammdaten ausführen und die Ergebnisse mit Hilfe einer Schleife verarbeiten, wobei das Programm die Namen mit Hilfe einer einfachen Meldungsbox anzeigt. Dim ws As Workspace Dim cn As Connection Dim rs As Recordset Set ws = DBEngine.CreateWorkspace("Test", "", "", dbUseODBC) Set cn = ws.OpenConnection("Test", dbDriverNoPrompt, False, "ODBC;DSN=MeineDB01;UID=UBEISPIEL;PWD=MANAGER;DATABASE=DB01") Set rs = cn.OpenRecordset("select * from ubeispiel.personalien")
Do While Not rs.EOF MsgBox rs.Fields("name") rs.MoveNext Loop rs.Close cn.Close ws.Close Listing 6.2: Erstellen einer direkten ODBC-Abfrage in Visual Basic
Der wesentliche Unterschied zum zuerst gezeigten Beispiel 6.1 ist zunächst einmal, dass Sie diesmal überhaupt keine Access-Datenbank benötigen. Sie können das Programmbeispiel in den click-Code einer Befehlsschaltfläche auf einem sonst leeren Formular ausprobieren. Das Einzige, was Sie in Ihrem VB-Projekt brauchen, ist ein Verweis auf die DAO-Library von Microsoft (Version 3.6). Nicht nur ODBC ist bei genauerer Betrachtung ein Sandwich mit viel Belag, sondern auch die darauf aufsetzenden DAO-Objekte machen aus dem Hamburger einen Big Mäc. Das untenliegende Brötchen entspricht dabei dem sogenannten Arbeitsbereich (Workspace), den wir mit Hilfe der createworkspace-Funktion erzeugen. Ein wichtiger Parameter dabei ist die vordefinierte Konstante dbUseODBC, die es uns ermöglicht, in diesem Arbeitsbereich die schon beschriebenen PassthroughAbfragen auszuführen. Im nächsten Schritt kümmern wir uns um eine Verbindung zur Oracle-Datenbank, d.h. wir legen auf dem Brötchen die erste Käsescheibe in Form eines sogenannten Connection-Objekts. Genau wie im letzten Beispiel verwenden wir hierbei wieder die schon beschriebene Verbindungszeichenfolge, damit der Treiber sich an der vorgegebenen Datenquelle anmelden kann. Auf unserem Connection-Objekt baut wiederum das im nächsten Schritt erstellte Recordset-Objekt auf, mit dem wir die durch die spezifizierte SQL-Abfrage gelieferten Datensätze verarbeiten können.
Grundlagen
6.1.2
491
Verwenden der Oracle-Objekte
Eine andere Möglichkeit schnell, einfach und komfortabel Oracle-Anwendungen zu entwickeln, besteht darin, bei der Entwicklung auf die Oracle Objects zurückzugreifen. Ähnlich wie bei ODBC, stehen Ihnen bei diesen Objekten allerdings nicht nur Hilfsmittel zur einfacheren Verbindung mit einer Oracle Instanz zur Verfügung, sondern Sie erhalten ebenfalls eine Menge Möglichkeiten Abfragen auf besonders einfache Weise auszuführen oder die empfangenen Daten mit Hilfe spezieller Steuerelemente darzustellen. Der wesentliche Unterschied zu ODBC ist allerdings, dass die Oracle Objects direkt auf Net8 aufsetzen, d.h. Sie benötigen für eine Datenbankanmeldung lediglich den üblichen Servicenamen, weitergehende Einstellungen oder Definitionen werden nicht gebraucht. Hierzu zählt beispielsweise das Oracle Data Control, das Sie beispielsweise innerhalb eines Visual Basic-Projekts verwenden können und mit dem Sie ähnlich wie bei dem Data Control von Microsoft Datenbankanwendungen zusammenklicken können, ohne hierfür eine Zeile Code programmieren zu müssen, indem das Data Control mit Hilfe der vorhandenen Eigenschaften an die Datenbank und die einzelnen Anzeigeelemente mit Hilfe ihrer Eigenschaften an das Data Control gebunden werden.
Abbildung 6.8: Beispiel einer einfachen VB-Anwendung
Sie finden das in Abbildung 6.8 gezeigte Beispiel übrigens ebenfalls im Verzeichnis \ANWENDUNG. Das Visual Basic-Projekt hat den Namen ORCLDEMO.VDP und das Formular heißt entsprechend ORCLDEMO.FRM (bzw. FRX). Der Tabelle 6.1 können Sie die wesentlichen Eigenschaften der verwendeten Steuerelemente entnehmen. Natürlich besteht auch die Möglichkeit, die Objekte innerhalb von Programmen zu benutzen und dabei ähnliche Konstruktionen wie bei den beiden ODBC-Beispielen zu bauen. Ich habe mich diesmal für eine Beispiel zusammen mit Microsoft Excel entschieden, d.h. wir werden dort im Folgenden eine kleine VBA-Funktion bauen, mit denen Sie direkt aus Ihren Excel-Arbeitsblättern auf die Datenbank zugreifen können.
492
Anwendungsentwicklung
Steuerelement/Eigenschaft
Belegung
Oracle Data Control Connect
Vorgabe von Benutzerkennung und Passwort, z.B. ubeispiel/manager.
Databasename
Servicename, z.B. DB01.
Recordsource
Auszuführende Abfrage, z.B. „select * from personalien“.
Textfelder Datafield
Anzuzeigendes Datenfeld, z.B. „persnr“.
Datasource
Datenquelle, in dem Fall der Name des eingesetzten Data Controls, z.B. ORADC1.
Tabelle 6.1: Wichtige Einstellung in dem Musterprojekt
Verwenden der Oracle-Objekte in Excel Bei erfolgreicher Installation der Oracle Objects, werden diese automatisch in Ihrer Windows-Umgebung registriert, so dass damit auch schon alle Voraussetzungen zu deren Verwendung erfüllt sind. Gleich wollen wir anhand eines kleinen Beispiels eine Funktion bauen, mit der Sie direkt aus Ihrem Arbeitsblatt heraus auf die Datenbank zugreifen können. Diese Funktion soll dabei genau wie eine Excel-Standardfunktion verwendbar sein, d.h. Sie können sie wie die Standardfunktion summe in allen denkbaren Varianten einsetzen. Das fertige Beispiel finden Sie im Listing 6.3 und auf der CD in der Datei \ANWENDUNG\LISTING63.TXT. Function fetch_field(service As String, connect As String, sqlbuff As String) As Variant Dim OraSession As Object Dim OraDatabase As Object Dim OraDynaset As Object On Error GoTo xxerr: 'Erstellen einer OLE-Verbindung zu den Objekten Set OraSession = CreateObject("OracleInProcServer.XOraSession") 'Connecten zur Datenbank Set OraDatabase = OraSession.OpenDatabase(service, connect, 0&) 'Abfrage ausführen Set OraDynaset = OraDatabase.CreateDynaset(sqlbuff, 0&) 'Erstes Feld im ersten Datensatz lesen, der Rest wird weggeworfen fetch_field = OraDynaset.fields(0).Value 'Cursor und Sitzung schließen OraDynaset.Close
Grundlagen
493
OraDatabase.Close Exit Function xxerr: fetch_field = Error End Function Listing 6.3: Kleine Funktion zur Datenbankabfrage in Excel
Ich finde, dieses Programmbeispiel hat durchaus Ähnlichkeiten zu den beiden letzten Beispielen 6.1 und 6.2. Zunächst müssen Sie mit Hilfe der Funktion CreateObject ein Sitzungsobjekt erstellen, dass Sie anschließend mit Hilfe der Methode OpenDatabase mit der Datenbank verbinden, wodurch Sie eine konkrete Datenbankverbindung erhalten, die Sie wiederum als Basis zur Ausführung von Abfragen verwenden können. Für alle diese Aufgaben benötigt unsere Funktion insgesamt drei Parameter. Der erste legt die zu verwendende Datenbankinstanz fest, indem dort der zugehörige Servicename für das Net8-Protokoll übergeben wird. Der zweite Parameter enthält die benötigten Anmeldeinformationen, also Benutzerkennung und das zugehörige Passwort und mit Hilfe des letzten Parameters wird die auszuführende Abfrage spezifiziert. fetch_field("db01", "ubeispiel/manager", "select ... ")
Installation der Funktion Gehen Sie folgendermaßen vor, um die hier beschriebene Funktion in einem ExcelArbeitsblatt verfügbar zu machen: 1. Starten Sie Excel mit einem neuen, leeren Arbeitsblatt. 2. Wählen Sie im Extras-Menü die Einträge „Makros“ und anschließend „Visual Basic Editor“. Anschließend wird der VBA-Editor mit seinen verschiedenen Arbeitsfenstern gestartet. 3. Sie können die Funktion ein einem vorhandenen oder separaten Modul speichern. Öffnen Sie das gewünschte Modul oder erstellen Sie ein neues, indem Sie im Einfügen-Menü des VBA-Editors den Eintrag „Modul“ auswählen. 4. Kopieren Sie das im Beispiel 6.3 gezeigte Listing vollständig in das ausgewählte Modul und speichern Sie die Änderungen anschließend. Anwenden der Funktion Zur Anwendung der Funktion habe ich eine kleine Personalstatistik erstellt, die Sie ebenfalls im Verzeichnis \ANWENDUNG in der Datei DEMO.XLS finden. Das Schöne an diesem Beispiel ist, dass abgesehen von den reinen Berechnungen wie die Summenfelder, alle anderen Spalten direkt aus der Datenbank gelesen werden und somit jederzeit per F9-Funktionstaste (manuelles Neuberechnen) aktualisiert werden können. Noch besser ist allerdings, dass wir die Parameter der Funktion im zweiten Arbeitsblatt speichern können, d.h. die Parameter der einzelnen Funktionsaufrufe bestehen selbst wiederum aus lauter Zellverweisen.
494
Anwendungsentwicklung
Abbildung 6.9: Verwenden unserer neuen Funktion in einer Personalstatistik
=fetch_field(Tabelle2!$A$1;Tabelle2!$A$2;Tabelle2!B7)
In dem zweiten Arbeitsblatt „Tabelle2“ stehen also die konkreten Anweisungen für unsere kleine Statistik (vlg. Abb. 6.10). In den Zellen A1 und A2 wurden der Servicename und die Anmeldeinformationen abgelegt, wobei man die Abfrage dieser Daten sicherlich auch noch parametergesteuert realisieren könnte. In den Zellen B7 bis C9 sind die auszuführenden SQL-Abfragen abgelegt, die zu den in Abbildung 6.9 gezeigten Ergebnissen führen.
Abbildung 6.10: Definition der Personalstatistik
Stichwortverzeichnis XSymbole != 256 (+) 279 >= 255 || 124
XA
Abfrage Auswahlabfragen 250 Abfragen 249 Ablaufkontrolle 249 Aliasnamen 251 Änderungsabfragen 249, 298 aufteilen 296 Auswahlabfragen 249–250 connect by-Klausel 294 Cursor verwenden 249, 309 distinct-Klausel 253–254 Einfügeabfragen 303 Existenzprüfung 271 for update-Klausel 306 from-Klausel 250–251, 269–270 group by-Klausel 261, 264 Gruppierungen 261 having-Klausel 261, 263–264 hierarchisch 287 insert into-Anweisung 303, 305 Joins 266 Löschabfragen 301 Mengenoperationen 281 order by-Klausel 250, 260, 282 Outer-Joins 278 returning-Klausel 317 select-Klausel 250 sortieren 259 Tabelle löschen 302 Transaktion 319 Tuning 249, 324 Unterabfragen 271, 273 update-Anweisung 298 where-Klausel 250, 254, 269 Abfragen ausführen siehe SQL*Plus Abfrageoptimierung siehe Tuning von Abfragen Abgleichengruppe 156 abs 398 accept 102 add constraint 194, 199 add_months 404 Aggregatfunktionen 262–263, 275 Aliasnamen 251, 270
all_clusters 119 all_db_links 123 all_ind_columns 131 all_indexes 131 all_tab_columns 479 all_tab_privs 456, 460 all_tables 457 all_views 214 alter cluster 119 alter database datafile 222 alter index 423 alter profile 365 alter sequence 156, 158 alter session 397 alter system kill session 353 alter table 185, 192 alter tablespace 222 alter trigger 212 alter type 179 analyze table 326 and 258 Änderungsabfragen 298 Änderungsprotokoll 170 Äquivalenz 258 Archiver 63 Array Types 114–116 ascending 260 ascii 401 Aufbau einer View ermitteln 350 Ausdruck 392 Ausführungsplan 329 Planungstabelle erstellen 329 Ausführungsplan lesen 331 Auswahlabfragen dynamsich erzeugen 425 avg 262
XB
Bedingungen siehe if-Anweisung begin 124, 384 Benutzer 357 cascade-Klausel 362 create user 361 drop user 361 Passwortregeln 364 Rechte vergeben 366 Rollen zuweisen 369 Systemrechte vergeben 369 Zugriff auf Funktionen oder Prozeduren 368 Zugriff auf Tabellen und Views 366
496
between 257 binary_integer 387 bind_variable 429 Blockstruktur 384 boolean 387
XC
cascade 362, 364 ceil 399 char 387 chartorowid 315, 406 chr 400, 446 CKPT 20 close 312, 420 close_cursor 423 Cluster 119 Cobol 382 Collections 172 column_value 428 commit 306, 316–317, 320 concat 400 connect 71, 90 connect by 290, 292, 294, 332 Constraint 465 Constraints 192 check 195 default 193 disable 200 enable novalidate 200 foreign key 197 null / not null 193 primary key 194 references 198 control_files 67 count 180, 262, 391, 472 create cluster 119 create database 72 create database link 121 create function 123 create index 128, 131, 224 create or replace function 117, 124–125, 365 create or replace package 456, 471, 475 create or replace package body 137, 143, 456, 476
create or replace procedure 149 create or replace trigger 206, 210, 461, 468, 472
create or replace type 133, 174–175 create or replace type body 136, 178, 180 create or replace view 213, 216, 295 create package 141 create package body 137 create procedure 149 create profile 363 create public synonym 172 create role 220–221, 363
Stichwortverzeichnis
create sequence 156 create snapshot 164 create snapshot log 160, 166 create synonym 171 create table 115–116, 135, 185, 192, 224, 296 create table as select 461 create type 132, 134, 173–174 create user 221, 362 create view 216 create_pipe siehe dbms_pipe current of 316 currval 158 Cursor 309, 313, 420 Datensätze abrufen 420 for-Schleife 421 öffnen 420 Parameter verwenden 422 schließen 420 Statusvariablen 421 cursor 310
XD
Data Definition Language siehe DDL Data Dictionary siehe Systemviews Data Manager 236 Database Buffer Cache 20 Database Link 120 date 387 Datei öffnen siehe utl_file Datei schließen siehe utl_file Dateiverarbeitung 441 Datenbank 13–14 anlegen 69, 72 erstellen 62 Instanz 62 Instanz erstellen 67 Kontrolldatei 64 löschen 91 öffnen und schließen 89 Parameterdatei 62 Protokolldateien 63 redo log 63 Rollback-Segment 64 Rollback-Segmente 67 Struktur 62 Tablespace 63 Datenbank-Management-System 14–15, 18 arbeitsplatzorientiert 14 Client/Server-Struktur 15 File Sharing Database System 15 Instanz 19 System-Identifer 19 Datenbankname ermitteln 352 Datenbankobjekte 109 Datenbankparameter abfragen 353 Datenbank-Pipes siehe dbms_pipe
Stichwortverzeichnis
Datenbankverknüpfung 120 Datenblock 23 DB_BLOCK_SIZE 23 Datenfeld 387–390 Datenfelder 114 Datenimport 230 Datensätze sperren 306 Datentypen 193 binary_integer 387 boolean 387 char 387 date 387 number 387 raw 117 Übersicht 386 varchar2 388 varray 116 Datentypkonvertierung 396 Datum konvertieren 405 Datumsfunktionen 403 DB_BLOCK_SIZE 23 db_files 66 db_name 66 DBA Studio 94, 360 dba_col_privs 378 dba_extents 188 dba_profiles 376 dba_role_privs 376 dba_roles 376 dba_segments 188 dba_sys_privs 376–377 dba_tab_privs 377–378 dba_users 376 DBA-Studio 109 DBMS siehe Datenbank-Management-System dbms_output 190, 318, 410, 457, 475 disable 411 enable 411 new_line 411 put 411 put_line 410 dbms_pipe 446 create_pipe 450 next_item_type 449 pack_message 447 Pipe auslesen 448 Pipe erstellen 447 purge 450 receive_message 448 reset_buffer 451 send_message 447 unpack_message 449 dbms_pipe siehe remove_pipe dbms_refresh 156, 167
497
dbms_snapshot 165 dbms_space 189–190 dbms_sql 423, 456, 480 bind_variable 429 close_cursor 423 column_value 428 define_column 427 execute 423, 427 execute_and_fetch 428 fetch_rows 428 last_row_count 428 open_cursor 423 parse 423 dbms_standard 432 raise_application_error 412 dbms_utility 327 DBWR 19 DDL-Abfragen dynamsich erzeugen 423 DDL-Anweisungen 321 declare 125, 150, 310, 385, 388 decode 265, 409 default 193, 394 define_column 427 define_editor 99 Deklarationsteil 125, 385 delete 301 deleting 463, 472 descending 260 Dienst 36–37 Dienste Batchdatei zum Starten 36 disable 200 disable siehe dbms_output distinct 253 DML-Abfragen dynamsich erzeugen 423 drop cluster 119 drop constraint 194 drop database link 121 drop function 123 drop index 128, 131 drop package body 137 drop procedure 149 drop profile 363 drop public synonym 172 drop role 220–221, 363 drop sequence 156 drop snapshot 164 drop snapshot log 160, 166 drop synonym 171 drop table 115, 135, 185, 296 drop trigger 206 drop user 221, 362 drop view 213, 216
498
dual 255 Dynamisches SQL 423
XE
edit 99 Einfügeabfragen 303 enable novalidate 200 enable siehe dbms_output end 124, 384 Erstellen eines Ausführungsplans 330 exception 431, 457, 460 exec 98 execute 150, 423, 427, 459, 479 execute_and_fetch 428 Existenzprüfung 271 exists 272, 300, 302 exit 416–417 exit when 312, 417 explain plan 330 Export von Daten 475 Extend 23 extend 183, 392, 472 Extents 186, 188
XF
fclose siehe utl_file Fehlerbehandlung siehe Laufzeitfehler Fehlerbehandlungsroutine 384 Fehlermeldung ausgeben siehe raise_application_error fetch 312, 420–421 fetch_rows 428 File Sharing Database System siehe Datenbank-Management-System floor 180, 399 fopen siehe utl_file for each row 208, 211 for loop 416 for update 306 for update nowait 307 for..loop-Schleife 126 foreign key 198, 465 Formatierung von Long-Feldern 350 Formel 392 from 250–251, 269 Funktion zur Passwortkontrolle 365 Funktionen 123, 125, 149, 398, 451 Aggregatfunktionen 262 Datenkonvertierung 405 Datum 403 numerische 398 sonstige 409 Übersicht 398 Zeichenfolgen 399 Funktionen analysieren 351
Stichwortverzeichnis
XG
Ganzzahldivision 144, 399 get 99 get_line siehe dbms_output Globale Variablen 141 goto 414 grant 220 greatest 409 group by 261, 263–264 Gruppierungen 261
XH
having 261, 264 Hints 327, 342–343 Hinweismeldungen 410 Histogramme 326 host 103
XI
identified by 362 if-Anweisung 412 Implikation 258 Import von Daten 230 in 256, 273 Index 127–129, 194 index by 389 Indexaufbau ermitteln 349 Indexe ermitteln 349 initcap 400 insersect 285 insert into 116, 135, 303, 305 inserting 211, 463 Installation 25 8er-Version 26 entfernen 32 Oracle-Homeverzeichnis 26, 32 Version 8i 32 Windows-Dienste 35 Installers 25 Instanz 19–20, 35, 62 Datenbank öffnen und schließen 89 erstellen 67 INITxxxx.ORA 66 konfigurieren 66 ORADIM.EXE 88 Servicename 45 starten und stoppen 88 verwalten 87 Instanzparameter 66 control_files 67 db_files 66 db_name 66 remote_login_passwordfile 67 rollback_segments 67 instead of 210
Stichwortverzeichnis
499
Instead of Trigger 209 instr 401 integer 385, 387 intersect 281 into 418 is not null 256 is null 256
lock table 308 Logische Verknüpfungsoperatoren 257 loop 416 lower 143, 145, 400 lpad 400 LRU-Algorithmus 20 ltrim 400
XJ
XM
Joins 266, 268 connect by-Klausel 290 Existenzprüfung 271 Inner-Joins 268 Nichtexistenz prüfen 273 Outer-Joins 278 Unterabfragen 273, 276
XK
Kollektionen 172, 390 Kommentare 386 Konfigurationsdatei 66 Konfigurationsparameter siehe Instanzparameter Konstruktor 177, 391–392 Kontrolldatei 64 Konvertierfunktionen siehe Funktionen
XL
last_day 404 last_row_count 428 Laufzeitfehler Austellung der Fehler 432 Behandlung im Programm 431 exception 431 pragma exception_init 434 protokollieren 437 raise 435 raise_application_errors 433 selbst auslösen 433 Sicherungspunkte 440 sqlcode 434 sqlerrm 434 Ursachen 429 when 431 least 409 length 401 Lesekonsistenz 322 Lesetransaktion 322 level 292 LGWR 19 like 256 Listener 16, 36, 39–41, 55 Konfigurationsdatei 41 Konsole 42 Protokolldatei 42 Verwaltung 42
max 262, 275, 419 member function 178 Mengenoperationen 281 Methoden count 391–392 delete 392 exists 392 extend 392 first 392 last 392 limit 392 next 392 prior 392 trim 392 min 262, 275 minus 281, 286 mod 143–144, 399 modify 201 months_between 180, 404 mutating 470
XN
nested table 176 Net8 38–40, 69, 120 Client konfigurieren 43 Configuration Assistant 48 Configuration Assistant für 8i 52 Connection Manager 61 Grundeinstellungen 44 Host-Naming 60 Konfigurationsdateien 40 Listener konfigurieren 41 LISTENER.ORA 41 Oracle Names 61 Oracle Net8 Assistant 53 Registrierungsdatenbank 40 Servicename 45 SQLNET.ORA 43–44 TNSNAMES.ORA 43, 45 Werkzeuge zur Anpassung 48 Net8-Protokoll 481 new_line siehe dbms_output next_day 404 next_item_type siehe dbms_pipe nextval 158 Nichtexistenz prüfen 273 nls_date_format 397
500
nlssort 401 not 258 not exists 273, 285, 300, 470 not in 256 not like 256 notfound siehe Cursor nowait 307 null-Werte 256 number 387 nvl 257, 409
XO
Object Types 132–133 Objektbezogene Rechte 358 ODBC 482 Datenquelle einrichten 485 Passthrough-Abfragen 485 Verbindungszeichenfolge 489 Online-Dokumentation 18 open 312, 420 Open Database Connectivity 482 open_cursor 423 Operatoren 251, 392–393 || 124 Logische Verknüpfungsoperatoren 257 Mengenoperatoren 281 Outer-Join 279 Vergleichsoperatoren 254 Optimierer 325 optimizer_mode 327 or 258 Oracle Call Interface 481 Oracle Data Control 491 Oracle Objects 491 Oracle Schema Manager 109 ORACLE_SID 70 Oracle-Homeverzeichnis 26 Oracle-Installer 25 Oracle-Protokoll-Adapter 38 Oracle-Protokoll-Adapters 39 ORADIM.EXE 68 order by 250, 260, 283 Outer-Joins 278
XP
pack_message siehe dbms_pipe Package 138, 142, 475 Package bodies 137 Package body 138–139, 142, 453 Packages 138–139, 452, 471 Pakete dbms_output 190, 318, 410 dbms_refresh 156, 167 dbms_snapshot 165 dbms_space 189
Stichwortverzeichnis
dbms_sql 423, 480 dbms_standard 412, 432 dbms_utility 327 utl_file 442, 480 Pakte dbms_pipe 446 Parameter überladen 146 Parameterleiste 125 parse 423 password_verify_function 365 Passwortregeln 364 pause 104 Pipe auslesen 448 erstellen 447 Pipe siehe dbms_pipe PL/SQL 381 PL/SQL-Engine 382 PMON 20 Potenzierung 399 power 399 pragma exception_init 434 pragma restrict_references 133, 141 prior 292 Profile 358, 363 cascade-Klausel 364 create profile 363 drop profile 363 Passwortregeln 364 Programmblock 124 Programmblöcke 384 Protokolldateien 63 Prozeduren 149, 452 Prozeduren analysieren 351 Prozeduren ausführen 150 Prozedurparameter 154 namensbezogene Übergabe 154 positionsbezogene Übergabe 154 Pseudospalten level 292 rowid 314 rownum 270 purge siehe dbms_pipe put siehe dbms_output put_line siehe dbms_output put_line siehe utl_file
XQ
Quadratwurzel 399 Queue Tables 151 quota unlimited 221
XR
raise 435 raise_application_error 207, 365, 412, 474 raise_application_errors 433
Stichwortverzeichnis
raw 117 rebuild 423 receive_message siehe dbms_pipe Rechte objektbezogen 358 Profil 358 Systemrechte 358 Rechtevergabe mit SQL 366 grant 366 revoke 366 record 387, 389 Redo Log Buffer 20 redo log Dateien 63 references 198 refresh 165 Refresh group 156 remote_login_passwordfile 67 remove_pipe siehe dbms_pipe Reorganisation eines Index siehe Index Reorganisieren eines Index 131 replace 400 Replikation 160 reset_buffer siehe dbms_pipe resize 222 restrict_references 134 returning 318–319 reverse 126 role_role_privs 377 rollback 306, 321, 475 rollback_segments 67 Rollback-Segment 64, 321 Rollback-Segmente 67 Rollen 357, 363 create role 363 drop role 363 round 399 rowid 314, 316, 387, 406 rowidtochar 315, 406 rownum 270 rowtype 396 rpad 400, 428 rtrim 400 run 98 Runden 399
XS
save 99 savepoint 323, 440 Schema 112 Benutzer-Id 113 löschen 113 Schema-Objekt Sequences 156 Snapshot logs 160 Schema-Objekte Abgleichengruppe 156
501
Ansicht 212 Array Types 114, 116 Cluster 119 Datenbank-Link 120 Funktionen 123, 125 Index 127–129 Object Types 132–133 Package 138 Package bodies 137 Prozeduren 149 Queue Tables 151 Refresh group 156 Snapshot Logs 166 Snapshots 160 Synonyms 170 Tabellen 185 Tabellentyp 172 Table Types 172 Tables 185 Trigger 203 Views 212 Warteschlangentabelle 151 Schleifen 415 Cursor-for-Schleife 421 exit 416–417 for loop 416 loop 416 Namen geben 417 while loop 416 Security Manager 359 Segment 23 Extend 23 Segmente 23, 186 Segmenttyp 188 select 250–251, 269 select..into 418 send_message siehe dbms_pipe Sequence 465 Sequences 156 Server Manager 69 Server-Manager 70, 72 @ 71 exit 70 Skript starten 71 serveroutput 410 Session beenden 353 Sessionaktivität ermitteln 355 Sessions ermitteln 353 set 410 set longwidth 1000 350 set serveroutput on 190, 317 set termout off 101 set transaction read only Lesetransaktion 322 set transaction use rollback 321
502
SGA siehe System Global Area Shared Pool 20 show errors 117, 124 shutdown 90 Sicherungspunkte 323, 440 SID siehe System-Identifer sign 399 Skripte 451 smallint 387 SMON 19 Snapshot aktualisieren 165–166 Snapshot Logs 166, 170 Snapshot logs 160 Snapshots 156, 160, 163–164, 168, 170 Sortieren 259 lexikalisch 401 Spaltenbezogene Rechte anaylsieren 378 Speicherbelegung 186 Sperrungen 306, 308 Sperrungen ermitteln 354 spool 101 Sprungmarken 414 SQL 249 SQL*Loader 236 feste Satzlänge 239 logische Datensätze 244 Steuerdatei 241 variable Satzlänge 243 SQL*Loaders 238 SQL*Net 38–39 SQL*Plus 96–97, 100, 103 Abfragen ausführen 97 accept 102 AIX 96 Ausgaben umleiten 101 Benutzereingaben 102 Betriebssystembefehle 103 define_editor 99 edit 99 Eingabepuffer 98 exec 98 get 99 host 103 Kommandozeilenparameter 100 Laden und Speichern von Dateien 99 MS-DOS 97 pause 104 run 98 save 99 Skript starten 100 spool 101 Texteditor 99 Windows 97 sqlcode 434 SQL-Editor 96
Stichwortverzeichnis
sqlerrm 434 SQL-Worksheet 105–106 SQR 382 sqrt 399 Standardwert festlegen 193 start with 291 startup 71, 90 stddev 262 Stichtagsbestände 170 Storage-Klausel 186 storage-Klausel 188 Storage-Manager für Oracle8 93 für Oracle8i 94 Protokollierung 94 Systembefehle analysieren 94 Structured Query Language siehe SQL substr 400 Suchen und Ersetzen 400 sum 262 SVRMGR30.EXE 69 SVRMGRL.EXE 69 Synonyms 170 sysdate 404, 463 System Global Area 19 System-Identifer 19 Systemrechte 358 Systemrechte einer Rolle ermitteln 377 Systemrechte ermitteln 376 Systemrechte vergeben 369 Systemview Aufbau einer View ermitteln 350 user_views 350 Systemviews 346, 375 all_clusters 119 all_db_links 123 all_ind_columns 131 all_indexes 131 all_views 214 Benutzer und Rollen analysieren 376 Datenbankname ermitteln 352 Datenbankparameter abfragen 353 dba_col_privs 378 dba_extents 188 dba_profiles 376 dba_role_privs 376 dba_roles 376 dba_segments 188 dba_sys_privs 376 dba_tab_privs 377 dba_users 376 Funktionen oder Prozeduren analysieren 351 Indexaufbau ermitteln 349 Indexe ermitteln 349
Stichwortverzeichnis
role_role_privs 377 Rollen ermitteln 376 Sessionaktivität ermitteln 355 Sessions ermitteln 353 Spaltenbezogene Rechte 378 Sperrungen ermitteln 354 Systemrechte einer Rolle ermitteln 377 Systemrechte ermitteln 376 Tabellen analysieren 347 Tabellenstruktur analysieren 347 Trigger analysieren 351 Übergabeparameter ermitteln 352 user_arguments 352 user_indexes 349 user_source 351 user_tab_columns 347, 349 user_tables 347 user_triggers 351 user_updatable_colums 215 v$database 352 v$lock 354 v$locked_object 354 v$parameter 353 v$sessions 353 v$sql 355 Zugriffsrechte analysieren 355
XT
Tabelle ändern 201 Tabelle kopieren 201 Tabelle löschen 302 Tabellen 185 Tabellen analysieren 347 Tabellen sperren 308 Tabellenstruktur analysieren 347 Tabellentyp 172 table 388 table is mutating 470 table of 390 Table Types 172 Tables 185 Tablespace 21–22, 63, 186, 188, 222 Datei hinzufügen 222 Dateien umziehen 223 vergrößern 222 Textfeld formatieren 350 TNS siehe Transparent Network Substrate TNS_ADMIN 40 to_char 206, 406 to_date 406 to_number 406 Transaktion 319 commit 320 in DDL-Anweisung 321 Lesekonsistenz 322
503
Lesetransaktion 322 rollback 321 Rollback-Segment zuweisen 321 savepoint 323 set transaction 321 Sicherungspunkte 323 translate 401 Transparent Network Substrate 38 Trigger 203, 206–207, 454, 461–462, 465 Ausführungsschema 470 deleting 463 inserting 463 mutating table 470 updating 463 Trigger analysieren 351 Trigger ein- und ausschalten 212 trunc 399, 404 truncate table 302 Tuning von Abfragen 324 analyze table 326 Ausführungsplan 329 Ausführungsplan erstellen 330 Ausführungsplan lesen 331 datenbankübergreifend 340 dbms_utility 327 explain plan 330 Grundregeln 336 Gruppierungen 341 Hints 327, 341 Histogramme 326 Joins und Mengenoperationen 337 kostenbasierte Optimierung 325 Operationen 334 Optimierer 325 optimizer_mode 327 regelbasierte Optimierung 325 Struktur und Aufbau 339 Unterabfragen 338 Views 340 type 389, 396, 471
XU
Übergabeparameter ermitteln 352 Überladen von Parametern 146 uid 409 union 281, 283 union all 281, 285 unpack_message siehe dbms_pipe Unterabfragen 271, 273, 276, 299, 301 Unterabfragen mit connect by 294 update 116, 298 updating 211, 463, 472 upper 143, 145, 401 user 409, 463 user_arguments 352 user_indexes 349
504
user_source 351 user_tab_columns 347, 349 user_tables 347 user_triggers 351 user_updatable_colums 215 user_views 350 userenv 409 utl_file 442, 480 Datei öffnen 443 Datei schließen 443 Datensätze lesen 444 Datensätze schreiben 445 fclose 443 fclose_all 444 fopen 443 get_line 444 new_line 445 put 445 put_line 445 Verzeichnisse freigeben 442 utl_file.fclose 443 utl_file.get_line 444 utl_file.put_line 445
XV
v$database 352 v$lock 354 v$locked_object 354 v$parameter 353 v$sessions 353 v$sql 355 varance 262
Stichwortverzeichnis
varchar2 388 Variablen anlegen 385 implizite Konvertierung 396 Initialisierung 394 null-Werte 395 Sichtbarkeit 385 Typableitung 395 Wertzuweisung 385 varray 116–117, 134, 388 Varrays 390 Vergleichsoperatoren 254 View Aufbau ermitteln 350 Views 212 Visual Basic 382 Vorzeichenfunktion 399
XW
Warteschlangentabellen 151 when 431 where 250, 254, 269 while loop 416 Windows-Dienste 35
XZ
Zahl konvertieren 405 Zahlengenerator 156 Zugewiesene Rollen ermitteln 376 Zugriffsrechte analysieren 355 Zugriffsrechte mit SQL vergeben 366