Embedded SQL mit C und Oracle

Werbung
Embedded SQL mit C und Oracle
Holger Jakobs – [email protected], [email protected]
2008-06-26
Inhaltsverzeichnis
1 Einleitung
2 SQL in Programmen
2.1 SQL-Kommandos im Quelltext . . . . .
2.2 Verbindung zur Datenbank . . . . . . . .
2.2.1 Herstellen der Verbindung . . . .
2.2.2 Lösen der Verbindung . . . . . .
2.3 Kommunkation Datenbank ↔ Programm
2.3.1 Zeichenketten & Oracle . . . . . .
2.4 Transaktionen . . . . . . . . . . . . . . .
2
.
.
.
.
.
.
.
2
3
3
3
4
4
5
7
3 1. Beispielprogramm
3.1 Compilation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.2 Precompiler-Ausgabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7
7
7
4 SELECT in Programmen
7
5 Weitere Embedded-SQL-Kommandos
5.1 Programmieraufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.2 Hinweise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8
9
9
.
.
.
.
.
.
.
.
.
.
.
.
.
.
6 Abfragen mit mehreren Ergebnistupeln
6.1 Bulk Select . . . . . . . . . . . . . . . . . .
6.2 Cursor-Select . . . . . . . . . . . . . . . . .
6.2.1 Einfache Cursor-Abfragen . . . . . .
6.2.2 Cursor-Abfragen mit where current
6.2.3 Oracle-Zusätze bei Cursor-Abfragen .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
10
10
11
11
12
13
7 Implizite Fehlerbehandlung
13
8 weitere Aufgaben
14
Hinweis: Alle Beispielprogramme finden Sie im Datenbank-Portal unter Quelltexte“.
”
1
2 SQL IN PROGRAMMEN
1 Einleitung
SQL kann nicht nur mit interaktiven Kommandos in sqlplus oder tora benutzt werden,
sondern SQL-Kommandos können auch in Programme eingebettet werden, die in klassischen Programmiersprachen geschrieben wurden, z. B. C oder C++. Voraussetzung ist,
dass neben dem Compiler für die jeweilige Sprache der passende Precompiler vorhanden
ist, der die eingebetteten SQL-Kommandos in Quelltext der Wirtssprache (host language)
umsetzt. Im folgenden wird nur C verwendet, aber es ist mit C++ genauso möglich. Für
C++ bietet sich allerdings auch die Verwendung eines objektorientierten Interfaces an,
sofern es das für die jeweilige Datenbank gibt. Für C (und C++) gibt es – je nach Datenbank – auch ein sogenanntes Call-Level-Interface, bei dessen Verwendung man direkt
Bibliotheksaufrufe in den Quellcode einbaut. Embedded SQL ist allerdings bequemer in
der Handhabung.
Darüber hinaus ist der Zugriff auf Datenbanken über genormte Schnittstellen von Bedeutung, hierbei ist besonders das plattformunabhängige JDBC zu erwähnen. Dies wird
aber in diesem Dokument nicht erläutert, da es hier nicht um Java geht.
Grundsätzlich funktioniert die Programmierung mit Embedded SQL mit allen Programmiersprachen und mit allen SQL-Datenbanken im wesentlichen gleich – sofern sie Embedded SQL überhaupt unterstützen. In Details gibt es aber Unterschiede. Dieser Text bezieht
sich ausdrücklich auf Oracle und C.
2 Verwendung von SQL in Programmen
In Programmen werden vorwiegend Datenmanipulationsbefehle benutzt, seltener Administrationskommandos für die Datenbank, die die Struktur verändern.
Es gibt davon vier:
SELECT Suchen von Daten
INSERT Einfügen von Tupeln
DELETE Löschen von Tupeln
UPDATE Ändern von Attributwerten
Diese Befehle können auf verschiedene Arten Datenmanipulationen durchführen. Nicht
alle Kombinationen sind möglich. Die möglichen Operationen sind angegeben (S=SELECT,
F=FETCH, I=INSERT, D=DELETE, U=UPDATE, WC=DELETE/UPDATE WHERE CURRENT):
1. Einfache Datenmanipulation: (S, I, D, U)
Alle Operationen betreffen die Übertragung von max. einem Tupel aus der Datenbank
ins Programm oder umgekehrt.
2. Sequentielle Tabellenverarbeitung: (S, F, WC)
Mit Hilfe eines Cursors (Tupelzeiger) können viele Tupel nacheinander bearbeitet
werden.
2
2 SQL IN PROGRAMMEN
2.1 SQL-Kommandos im Quelltext
3. Bulk-Operation (Array-Operation): (S, F, I)
Es werden viele Tupel mit einem Befehl bearbeitet.
4. Dynamische Operation: (S, F, I, D, U)
Die Operation wird erst zur Laufzeit spezifiziert. Diese Art Programme werden in
diesem Dokument nicht erläutert.
2.1 SQL-Kommandos im Quelltext
Die eingebetteten SQL-Kommandos müssen als solche für den Precompiler kenntlich gemacht werden. Ein SQL-Kommando sieht so aus:
exec sql ... ;
Alles zwischen exec sql und dem Semikolon wird vom SQL-Precompiler in reinen CQuelltext umgewandelt. Das Original-Kommando wird also durch C-Code ersetzt. Gelegentlich kann man es noch im C-Code erkennen, aber das ist nicht immer der Fall. Der
SQL-Precompiler-Output wird anschließend mit dem C-Compiler ganz normal compiliert
und unter Verwendung datenbankspezifischer Bibliotheken gelinkt.
2.2 Verbindung zur Datenbank
2.2.1 Herstellen der Verbindung
Vom fertigen Programm muss eine Verbindung zur Datenbank hergestellt werden, so wie
das bei tora auch der Fall ist. Dazu muss man ausdrücklich ein connect-Statement verwenden, was bei Oracle in der Syntax vom ANSI-Standard abweicht. Bei ANSI-Datenbanken
schreibt man connect to :datenbank , wobei :datenbank die Datenbank bzw. der Server
ist, mit der man die Verbindung herstellen möchte. Bei Oracle sieht es im Programm so aus:
exec sql connect :user identified by :password using :connectstring ;, wobei
connectstring ; einen Oracle-Net8-Database Identification String enthält. Dieser wird
mit Hilfe einer Datei namens tnsnames.ora einem Server und einer Datenbank zugeordnet.
Wahlweise kann man statt des Benutzernamens und des Passwords separat exec sql
connect :user_passwd angeben, wobei im user_passwd Benutzernamen und Password
dann durch einen Slash getrennt werden.
Die Variablen :datenbank , :user , :password ; und :connectstring ; sind sogenannte Hostvariablen (siehe Abschnitt 2.3 auf der nächsten Seite).
Man kann sie im Quelltext mit festen Werten initialisieren (bietet sich für den Datenbanknamen an, bei uns ora10.bg.bib.de) oder auch zur Laufzeit mit Werten versehen,
z. B. durch Einlesen. Das ist insbesondere für das Password anzuraten, wobei man da noch
das Bildschirmecho abschalten muss, damit einem niemand das Password vom Bildschirm
ablesen kann. Hierzu können Sie Routinen in /usr/local/include/liesterm.h finden.
Die Bibliotheksdatei libbib.a in /usr/local/lib/ wird von unserem Script sqlproc
automatisch dazugebunden.
3
2.3 Kommunkation Datenbank ↔ Programm
2 SQL IN PROGRAMMEN
Tabelle 1: Nullwerte und ihre Bedeutung
Wert
−1
0
>0
Beschreibung
Das Attribut hatte den Nullwert
Es war ein Wert vorhanden, der übertragen wurde.
Es war ein Zeichenkettenwert vorhanden, der aber abgeschnitten
wurde. Es wurden nur so viele Zeichen übertragen wie in die Hostvariable passten. Wert gibt an, wieviele Zeichen hätten übertragen
werden sollen.
2.2.2 Lösen der Verbindung
Das Lösen der Verbindung zur Datenbank geschieht automatisch beim Beenden des Programms, d. h. man ist nicht gezwungen, dies explizit zu tun. Das ist also genauso wie
bei Dateien, die beim Ende des Programms ebenfalls automatisch geschlossen werden.
Trotzdem ist es natürlich guter Stil, die Verbindung zu schließen. Im ANSI-SQL-Standard
geschieht das mittels exec sql disconnect;, was außerhalb von Transaktionen zu geschehen hat, bei Oracle mit exec sql commit release; (bei Durchführen einer Transaktion)
bzw. exec sql rollback release; (bei Abbrechen einer Transaktion).
2.3 Kommunikation zwischen Datenbank und Programm
Zwischen Programm und Datenbank werden Daten ausgetauscht, z. B. Datenbank → Programm beim select und umgekehrt beim insert. Dies geschieht über sogenannte HostVariablen, die in den SQL-Kommandos durch einen vorangestellten Doppelpunkt gekennzeichnet werden. Es kann sich bei den Variablen um globale oder auch lokale Variablen
handeln. Nullwerte werden, da sie nicht in den Variablen selbst darstellbar sind, über zusätzliche Variablen vom Typ short int dargestellt (im Standard wird nur integer als
Datentyp erwähnt, bei Oracle müssen Sie short sein). Diese werden Nullwert-Indikatoren
genannt und werden in der Tabelle 1 erläutert.
Beim Abfragen von Daten (SELECT) muss man stets Nullwert-Indikatoren verwenden, wenn
ein Attribut Nullwerte zulässt, damit es zur Laufzeit keine unerwarteten Fehler gibt. Aber
auch beim Einfügen von Daten (INSERT)verwendet man Nullwert-Indikatoren, die man mit
−1 füllt, wenn man einen Nullwert übertragen möchte, und ansonsten mit 0.
Man kann auch beim Abfragen von not-null-Zeichenketten-Attributen Indikatoren verwenden, aber dann ist der Indikatorwert 0, wenn alles gut gegangen ist. Für den Fall, dass
die Variable zu kurz war, um den Wert des Attributs aufzunehmen, enthält die Indikatorvariable die Anzahl Zeichen, die hätten übertragen werden sollen.
Das Datenbanksystem muss dem Programm über die reinen auszutauschenden Daten
hinaus auch noch mitteilen, ob die gewünschten Operationen ausgeführt werden konnten.
Dazu dient die SQL-Communication Area, kurz sqlca. Sie muss im Programm glo-
4
2 SQL IN PROGRAMMEN
2.3 Kommunkation Datenbank ↔ Programm
Tabelle 2: ANSI-sqlcode-Werte und ihre Bedeutung
Werte
sqlcode = 0
sqlcode < 0
sqlcode = 100
Bedeutung
kein Fehler, keine Warnung
Fehler, Kommando nicht ausgeführt
kein passendes Tupel gefunden
Damit dieser im ANSI-Standard festgelegte Wert von Oracle
gemeldet wird, muss man beim Preprocessing MODE=ANSI angeben, was unser Script sqlproc aber immer tut.
Tabelle 3: Oracle-sqlcode-Werte und ihre Bedeutung
sqlcode-Werte Bedeutung
−1017
Autorisierung falsch, keine Anmeldung an der Datenbank
möglich.
−1861
Literal (z. B. für Datum/Zeit-Wert) entspricht nicht dem Format. Hinweis: Durch die im b.i.b. eingestellten Werte verlangt
Oracle Datumswerte nach dem Schema von ISO8601, z. B.
1999-12-18“.
”
−1
Primärschlüssel-/Uniqueklausel-Verletzung
−2291
Fremdschlüssel-Verletzung, Eltern-Datensatz nicht gefunden
−2292
Fremdschlüssel-Verletzung, Kind-Datensatz gefunden
−2290
Check-Constraint-Verletzung
−2112
Mehrere Ergebnistupel bei einem Single Select
Weitere Werte schauen Sie bitte in den Oracle-Unterlagen nach (z. B.
online über das Datenbanken-Portal).
bal deklariert werden, was durch das Kommando exec sql include sqlca; geschieht.
Es handelt sich dabei um eine Datenstruktur, von der u. a. folgende Komponenten zur
Status-Überprüfung benutzt werden: sqlcode und sqlerrm.sqlerrmc. Bei Oracle sollte
man vorher das Symbol SQLCA_INIT definieren, damit die sqlca initialisiert wird.
Die Bedeutung der Variablen sqlcode nach ANSI ist in der Tabelle 2 erläutert. Weitere
Werte sind datenbankabhängig, siehe Tabelle 3.
Tip: In dem vom Precompiler erzeugten C-Code ist die SQL Communication Area schön
kommentiert enthalten. Schauen Sie mal rein!
2.3.1 Zeichenketten & Oracle
Bei Zeichenketten gibt es im Zusammenhang mit Oracle einige Besonderheiten zu berücksichtigen. Oracle verwaltet Zeichenketten wie die Programmiersprache Pascal, d. h. nicht als
5
2.3 Kommunkation Datenbank ↔ Programm
2 SQL IN PROGRAMMEN
ASCII-NUL-terminierte Arrays, sondern mit einer separaten 16 Bit großen Längenangabe.
Die Konvertierung zwischen der C-Darstellung und der Oracle-Darstellung geschieht nicht
automatisch; der Precompiler fügt den hierzu notwendigen Code nicht in das Programm
ein – im Gegensatz zu den Precompilern anderer Datenbanken.
Verwendet man als Hostvariablen char-Arrays wie in C üblich, so werden diese beim
Lesen von Daten aus der Datenbank immer mit Blanks bis zum Ende vollgeschrieben,
auch wenn der Attributwert vom Typ varchar kürzer ist (blank-padded).
Alternativ dazu kann man die Hostvariablen als varchar deklarieren, was vom Precompiler in eine Struktur umgewandelt wird. Hier ein Beispiel: varchar vorname [16]; wird
vom Precompiler umgewandelt in
struct {
unsigned short len;
unsigned char arr[16];
} vorname;
Das muss man bei der folgenden C-Programmierung natürlich berücksichtigen. Der Versuch, den Vornamen jetzt mittels printf ("%s\n", vorname); auszugeben, ist zum Scheitern verurteilt (leider erst zur Laufzeit, weil der C-Compiler die Parameter von printf
hinter der Formatangabe wegen der Ellipse nicht prüfen kann) – es gibt evtl. einen Segmentation Fault (nicht immer reproduzierbar).
Statt dessen muss man folgendes verwenden:
vorname.arr[vorname.len] = '\0';
printf ("Vorname: '%s'\n", vorname.arr);
Diese Vorgehensweise entspricht soweit dem ANSI-SQL-Standard. Aber man muss beim
Eintragen von Zeichenketten in die Datenbank vorher mittels strlen die Länge ermitteln
und in das len-Element der Struktur eintragen:
vorname.len = strlen (vorname.arr);
exec sql insert into tabelle values (:nr, :vorname);
Eine weitere Besonderheit ist, dass Oracle bei der Verarbeitung davon ausgeht, dass die
leere Zeichenkette identisch ist mit dem NULL-Value. Das ist leider nicht so, denn die Information, dass eine Zeichenkette leer ist, ist nicht identisch mit der fehlenden oder nicht
zutreffenden Information.
Man kann also – im Gegensatz zu anderen, ANSI-konformen Datenbanken – in ein NOT
NULL-Attribut keine leere Zeichenkette eintragen. Ein möglicher Workaround ist, eine Zeichenkette einzutragen, die aus einem Leerzeichen besteht.
6
4 SELECT IN PROGRAMMEN
2.4 Transaktionen
2.4 Transaktionen
Bei Oracle behindern sich beliebig viele Lesetransaktionen und eine Schreibtransaktion
nicht gegenseitig. Die Lesetransaktionen bekommen immer die Daten geliefert, wie sie
zum Zeitpunkt des Beginns in der Datenbank gestanden haben. Sie werden nötigenfalls
aus dem Rollback-Segment der Datenbank gelesen, wenn sie zwischenzeitlich durch eine
Schreibtransaktion verändert wurden.
Änderungen am Datenbestand müssen mittels commit work dauerhaft gemacht werden.
Wahlweise können alle Änderungen einer Transaktion auch ungeschehen gemacht werden,
indem man statt dessen rollback work verwendet. Das im Standard enthaltene Wort work
hinter dem commit ist bei Oracle optional, sollte aber der Portabilität wegen verwendet
werden.
Transaktionen beginnen automatisch, sobald man Aktionen mit der Datenbank macht.
Beendet werden sie durch eines der oben genannten Kommandos oder durch Beenden der
Verbindung zur Datenbank. Änderungen an der Datenbankstruktur führen übrigens vorher
und nachher automatisch und ungefragt ein commit work aus.
3 Erstes Beispielprogramm
Das Beispielprogramm finden Sie in der Datei sqlprogc_beispiel1.pc; es wird die bekannte Sportlerdatenbank verwendet (bzw. die Tabelle sp_spieler darin).
3.1 Compilation eines SQL-Programms
Ein C-Programm mit Embedded SQL muss mehrfach übersetzt werden. Daher haben wir
auf unserer Maschine ein kleines Script für diesen Zweck mit dem Namen sqlproc. Schauen
Sie es sich bitte genau an, denn schließlich sollen Sie auch ohne dieses Script in der Lage
sein, ein SQL-Programm in eine ausführbare Datei umzuwandeln.
3.2 Vom SQL-Precompiler umgewandelte EXEC-SQL-Aufrufe
Übersetzen Sie das Beispielprogramm sqlprogc_beispiel1.pc und drucken Sie den Quellcode sowie den erzeugen C-Quelltext sqlprogc_beispiel1.c aus. Vergleichen Sie die Dateien und suchen Sie die exec sql-Anweisungen im übersetzten Code.
4 Das SELECT-Kommando in Programmen
Nach der Eingabe von Daten mit dem insert-Kommando wollen wir nun Daten aus dem
Bestand abfragen. Vom select-Kommando gibt es mehrere Varianten: das Single Select“,
”
das Bulk Select“ und das Sequential Select“. Das erste wird hier erläutert, das Bulk Se”
”
”
lect“ in Abschnitt 6.1 auf Seite 10 und das Sequential Select“ (auch Cursor-Select genannt)
”
in Abschnitt 6.2 auf Seite 11.
7
5 WEITERE EMBEDDED-SQL-KOMMANDOS
Das Single Select“ muss so formuliert sein, dass das Ergebnis nicht mehr als ein einziges
”
Tupel umfasst; andernfalls kommt es zu einem Fehler (−2112) in sqlca.sqlcode).
Das Programm stellt für das Ergebnis des Kommandos nur einen Datensatz zur Verfügung, der sofort gefüllt wird, sofern ein Tupel gefunden wird. Beispiel für eine Deklaration:
exec sql begin declare section;
char
SpielName [16];
/* alle Zeichenketten 1 Byte laenger, */
char
Vorname
[ 4];
/* damit Platz fuer Terminator \0 ist. */
char
PLZ
[ 6];
char
Ort
[21];
exec sql end declare section;
Zeichenketten sollten grundsätzlich 1 Byte länger als in SQL deklariert werden, damit
Platz für das Terminatorzeichen '\0' ist, was von Oracle bei char-Variablen leider nur ganz
am Ende angehängt wird, so dass alle Zeichenketten bis zum Ende mit Blanks aufgefüllt
geliefert werden. Dies kann man durch die Verwendung von varchar-Variablen verhindern,
die vom Precompiler in Strukturen umgesetzt werden (siehe Abschnitt 2.3 auf Seite 5).
Das Select-Statement sieht im Programm dann so aus:
exec sql select spielname, vorname, plz, ort
into :spielname, :vorname, :plz, :ort
from spieler
where spielnr = :spielnr;
Dieses Select erzeugt wegen der Eindeutigkeit der Spielernummer garantiert höchstens ein
Tupel. Die Variable sqlcode aus der sqlca kann den Wert 100 enthalten, wenn kein passendes Tupel enthalten ist; oder es kann ein Fehler auftreten, wenn mehr als ein passendes
Tupel gefunden wurde. Die Select-Kommandos können auch bei einem Single Select beliebig
komplex sein. Ein komplettes Beispielprogramm mit einem Single-Select finden Sie unter
singleselect.pc. Schauen Sie es sich mit besonderem Augenmerk auf die Behandlung
von Nullwerten an.
Bei allen Attributen, die Nullwerte enthalten können (also nicht mit not null versehen sind), ist es zwingend erforderlich, dass ein Nullwert-Indikator verwendet wird. Dabei
handelt es sich um eine Integer-Host-Variable (bei Oracle abweichend vom Standard zwingend short int), die hinter dem Attribut ohne Komma dazwischen angegeben wird. Diese
Variable wird mit einer Null gefüllt, wenn ein gültiger Wert geliefert wurde (also logisch
false). Falls im Attribut ein Nullwert stand, bekommt der Nullwert-Indikator einen Wert
ungleich Null (also logisch true).
5 Weitere Embedded-SQL-Kommandos
Im folgenden sollen Sie außer dem Single Select und dem Insert auch Delete und Update
einsetzen. Delete unterscheidet sich nicht von der interaktiven Version. Bei Update lautet
8
5 WEITERE EMBEDDED-SQL-KOMMANDOS
5.1 Programmieraufgaben
die Syntax: exec sql update tabellenname set attribut = attributwert /:hostvariable where bedingung ;. Es kann also wahlweise ein direkter Attributwert oder eine
Hostvariable angegeben werden, deren Inhalt dem genannten Attribut aller Tupel zugewiesen wird, die auf die Bedingung passen.
5.1 Programmieraufgaben mit Embedded SQL zur
Krankenhaus-Datenbank
Schreiben Sie ein C-Programm – ggf. mit Unterprogrammen –, das folgendes leistet:
1. Erfassen von Daten (insert)
ˆ
ˆ
ˆ
ˆ
ˆ
ˆ
ˆ
Station
Zimmer
Medikament
Patient (Zimmer darf nicht überbelegt werden!)
Angestellter/Arzt (Fach + Telefonnummer oder keins von beiden!)
Behandlung
Verordnung
2. Lesen von Daten (single select)
Daten für ein Zimmer anzeigen, d. h. Stationsname, Zimmernummer und Anzahl der
freien und belegten Betten.
3. Verändern von Daten (update)
ˆ Verlegen eines Patienten von einem Zimmer in ein anderes, wobei auch hier das
neue Zimmer nicht überbelegt werden darf.
ˆ Eintragen/Ändern/Löschen einer Diagnose
4. Löschen von Daten (delete)
Entlassung eines Patienten, wobei nach Eingabe der Patientennummer sämtliche den
Patienten betreffenden Daten aus der Datenbank gelöscht werden. Natürlich wird
vorher gefragt, ob man den Patienten mit soundsoviel Behandlungen und soundsoviel
Verordnungen wirklich löschen möchte, sofern der Patient noch in Behandlung ist.
5.2 Hinweise zu den Programmieraufgaben
Arbeiten Sie mit Haupt- und Untermenüs, wobei das Hauptmenü die obigen Punkte zur
Auswahl anbietet. Schließen Sie jede Transaktion (aber nicht unbedingt jedes SQL-Statement) mit commit work ab. Möchten Sie zwischendurch eine Transaktion abbrechen, so
verwenden Sie rollback work.
Vor der Programmierung sollen Struktogramme entworfen werden, die die SQL-Anweisungen bereits enthalten. Dies dient der Konzentration auf Abläufe und Datenbanken, ohne
gleichzeitig an die Programmiersprache denken zu müssen.
Bauen Sie Fehlerbehandlungen mit Klartext-Meldungen in Deutsch für alle Fehlersituationen ein, die Sie vorhersehen können, beispielsweise Station nicht vorhanden!“ bei der
”
9
6 ABFRAGEN MIT MEHREREN ERGEBNISTUPELN
Zimmereingabe. Nur für nicht vorhersehbare Fehler soll die Standard-Fehlerbehandlung
einer Funktion StatusAnzeige() oder ähnlich verwendet werden.
6 Abfragen mit mehreren Ergebnistupeln
Wenn bei einem SELECT mehr als ein einziges Tupel herauskommen kann, reicht ein
Single Select“ nicht aus. Man muss ein Bulk Select“ oder eine sequentielle Tabellenverar”
”
beitung mit cursor und fetch machen.
6.1 Bulk Select – Abfragen mehrerer Tupel auf einmal
Bei einem Bulk Select wird die Ergebnistabelle auf einmal in den Datenbereich des Programms übertragen. Das bedeutet, dass das Programm einen ausreichend großen Datenbereich zur Verfügung stellen muss. Der Programmierer muss also schon zur Compilationszeit abschätzen, wieviele Tupel/Datensätze beim Select entstehen können. Bulk Select ist
im ANSI-SQL-Standard nicht enthalten, wird aber von vielen Datenbanksystemen angeboten – aber natürlich nicht auf exakt gleiche Art.
Im Gegensatz zum Single Select werden hier nicht einzelne Host-Variablen angelegt,
sondern Host-Arrays (siehe Kapitel 8 im Oracle .Pro*C/C++ Precompiler Programmer’s
Guide).
exec sql begin declare section;
int nr
[20];
/* 20 int-Werte */
char spielname[20][30]; /* 20 Zeichenketten der Länge 29 */
exec sql end declare section;
Bei obiger Deklaration war keine Indikatorvariable notwendig, weil alle Attribute mit not
null versehen sind. Werden Indikatorvariablen benötigt, so sind sie ebenfalls als Array zu
deklarieren. Die Aufrufsyntax des Bulk Select unterscheidet sich vom gewöhnlichen Select
kaum und sieht beispielsweise so aus:
exec sql select spielnr, spielname
into :nr, :spielname
from spieler;
In der Variablen sqlerrd [2] der SQL Communication Area teilt uns SQL mit, wieviele
Tupel in unser Array übertragen worden sind. Dies ist eine weitere Statusinformation über
den sqlcode hinaus. Zur Weiterverarbeitung in unserem Programm benötigen wir diese
Angabe z. B. in einer for-Schleife:
for (i=0; i<sqlca.sqlerrd[2]; i++) {
printf ("Spieler[%d]: %d, %s\n",
i, nr[i], spielname[i]);
}
10
6 ABFRAGEN MIT MEHREREN ERGEBNISTUPELN
6.2 Cursor-Select
Falls die Arrays groß genug waren, um alle Daten aufzunehmen, enthält sqlca.sqlcode
den Wert 100, da das Ende der Daten erreicht wurde.
Sollte es mehr Spieler geben als in die Arrays passen, so gibt es wieder den Fehlerstatus
−2112 in sqlca.sqlcode, Die Variable sqlca.sqlerrd[2] enthält jetzt auch die Anzahl
der passenden Tupel, aber es sind nicht alle Daten übertragen worden.
Ein vollständiges Beispielprogramm finden Sie in bulkselect.pc
6.2 Sequentielle Tabellenverarbeitung mit Cursor
6.2.1 Einfache Cursor-Abfragen
Bei der sequentiellen Tabellenverarbeitung wird von SQL im Hintergrund eine Tabelle erzeugt, die das Ergebnis des Select enthält. Das Programm benötigt wie beim Single Select
nur die Variablen für ein einziges Tupel, weil diese aus der Hintergrundtabelle wie aus einer
sequentiellen Datei gelesen werden. Ein Cursor dient als Satzzeiger auf die Hintergrundtabelle.
Bei diesem sequential Select“ muss man nicht vorher wissen, wieviele Tupel die Ergeb”
nistabelle enthalten wird. Dafür ist die Anzahl der Zugriffe auf die Datenbank viel höher,
und Sperren auf den Datenbestand bestehen im Allgemeinen länger.
Folgende Embedded-SQL-Kommandos gibt es für das Arbeiten mit sequential Selects:
declare cursorname cursor for selectcommand ;
verbindet einen Cursor über seinen Namen mit einem spezifizierten Select-Kommando. Der
Cursor ist ein Zeiger auf ein Tupel der Ergebnistabelle, die durch das Select erzeugt werden
kann. Die Ergebnistabelle wird durch declare cursor noch nicht erzeugt.
open cursorname ;
führt das mit dem Cursor verbundene Select-Kommando tatsächlich durch und erzeugt die
Ergebnistabelle ( active set“) im Hintergrund. Der Cursor steht jetzt vor dem ersten Tupel.
”
Ab jetzt können Sperren auf die Datenbank bestehen.
fetch cursorname into hostvariablen ;
bewegt den Cursor auf das nächste Tupel der Ergebnistabelle und überträgt die Werte des
neuen Tupels in die Hostvariablen.
delete from tablename where current of cursorname ;
löscht das Tupel, auf das der Cursor zeigt. Der Cursor steht anschließend vor dem nächsten
Tupel (nicht auf dem nächsten). Geht natürlich nur bei Abfragen auf eine Einzeltabelle,
nicht bei Joins.
update tablename set columnname = expression
where current of cursorname ;
verändert in dem Tupel, auf das der Cursor zeigt, das genannte Attribut entsprechend
dem im Ausdruck angegebenen Wert. Es können mehrere Anweisungen columnname =
expression verwendet werden, die durch Komma getrennt werden. expression kann ein
SQL-Ausdruck, eine Hostvariable (ggf. mit Nullwertindikator), ein Literal oder NULL sein.
11
6.2 Cursor-Select
6 ABFRAGEN MIT MEHREREN ERGEBNISTUPELN
close cursorname ;
deaktiviert die Ergebnistabelle (active set). Nach close sind fetch, delete where current
und update where current nicht mehr möglich. Durch commit work und rollback work
werden alle Cursor ebenfalls geschlossen.
Um die Strafentabelle mit sequentieller Tabellenverarbeitung anzuzeigen, programmiert
man beispielsweise so (ohne die notwendige Fehlerprüfung):
exec sql declare kursor cursor for
select zahlnr, st.spielnr, spielname, datum, betrag
from strafen st, spieler sp
where st.spielnr = sp.spielnr;
exec sql open kursor;
while (1) {
exec sql fetch kursor
into :zahlnr, :spielnr, :spielname, :datum, :betrag;
if (sqlca.sqlcode != 0) break;
printf ("%4d %4d %-30s %10s %10.2f\n",
zahlnr, spielnr, spielname, datum, betrag);
}
exec sql close kursor;
Es finden bei der sequentiellen Tabellenverarbeitung dieselben Fehlercodes Anwendung wie
beim Single Select. Es wird aus der Ergebnistabelle gelesen, bis kein Tupel mehr übrig ist
– dann wird sqlca.sqlcode den Wert 100 erhalten.
Das vollständige Programm (mit der notwendigen Fehlerprüfung) finden Sie in der Datei
cursor_strafen.pc
Aufgabe: Bauen Sie in die Patientenaufnahme ein, dass bei der Eingabe eines belegten
Zimmers eine Liste aller anderen Zimmer derselben Station angezeigt wird, in denen noch
Betten frei sind. Sind auf der Station keine Zimmer mehr frei, soll auch dies im Klartext
gemeldet werden.
6.2.2 Cursor-Abfragen mit where current
Möchte man am aktuellen Tupel nach dem FETCH Änderungen vornehmen oder das Tupel
löschen, so kann man die besondere Klausel WHERE CURRENT OF cursorname benutzen.
Das sieht dann so aus:
exec sql update spieler
set gebjahr = gebjahr +1
where current of kursor;
12
7 IMPLIZITE FEHLERBEHANDLUNG
oder
exec sql delete spieler
where current of kursor;
Ein vollständiges Beispiel finden Sie unter dem Namen cursor_update_where_current.pc.
6.2.3 Oracle-Zusätze bei Cursor-Abfragen
Oracle erlaubt bei Cursor-Abfragen ein FETCH auch mit Arrays, was auch BULK FETCH“
”
genannt wird. Hierbei werden die Tupel nicht einzeln, sondern in Gruppen abgeholt. Näheres dazu finden Sie in der Oracle-Dokumentation unter der Überschrift Cursor Fetches“
”
im Kapitel 8 des Oracle .Pro*C/C++ Precompiler Programmer’s Guide.
7 Implizite Fehlerbehandlung
Bislang haben wir alle Fehler, die in den Programmen auftreten konnten, explizit behandelt,
d. h. die Rückmeldungen von SQL wurden von unseren Programmcode ausgewertet. SQL
bietet aber noch eine andere Art der Fehlerbehandlung an, bei der man weniger schreiben
muss, dafür aber auch weniger Möglichkeiten hat. Am Anfang des Programms wird eine
whenever-Anweisung verwendet. Die Parameter dieser Anweisung sind in Tabelle 4 auf der
nächsten Seite erläutert.
exec sql whenever condition action ;
Solange keine whenever-Anweisung im Programm verwendet wurde, gilt die Aktions-Voreinstellung continue. Das Kommando whenever condition continue wird nur dazu gebraucht, ein früheres whenever mit einer anderen Aktion wieder auf diesen voreingestellten
Wert zurückzusetzen. Die mit whenever festgelegten Aktion gelten solange, bis ein erneutes
whenever verwendet wird. Die whenever-Anweisungen werden vollständig vom Precompiler ausgewertet und bewirken die Erzeugung von entsprechenden C-Anweisungen.
Eine stop-Aktion hält das Programm unter Zurückfahren der aktuellen Transaktion an,
zeigt aber leider keinerlei Meldung über den Grund an. Eine continue-Aktion sollte man
nur dann einstellen, wenn man die Fehlerbehandlung selbst übernimmt, denn ignorierte
Fehler führen zu nicht absehbaren Reaktionen des Programms. Das Springen zu einer Marke
sollte nur in Notfällen verwendet werden. Eine do break-Aktion gibt es nur bei wenigen
Datenbanken; außerdem ist hierbei zu berücksichtigen, dass ein break nur in einer Schleife
oder einem switch-Statement auftauchen darf. Fügt der Precompiler es an anderer Stelle
ein, so erzeugt das einen Fehler beim C-Compiler. Gleiches gilt für do continue.
Die Aktion do Funktionsaufruf (...) ist ebenfalls Oracle-spezifisch, sollte also mit
Vorsicht eingesetzt werden.
13
8 WEITERE AUFGABEN
Tabelle 4: WHENEVER-Übersicht
Anweisungsteil Schlüsselwort
condition
sqlerror
sqlwarning
not found
action
stop
continue
goto label
do break
do continue
do Funktionsaufruf
Bedeutung
sqlcode < 0
sqlwarn [0] = ’W’ (kein ANSI-SQL)
sqlcode = 100
Programmende mit rollback work
keine Aktion, wie ohne WHENEVER
Sprung zur Marke (igitt!! )
führt break aus (kein ANSI-SQL)
führt continue aus (kein ANSI-SQL)
ruft genannte C-Funktion auf; nach
Verlassen der Funktion läuft das Programm ganz normal weiter (kein ANSISQL)
8 weitere Aufgaben
1. Auch wenn Sie es außerhalb dieser Übung niemals verwenden: Schlagen Sie in einem
C-Buch nach, wie man mit goto umgeht und bauen Sie es in ein Programm mit
whenever ein.
2. Verwenden Sie whenever in einem Ihrer Programme und schauen Sie nach, was der
SQL-Precompiler aus Ihren SQL-Statements macht. Was verändert sich gegenüber
der Voreinstellung? Vergleichen Sie mit SQL-Precompiler-Outputs ohne whenever.
Tip hierzu: Vergleichen Sie die zwei C-Quellcodes mit tkdiff (geht nur unter X).
3. Schauen Sie sich das Beispielprogramm whenever.pc zur Verwendung von whenever an. Bauen Sie in das Programm folgende Fehler ein und schauen Sie, wie das
Programm darauf reagiert:
a) connect-Problem: Geben Sie eine Datenbank an, die nicht existiert.
b) select-Problem: Geben Sie eine Tabelle oder ein Attribut an, das
nicht existiert.
c) select-Problem: Verwenden Sie bei einem Attribut einen falschen
Datentyp oder ein nicht existierendes Attribut.
d) insert-Problem: Versuchen Sie, ein Tupel einzutragen, das nicht
eingetragen werden darf, weil es gegen eine Bedingung (Primärschlüssel, Fremdschlüssel, Checkbedingung) vestößt.
$Id: SQL-Programmierung-C-Oracle.tex,v 1.1 2008-03-28 10:56:02 hj Exp $
14
Herunterladen