SQL Nitty Gritty - *ISBN 3-8273-1990-0*

Werbung
Heinz-Gerd Raymans
SQL
An imprint of Pearson Education
München • Boston • San Francisco • Harlow, England
Don Mills, Ontario • Sydney • Mexico City
Madrid • Amsterdam
4
Dies und Das
In diesem vorletzten Kapitel habe ich für Sie noch eine Menge zusätzlicher Informationen zusammengestellt, bei denen es sich teilweise um
Ergänzungen zu dem im zweiten Kapitel behandelten Stoff handelt.
Exkurs zur Datenbankstruktur
4.1.1 Struktur einer MySQL-Datenbank
Wie Sie schon wissen, werden MySQL-Datenbanken durch entsprechende Unterverzeichniseinträge im File-System gebildet, wobei die
einzelnen Tabellen durch entsprechende Dateien in diesen Unterverzeichnissen repräsentiert werden. Aus dieser sehr engen Verzahnung zwischen dem vom Betriebssystem kontrollierten Dateisystem
auf der einen und dem Datenbanksystem auf der anderen Seite resultiert die Tatsache, dass Sie für die in MySQL gespeicherten Objekte manchmal auf die exakte Schreibweise (Groß-/Kleinschreibung) achten müssen.
Aus Sicht des Betriebssystems stellt das MySQL-DBMS lediglich eine
Liste von Unterverzeichnissen dar, die alle direkt unter einem bestimmten Wurzelverzeichnis angelegt werden. Dabei ist ein besonderes Unterverzeichnis mit dem Namen „mysql“ immer vorhanden. Hierbei handelt es sich um die MySQL-Systemdatenbank, in der sich eine
Reihe von vordefinierten Tabellen befinden, mit denen die zugelassenen Benutzer und deren Zugriffsberechtigungen gespeichert werden.
Dieses Wurzelverzeichnis kann im Rahmen der MySQL-Konfiguration
vorgegeben werden; standardmäßig zeigt es auf das \data-Unterverzeichnis der MySQL-Installation. Aber selbst mit einem SQL-Editor
kann man feststellen, wo sich dieses Wurzelverzeichnis befindet.
Hierzu muss man wissen, das MySQL viele Betriebsparameter zur
Laufzeit in so genannten Systemvariablen vorhält, die mit Hilfe einer
speziellen Variante des schon bekannten show-Kommandos abgefragt werden können.
show variables [like <Einschränkung]
Dies und Das
331
4
Nitty Gritty • Take that!
4.1
Wenn Sie diesen Befehl einmal ohne weiteren Parameter verwenden,
dann erhalten Sie am Bildschirm eine Übersicht über sämtliche bereitgestellte Systemvariablen.
Variable_name
Value
==================================================
ansi_mode
OFF
back_log
50
basedir
c:\mysql\
bdb_cache_size
8388600
bdb_log_buffer_size
32768
bdb_home
c:\mysql\data\
Mit Hilfe der like-Klausel können Sie hingegen jede einzelne Variable
gezielt abfragen, d.h. auch den aktuellen Wert der datadir-Variablen,
die das verwendete Wurzelverzeichnis enthält, könnten Sie auf diese
Weise ermitteln.
Nitty Gritty • Take that!
4
show variables like 'datadir'
Variable_name
Value
==================================================
datadir
c:\mysql\data\
Eine ausführliche Beschreibung sämtlicher verfügbarer
Systemvariablen finden Sie in der Online-Hilfe Ihres
MySQL-Systems. Suchen Sie in dem HTML-Dokument
beispielsweise nach der Zeichenfolge „show variables“
und wiederholen Sie den Suchvorgang anschließend ein
paar Mal, um das entsprechende Kapitel zu finden.
Jede neue Datenbank führt also zu einem weiteren Unterverzeichnis,
und jede angelegte Tabelle führt dort zu neuen Dateien. Dabei entspricht der Dateiname zunächst einmal dem bei der create table-Anweisung vergebenen Namen, wobei die genaue Anzahl der Dateien
und die vergebene Dateinamenserweiterung von der bei der Anlage
verwendeten type-Klausel abhängt. Das MySQL-System besitzt zwar
eine einheitliche Kommandoebene, bedient sich aber unterschiedlichster Zugriffsmethoden, die teilweise sogar von unterschiedlichen
Herstellern erstellt bzw. gewartet werden.
332
Exkurs zur Datenbankstruktur
Standardmäßig werden alle Tabellen vom Typ „MYISAM“ angelegt,
und hierbei entsteht zunächst einmal eine FRM-Datei, in der die Tabellenstruktur abgelegt wird. Daneben existieren eine Datendatei
(*.MYD) und eine Datei (*.MYI), in der alle Indexeinträge gespeichert
werden. Dagegen werden bei den Barkley_DB-Tabellen Daten und Indexeinträge in einer gemeinsamen Datei (*.DB) gespeichert; außerdem entsteht wieder eine FRM-Datei, in der sich wieder die Tabellenstruktur befindet.
Die gesamte Struktur eines MySQL-Systems ist einfach und dennoch
effizient. Der Vor- und manchmal auch Nachteil eines derartig durchschaubaren Systems ist allerdings, dass man häufig geneigt und in
der Lage ist, mit einfachen Betriebssystembefehlen in die Datenbankstruktur einzugreifen; ich kenne sonst keine Datenbank (lassen
wir Access einmal außen vor), bei der man mit dem Windows-Explorer oder einem entsprechenden FTP- oder Telnet-Programm Datenbanken oder Tabellen so einfach umbenennen oder kopieren kann.
Und noch eine Erkenntnis ist zwar banal, aber trotzdem unterschiedlich zu vielen anderen Datenbanksystemen. Der Erfolg einer Einfügeoperation hängt bei MySQL letztendlich von dem noch verfügbaren
Platz auf dem Datenträger bzw. dem verwendeten Datei-System ab,
d.h., das Datenbanksystem konkurriert permanent mit allen anderen
Prozessen, die ebenfalls Platz auf dem gleichen Datenträger bzw. Datei-System beanspruchen bzw. verbrauchen.
Falls Ihnen der Begriff des „Datei-Systems“ nichts sagt, so möchte
ich im Folgenden einen kurzen Erklärungsversuch starten. Üblicherweise bieten die heutzutage gängigen Betriebssysteme die Möglichkeit, einen Datenträger (Festplatte) in beliebige logische Bereiche
aufzuteilen. Auch unter Windows/DOS kann man einen weiteren
Laufwerksbuchstaben also nicht unbedingt mit einer zweiten physiDies und Das
333
4
Nitty Gritty • Take that!
Die Größe einer MySQL-Datenbank ist übrigens zunächst einmal nur
durch den bereitgestellten Platz des Datenträgers bzw. des DateiSystems begrenzt. Mit jedem neuen Datensatz einer Tabelle wird die
zugehörige Datendatei größer. Folglich wächst auch der von dem zur
Datenbank gehörenden Unterverzeichnis beanspruchte Platz, d.h.,
die Datenbankgröße hängt direkt von dem Wachstumsverhalten ihrer zugehörigen Tabellen ab.
kalischen Festplatte gleichsetzen. Unter Windows/DOS nennt man
diese logischen Bereiche üblicherweise Partition (vgl. „Festplatte
partitionieren“), wohingegen man in der UNIX-Welt von der Anlage
bzw. Definition eines Datei-Systems spricht.
4.1.2
Struktur einer SQL-Server-Datenbank
Auch der SQL-Server benutzt zum Speichern der verschiedenen Datenbankobjekte natürlich das vom Betriebssystem kontrollierte
Dateisystem, jedoch ist das Zusammenspiel abstrakter geregelt.
Eine SQL-Server-Installation muss zunächst einmal nur den Weg zur
Primärdatei der so genannten Master-Datenbank finden. Von da an
ist der Zugriff auf wichtige Systemtabellen gegeben und anschließend kann sich die Datenbank über sämtliche verfügbare Festplatten
erstrecken.
Nitty Gritty • Take that!
4
Zunächst einmal besitzt eine SQL-Server-Installation die Datenbanken „master“ und „tempdb“, wobei im Rahmen einer Standardinstallation meistens weitere Datenbanken angelegt werden. Dabei ist die
Master-Datenbank das Herzstück des SQL-Servers, denn in ihr befinden sich zentrale Systemtabellen, ohne die das gesamte DBMS nicht
lauffähig ist. Das beginnt beispielsweise mit der Systemtabelle „sysdatabases“, in der jede verfügbare Datenbank mit ihrer Primärdatei
registriert wird.
Schon jetzt sollte klar sein, dass man beim SQL-Server den dort verwalteten Datenbanken nicht mit Betriebssystemkommandos zu
Leibe rücken sollte. Wenn Sie hier Verzeichnis- oder Dateinamen ändern, dann passen anschließend vielleicht die in den Systemtabellen
gespeicherten Einträge nicht mehr und anschließend kann die zugehörige Datenbank oder sogar das Gesamtsystem nicht mehr hochgefahren werden.
Eine SQL-Server-Datenbank besteht mindestens aus zwei Dateien.
Eine dieser beiden Dateien dient zur Aufnahme der datenbankindividuellen Systemtabellen und zur Speicherung der Benutzertabellen
nebst zugehörigen Daten; in der anderen Tabelle wird das Transaktionsprotokoll der Datenbank gespeichert.
334
Exkurs zur Datenbankstruktur
64/6HUYHU
PDVWHU
WHPSGE
SULPDU\
SULPDU\
P\GE
SULPDU\
JUS
ORJ
ORJ
'DWHQEDQNHQ
'DWHLJUXSSHQ
ORJ
)HVWSODWWHQ
Für den Datenbereich ist die Datenbank allerdings nicht direkt mit
dieser Datendatei gekoppelt, sondern zwischen der Datenbank auf
der einen und der Datei auf der anderen Seite liegt noch einmal der
Begriff der so genannten Dateigruppe. Eine Datenbank basiert nämlich auf einer oder mehreren Dateigruppen und jede Dateigruppe
kann aus einer oder mehreren Dateien bestehen, die überall auf den
verfügbaren Festplatten verstreut sein können (vgl. Abb. 4.1).
Jede Datenbank besitzt also mindestens einen Protokollbereich und
basiert auf mindestens einer Dateigruppe, der mindestens eine Datei
zugeordnet ist, in der die Datenbankobjekte mitsamt den darin enthaltenen Daten gespeichert werden.
Schon während der Anlage einer Datenbank wird sowohl dem Protokoll- als auch dem Datenbereich eine bestimmte Größe zugeordnet,
wobei diese Dateien in der Tat sofort diese vorgegebene Größe annehmen, d.h., wenn Sie hierbei großzügigerweise ein paar hundert
Dies und Das
335
4
Nitty Gritty • Take that!
Bild 4.1: Schematischer Aufbau der logischen Datenbank und Dateigruppen
auf der einen und der physischen Dateien und Festplatten auf der anderen
Seite
Mbyte vergeben, dann muss dieser Platz zum einen auf dem Datenträger verfügbar sein und er steht zum anderen den übrigen Prozessen ab sofort nicht mehr zur Verfügung. Auf diese Weise ist es also
möglich, dass selbst leere Datenbanken mehrere hundert Megabytes
groß sein können. Das hat natürlich den Vorteil, dass die Datenbank
anschließend nicht permanent um Platz auf dem Datenträger ringen
muss; auf der anderen Seite kann es hierbei passieren, dass Sie eine
„Datenbank voll“-Meldung erhalten, obwohl auf der Festplatte noch
reichlich Platz vorhanden ist.
Sie werden es vielleicht kaum glauben, aber in der Praxis ist das nicht
immer unproblematisch. Datenbanken werden häufig mit Hilfe vorgefertigter Skripts erstellt und wenn sich Datenbanken im Rahmen
eines Projekts anschließend wie von selbst vervielfältigen (noch eine
Demodatenbank für Herrn X), dann führt das zu einem entsprechenden Platzverbrauch auf dem Server, obwohl alle einzelnen Datenbanken relativ leer sind.
Nitty Gritty • Take that!
4
Zumindest für Test-, Spiel- oder auch Entwicklungsdatenbanken
kann man dem aber entgegenwirken, indem man die Datenbankdefinition von vornherein sehr klein wählt und den zugehörigen Dateibereich so konfiguriert, dass dieser im Bedarfsfall automatisch wächst.
Eine solche Wachstumserlaubnis kann man dabei uneingeschränkt
erteilen, d.h., die Datenbank kann anschließend so lange wachsen,
bis die Festplatte voll ist, oder man legt eine Obergrenze fest, bis zu
der die zugehörige Datei wachsen darf.
Vorgaben bei der Tabellenanlage
Jede neue Tabelle wird bei der Anlage automatisch in einer zur Datenbank gehörenden Datendatei angelegt. Konkret wird die Tabelle allerdings zunächst der zur Datenbank gehörenden primären Dateigruppe
zugeordnet, und erst danach legt das DBMS fest, in welcher der zugehörigen Datendateien das neue Objekt angelegt wird. Prinzipiell haben
Sie die Möglichkeit, Einfluss auf diese Entscheidung zu nehmen, indem
Sie die gewünschte Dateigruppe bei der Anlage der Tabelle vorgeben.
create table … ( <Spalten- und ConstraintDefinitionen>)
[on <Dateigruppe> ]
[textimage_on <Dateigruppe>]
336
Exkurs zur Datenbankstruktur
Hierzu müssen Sie nach der Definition aller zur Tabelle gehörenden
Spalten und Constraint-Definitionen die gewünschte Dateigruppe, in
deren Dateien die neue Tabelle angelegt werden soll, mit Hilfe der onKlausel vorgeben. Zusätzlich existiert auch noch die Klausel
„textimage_on“, mit deren Hilfe Sie die Speicherung der text- und
image-Spalten in eine andere Dateigruppe und damit in andere Dateien auslagern können.
Sicherlich fragen Sie sich gerade, worum es im letzten Absatz überhaupt ging, so dass ich jetzt damit beginnen werde, Ihnen ein paar
Hintergrundinformationen über die Struktur einer Oracle-Datenbank
zu vermitteln.
Wie Sie schon wissen, unterscheidet sich Oracle vom SQL-Server beispielsweise dadurch, dass für jede Oracle-Datenbank eine eigene Instanz der Datenbanksoftware läuft. Dementsprechend gibt es natürlich
keine Notwendigkeit für zentrale System- oder Temporär-Datenbanken,
denn jede Oracle-Datenbank ist für sich allein eigenständig.
Zu jeder Oracle-Datenbank gehören in der Regel mehrere (gespiegelte) Kontrolldateien, die das Herzstück der Datenbank darstellen
und mit den zentralen Systemtabellen der Systemdatenbank des
SQL-Servers vergleichbar sind. In dieser Kontrolldatei ist beispielsweise definiert, wo ein bestimmter Systembereich der Oracle-Datenbank (physikalisch) gespeichert ist. Ist dieser Systembereich erst
Dies und Das
337
4
Nitty Gritty • Take that!
4.1.3 Struktur einer Oracle-Datenbank
In Kapitel 2.2.1 haben Sie gesehen, dass es auch in einer Oracle-Datenbank prinzipiell möglich ist, eine Tabelle mit Hilfe einer einfachen
create table-Anweisung anzulegen. Allerdings muss ich gestehen,
dass ich solch einfache Anweisungen in meinem bisherigen OracleDasein noch niemals gesehen habe. Das mag zum Teil damit zusammenhängen, dass diese einfache Form des Befehls aufgrund ungeschickter Einstellungen im Profil des Benutzerkontos gar nicht mehr
funktioniert; zum anderen mag es daran liegen, dass solche Datendefinitionsanweisungen immer weniger getippt, sondern immer häufiger generiert werden, so dass auch für die zusätzlichen Klauseln
des create table-Kommandos zumindest irgendwelche Standards zur
Anwendung kommen.
einmal gefunden, dann ist es aufgrund der dort gespeicherten Informationen möglich, die weiteren zur Datenbank gehörenden Dateien
zu öffnen.
Letztendlich speichert auch Oracle die Datenbankinformationen in
ganz gewöhnlichen Dateien, die Sie genau wie beim SQL-Server beliebig auf den verfügbaren Festplatten bzw. File-Systemen verteilen
können. Eine solche Datei bzw. mehrere solcher Dateien bilden zunächst einmal das logische Konstrukt des so genannten Tablespaces,
der in gewisser Weise mit einer SQL-Server-Dateigruppe vergleichbar
ist. Damit ist die Struktur einer Oracle- und SQL-Server-Datenbank
von einem gewissen Standpunkt aus betrachtet gar nicht so unterschiedlich (vgl. Abb. 4.2).
Nitty Gritty • Take that!
4
Unterschiedlich ist eigentlich nur, dass vom diesem Bereichs- bzw. Tablespace-Konzept in Oracle-Datenbanken meistens wesentlich mehr
Gebrauch gemacht wird. Schon mit der Anlage einer einfachen OracleDatenbank erhalten Sie sechs verschiedene Tablespace-Definitionen,
die zur Aufnahme verschiedener System- und Benutzerobjekte vorgesehen sind. Mit der Installation zusätzlicher Softwareprodukte erhält
man üblicherweise ebenfalls weitere Tablespace-Definitionen; so verteilt eine PeopleSoft-Installation die zugehörigen Tabellen beispielsweise auf ca. 60 hierfür angelegte Tablespaces.
Logisches Modell
Datenbank
Tablespaces
Tablespace 1
Physische Dateien
Datei 1
Tablespace 2
Datei 2
Festplatten
Bild 4.2: Schematischer Aufbau einer Oracle-Datenbank
338
Exkurs zur Datenbankstruktur
Datei 3
Wie Sie der Abbildung 4.2 entnehmen können, basiert ein solcher Tablespace genau wie eine Dateigruppe auf einer oder mehreren Dateien. Ähnlich wie beim SQL-Server und entgegen der MySQL-Verfahrensweise wird die Größe einer solchen Datei schon mit der
Definition des Tablespace festgelegt und bei der Anlage auf dem Datenträger beansprucht. Das impliziert somit wieder alle schon im vorhergehenden Kapitel beschriebenen Vor- und Nachteile, denn auch in
Oracle können leere Datenbanken eine beachtliche Größe besitzen;
dafür muss das DBMS nicht permanent mit anderen Prozessen um
Speicherplatz ringen.
Es gibt sogar noch weitere Gemeinsamkeiten mit dem zuvor beschriebenen SQL-Server-DBMS, denn auch die in Oracle definierten Tablespaces bzw. die dazugehörigen Dateien können dynamisch wachsen.
create table <Name der Tabelle> (
<Definition der Spalten...>
)
[<Tablespace-Klausel>]
[<Storage-Klausel>];
Wenn Sie bei der Tabellenanlage diese beiden Klauseln weglassen,
dann werden hierfür Standardwerte (vgl. Kap. 5.3.2) verwendet, was
aber nicht unbedingt immer zu einem funktionierenden Datenbankobjekt führt. Beim Tablespace verwendet Oracle beispielsweise entweder
den dem Benutzer zugeordneten Standardwert oder den Tablespace
„system“ und häufig ist es nicht erlaubt, in diesem zuletzt genannten
Bereich Daten zu speichern, d.h., eine anschließende Einfügeanweisung würde mit einer entsprechenden Fehlermeldung abbrechen.
Wenn Sie bei der Tabellenanlage die zusätzliche Storage-Klausel
weglassen, dann verwendet das DBMS hierfür die im Tablespace verankerten Standardwerte.
Dies und Das
339
4
Nitty Gritty • Take that!
Bei einem solchen intensiven Gebrauch des Dateigruppenkonzepts
ist es schon mehr als nahe liegend, dass sich dieses Prinzip auch bei
der Anlage der einzelnen Datenbankobjekte niederschlägt, weshalb
auch die create table-Anweisung üblicherweise Zusatzoptionen enthält, mit denen festgelegt wird, wo und wie sich eine Tabelle in der
Datenbank ausbreiten soll.
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. 4.3) 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.
Festplatte
Segment
1. Extend
2Kb 2Kb 2Kb 2Kb 2Kb 2Kb
2. Extend
2Kb 2Kb 2Kb 2Kb 2Kb 2Kb
4
Nitty Gritty • Take that!
2Kb 2Kb 2Kb 2Kb 2Kb 2Kb
7DEOHVSDFH
2Kb 2Kb 2Kb 2Kb 2Kb 2Kb
2Kb 2Kb 2Kb 2Kb 2Kb 2Kb
Bild 4.3: Segmente, Extends und Datenblöcke
„Segment“ steht hierbei als Überbegriff für alle diejenigen Objekte,
die in der Datenbank Platz zum Speichern von System- oder Benutzerdaten benötigen. Jede angelegte Tabelle ist also genauso wie jede
Indexdefinition ein solches Segment. Die einzelnen Ausprägungen
dieser Klausel werden dabei hinter dem Wörtchen storage in Klammern spezifiziert.
storage (initial ... next ... minextents ...
maxextents ... pctincrease ...)
Hinter den verschiedenen Schlüsselwörtern müssen Sie die jeweils
benötigten Werte angeben. 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.
340
Exkurs zur Datenbankstruktur
in beliebiger Kombination verwenden können. Die genaue Bedeutung der einzelnen storage-Parameter können Sie der Tabelle 4.1 entnehmen.
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 4.1: Beschreibung der storage-Klausel
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.
Dies und Das
341
4
Nitty Gritty • Take that!
StorageKlausel
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 und
legen wir hierzu die Gehaltstabelle noch einmal an, wobei wir diesmal die Tablespace- und Storage-Klausel verwenden wollen.
Nitty Gritty • Take that!
4
create table gehalt (
persnr varchar2(10) not null,
g_ab date default sysdate not null,
la
varchar2(3),
betrag decimal(9,2),
constraint gehalt_pk primary key(persnr, g_ab),
constraint gehalt_001 check (betrag >= 0),
constraint gehalt_002 foreign key (la)
references lohnarten(la)
)
tablespace hrlarge
storage (initial 1M
next 1M
minextents 2
maxextents 10)
pctused 50 pctfree 20;
So wie die Gehaltstabelle jetzt angelegt wurde, belegt sie von vornherein 2Mbyte (2x 1048576 Byte) im Tablespace „hrlarge“. Jede weitere benötigte Erweiterung (Extent) wird ebenfalls als 1Mbyte-Block
angelegt, wobei insgesamt zehn solcher Extents möglich sind.
342
Exkurs zur Datenbankstruktur
Wichtige Systemtabellen
Im bisherigen Verlauf dieses SQL-Buches haben Sie schon mehrfach
verschiedene Systemtabellen bzw. Views kennen gelernt, mit deren
Hilfe Sie Informationen über die Datenbank bzw. über die darin enthaltenen Objekte sammeln konnten. Auch für die Speicherbelegung
existieren solche Systemobjekte. Da wäre zunächst einmal die View
v$tablespace zu nennen, mit deren Hilfe Sie die Namen der vordefinierten Tablespaces ermitteln können.
select name from v$tablespace;
NAME
-----------------------------SYSTEM
RBS
USERS
TEMP
TOOLS
INDX
AMAPP
BDAPP
BNAPP
BNLARGE
…
Mit Hilfe einer Abfrage auf die View dba_segments können Sie die
definierte Speicherbelegung einer jeden Tabelle bzw. eines jeden
Segments abfragen. 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.
select bytes as B, extents as E, initial_extent as IE,
next_extent as NE, min_extents as MIN,
max_extents as MAX
from dba_segments
where segment_name = 'GEHALT';
B
E
IE
NE
MIN MAX
--------- ---- -------- ---------- ---- ------2129920 2
1048576 1048576
2
10
Dies und Das
343
Nitty Gritty • Take that!
4
4.2
Verwenden von Skripten
Werden mehrere SQL-Befehle gemeinsam gespeichert, so dass sie
vom DBMS als eine Einheit verarbeitet werden, dann spricht man bei
diesem Befehlspaket üblicherweise von einem Skript. Dabei muss
man zunächst einmal beachten, dass nicht jedes DBMS die Verwendung von Skripten unterstützt.
Access
Beginnen bei unserer Betrachtung beispielsweise beim AccessDBMS. Wenn Sie hier im Abfragefenster mehrere Befehle hintereinander erfassen und anschließend absenden, dann erhalten Sie eine
Fehlermeldung, auch wenn alle im Skript enthaltenen Anweisungen
fehlerfrei sind.
Nitty Gritty • Take that!
4
Bild 4.4: Ausführen eines Skripts im Access-Abfragefenster
MySQL
MySQL ist da nicht besser, auch wenn der mitgelieferte SQL-Monitor
oder das von mir erstellte Worksheet Ihnen etwas anderes vorgaukelt. „Gaukeln“ ist hier das richtige Wort, denn die scheinbare Fähigkeit, ganze Stapel von SQL-Anweisungen verarbeiten zu können, ist
eine Illusion, da die im SQL-Fenster erfasste Befehlskette mit Hilfe
des Programms in einzelne Anweisungen zerlegt wird. Dabei wird
jede im Skript enthaltene Anweisung einzeln an das DBMS geschickt,
344
Verwenden von Skripten
wonach das zugehörige Ergebnis in Empfang genommen und im Ausgabefenster angezeigt wird.
Aber auch ohne diese Skriptfähigkeit lassen sich in MySQL statementübergreifende Aktivitäten realisieren, sofern die verschiedenen
Statements mit derselben Verbindung zum DBMS bzw. im Rahmen
der gleichen Sitzung abgesendet werden. Bei der Beschreibung der
last_insert_id-Funktion haben Sie hierfür auch schon ein Beispiel
kennen gelernt, denn zunächst wird hierbei mit Hilfe einer Einfügeabfrage ein neuer Datensatz in die Tabelle geschrieben und anschließend wird der hierbei automatisch vergebene Autowert mittels Auswahlabfrage und last_insert_id-Funktion abgefragt.
insert into .....
select last_insert_id()
Die beginnen in MySQL grundsätzlich mit einem Klammeraffen „@“,
dem der eigentliche Variablenname folgt, den Sie mit Hilfe beliebiger
alphanumerischer Zeichen bilden können. Die Wertzuweisung zu einer solchen Variablen kann unter anderem mit Hilfe des set-Kommandos erfolgen.
set @text = 'Guten Morgen!';
set @my_datum = '2001-01-01';
Anschließend können Sie diese Variable beispielsweise im Rahmen
der bisher kennen gelernten Abfragen verwenden und im einfachsten
Fall wird der aktuelle Inhalt mit Hilfe einer select-Anweisung abgefragt.
Dies und Das
345
4
Nitty Gritty • Take that!
Eine solche abfrageübergreifende Kommunikation mit dem MySQLDBMS kann allerdings noch weitergehender genutzt werden. MySQL
bietet Ihnen nämlich die Möglichkeit, eigene Benutzervariablen zu
definieren, mit deren Hilfe Sie beispielsweise Zwischenergebnisse
von einer Abfrage in die nächste übertragen können. Sofern Ihr SQLProgramm (z.B. MySQL-Worksheet oder Ihr konkret eingesetztes Anwendungsprogramm) also das Ablaufen von Skripten simuliert, so
kann man auf diese Weise doch noch das ein oder andere Abfrageszenario quasi in einem Schritt ausführen; doch beginnen wir ganz
von vorne, nämlich bei der Definition solcher Variablen.
select @text, @my_datum;
@text
@my_datum
==================================================
Guten Morgen!
2001-01-01
Allerdings können solche Variablen auch an beliebigen anderen Stellen in der Abfrage auftauchen, so dass sich mit ihrer Hilfe beispielsweise auch flexible where-Bedingungen realisieren lassen. Ein Anwendungsbeispiel wäre vielleicht die Parametrierung immer
wiederkehrender Änderungsabfragen, bei denen Sie alle variablen
Kriterien zunächst durch entsprechende Variablen ersetzen.
Nehmen wir einmal an, die Gehaltsaktualisierung eines Mitarbeiters
soll nach dem folgenden Schema ablaufen: Zunächst soll der eventuell schon vorhandene Datensatz für den Mitarbeiter gelöscht werden,
so dass anschließend in jedem Fall eine Einfügeanweisung verwendet werden kann. Ein solches Abfrageskript könnte etwa folgendermaßen aussehen.
Nitty Gritty • Take that!
4
delete from gehalt
where persnr = @persnr
and g_ab = @g_ab;
insert into gehalt(persnr, g_ab, la, betrag)
values (@persnr, @g_ab, '100', @betrag);
Jetzt haben wir alle variablen Bestandteile dieser Abfrage durch entsprechende benutzerdefinierte Variablen ersetzt, d.h., bevor wir die
Abfragen jetzt an das DBMS senden können, müssen zunächst die
Variablen mit geeigneten Werten vorbelegt werden.
set @persnr = '4711';
set @g_ab = '2001-01-01';
set @betrag = 1566.22;
delete from gehalt
where persnr = @persnr
and g_ab = @g_ab;
insert into gehalt(persnr, g_ab, la, betrag)
values (@persnr, @g_ab, '100', @betrag);
Viel interessanter sind solche Variablen allerdings in speziellen Fällen der Anwendungsentwicklung, denn mit ihrer Hilfe kann in man-
346
Verwenden von Skripten
chen Fällen ein „Workaround“ für die nicht unterstützten Unterabfragen geschaffen werden. Nehmen wir einmal an, Sie müssen die
aktuellen Gehaltsdaten des Mitarbeiters „4711“ am Bildschirm anzeigen. In MySQL müssten Sie jetzt zunächst einmal das entsprechende
Gültigkeitsdatum abfragen.
select max(g_ab) from gehalt where persnr='4711';
Das von dieser Abfrage gelieferte Datum müssten Sie sich jetzt im
Programm merken und innerhalb einer zweiten Abfrage verwenden.
select * from gehalt
where persnr='4711'
and g_ab = '2001-01-01';
Mit Hilfe der Benutzervariablen lassen sich diese beiden Abfragen
aber doch noch verschmelzen, denn der Wert einer Variablen kann
auch mit Hilfe einer select-Abfrage gesetzt werden.
select @mytvar := max(g_ab)
from gehalt
where persnr='4711';
select *
from gehalt
where persnr='4711'
and g_ab = @mytvar;
Beachten Sie hierbei allerdings, dass die Wertzuweisung diesmal
nicht allein mit einem Gleichheitszeichen erfolgt, sondern dass Sie
vor diesem Gleichheitszeichen auch noch einen Doppelpunkt kodieren müssen.
Ein weiteres Anwendungsbeispiel für solche Skripts findet sich auch
immer dann, wenn es darum geht, Datenbankobjekte anzulegen. Soll
eine neue Anwendung installiert werden, so müssen hierfür vielleicht
auch eine Reihe von Tabellen und Indizes in der Datenbank erzeugt
werden, wobei sich alle diese DDL-Anweisungen in einem Skript befinden. Oder Sie spielen eine neue Version des Softwarepakets ein,
wozu auch eine Aktualisierung der Datenbankstruktur notwendig ist.
Wenn Sie DDL-Anweisungen für MySQL in ein Skript packen, dann
sollten Sie wenigstens beim Löschen von Tabellen die in dem Zusam-
Dies und Das
347
Nitty Gritty • Take that!
4
menhang verfügbare exists-Klausel verwenden, so dass während der
Skriptausführung keine bzw. weniger Fehler ausgegeben werden, da
das die anschließende Kontrolle umfangreicher SQL-Stapel vereinfacht.
drop table if exists MyTable;
SQL-Server, Oracle
Der SQL-Server und Oracle unterstützen die Verwendung ganzer Abfragestapel, wobei solche Skriptdateien in der Praxis manchmal mehrere hundert bis tausend Zeilen Umfang haben. Der Unterschied zu
MySQL ist hierbei der, dass das Abspielen des Skripts eben nicht mit
Hilfe eines getürkten Anwendungsprogramms simuliert werden
muss, sondern das gesamte SQL-Paket wird diesmal wirklich vollständig an das DBMS zur Ausführung übergeben.
Nitty Gritty • Take that!
4
Bei den in diesem Pakt zusammengefassten Abfragen kann es aber
sein, dass zwischen den einzelnen Abfragen Abhängigkeiten bestehen, die einer Ausführung aus Sicht der im DBMS vorgelagerten
Statementprüfung entgegenstehen. So verlangt der SQL-Server beispielsweise, dass bestimmte DDL-Anweisungen (z.B. „create view“)
immer am Anfang eines Abfragestapels stehen.
Aus diesen Gründen haben Sie sowohl beim SQL-Server als auch in
Oracle die Möglichkeit, das gesamte Skript noch einmal in logische
Teilschritte zu unterteilen, so dass anschließend jeweils immer nur
diese Teilschritte vom DBMS am Stück geprüft und verarbeitet werden.
Beim SQL-Server wird ein solches Stapelende durch die Anweisung
„go“ definiert, wohingegen Sie in Oracle das Slash-Zeichen (/) verwenden können. Im Folgenden finden Sie hierfür noch einmal ein Beispiel für ein solches SQL-Server-Skript, das aufgrund der verwendeten go-Kommandos in drei Schritten ausgeführt wird.
select count(*) from gehalt;
drop view xx;
go
create view xx (persnr) as select persnr from gehalt
go
select count(*) from xx;
348
Verwenden von Skripten
Möchten Sie das Skript in Oracle ebenfalls in diesen drei Teilschritten
ausführen, so müssen Sie einfach das go-Kommando durch ein
Slash-Zeichen (/) ersetzen.
Wie schon mehrfach erwähnt, beinhaltet der Sprachumfang des SQLServers nicht nur die bisher verwendeten SQL-Anweisungen zum Anlegen, Bearbeiten oder Abfragen von Tabellen der Datenbank. Im
Laufe der Zeit haben sich um die eigentlichen SQL-Anweisungen eine
Vielzahl von Befehlen gereiht, so dass mittlerweile eine richtige Programmiersprache entstanden ist.
Wie gesagt, lassen sich mit Transact-SQL richtige Programme erstellen. In der Praxis taucht ein solches Programmwerk zunächst einmal
bei der Realisierung von Triggern oder gespeicherten Prozeduren
auf, denn in diesen Objekten werden meistens nicht nur einfache
SQL-Abfragen ausgeführt, sondern daneben existieren oftmals komplexe Berechnungen oder die Ausführung der Abfragen ist von speziellen Bedingungen abhängig.
Aber auch gewöhnliche Skripts bestehen oftmals nicht nur aus der
Anhäufung verschiedener SQL-Abfragen, sondern auch hier wird beispielsweise mit Hilfe verschiedener Prüfungen zur Laufzeit entschieden, ob diese oder jene Abfragen auszuführen sind. Ein sehr einfaches Anwendungsbeispiel für eine solche bedingungsabhängige
Abfrageausführung besteht darin, Skripten mit verschiedenen DDLAnweisungen frei von Laufzeitfehlern zu gestalten, d.h., drop-Anweisungen dürfen in dem Fall nur dann ausgeführt werden, wenn das zu
löschende Objekt überhaupt in der Datenbank existiert.
Dies und Das
349
4
Nitty Gritty • Take that!
4.2.1 Transact-SQL
Beim SQL-Server werden diese Spracherweiterungen als „TransactSQL“ bezeichnet. Natürlich würde es im Rahmen dieses Buches zu
weit führen, an dieser Stelle systematisch und umfassend in die
Transact-SQL-Programmierung einzuführen, aber einen kurzen Einblick möchte ich Ihnen schon gewähren und schließlich bin ich auch
noch das Beispiel mit der mehrfach zitierten Änderungsschleife
schuldig.
Variablen verwenden
Das folgende Beispiel zeigt Ihnen hierfür eine mögliche Vorgehensweise, wobei ich es so konstruiert habe, dass möglichst viele Sprachkonstrukte zur Anwendung kommen.
declare @t integer;
select @t = (select count(name)
from sysobjects
where name = 'xxgehalt');
if @t <> 0
begin
print 'Tabelle wird gelöscht';
drop table xxgehalt;
end;
create table xxgehalt (xx char(10));
Nitty Gritty • Take that!
4
Zunächst einmal habe ich eine benutzerdefinierte Variable angelegt,
was mit Hilfe der declare-Anweisung funktioniert. Diese Variablen
beginnen alle mit einem Klammeraffen (@), dem der Variablenname
folgt, der aus Buchstaben, Ziffern und dem Unterstrich (_) bestehen
kann. Hinter der Variablen folgt die Festlegung des Variablentyps,
wobei Sie hierbei alle Typenbezeichner verwenden können, die Sie
zusammen mit der create table-Anweisung kennen gelernt haben.
declare
declare
declare
declare
@v1
@v2
@v3
@v4
integer;
varchar(10);
datetime;
numeric(9,2);
Anschließend finden Sie das Paradebeispiel dafür, wie Sie einer solchen Abfrage einen Wert zuweisen können, denn das erfolgt in Transact-SQL immer mit Hilfe einer speziellen select-Anweisung. Dem
Schlüsselwort „select“ folgen hierbei der Variablenname, ein Gleichheitszeichen und danach kommt ihr neuer Wert in Form einer Konstanten, eines zur Variablen passenden Ausdrucks oder sogar einer
Datenbankabfrage, die allerdings (genau) einen entsprechenden
Wert zurückliefern muss.
350
Verwenden von Skripten
select @t = 34.2;
select @t1 = 33 + @t * 2;
select @t2 = (select max(...) from ...) + @t1;
Solche Abfragen entsprechen den schon bekannten exists- oder
wertliefernden Unterabfragen, denn diese beiden Unterabfragetypen liefern jeweils genau einen Wert zurück.
Kontrollstrukturen
Da die in einem Skript oder Programm enthaltenen Befehle und Anweisungen nicht immer der Reihe nach ausgeführt werden sollen,
bietet Ihnen Transact-SQL verschiedene Möglichkeiten, den Ablauf
der einzelnen Anweisungen zu kontrollieren. Die einfachste Form einer solchen Kontrolle bietet der if-Befehl mit dessen Hilfe die Ausführung einer oder einer Reihe von Anweisungen von einer Bedingung
abhängig wird.
If <Bedingung>
begin
... Transact-SQL-Anweisungen
end
[else
begin
... Transact-SQL-Anweisungen
end;
Die Konstruktion einer solchen if-Bedingung folgt den Regeln, die Sie
bei der Erstellung von where-Bedingungen kennen gelernt haben,
d.h., Sie können auch hier die damals beschriebenen Vergleichs- und
Verknüpfungsoperatoren verwenden. Ist die if-Bedingung erfüllt, so
wird die direkt dahinter programmierte Anweisung ausgeführt; optional können Sie auch eine Anweisung programmieren, die nur dann
ausgeführt werden soll, wenn die if-Bedingung nicht erfüllt ist, indem
Sie die if-Konstruktion mit Hilfe des Schlüsselwortes „else“ erweitern.
Blockstruktur
Sicherlich ist die Situation, dass man im Rahmen einer if- bzw. elseAusführung genau eine einzelne Anweisung ausführen muss, eher
selten gegeben, sondern meistens sollen stattdessen mehrere ver-
Dies und Das
351
Nitty Gritty • Take that!
4
schiedene Anweisungen ausgeführt werden. Aus diesem Grund gibt
es in Transact-SQL genau wie in vielen anderen Programmiersprachen auch des Konzept der so genannten Programmblöcke. Mit Hilfe
dieser Blöcke können Sie mehrere Anweisungen zu einer logischen
Einheit bündeln, so dass sie anschließend beispielsweise aus Sicht
der if-Anweisung eine einzige (Block-) Anweisung darstellen.
Solche Programmblöcke werden in Transact-SQL mit Hilfe der Schlüsselwörter „begin“ und „end“ gebildet. Manchmal ist deren Verwendung optional, beispielsweise wenn nach der if-Anweisung wirklich
nur ein einzelner Befehl folgt; in solchen Fällen kann die freiwillige
Bildung von Programmblöcken eventuell die Lesbarkeit des Skripts
verbessern. Manchmal sind sie aber notwendig, um bestimmte Ablaufkonstruktionen zu realisieren. Ich persönlich habe mir angewöhnt, Programmblöcke auch dann (freiwillig) zu verwenden, wenn
im Rahmen einer if-Anweisung nur eine Anweisung ausgeführt werden soll.
Nitty Gritty • Take that!
4
In unserem kleinen Beispiel führen wir innerhalb des if-Programmblocks die drop-Anweisung aus, mit der die später im Skript erstellte
Tabelle gelöscht wird. Insgesamt führen wir diesen Löschblock allerdings nur aus, wenn die Tabelle schon in der Datenbank existiert.
Durch diesen kleinen Eingriff stellen wir also sicher, dass unser Skript
im Normalfall ohne Fehlermeldung läuft, d.h., wenn wir jetzt eine Fehlermeldung erhalten, dann ist wirklich etwas schief gelaufen.
Neben der if-Anweisung kennt Transact-SQL auch noch
weitere Kontrollstrukturen wie „case“ oder die whileSchleife, mit deren Hilfe sich ein Programmblock bedingungsabhängig wiederholen lässt. Mehr Informationen
zu „case“ und „while“ finden Sie im Referenzteil.
Cursor verwenden
Manchmal ist es notwendig, im Rahmen eines solchen Skripts neben
der Ausführung von DDL- oder Änderungsabfragen oder neben dem
Abfragen einzelner Werte auch komplexere Auswahlabfragen auszuführen, die anschließend mehr als einen Datensatz liefern; Sie merken, wir nähern uns jetzt so langsam der mehrfach erwähnten Änderungsschleife.
352
Verwenden von Skripten
Zur Selektion von Daten in einem Transact-SQL-Skript oder -Programm müssen Sie einen so genannten Cursor erstellen, mit dessen
Hilfe Sie eine beliebige Abfrage erstellen und anschließend die zugehörigen Daten in einer Schleife Satz für Satz abrufen können.
declare gehalt_cursor cursor for
select persnr, g_ab, la, betrag from gehalt;
Die Definition eines solchen Cursors beginnt genau wie die Anlage einer Benutzervariablen mit dem Schlüsselwort „declare“, dem der
von Ihnen vorzugebende Name des Cursors folgt. Anschließend müssen Sie die Schlüsselwörter „cursor for“ spezifizieren, denen die vom
Cursor auszuführende Auswahlabfrage folgt.
Was jetzt folgt, entspricht fast dem sequenziellen Lesen irgendwelcher Textdateien, wie Sie es vielleicht schon von irgendeiner Programmiersprache her kennen, d.h., zunächst wird der Cursor geöffnet, dann werden alle enthaltenen Datensätze abgerufen, und am
Ende wird der Cursor wieder geschlossen.
open gehalt_cursor;
...
close gehalt_cursor;
deallocate gehalt_cursor;
Das Öffnen des Cursors erfolgt durch den Einsatz der open-Anweisung, die Sie zusammen mit dem bei der Cursordefinition vergebenen Namen verwenden müssen und führt intern zur Ausführung der
zugehörigen Abfrage, wobei beim erstmaligen Öffnen zusätzlich
noch gewisse Initialisierungsroutinen gestartet werden, die das
DBMS zum Bearbeiten des Cursors benötigt.
Wird der Cursor nicht mehr benötigt, dann können Sie ihn mit Hilfe
der close-Anweisung schließen; und sofern Sie ihn innerhalb Ihres
Skripts oder Programms überhaupt nicht mehr benötigen, so müssen
Sie auch die vom DBMS bereitgestellten Strukturen entfernen, indem
Sie die deallocate-Anweisung verwenden. „Können“ hört sich in diesem Zusammenhang zugegebenerweise etwas bittend an; verwenden Sie die beiden Anweisungen auf jeden Fall, da hierdurch der vom
Cursor verbrauchte Speicher freigegeben wird.
Dies und Das
353
Nitty Gritty • Take that!
4
Zwischen den open- und close-Anweisungen können Sie nun die vom
Cursor bzw. der zugehörigen Abfrage bereitgestellten Datensätze abrufen, was konkret durch den Einsatz des fetch-Befehls geschieht.
fetch <Fetchmodus> from <Cursorname>
[into <Variablenliste>]
Das Abrufen der zum Cursor gehörenden Datensätze kann auf unterschiedliche Art und Weise erfolgen, wobei uns hier nur der Modus
„next“ interessiert, der zum sequenziellen Abrufen der Datensätze
führt. Hinter dem Schlüsselwort „from“ müssen Sie wieder den bei
der Definition festgelegten Cursornamen angeben und mit Hilfe der
into-Klausel übertragen wir die von der Abfrage gelieferten Spalten
in entsprechende Benutzervariablen.
fetch next from gehalt_cursor into @persnr;
Nitty Gritty • Take that!
4
Jeder Aufruf von „fetch” führt intern zum Setzen der Systemvariablen
„@@fetch_status“, mit deren Hilfe Sie feststellen können, ob der Lesevorgang erfolgreich war oder nicht; enthält diese Variable den Wert
0, so war der vorhergehende fetch-Aufruf erfolgreich und alle anderen Werte deuten auf einen Fehler hin. Mit Hilfe dieser Systemvariablen und einer while-Schleife lassen sich nun recht einfach alle vom
Cursor gelieferten Datensätze abrufen.
declare @persnr varchar(10);
declare gehalt_cursor cursor for
select persnr from gehalt;
open gehalt_cursor;
fetch next from gehalt_cursor into @persnr;
while @@fetch_status = 0
begin
print @persnr;
fetch next from gehalt_cursor into @persnr;
end;
close gehalt_cursor;
deallocate gehalt_cursor;
354
Verwenden von Skripten
Konkretes Beispiel
Betrachten wir nach diesem einführenden Beispiel noch ein kleines
Anwendungsbeispiel. In diesem soll die Gehaltstabelle noch einmal
aktualisiert werden, wobei für jeden Mitarbeiter der Datensatz zum
01.01.2001 aktualisiert werden soll. Ist dieser überhaupt nicht vorhanden, so soll er im Rahmen der Aktualisierung angelegt werden.
declare
declare
declare
declare
@persnr
@la
@betrag
@g_ab
varchar(10);
varchar(3);
numeric;
datetime;
declare gehalt_cursor cursor for
select a.persnr, a.g_ab, a.la, a.betrag
from gehalt a
where a.g_ab = (select max(g_ab)
from gehalt a1
where a1.persnr = a.persnr
and a1.g_ab <= '01-01-2001');
4
Nitty Gritty • Take that!
open gehalt_cursor;
fetch next from gehalt_cursor
into @persnr, @g_ab, @la, @betrag;
while @@fetch_status = 0
begin
if @g_ab = '01-01-2001'
begin
print 'Geändert ' + @persnr;
update gehalt
set betrag = betrag * 1.05
where current of gehalt_cursor;
end;
else
begin
print 'Neuer Satz ' + @persnr + ' ' +
Dies und Das
355
convert(varchar,@g_ab,104);
insert into gehalt (persnr, g_ab, la, betrag)
values (@persnr, '01-01-2001', @la,
@betrag * 1.05);
end;
fetch next from gehalt_cursor
into @persnr, @g_ab, @la, @betrag;
end;
close gehalt_cursor;
deallocate gehalt_cursor;
Betrachten wir nun noch einmal dieses kleine Beispiel. Zunächst wird
mit Hilfe der im Cursor gespeicherten Abfrage für jeden Mitarbeiter
der zum 01.01.2001 gültige Datensatz selektiert. Das passiert mit
Hilfe einer Unterabfrage, die das größte Gültigkeitsdatum liefert, das
kleiner oder gleich unserem Stichtag (01.01.2001) ist.
Nitty Gritty • Take that!
4
select a.persnr, a.g_ab, a.la, a.betrag
from gehalt a
where a.g_ab = (select max(g_ab)
from gehalt a1
where a1.persnr = a.persnr
and a1.g_ab <= '01-01-2001');
Falls Sie Verständnisprobleme mit dieser Abfrage haben, so möchte
ich Sie auf das Kapitel 2.5.3 verweisen, wo diese spezielle „wertlieferende“ Unterabfrage näher erläutert wird. Die von der Abfrage gelieferten Daten werden nun mit Hilfe der fetch-Anweisung und der
while-Schleife Satz für Satz abgerufen, wobei die eigentliche Verarbeitung innerhalb der Schleife stattfindet.
Dort wird zunächst einmal geprüft, ob das selektierte Gültigkeitsdatum unserem Stichtag entspricht, denn in diesem Fall existiert
bereits ein Datensatz zum 01.01.2001 und wir können die Aktualisierung des Datensatzes mit Hilfe einer update-Anweisung durchführen. Betrachten Sie bei dieser update-Anweisung allerdings einmal
die where-Bedingung. Im Rahmen der hier gezeigten Cursorverarbeitung wird programmintern ein Zeiger auf den aktuell vom Cursor gelieferten Datensatz mitgeführt. Diesen Zeiger können Sie im Rahmen
356
Verwenden von Skripten
einer Aktualisierungsabfrage nutzen, indem Sie in der where-Bedingung einfach die Klausel „where current of“ zusammen mit dem Cursornamen verwenden.
where current of gehalt_cursor
Selbstverständlich könnten Sie auch eine reguläre where-Bedingung
(„where persnr = @persnr and g_ab = @g_ab“) konstruieren, allerdings ist das „current of“ zweifellos praktischer. Das gilt vor allem
dann, wenn man die aktuelle Reihe, z.B. wegen fehlender Schlüssel,
gar nicht so einfach identifizieren kann.
Entspricht das von unserer Abfrage gelieferte Gültigkeitsdatum nicht
dem 01.01.2001, so müssen wir in der Tabelle einen neuen Datensatz
einfügen, was konkret in dem else-Programmblock passiert. Übrigens, im Rahmen solcher Cursorabfragen können Sie beim SQL-Server auch die Klausel „for update“ (vgl. Kap. 2.6.6) verwenden, wodurch der aktuell gelesene Datensatz automatisch gesperrt wird.
4.2.2 PL/SQL
Also noch einmal. Das gilt natürlich nur, wenn Sie schon das Kapitel
4.2.1 gelesen haben, denn jetzt geht es um die Skriptprogrammierung in einer Oracle-Datenbank. Wie schon gesagt, wird der gesamte
Sprachumfang in Oracle mit PL/SQL bezeichnet. Ähnlich wie beim
SQL-Server kann man PL/SQL auch in einem Oracle-DBMS an vielen
verschiedenen Stellen einsetzen. Die einfachste Anwendungsform
besteht auch diesmal wieder in der Programmierung eines Skripts,
das neben einfachen SQL-Abfragen auch Anweisungen zur Programmsteuerung oder Cursor mit komplexer Logik zur Datenänderung enthält.
Dies und Das
357
4
Nitty Gritty • Take that!
Damit haben Sie jetzt ein Beispiel gesehen, wie man beim SQL-Server auch mit Hilfe eines Skripts Änderungen in der Datenbank durchführen kann. So ganz nebenbei haben Sie dabei vielleicht Ihre ersten
Gehversuche in der Transact-SQL-Programmierung gemacht. Das soll
an dieser allerdings Stelle genügen; mehr Informationen über Transact-SQL im Allgemeinen oder zu den einzelnen Befehlen finden Sie in
der Online-Hilfe Ihrer SQL-Server-Installation.
Daneben können auch in einer Oracle-Datenbank Objekte existieren,
die üblicherweise PL/SQL-Sprachelemente enthalten. Neben den
Triggern und Prozeduren existieren in Oracle aber noch weitere Objekte. So lassen sich mit PL/SQL beispielsweise benutzerdefinierte
Funktionen erstellen, die Sie anschließend genau wie die vom System vordefinierten Funktionen verwenden können. Letztendlich gibt
es in Oracle auch noch ein so genanntes Package-Objekt.
Ein solches Paket entspricht in der sonstigen Datenverarbeitung einem Programm. Wie Sie vielleicht wissen, können Programme intern
über eine Vielzahl von Prozeduren oder Funktionen verfügen, die von
außen gar nicht oder nur teilweise sichtbar sind, d.h., das eigentliche
Programm wird mit Hilfe einer speziellen Startprozedur gestartet und
ruft anschließend die enthaltenen Prozeduren und Funktionen entsprechend der vorgegebenne Programmierung auf. Genau so ist es
auch bei den Package-Objekten, denn auch hier verschmelzen eine
Vielzahl programmierter Prozeduren oder Funktionen zu einer logischen Einheit.
Nitty Gritty • Take that!
4
Blockstruktur
PL/SQL ist wie viele andere Programmiersprachen auch blockorientiert, wobei diese Blockstruktur aus den Schlüsselwörtern „begin“
und „end“ gebildet wird, was zumindest mich irgendwie an die Programmiersprache Pascal erinnert.
Diese Blöcke besitzen in einem PL/SQL-Programm zwei Aufgaben.
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 ich hierauf nicht weiter eingehen werde.
Ein PL/SQL-Programm bzw. Skript hat damit etwas vereinfacht dargestellt zunächst folgenden schematischen Aufbau:
begin
Anweisungen des Blocks 1
358
Verwenden von Skripten
begin
Anweisungen des Blocks 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 auch für das Schlüsselwort
end, d.h., neben den einzelnen Anweisungen wird auch ein gesamter
Block mit einem Semikolon beendet.
Theoretisch kann jeder PL/SQL-Block einen eigenen Deklarationsteil
besitzen, mit dessen Hilfe Sie alle benötigten Variablen oder Cursor
definieren können. Die hierzu benötigte Anweisung heißt auch in PL/
SQL wieder „declare“. Ihr folgt der Name der Variablen, dem der gewünschte Datentyp folgt, wobei Sie hierbei alle Typenbezeichner verwenden können, die Sie zusammen mit der create table-Anweisung
kennen gelernt haben.
declare v1
v2
v3
v4
integer;
varchar(10);
date;
numeric(9,2);
Im Unterschied zu Transact-SQL beginnen die Variablen diesmal aber
nicht mit einem speziellen Sonderzeichen. Außerdem leitet das declare-Kommando den gesamten Deklarationsteil ein, denn anschließend können Sie alle benötigten Variablen definieren, ohne hierbei
jedes Mal das declare-Schlüsselwort erneut nennen zu müssen.
Dies und Das
359
Nitty Gritty • Take that!
4
Variablen verwenden
Kommen wir jetzt zum ersten konkreten Anwendungsbeispiel eines
solchen PL/SQL-Skripts, bei dem wir das Löschen einer Tabelle wieder davon abhängig machen, ob die zugehörige Tabelle bereits in der
Datenbank existiert.
declare tt integer;
begin
tt := 0;
select count(table_name) into tt
from user_tables
where table_name = 'XXGEHALT';
if tt > 0 then
execute immediate 'drop table xxgehalt';
end if;
execute immediate
'create table xxgehalt (xx varchar(10))';
end;
Nitty Gritty • Take that!
4
In PL/SQL erfolgt für eine Variable die Wertzuweisung nicht mit Hilfe
einer speziellen Anweisung, sondern die Zuweisung erfolgt genau
wie in vielen anderen Programmiersprachen mit Hilfe einer Kombination aus Doppelpunkt und Gleichheitszeichen (:=).
Variable := <Konstante> | <Ausdruck>;
Bei einer solchen Wertzuweisung steht immer links die Variable und
rechts befindet sich der neue Wert in Form einer Konstanten oder eines beliebigen Ausdrucks. Ansonsten ist hierbei eigentlich nur wichtig, dass die Variablen auf der linken und das Ergebnis auf rechten
Seite des Zuweisungsoperators (:=) den gleichen oder einen kompatiblen Datentyp besitzen müssen.
t := 34.2;
t1 := 33 + t * 2;
a b e r !!
select max(...) into t2 from ...
360
Verwenden von Skripten
Möchten Sie den Wert einer Variablen mit Hilfe einer Datenbankabfrage verändern, so können Sie hierzu eine gewöhnliche Auswahlabfrage erstellen, die Sie mit Hilfe der into-Klausel erweitern, hinter der
Sie für jede selektierte Spalte eine passende Variable spezifizieren.
Hierbei wird die erste Selektionsspalte in die erste aufgeführte Variable, die zweite Spalte in die zweite Variable usw. kopiert.
select a, b, c, ... into var_a, var_b, var_c, ... from
Genau wie beim SQL-Server gilt allerdings auch hier, dass diese Abfrage genau eine Ergebniszeile selektieren muss. Liefert die Abfrage
kein Ergebnis oder ein Ergebnis in Form mehrerer Datensätze, so erhalten Sie bei der Ausführung des Skripts einen Laufzeitfehler. Aus
diesem Grund ist es hier genau wie bei den Unterabfragen sinnvoll,
auf Aggregatfunktionen (z.B. count, max usw.) zurückzugreifen.
Kontrollstrukturen
If <Bedingung> then
... PL/SQL-Anweisungen
[else
... PL/SQL-Anweisungen]
end if;
Die Konstruktion einer solchen if-Bedingung folgt den Regeln, die Sie
bei der Erstellung von where-Bedingungen kennen gelernt haben,
d.h., Sie können auch hier die damals beschriebenen Vergleichs- und
Verknüpfungsoperatoren verwenden. Ist die if-Bedingung erfüllt, so
werden die direkt dahinter programmierten Anweisungen ausgeführt; optional können Sie auch eine Anweisungsfolge programmieren, die nur dann ausgeführt werden soll, wenn die if-Bedingung
nicht erfüllt ist, indem Sie die if-Konstruktion mit Hilfe des Schlüsselwortes „else“ erweitern.
Dies und Das
361
4
Nitty Gritty • Take that!
Da die in einem Skript oder Programm enthaltenen Befehle und Anweisungen nicht immer der Reihe nach ausgeführt werden sollen,
bietet Ihnen PL/SQL verschiedene Möglichkeiten, den Ablauf der einzelnen Anweisungen zu kontrollieren. Die einfachste Form einer solchen Kontrolle bietet der if-Befehl, mit dessen Hilfe die Ausführung
einer oder einer Reihe von Anweisungen von einer Bedingung abhängig wird.
In jedem Fall muss die if-Anweisung durch die Schlüsselwörter „end
if“ abgeschlossen werden; außerdem erwartet Oracle das Wörtchen
„then“, mit dem die gesamte Prüfbedingung abgeschlossen wird. Ein
weiterer Unterschied zu Transact-SQL ist, dass der if-Befehl in Oracle
nicht nur eine einzige Blockanweisung erlaubt, sondern Sie können
hier diesmal beliebig viele Anweisungen kodieren.
Das ist aufgrund des formellen Abschlusses der if-Anweisung möglich; der zum „if“ gehörende Programmteil beginnt hinter dem „then“
und geht entweder bis zum „else“ oder bis zum abschließenden „end
if“. Entsprechendes gilt auch, wenn mehrere if-Anweisungen ineinander geschachtelt werden, wobei man das auch mit dem Setzen von
Klammern bei komplexen numerischen Ausdrücken vergleichen
könnte. Jeder geöffneten Klammer folgt irgendwann einmal eine
schließende; jeder if-Anweisung muss irgendwann einmal ein abschließendes „end if“ folgen.
Nitty Gritty • Take that!
4
In unserem kleinen Beispiel führen wir innerhalb des if-Programmblocks wieder die drop-Anweisung aus, mit der auch diesmal wieder
die später im Skript erstellte Tabelle gelöscht wird. Allerdings können
wir diese Anweisung diesmal nicht wie beim SQL-Server im Klartext
programmieren, da die direkte Verwendung von DDL-Anweisungen in
einem PL/SQL-Programm zu Umwandlungsfehlern führt.
Derartige Befehle müssen in einem Oracle-Skript zur Laufzeit dynamisch abgesetzt werden, wofür es den Befehl „execute immediate“
gibt. Hinter dieser Anweisung folgt das auszuführende SQL-Statement in Form einer Textkonstante oder Textvariablen, das von dem
Skript gestartet wird, wenn das Programm die entsprechende Stelle
erreicht.
Durch diesen Eingriff wird die Tabelle beim Ablauf des Skripts somit
wieder nur dann gelöscht, wenn die Tabelle bereits in der Datenbank
existiert; hierdurch läuft das Skript im Normalfall dann wieder ohne
Fehlermeldung ab, was bei umfangreichen Installationsarbeiten die
nachträgliche Kontrolle erleichtert. Anschließend wird die Tabelle
wieder mit Hilfe eines geeigneten create table-Befehls angelegt. Das
geht aus den vorhin genannten Gründen jedoch auch nur über den
Umweg der gerade beschriebenen execute immediate-Anweisung.
362
Verwenden von Skripten
Schleifen
Wie schon mehrfach erwähnt wurde, läuft ein Programm nur selten
sequenziell 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.
Je nach verwendeter Programmiersprache gibt es immer verschiedene Varianten, um diese Schleifen zu programmieren; so haben Sie
im letzten Abschnitt bei der Einführung in Transact-SQL die while-Anweisung kennen gelernt, bei der die Wiederholung eines Programmblocks von einer Bedingung abhängig war. Eine solche Schleife existiert auch in PL/SQL, wobei insgesamt sogar die nachfolgend
beschriebenen Schleifenkonstruktionen möglich sind.
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 loopSchleife entspricht dem folgenden Schema:
while <Bedingung> loop
<Anweisungen>
end loop;
T
loop-Schleife
Bei der loop-Schleife handelt es sich um eine Sonderform der soeben
beschriebenen while loop-Schleife. Wie Sie schon an der Begriffsfwahl 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 neuesten wissenschaftlichen Erkenntnissen als Fehler - gibt es die exit-Anweisung, mit der Sie die
Schleife verlassen können, so dass die Verwendung der loopSchleife nach etwa folgendem Schema erfolgt:
Dies und Das
363
4
Nitty Gritty • Take that!
T
loop
<Anweisungen>
if <Bedingung> then
exit;
end if;
end loop;
T
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 loop-Schleife sieht in
etwa folgendermaßen aus:
4
Normalerweise wird das vorgegebene Intervall in aufsteigender Reihenfolge durchlaufen, d.h., die Laufvariable nimmt mit jedem Schleifendurchlauf den nächsten Wert des Intervalls an.
Nitty Gritty • Take that!
for <Laufvariable> in [reverse] <Interval> loop
<Anweisungen>
end loop;
Cursor verwenden
Sollten Sie das Transact-SQL-Beispiel ausführlich gelesen haben, so
folgt jetzt wieder eine kleine Wiederholung, denn es geht auch jetzt
wieder darum, in einem PL/SQL-Skript eine normale Abfrage auszuführen und deren Datensätze zu empfangen bzw. zu verarbeiten.
Auch in PL/SQL müssen Sie zur Selektion von Daten in einem Skript
oder Programm einen so genannten Cursor erstellen, mit dessen
Hilfe Sie eine beliebige Abfrage erstellen und anschließend die zugehörigen Daten in einer Schleife Satz für Satz abrufen können. Hierzu
müssen Sie im Deklarationsteil des Skripts eine entsprechende Definition durchführen, was mit Hilfe des Schlüsselworts „cursor“ erfolgt.
declare cursor gehalt_cursor is
select persnr, g_ab, la, betrag from gehalt;
364
Verwenden von Skripten
Hinter der cursor-Anweisung folgt der Name des Cursors und hinter
der is-Klausel folgt die zugehörige Abfrage. Was jetzt folgt, entspricht
fast dem sequenziellen Lesen irgendwelcher Textdateien, wie Sie es
vielleicht schon von irgendeiner Programmiersprache her kennen,
d.h., zunächst wird der Cursor geöffnet, dann werden alle enthaltenen Datensätze abgerufen und am Ende wird der Cursor wieder geschlossen.
open gehalt_cursor;
...
close gehalt_cursor;
Das Öffnen des Cursors erfolgt durch Verwendung der open-Anweisung, die Sie zusammen mit dem bei der Cursordefinition verwendeten Namen benutzen müssen und was intern zur Ausführung der zugehörigen Abfrage führt. Wird der Cursor nicht mehr benötigt, so
können Sie ihn mit Hilfe der close-Anweisung schließen.
fetch <Cursorname> into <Variablenliste>
Hinter „fetch“ folgt hierbei der Name des Cursors, von dem Sie die
aktuelle Datenzeile abrufen wollen unter hinter „into“ müssen Sie
die Liste der Variablen spezifizieren, in die die zugehörigen Daten kopiert werden sollen.
fetch gehalt_cursor into persnr, g_ab, la, betrag;
Jeder Aufruf von „fetch” führt auch in PL/SQL zum Setzen einer Statusvariablen, die diesmal allerdings im Cursorobjekt vorhanden ist.
gehalt_cursor%notfound
Diese Statusvariable wird wahr (true), wenn ein zuvor ausgeführter
fetch-Befehl nicht erfolgreich ausgeführt werden konnte. Weitere
Statusvariablen finden Sie in der folgenden Tabelle 4.2.
Dies und Das
365
4
Nitty Gritty • Take that!
Zwischen den open- und close-Anweisungen können Sie nun die vom
Cursor bzw. der zugehörigen Abfrage bereitgestellten Datensätze abrufen, was konkret durch den Einsatz des fetch-Befehls passiert.
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 4.2: Statusvariablen bei der Cursorverarbeitung in Oracle
Damit haben wir jetzt das Rüstzeug, um das im letzten Kapitel erstellte Transact-SQL-Beispiel in PL/SQL umzuschreiben, was dann
etwa folgendermaßen aussehen könnte.
Nitty Gritty • Take that!
4
declare
persnr varchar2(10);
la
varchar2(3);
g_ab date;
betrag numeric(9,2);
cursor gehalt_cursor is
select persnr, g_ab, la, betrag
from gehalt a
where g_ab = (select max(g_ab)
from gehalt a1
where a1.persnr = a.persnr
and a1.g_ab <=
to_date('2001-01-01',
'YYYY-MM-DD'))
for update of persnr;
begin
open gehalt_cursor;
fetch gehalt_cursor into persnr, g_ab, la, betrag;
while not gehalt_cursor%notfound loop
366
Verwenden von Skripten
if g_ab = to_date('2001-01-01','YYYY-MM-DD') then
update gehalt
set betrag = betrag * 1.05
where current of gehalt_cursor;
else
insert into gehalt (persnr, g_ab, la, betrag)
values (persnr,
to_date('2001-01-01','YYYY-MM-DD'),
la, betrag * 1.05);
end if;
fetch gehalt_cursor into persnr, g_ab,
la, betrag;
end loop;
close gehalt_cursor;
commit;
end;
Genau wie beim Transact-SQL-Beispiel führen wir auch hier den Update mit Hilfe der Klausel „current of“ durch. Dabei müssen Sie beachten, dass diese Klausel in Oracle nur dann funktioniert, wenn Sie
in der Cursorabfrage das Statement „for update“ (vgl. Kap. 2.6.6) verwenden. Dieses Statement hätte ich beim SQL-Server zwar auch verwenden können, jedoch ist es dort optional und hier zwingend, damit
der Zugriff auf den aktuellen Datensatz des Cursors mit „where current of“ funktioniert.
Ansonsten unterscheidet sich die Oracle-Version einmal abgesehen
von der einen oder anderen kleinen Formalität eigentlich nur noch
dadurch, dass wir die durchgeführten Änderungen und die damit automatisch gestartete Transaktion mit Hilfe einer commit-Anweisung
abschließen.
Dies und Das
367
4
Nitty Gritty • Take that!
Finden Sie nicht auch, dass dieses Skript verblüffend viel Ähnlichkeit
zur Transact-SQL-Variante hat? Das war natürlich Absicht, denn das
Abrufen der vom Cursor gelieferten Sätze kann man in Oracle etwas
einfacher programmieren, aber darauf werde ich erst etwas später
eingehen. Bleiben wir zunächst noch etwas bei den Gemeinsamkeiten.
for-Schleifen für Cursor
Wie schon gesagt, habe ich das letzte Beispiel so konstruiert, um zunächst einmal möglichst viele Gemeinsamkeiten zwischen der Transact-SQL- und der PL/SQL-Variante zu erhalten. Mit den in PL/SQL verfügbaren Mitteln kann man die Verarbeitung eines solchen Cursors
nämlich etwas eleganter programmieren, in dem man anstelle einer
while- oder loop-Schleife die for-Schleife verwendet.
Im ureigentlichen Sinne handelt es sich bei der for-Schleife um eine
Zählschleife, mit deren Hilfe eine Zählvariable von einem Startwert
aus mit einer voreingestellten Schrittweite bis zu einem Endwert
hoch- bzw. herruntergezählt wird.
Nitty Gritty • Take that!
4
Das kann man allerdings auch anders sehen und stattdessen behaupten, dass die Zählvariable durch eine Menge bestimmter (sortierter) Elemente bewegt wird und dabei den Wert eines jeden Elements annimmt. Aus diesem Blickwinkel (genau wie in den meisten
modernen Programmiersprachen auch) wird die for-Schleife in PL/
SQL verwendet und von daher ist es nur noch ein kleiner Schritt, die
zu durchlaufende Elementmenge nicht nur auf einfache Zahlen zu beschränken.
Die vom Cursor bereitgestellten Datensätze bilden schließlich auch
eine Menge, nämlich die vom ersten bis zum letzten selektierten Datensatz. Diese Menge von Datensätzen kann jetzt mit Hilfe der forSchleife durchlaufen werden. Hierbei ist die Laufvariable ein Objekt,
das jeweils den vollständigen aktuellen Datensatz beinhaltet.
declare
cursor gehalt_cursor is
select persnr, g_ab, la, betrag
from gehalt a
where g_ab = (select max(g_ab)
from gehalt a1
where a1.persnr = a.persnr
and a1.g_ab <=
to_date('2001-01-01',
'YYYY-MM-DD'))
for update of persnr;
368
Verwenden von Skripten
Herunterladen