1.3. Eingebettetes SQL 1.3.1. Eingebettetes SQL (1.Stufe) Eingebettetes SQL (=embedded SQL) ist eine Erweiterung einer Gastsprache (z.B. C) um eingebettete SQL-Befehle. Die Sprache SQL läßt sich in zahlreiche gängige Programmiersprachen einbetten, unter anderem C/C++, Pascal, Fortran, Cobol, Ada. Die Einbettung des jeweiligen SQL-Dialektes ist nicht vollständig standardisiert, ist aber doch von DBMS zu DBMS sehr ähnlich. Bei eingebettetem SQL werden zwei Übersetzungsschritte durchgeführt, wovon der erste (=der Precompiler) die eingebetteten SQL-Befehle übersetzt in Funktionsaufrufe des DBMS-Kerns und in Funktiosaufrufe und Datenstrukturen zur Kommunikation des DBMS mit der jeweiligen Gastsprache. Eingebettete Befehle sind für den Precompiler leicht zu erkennen, da sie mit exec sql beginnen. Das folgende Beispiel orientiert sich an ESQL/C, der Einbettung des Sybase SQL Servers in die Programmiersprache C. Die ESQL/C-Dateien haben die Endung .cp und werden vom Sybase Precompiler in eine Datei mit Endung .c übersetzt, die dann von einem normalen C-Compiler weiter übersetzt werden kann. Die sogenannte SQL Communication Area (sqlca) ist eine Datenstruktur, die Informationen zum Betriebszustand des DBMS an die Gastsprache (z.B. C) weitergeben kann. Sie wird durch die Schreibweise exec sql include sqlca; /* SQL Communication Area einrichten - immer noetig */ bereits vom Precompiler textuell ersetzt (include auf Precompilerebene). So kann die folgende C Funktion fehler( ) z.B. nachsehen, welchen Fehlercode ein DBMS -Befehl auslöste. void fehler( ) /* eine C-Funktion ! */ /* Zeige Fehler-Code und Fehlermeldung */ { printf("\nFehler %d aufgetreten:\n%s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); } Von der SQL Communication Area zu unterscheiden sind sogenannte Kommunikationsvariablen, in denen Daten von der Gastsprache (C) zum DBMS (Sybase) oder vom DBMS zur Gastsprache trasferiert werden. Diese werden innerhalb einer sogenannten declare section deklariert. exec sql begin declare section; /* Kommunikationsvariablen deklarieren */ CS_CHAR user[31], passwd[31]; exec sql end declare section; Die Datentypen CS_CHAR, CS_INT usw. sind nicht die Standard-C-Datentypen char oder int, sondern werden vom DBMS speziell zum Datenaustausch angeboten. Jedoch kann eine Variable vom Typ CS_CHAR[31] im C-Programm wie eine Variable vom Typ char[31] benutzt werden, und eine Variable vom Typ CS_INT wie eine Variable vom Typ int. Die Verbindung zum DBMS wird aufgebaut mit dem connect-Befehl: exec sql connect :user identified by :passwd; /* DBMS -Verbindung aufbauen */ Dabei wird der Username aus der C-Variablen user gelesen und an den connect-Befehl uebergeben und das Passwort wird aus der C-Variablen passwd gelesen und an den connect-Befehl uebergeben. Eine konkrete Datenbank wird geöffnet mit dem use-Befehl: exec sql use db_ss98; /* DB oeffnen */ Die Befehle zum Anlegen einer Tabelle und zum Einf ügen von Tupeln stellen nur ein exec sql vor die entsprechenden interaktiven SQL-Befehle, z.B.: exec sql create table student (Mnr int, Vorname char(20), Name cahr(20), Semester char(3) ); exec sql insert into student values (1001, 'Anna', 'Arm', 'ti2'); exec sql insert into student values (1002, 'Rita', 'Reich', 'ti2'); exec sql insert into student values (1003, 'Peter', 'Reich', 'ti2') ; exec sql insert into student values (1004, 'Peter', 'Petersen', 'ie7') ; Transaktionen werden beendet mit exec sql commit work; /* Transaktion abschliessen */ und die Verbindung zum DBMS wird wieder abgebaut mit dem disconnect-Befehl: exec sql disconnect all; /* DBMS-Verbindung abbauen */ Ein Ausnahmebehandlung für SQL-Fehler (z.B. anzulegende Tabelle existiert bereits) wird in der Regel in einer Funktion der Gastsprache durchgeführt, hier in der C-Funktion fehler(). Der automatische Aufruf dieser Funktion im Fehlerfall wird erreicht durch den whenever-Befehl: exec sql whenever sqlerror call fehler(); /* Fehlerbehandlung initialisieren */ Insgesamt ergibt sich folgendes ESQL/C-Programm (example1.cp): /* ESQL/C - Beispiel 1: Anlegen einer Tabelle und einfuegen von Tupeln */ exec sql include sqlca; /* SQL Communication Area einrichten - immer noetig */ void fehler( ) /* Zeige Fehler-Code und Fehlermeldung */ { printf("\nFehler %d aufgetreten:\n%s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); } main( ) { exec sql begin declare section; /* Kommunikationsvariablen deklarieren */ CS_CHAR user[31], passwd[31]; exec sql end declare section; exec sql whenever sqlerror call fehler(); /* Fehlerbehandlung initialisieren */ strcpy(user,"stb") ; printf("\nPassword ? ") ; gets(passwd) ; exec sql connect :user identified by :passwd; /* DBMS -Verbindung aufbauen */ exec sql use db_ss98; /* DB oeffnen */ exec sql create table student (Mnr int, Vorname char(20), Name cahr(20), Semester char(3) ); exec sql insert into student values (1001, 'Anna', 'Arm', 'ti2'); exec sql insert into student values (1002, 'Rita', 'Reich', 'ti2'); exec sql insert into student values (1003, 'Peter', 'Reich', 'ti2') ; exec sql insert into student values (1004, 'Peter', 'Petersen', 'ie7') ; exec sql commit work; /* Transaktion abschliessen */ exec sql disconnect all; /* DBMS-Verbindung abbauen */ } Ein weiteres Beispiel zu DB-Änderungen in ESQL/C 1.3.2 Anfragen mit Einzelergebnissen in eingebettetem SQL Bei der Einbettung von Anfragen muß man unterscheiden, ob die Anfrage ein Ergebnis oder mehrere Ergebnisse liefert. Liefert sie nur ein Ergebnis, so kann sie änhlich einfach eingebetten werden wie Insert-Befehle. Beispiel: exec sql begin declare section ; /* Kommunikationsvariablen deklarieren */ CS_INT anzahl ; exec sql end declare section ; ... exec sql select count ( * ) into :anzahl from student ; /* liest ein Ergebnis in die Variable anzahl des Umgebungsprogramms */ 1.3.3 Anfragen mit mehreren Ergebnissen in eingebettetem SQL Liefert eine Anfrage mehrere Ergebnistupel, so kann man in embedded SQL einen Cursor (=Iterator über das Anfrageergebnis) anlegen mit: exec sql declare cursor studenten_aus_ti2 for /* generiert Code f ür folgende Anfrage */ select * from student s where s.Semester = 'ti2' ; /* suche Studenten aus Semester ti2 */ Man nennt studenten_aus_ti2 Cursor für die nachfolgende Anfrage. Der Precompiler speichert den SQL-Befehl zusammen mit seinem Cursornamen, generiert aber keinen Code. Wenn der Cursor zur Laufzeit geöffnet wird, wird die im declare-Befehl abgelegte Anfrage an den DBMS-Server stellt. Statt der Konstanten 'ti2' könnte auch eine Variable vom Typ CS_CHAR verwendet werden. Der Wert dieser Variablen würde beim open-Befehl für die Variable eingesetzt, bevor die Anfrage an das DBMS geschickt wird. . Die Ergebnisse der Anfrage kann man sich der Reihe nach ansehen mit fetch, bis kein weiteres Ergebnis mehr da ist (sqlca.sqlcode==100) , z.B.: for ( ; ; ) { exec sql fetch for studenten_aus_ti2 into :Mnr , :Vor , :Name , :Sem if ( sqlca.sqlcode==100 ) break ; /* Verlasse Schleife, wenn alles gelesen */ verarbeite ( Mnr , Vor , Name , Sem ) ; /* Aufruf einer beliebigen C -Funktion */ } Voraussetzung für den fetch-Befehl ist wiederum, daß Mnr, Vor, Name und Sem in einer exec sql declare section (irgendwo) vor dem fetch-Befehl deklariert wurden. Schließlich sollte der Cursor nach Gebrauch wieder geschlossen werden mit exec sql close studenten_aus_ti2 ; Man kann einen Cursor (abwechselnd) mehrfach öffnen und schließen. Man kann mehrere verschiedene Cursor gleichzeitig ge öffnet halten, z.B. um in zwei geschachtelten Schleifen einen Join nachzuimplementieren. Es ist möglich, daß zwei Cursor denselben SQL-Befehl als Anfrage enthalten. Ein weiteres Beispiel zu Cursorn in ESQL/C 1.3.4 Parametrisierbare Anfragen in eingebettetem SQL Was im vorigen Beispiel nicht möglich war, ist mit Methode 3 des dynamischen SQL möglich: Anfragen können zur Laufzeit parametrisiert werden, wobei Anzahl und Typen der Parameter bei Methode 3 bereits zur Compilezeit festgelegt werden. Ein Beispiel zu ESQL/C mit parametrisierbaren Anfragen 1.3.5 Volles dynamisches SQL / Methode 4 Was im vorigen Beispiel nicht möglich war, ist mit Methode 4 des dynamischen SQL möglich: Anzahl und Typ der Eingabe- und Ausgabeparameter werden erst zur Laufzeit bestimmt. Dazu werden sogenannte dynamische Descriptoren benutzt. Weitere Informationen gibt es im Kapitel 7 / Method 4 des [Handbuch zu ESQL/C]. 1.3.6 Stored Procedures Weitere Informationen gibt es im [Handbuch zu ESQL/C].