Datenbanken unter Linux — ein Einstieg Carsten Zerbst 13. Juni 2001 Möchte man Daten, wie zum Beispiel Adressen, Rechnungen oder die Videosammlung verwalten, bieten sich Datenbanken an. Dieser Artikel soll einen Einstieg in das Arbeiten mit Datenbanken unter Linux geben. Nach einer Einführung wird die Installation von PostgreSQL beschrieben. Daraufhin werden die Möglichkeiten zur Strukturierung von Daten, ihre Eingabe und Abfrage erläutert. Zum Schluß werden die verschiedenen Möglichkeiten zur Kopplung von externen Programmen mit der Datenbank vorgestellt. 1 Was ist eine Datenbank ? Hinter dem Schlagwort Datenbank verbergen sich verschiedene Komponenten. Dies ist zum einen das Datenbank-Management-System (DBMS), das heißt die Software, welche die Daten verwaltet. Unter Linux sind diese beispielsweise PostgreSQL, MySQL, Adabas oder Oracle. Die Datenbank bezeichnet eigentlich den Bestand der Daten, der durch das DBMS verwaltet wird. Das Datenbanksystem (DBS) ist die Gesamtheit aus DBMS und Datenbank. den auch als Relationen bezeichnet, was den Systemen auch den Namen relationale DBMS einbringt. Die Tabellen bestehen aus einzelnen Spalten oder Attributen, in denen jeweils nur Daten eines Datentyps gespeichert werden können. Die Zeilen der Tabelle enthalten die Daten oder Tupel. Der Aufbau der Tabellen wird auch als Datenbankschema bezeichnet. Relationenname Attribute Mitglieder Id Name 1 2 3 Aufgabe eines DBS ist die dauerhafte Verwaltung von großen Datenmengen. Damit das Ganze reibungslos abläuft, werden einige Anforderungen an das DBS gestellt: Mustermann Musterfrau Musterstudent Vorname Geburtstag Hans Hanna Hanno 23.04.1956 02.11.1945 11.07.1968 ... Tupel Relation Atomarität Es muß möglich sein, eine Anzahl von Änderungen zu einer Einheit zusammenzufassen. Das DBMS muß gewährleisten, daß alle Änderungen erfolgreich bearbeitet oder verworfen werden. Wenn ein Fehler auftritt, muß der Zustand vor der ersten Änderung wiederhergestellt werden. Integrität Die Daten müssen integer sein. So darf es keine Rechnungen ohne Kunden geben oder fünf verschiedene Schreibweisen einer Person. Dieses Integrität muß immer gewährleistet sein; auch eine Fehlbedienung oder ein Stromausfall darf daran nichts ändern. Isolation Änderungen an einem Datensatz dürfen keine ungewünschten Nebenwirkungen auf andere Datensätze haben. Persistenz Alle Daten müssen persistent (dauerhaft) gespeichert sein. Bei den heute üblichen DBMS werden die Daten in tabellenähnlichen Gebilden gespeichert. Diese Tabellen wer- 2 Die Datenbanksprache Bei relationalen DBMS werden alle Operationen mit Hilfe einer speziellen Sprache, der Standard Query Language (SQL, manchmal auch Sequel ausgesprochen), durchgeführt. Die Sprache verfügt über eine Reihe von Kommandos oder Statements. Mit ihr lassen sich die Tabellen definieren und ändern, Daten behandeln, aber auch die Benutzerverwaltung durchführen. Für SQL gibt es mehrere ISO-Standards, die allerdings unterschiedlich weit von den DBMS unterstützt werden. Die meisten DBMS bringen außerdem eigene Erweiterungen mit, die nicht auf anderen Systemen funktionieren. Die weiter unten vorgestellten Statements gehören zum Umfang von SQL92 und laufen unter den meisten freien Systemen, die Ausnahmen sind extra gekennzeichnet. Es handelt sich bei SQL nicht um eine selbstständige Programmiersprache, sie wird über ein Abfragewerkzeug oder eingebettet in Sprachen wie Tcl, Perl oder C verwendet. Beispiel Vereinsverwaltung Für einen Verein soll eine Mitgliederverwaltung erstellt werden. Es sind für 1500 persönliche und korporative Mitglieder die Daten zu verwalten. Die Daten umfassen im einzelnen die persönlichen Daten der Mitglieder wie Name und Vorname, bis zu drei Adressen pro Mitglied sowie die Beitragsrechnung. Neben der reinen Verwaltung der Daten soll auch die Möglichkeit gegeben sein, Serienbriefe und Beitragsrechnungen zu erstellen. 3 nen ist schon ein Nutzer postgres eingerichtet. Unter diesem Unix-User laufen dann alle Prozesse, die zur Datenbank gehören. Die Daten von PostgreSQL werden in einem beliebigen Verzeichniss gespeicher, z.B. /usr/local/pgsql/data. Hat man Postgresql selber kompiliert, muß dieses Verzeichnis muß dem Nutzer postgresql gehören und initialisiert werden: # mkdir -p /usr/local/pgsql/data # chown postgresql /usr/local/pgsq/data # su postgresql > /usr/local/pgsql/bin/initdb ➥ -D /usr/local/pgsql/data Installation einer Datenbank Bevor man die erste Zeile SQL schreibt, braucht man zuerst ein lauffähiges DBMS. Aus der Vielzahl der in Frage kommenden DBMS wurde hier PostgreSQL gewählt. Von den klassischen freien DBMS unterstützt es die meisten SQL Statements, außerdem bietet es viele Schnittstellen, um es leicht mit anderen Programmen zu verknüpfen. Eine weitere beliebte Datenbank ist MySQL [8]. Dieses DBMS ist bei einfachen Operationen schneller, unterstützt dafür aber weniger SQL Statements. Ein Vergleich zwischen MySQL und PostgreSQL findet sich zum Beispiel in [9]. Die Quellen von PostgreSQL können von [2] bezogen werden, viele Distributionen bieten es auch als fertig zum Installieren an. Wählt man die Quelltextform, muß das DBMS erst kompiliert werden.1 Zur Installation von PostgreSQL sind folgende Schritte durchzuführen: # # # # cd /usr/local/ tar -xzf postgresql-7.0.3.tar.gz cd PostgreSQL-7.0.3/src/ ./configure --prefix=/usr/local/ ➥ --with-tcl --with-odbc # make # make install Postgresql ist bei den meisten Distributionen fertig kompiliert vorhanden. Es werden die Packete postgresql, postgresql-lib, postgresql-odbc, postgresql-server, postgresql-tcl und postgresql-tk benötigt. Bei Suse-Linux muß man nach der Installation im Yast unter AdministrationKonfigurationsdatei verändern die Variable START POSTGRES auf yes gesetzt werden. Nach dem nächsten Systemstart steht das DBMS zur Verfügung und muß nicht initialisiert werden. Die komplette Dokumentation befindet sich unter /usr/share/doc/packages/postgresql/. Die Datenbank selber darf nicht unter dem Benutzer root laufen. Hierfür richtet man einen eigenen Benutzer, z.B. postgresql, ein. Bei einigen Distributio- Bei allen weiteren Kommandos wird davon ausgegangen, daß sie vom Nutzer PostgreSQL ausgeführt werden. Das eigentliche Datenbankprogramm von PostgreSQL heißt postmaster. Man kann es jetzt starten: > nohup postmaster -D /usr/local/pgSQL/data ➥ -i 2>&1 /tmp/PostgreSQL.log & Damit werden die Meldungen der Datenbank in die Datei /tmp/PostgreSQL.log umgelenkt. Soll die Datenbank beendet werden, muß man nur mit kill den Prozeß beenden2 . In den Quellen befindet sich unter contrib/linux ein Beispielskript, mit dem die Datenbank automatisch mit dem Betriebsystem gestartet und beendet wird. Läuft PostgreSQL, sollte man sich erst einmal eine Datenbank zum Testen anlegen. Für den Rest des Textes wird von einer Datenbank namens spielbank“ ausgegan” gen. Hierzu dient das Programm createdb createdb spielbank Um mit der Datenbank arbeiten zu können, gibt es für PostgreSQL zwei Programme, das Kommandozeilenprogramm psql und pgaccess, ein GUI Programm auf Tcl/Tk Basis. Beide können gleichermaßen dazu benutzt werden, die im Weiteren vorgestellten SQLKommandos auszuprobieren. Beim Aufruf von psql muß die gewünschte Datenbank mit angegeben werden. Alle SQL-Statements bei psql müssen mit einem Semikolon beendet werden, zusätzlich bietet das Programm noch spezielle Kommandos, mit denen zum Beispiel Dateien eingelesen werden können oder der Aufbau einer Tabelle angezeigt werden kann. Anstatt die Statements direkt in psql einzugeben, ist es meistens angenehmer, sie mit einem Editor in eine Datei zu schreiben und diese dann in psql einzulesen. Dies kann entweder auf der Kommandozeile mit der Option psql -f Dateiname geschehen oder interaktiv mit \i Dateiname geschehen.3 1 Um die graphische Benutzeroberfläche pgaccess später nutzen zu können, sollte auch Tcl/Tk auf dem System vorhanden sein. Dies ist unter [3] zu beziehen. Soll PostgreSQL auch von Perl aus benutzt werden, ist bei ./configure auch --perl mit anzugeben. 2 Bei anderen Datenbanken, zum Beispiel Adabas D, muß die Datenbank mit einem speziellen Kommando runtergefahren werden! 3 Die SQL-Statements der Beispiele stehen auf der Homepage des Autors zur Verfügung. > psql spielbank Welcome to psql, the PostgreSQL interactive terminal. Type: \copyright for distribution terms \h for help with SQL commands \? for help on internal slash commands \g or terminate with semicolon to execute query \q to quit spielbank=\q > Bei pgaccess kann man sich unter DatabaseOpen mit der Datenbank verbinden, als User postgresql braucht man nur den Namen der Datenbank einzugeben. Es bietet eine Reihe von Hilfen bei der Arbeit mit Datenbanken. So lassen sich oft benutzte SQL-Statements speichern, einfach neue Tabellen erstellen usw. ODBC [6]. Jedes DBMS hat eine eigene Bibliothek, mit der sich auf die Daten zugreifen läßt. Um eine Anwendnung mit unterschiedlichen DBMS laufen zu lassen, gibt es ODBC als Zwischenschicht. Es läßt sich mit dem üblichen Vorgehen installieren:4 # # # # # # cd /usr/local tar -xzf unixODBC-2.0.5.tar.gz cd unixODBC-2.0.5 ./configure --prefix=/usr/local make make install Bevor man eine Datenbank mittels ODBC benutzen kann, muß erst die Konfigurationsdatei odbc.ini angepaßt werden. Dies ist in Kapitel 11 beschrieben. 4 Tabellen erzeugen Das Datenbankdesign, also der Entwurf der Tabellen zur Datenspeicherung, ist eine anspruchsvolle Aufgabe. Mit diesem ersten Schritt entscheidet man darüber, wie sinnvoll man mit den Daten umgehen kann. Der saubere Weg beim Entwurf des Schemas, zum Beispiel mit dem Entity Relationship Modell, wird in diversen Büchern behandelt. Wegen der Kürze der Zeit wird hier mehr ein handwerklicher“ Ansatz verfolgt. Wichtige Punkte bei ” der Erstellung des Datenbankschemas sind: • das Schema muß die zu speichernden Daten mit allen ihren Aspekten abbilden können • die Integrität des Datenbestandes soll durch das Schema weitestgehend erzwungen werden Für PostgreSQL gibt es eine beachtliche Menge an Dokumentation. Diese befindet sich im Unterverzeichnis doc der Quellen oder kann von [2] unter dem Punkt User’s ” Lounge“ als HTML geladen werden. Die Beschreibung der SQL-Kommandos ist in dem User’s Guide enthalten, die Beschreibung der Tcl-Erweiterung im Programmer’s Guide. Ein letzter PostgreSQL spezifischer Tip: Beim Lauf werden Teile der Daten gespeichert, um im Fall eines Absturzes ein konsistentes System zu garantieren. Diese Daten sollten regelmäßig entfernt werden, bei normalen Betrieb einmal am Tag. Dies kann mit dem SQL-Kommando vacuum analyze oder mit dem Programm vacuumdb geschehen. Möchte man die Datenbank in Verbindung mit Staroffice für Serienbriefe verwenden, benötigt man noch unix4 Das Die Daten werden in Tabellen abgespeichert, wobei jede Tabelle ein oder mehrere Spalten hat. Jede Spalte wird durch einen Namen gekennzeichnet und kann Daten eines Datentyps speichern. Folgende Datentypen kann man bei allen DBMS erwarten: Name Beschreibung boolean charachter(n) date decimal(p,s) float smallint integer time varchar(n) Boolsche Variable Zeichenkette fester Länge Datum Gleitzahlen mit s Nachkommastellen Gleitkommazahl Integer [-32768,+32767] Integer [−232 , 232 − 1] Zeit variable Zeichenkette, max n Zeichen Die meisten DBs haben zusätzliche Typen für spezielle Aufgaben, zum Beispiel IP-Adressen oder Bilder sein. Konfigurationsprogramm ODBCConfig setzt QT voraus, die gtk+ Alternative gODBCConfig verlangt beim Kompilieren etwas Handarbeit. Diese Typen können einem viel Arbeit ersparen, allerdings kann man seine Datenstruktur dann nicht einfach auf eine anderen DB portieren. Für die Datentypen gibt es auch eine Reihe von Operatoren, die für Vergleiche oder Berechnungen verwendet werden können. Dies sind zum Beispiel: Name Beschreibung < ,> <=, >= <> NOT, AND, OR *, +, - ,/ kleiner als, größer als kleiner gleich, größer gleich ungleich logische Operatoren Zahlenoperationen Dies ist natürlich nur ein kleiner Ausschnitt. Die meisten Datenbanken bieten viel mehr Operatoren an. Eine ausführliche Übersicht für PostgreSQL findet sich im User’s Guide“ im Kapitel 5. ” Zum Anlegen von Tabellen wird das CREATE TABLE Statement verwendet. Bei ihm werden alle gewünschten Spalten mit Name und Type aufgezählt.5 6 Mit dem folgenden Statement wird eine einfache Tabelle für die Mitglieder angelegt: reicht, daß die Spalte mit dem Namen und Vornamen immer einen Eintrag aufweisen muß. Mit CHECK kann eine Bedingung untersucht werden. Geburtsdaten älter als der 1. Januar 1910 werden hier nicht zugelassen.7 Mit DEFAULT kann für eine Spalte ein Standardwert vorgegeben werden. Er wird verwendet, wenn kein Eintrag explizit eingegeben wird. Hier wird mit ’today’ automatisch das aktuelle Datum vergeben. Generell werden alle Werte, die keine Zahl sind, bei SQL mit dem einfachen Hochkommata ’ eingefaßt. Dies trifft nicht nur bei der Definition der Defaultwerte zu, sondern auch später bei der Eingabe und Abfrage von Daten. Soll ein Wert ein Hochkomma enthalten, geschieht dies durch zwei Hochkommata, zum Beispiel DEFAULT ’eh” es Probleme gibt’ Nachdem die Daten der Mitglieder gespeichert wurden, sollen als nächstes die Adressen behandelt werden. Der naheliegendste Gedanke ist natürlich, die Tabelle einfach um die Spalten für Straße, PLZ und Ort zu erweitern. Allerdings werden bei diesem speziellen Verein bis zu drei Adressen pro Mitglied verwaltet: eine Postanschrift, eine Firmenanschrift und eine Privatanschrift.8 Die Triviallösung sieht etwa so aus: Tabelle Mitglieder CREATE TABLE mitglieder01 ( 2 id INTEGER , 3 name VARCHAR(100) , 4 vorname VARCHAR(100) , 5 geburt DATE , 6 eintritt DATE 7 ); 1 Allerdings läßt diese Tabelle noch einige Wünsche offen. Die Mitgliedernummern können mehrfach vergeben werden, Name und Vorname können leer bleiben, ebenso Geburts- und Eintrittsdatum. Dies soll mit der zweiten Version behoben werden: CREATE TABLE mitglieder02 ( id INTEGER PRIMARY KEY, 11 name VARCHAR(100) NOT NULL, 12 vorname VARCHAR(100) NOT NULL, 13 geburt DATE CHECK (geburt > ’1910−01−01’), 14 eintritt DATE DEFAULT ’today’ 15 ); 9 10 Hier sind neben den Spaltennamen und -typ eine Reihe weiterer Bedingungen an Spalten gestellt. Mit PRIMARY KEY wird erreicht, daß diese Spalte als Zugriffsschlüssel für die Tabelle angesehen wird. Damit wird erreicht, daß es keine zwei Tupel (Reihen) mit der selben Mitglieds-Id geben kann. Mit NOT NULL wird er- Id Name Vorname Adresstyp Strasse PLZ 1 1 1 Abels Abels Abels Bernd Bernd Bernd Post Privat Firma 21073 21073 22304 1476 1476 1476 Yong Yong Yong Yu Yu Yu Post Privat Firma 22177 22177 21079 Damit hätte man sich aber ein kapitales Problem eingehandelt. Alle Daten in einer Datenbank sollten redundanzfrei, also nur ein einziges Mal vorliegen. Bei der obigen Tabelle kann ein Mitglied bei gleicher Mitglieds-Id nicht nur drei verschieden Anschriften, sondern auch drei verschiedene Namen haben. Dies ist etwas, was durch die Benutzung von PRIMARY KEY in der ersten Tabelle ja verhindert werden sollte. Besser ist es dagegen, zusätzlich zur Tabelle mitglieder02 eine weitere Tabelle für die Adressen zu definieren: CREATE TABLE adressen01 ( id INTEGER REFERENCES mitglieder02, 19 typ CHAR(6) CHECK ( 20 (typ = ’ Post’ ) OR 21 (typ = ’ Firma’) OR 22 (typ = ’ Privat ’ )), 23 strasse1 VARCHAR(100), 17 18 5 Die Sprache SQL kümmert sich wie Fortran nicht um Groß- und Kleinschreibung, wegen der leichteren Unterscheidung wurden hier aber alle Schlüsselwörter groß geschrieben. 6 Im Programm psql kann man sich mit dem Befehl \d TABELLENNAME die Tabellendefinition anzeigen lassen. 7 Bei der Erzeugung der Tabellen werden Daten im ISO-Format ‘Jahr-Monat-Tag’ angegeben, später kann man das Datumsformat wählen. 8 Das gleiche Problem stellt sich natürlich auch in anderen Bereichen, wo zum Beispiel die Rechnungs- und Lieferanschrift unterschiedlich sein können. strasse2 VARCHAR(100), plz VARCHAR(12), ort VARCHAR(100), land CHAR(3) 24 25 26 27 28 die über die Vergleichsoperationen ==,<,> darüber hinaus gehen. Diese sind allerdings nicht immer zwischen den Datenbanken portabel. ); Mit der Einschränkung REFERENCES wird dafür gesorgt, daß nur Adressen angelegt werden können, zu denen auch eine Id in der Tabelle Mitglieder02 existiert. Die Id dient hier als Fremdschlüssel für die Tabelle adressen01.9 Fremdschlüssel sind eine sinnvolle Möglichkeit, die Konsistenz von Datenbanken über mehrere Tabellen hinweg zu erzwingen. Es kann nun kein Eintrag mehr aus der Tabelle mitglieder02 gelöscht werden, ohne daß vorher die dazugehörenden Adressen gelöscht wurden. Bei dieser Definition bleiben noch ein paar Probleme. Bei der Postleitzahl ist nur eine Spalte mit Zeichenkette vorgesehen. Dies liegt daran, daß viele Länder (Großbritanien z.B.) eine Kombination von Zahlen und Buchstaben für die Postleitzahl benutzen. Hat man nur Adressen im Inland, wäre man mit folgender Definition besser bedient: Und der Euro? Wenn man vor 2002 die Datenbank verwendet, muß man ja mit zwei verschiedenen Währungen arbeiten. Hierzu kann man alle Geldbeträge gleich in Euro speichern und rechnet sie bei Bedarf in DM um, oder man speichert die Währung mit. Für letzteres muß aber die Tabelle rechnungen01 geändert werden. In der Entwurfsphase ist das Löschen und Neuanlegen der Tabelle am einfachsten. Zum Löschen wird das drop table Statement verwendet: DROP TABLE mitglieder01; Die andere Möglichkeit ist, eine Tabelle mit dem ALTER TABLE Statement zu ändern. Allerdings ist das Statement sehr beschränkt in seinen Fähigkeiten. Zum hinzufügen einer weitern Spalte reicht es aber: ALTER TABLE rechnungen01 ADD COLUMN waehrung CHAR(3); 45 ALTER TABLE rechnungen01 46 ALTER COLUMN waehrung SET DEFAULT ’EUR’; 43 44 plz INTEGER CHECK (plz > 1000 AND plz < 100000) Allerding müßte vor dem Ausdrucken dann die vierstelligen Postleitzahlen von Dresden und Umgebung mit einer führenden Null versehen. Noch besser ist natürlich eine getrennte Speicherung von Straße, Hausnummer und Ort, damit man anhand einer PLZ-Datenbank automatisch die Gültigkeit der Adresse überprüfen kann. Beim Land bietet es sich an, eine Tabellen mit Länderkürzeln entsprechend ISO 3166 [10] anzulegen und diese dann als Fremdschlüssel zu verwenden. Als letztes müssen noch die Rechnungen der Mitglieder gespeichert werden. Die Definition der Tabelle bietet nichts wesentlich neues: CREATE TABLE rechnungen01 ( id INTEGER REFERENCES mitglieder02, 32 jahr INTEGER CHECK ((jahr > 1995) AND 33 ( jahr < ( DATE PART(’year’, TIMESTAMP ’now’) +5)) 34 ), 35 rechnungsbetrag DECIMAL(9,2), 36 datum DATE DEFAULT ’today’, 37 eingangsbetrag DECIMAL(9,2), 38 spesen DECIMAL(9,2), 39 eingangsdatum DATE, 40 mahnungsdatum DATE 41 ); 30 31 Bei dem Rechnungsjahr wird untersucht, ob es nach 1995 aber innerhalb der nächsten zwei Jahre liegt. Dies ist ein kleines Beispiel, was mit den eingebauten Funktionen für die Zeitberechnung angefangen werden kann. Für die meisten Datentypen gibt es eine Reihe von Funktionen, 9 Das DBMS MySQL unterstützt keine Fremdschlüssel. 5 Und hinein ! Nachdem die Tabellen definiert sind, müssen natürlich Daten in die Datenbank gefüllt werden. Hierzu ist das Statement INSERT INTO vorgesehen. INSERT INTO mitglieder02 VALUES (1, ’ Mustermann’,’Hans’,’1970−01−01’,’2001−03−13’); 3 INSERT INTO mitglieder02 (id,name,vorname) VALUES 4 (2, ’ Musterfrau’, ’ Hanna’); 1 2 In der einfache Form werden Werte für alle Spalten angegeben. Diese müßen in der Reihenfolge der Tabellenspalten angegeben werden. Alternativ kann man die Spalten angeben, die man füllen möchte. Spalten, die keinen Eintrag bekommen, bleiben leer oder werden mit dem Defaultwert gefüllt. Es müssen aber mindestens die Spalten gefüllt werden, die als Primär- oder Fremdschlüssel definiert wurden oder die NOT NULL Einschränkung haben. Verstößt man gegen die Tabellenstruktur bei der Eingabe, sei es durch einen falschen Typ oder einen doppelt vergebenen Primärschlüssel, bekommt man eine Fehlermeldung und das ganze Statement wird nicht durchgeführt. 6 Wo bist Du ? Nachdem die Datenbank nun gefüllt ist, geht es an den wichtigsten Punk, das Abfragen. Die mächtigen Möglichkeiten der Abfrage machen aus den relationalen Datenbanken das Werkzeug, welches bei vielen Aufgaben unverzichtbar ist. Aus Platzgründen wird bei den folgenden Beispielen auf die Abbildung der Ergebnisse verzichtet, am besten vollzieht man die Beispiele selber am Rechner nach. Zur Abfrage wird das SELECT FROM Statement verwendet. Seine einfachste Form in Zeile 1 gibt alle Einträge der Tabelle aus. Möchte man nur einige Spalten im Ergebniss haben, kann dies wie in Zeile 2 eingeschränkt werden. SELECT * FROM mitglieder02; SELECT name FROM mitglieder02; 3 SELECT COUNT(id) FROM mitglieder02; 4 SELECT SUM(rechnungsbetrag) FROM rechnungen01; 5 SELECT SUM(rechnungsbetrag) / COUNT(rechnungsbetrag) 6 FROM rechnungen01; 7 SELECT DISTINCT(ort) from adressen01; 1 2 Statt der eigentlichen Inhalte lassen sich auch Berechnungen auf dem Ausgang ausführen. Es sind alle Möglichkeiten zugelassen, die die Datenbank an Berechnungen bietet. Hier bekommt man als Ausgabe die Anzahl der Mitglieder (Z. 3), die Summe der Rechnungsbeträge aller Rechnungen (Z. 4) oder den mittleren Rechnungsbetrag aller Rechnungen (Z. 5–6). Datebankfunktionen stehen nicht nur für numerische Typen zur Verfügung, mit dem DISTINCT Schlüsselwort läßt sich die Ausgabe von Doubletten verhindern (Z. 7). Alle bisherigen Abfragen dient immer eine ganze Tabelle als Grundlage, hier kann man die Abfrage weiter einschränken. Man kann dabei vorgeben, daß ein oder mehrere bestimmte Werte in einer Zeile enthalten sein müssen (Z. 10, 11), oder auch einen Wertebereich einschränken (Z. 12). Mit dem Schlüsselwort ISNULL lassen sich Einträge selektieren, die noch nicht belegt sind, dies sind hier alle unbezahlten Rechnungen. SELECT * FROM adressen01 WHERE id=1; SELECT * FROM adressen01 WHERE id=1 AND typ=’Post’; 12 SELECT * FROM rechnungen01 13 WHERE rechnungsbetrag < 20; 14 SELECT * FROM rechnungen01 15 WHERE eingangsbetrag ISNULL; 10 11 Ist man von Sprachen wie Tcl, Perl oder Python die ganze Macht regulärer Ausdrücke gewöhnt, wird man von SQLs Fähigkeiten enttäuscht sein. Es gibt nur zwei Wildcard: % für ein oder mehrere Zeichen und für genau ein Zeichen und keine vollwertigen regulären Ausdrücke. So bekommt man aus Zeile 17 Mustermann und Musterfrau, aus Zeile 18 hingegen nur Mustermann da Hanna zuviele Buchstaben hat.10 Einen Ausweg bietet der Operator 10 PostgreSQL IN an, er vergleicht die Einträge mit einer Liste gültiger Werte (Z. 19–20). Dies funktioniert mit allen Datentypen. SELECT * FROM mitglieder02 WHERE name like ’Muster%’; SELECT * FROM mitglieder02 WHERE vorname like ’Ha ’; 19 SELECT * FROM mitglieder02 20 WHERE vorname IN (’Hans’,’Hanna’); 17 18 Alle Operationen fanden bisher auf einer Tabelle statt. Nun sollen auch die Daten von mehreren Tabellen verknüpft werden. Das Statement in Zeile 22 gibt jedoch bestimmt nicht das gewünschte Ergebniss, hier werden alle Kombinationsmöglichkeiten aus den beiden Tabellen zurückgegeben. Statt der erwarteten 6 Ergebnisse erhält man deren 12. Um die Mitglieder mit den zu ihnen gehörenden Adressen zu erhalten, muß man ein Statement wie in Zeile 23 – 25 verwenden. Da die Spalte id in beiden Tabellen vorhanden ist, muß mit dem vorangestellten Tabellennamen gekennzeichnet werden, aus welcher Tabelle der Wert gewünscht ist. SELECT * FROM mitglieder02, adressen01; SELECT mitglieder02.id, name, vorname, strasse2, ort 24 FROM mitglieder02, adressen01 25 WHERE mitglieder02.id = adressen01.id; 26 SELECT id, name, vorname 27 FROM mitglieder02 28 WHERE mitglieder02.id IN ( 29 SELECT id FROM rechnungen01 30 WHERE eingangsbetrag ISNULL 31 ); 22 23 Eine andere Art, mehrere Tabellen miteinander zu verknüpfen, sind sogenannte Subselects.11 Hier wird das Ergebniss einer Abfrage als Eingang für eine weitere Abfrage verwendet. Das Statement in Zeile 26 – 31 ist ein Beispiel hierfür, es gibt die Mitglieds-Id, Namen und Vorname derjenigen Mitglieder aus, die noch nicht bezahlt haben. Wenn man endlich Ergebnisse abgefragt hat, an denen man interessiert ist, kann man mit ORDER BY noch die Reihenfolge der Anzeige beeinflussen. Die Spalte, nach der sortiert werden soll muß dabei in der Ausgabe enthalten sein. Geordnet wird nach der normalem Reihenfolge des jeweiligen Datentyps. Die kann die Größe oder die alphabetische Reihenfolge sein.12 Im Normalfall werden die Ergebnisse in aufsteigender Reihenfolge ausgegeben, mit DESC läßt sich die Reihenfolge auch umkehren. 33 34 SELECT * FROM mitglieder02 ORDER BY geburt; SELECT * FROM mitglieder02 ORDER BY name desc; In den Beispieldateien sind 30 Beispielmitglieder enthalten, an denen man verschiedene Statements ausprobieren kann. bietet noch mehr Operatoren für reguläre Ausdrücke an, diese gehören aber zu keinem SQL-Standard. werden nicht von MySQL unterstützt. 12 Wenn da nicht die Umlaute wären. PostgreSQL kennt leider nicht die im Deutschen übliche Einordnung der Umlaute (Ac,Ae,Ä,Af). Dies muß man in einem externen Programm realisieren oder auf ADABAS D zurückgreifen. Dort lassen sich beliebige Sortierreihenfolgen erreichen. 11 Subselects 7 Oder so ? Endlich hat ein Mitglied seine Beitrag bezahlt, nun soll das auch in der Datenbank geändert werden. Zum Ändern der Daten dient das UPDATE Statement. UPDATE mitglieder SET eingangsbetrag=23.20 WHERE jahr=2001 AND 3 id = ( SELECT id FROM mitglieder02 4 WHERE name=’Mustermann’); 5 UPDATE rechnungen01 SET 6 spesen=spesen+10 WHERE spesen=0; 1 2 Bei dem UPDATE Statement sind die gleichen Einschränkungen wie bei SELECT möglich, aber auch einfache Berechnungen wie in Zeile 5. Ein letzter Punkt zu Datenbearbeitung ist das Löschen. Hierfür gibt es das DELETE Statement. Mit dem Statement wird immer eine ganze Zeile gelöscht, das Löschen einzelner Spalten ist nicht möglich. Vorsicht mit dem folgenden Statement, damit werden alle Daten einer Tabelle gelöscht! DELETE FROM mitglieder01; 8 Ferner liefen . . . Alle Eingaben und Abfragen wurden bisher als Datenbank-Superuser durchgeführt. Das ist allerdings genauso sinnvoll, wie immer als root unter Unix zu arbeiten. Bei Datenbanken lassen sich ebenso wie unter Unix Benutzer einrichten und Zugriffsrechte zuordnen. Es besteht keine automatische Kopplung zwischen einem Datenbanknutzer und einem Systemnutzer. Meldet man sich mit psql bei der Datenbank an, wird allerdings bequemerweise davon ausgegangen, daß die beiden Namen identisch sind. Zum Einrichten eines Nutzers dient das CREATE USER Statement: CREATE USER abfrage WITH password ’fragen’; CREATE USER aenderung WITH password ’aendern’ 3 VALID UNTIL ’2001−12−31’; 4 CREATE USER spielen WITH password ’testen’ 5 CREATEDB CREATEUSER; 1 2 6 GRANT select ON mitglieder02, adressen01, 8 rechnungen01 TO public; 9 GRANT insert,update ON mitglieder02, adressen01, 10 rechnungen01 TO aenderung; 11 GRANT all ON mitglieder02, adressen01, rechnungen01 12 TO spielen; 7 13 Es werden zuerst zwei Nutzer eingerichtet und ihnen ein Passwort zugeteilt. Bei dem Nutzer aenderung wird außerdem seine Gültigkeit zeitlich eingeschränkt. Der dritte Nutzer bekommt zusätzlich noch die Fähigkeit, neue Datenbanken und Nutzer anzulegen. Nachdem die Nutzer angelegt wurden, müssen ihnen noch Rechte zugewiesen werden. Hierfür dient das GRANT Statement. Bei den Rechten wird zwischen SELECT, INSERT, UPDATE und DELETE unterschieden, mit ALL sind alle Zugriffe gemeint. Hiermit können zum Beispiel Datenbankbenutzer angelegt werden, die nur Abfragen können, solche, die nur Änderungen vornehmen können, und solche, die neue Mitglieder anlegen oder alte löschen können. Dies kann entweder für jeden Nutzer explizit angegeben werden oder mit PUBLIC für alle Nutzer. 9 Für Fortgeschrittene Alle Datumsangaben sind bisher im ISO-8601 Format Jahr-Monat-Tag eingegeben worden, was in Deutschland eher ungewöhnlich ist. Die meisten Datenbanken bieten an, das Format der Datumsangabe ändern zu können, bei PostgreSQL geschieht dies folgendermaßen: SET datestyle TO ’European’; Bei den Anforderungen an ein DBMS war gefordert, daß eine Reihe von Eingaben zu einer Einheit (Transaktion) zusammengefaßt werden können. Im Normalfall wird jedes SQL-Statement als Einheit angesehen, dies wird als AUTOCOMMIT-Modus bezeichnet. Sollen mehrere Befehle zu einer Transaktion zusammengefaßt werden, geschieht dies folgendermaßen: BEGIN work; INSERT INTO mitglieder02 (id,name,vorname) VALUES 5 (3, ’ Musterstudent’,’Hanno’); 6 INSERT INTO adressen01 VALUES 7 (3, ’ Post’ , ’ ’ , ’ä Lmmersieth 90’,’22305’,’Hamburg’,’DE’); 8 COMMIT work; 3 4 Tritt ein Fehler während der Transaktion auf, wird der Datenbestand komplett auf den alten Stand gebracht, was als Rollback bezeichnet wird. Dies kann alternativ während einer Transaktion auch mit dem ROLLBACK Statement ausgelöst werden. Durch den Transaktionsmechanismus wird Integrität von zusammengehörenden Daten sichergestellt. Manchmal kommt es vor, daß die Daten zwar in einem sauberen Schema vorliegen, man aber gerne eine andere Anordnung hätte. Statt der sauberen Aufteilung bräuchte man eventuell eine große Tabelle, oder man möchte einen Nutzer nur auf Teile einer Tabelle zugreifen lassen. Dies kann mit Nutzersichten erfolgen. Diese sind für den Benutzer wie normale Tabellen zu benutzen, sind aber nur eine virtuelle Abbildung von Daten aus real existierenden Tabellen. Sie entsprechen in etwa einem logischen Link im Dateisystem, die Daten sind nur einmal vorhandend, können aber mit verschiedenen Namen abgefragt werden. Nutzersichten werden mit dem CREATE VIEW Statement angelegt. CREATE VIEW mgaddr AS SELECT mitglieder02. id , name, vorname, eintritt , geburt, 12 strasse1, strasse2, plz , ort , land 13 FROM mitglieder02, adressen01 14 WHERE adressen01.id = mitglieder02.id 15 AND typ = ’Post’; vor. Der Generator zur Erstellung von einfachen Oberflächen (Forms) unter pgaccess ist so zur Zeit aufwendig zu bedienen, daß man mit der kompletten Eigenentwicklung schneller ist (siehe Kapitel 12). 10 11 Hiermit wird eine Nutzersicht angelegt, die alle Daten eines Mitglieds in Kombination mit seiner Postanschrift enthält. Mit dieser Nutzersicht läßt sich wie mit einer normalen Tabelle arbeiten, nur daß man hier alles zusammen hat, was man für Anschreiben et cetera benötigt. Was in die Nutzersicht einfließen soll, wird mit der gleichen Syntax wie bei dem SELECT Statement angegeben. Anstatt ein häufig benötigtes Statement immer wieder auszuführen, bietet sich dann das Anlegen einer Benutzersicht an.13 10 Eine der häufigsten Anwendungen für Datenbanken ist als Datenquelle für Serienbriefe, Rechnungen und dergleichen. Als Textverarbeitung unter Linux bietet sich Staroffice an. Dieses Programm kann aber nicht direkt mit einer Datenbank kommunizieren, es benötigt als Zwischenschicht ODBC. Dies ist ein Interface, welches Anwendungsprogrammen einen einheitlichen Zugriff ermöglicht, mehr oder weniger unabhängig von dem zu Grunde liegenden DBMS. Zur Sicherheit PostgreSQL ist ziemlich robust was Störungen von Außen betrifft. Es muß nicht extra runtergefahren werden, und mangelnde Pflege (VACUUM !) wird auch nur mit sinkender Leistung bezahlt. Dennoch sollte man auch über ein Backup nachdenken. Es gibt zwei mögliche Formen des Backups. Entweder man sichert die Dateien, in denen PostgreSQL seine Daten speichert. In diesem Beispiel wären es alle Dateien unter /usr/local/pgSQL/data. Alternativ kann man den Inhalt und das Datenbankschema in einfache ASCII-Dateien mit den SQL-Statements schreiben. Aus diesen Dateien läßt sich dann jederzeit wieder eine neue Datenbank aufsetzen.14 . Zu diesem Zweck gibt es das pg dump Programm, mit ihm lassen sich solche Sicherungen anfertigen:15 > pg_dump -s spielbank > schema.SQL > pg_dump -a -d spielbank > daten.SQL 11 Zugreifen ! Nachdem man sich mit den SQL-Statements vertraut gemacht hat, sollen die Daten in irgendeiner Form verwendet werden. Mit pgSQL lassen sich Daten in ASCII oder HTML-Tabellen ausgeben, pgaccess bietet auch den Imbzw. Export von Dateien im CSV-Format an. Außerdem lassen sich pgaccess unter dem Punkt Reports Ausdrucke aus der Datenbank erstellen, diese liegen in Postscript 13 Eine andere Möglichkeit sind Funktionen. Diese werden einmal definiert und können dann durch das DBMS ausgeführt werden. Siehe in [5] unter CREATE FUNCTION. 14 Dieser Weg bietet sich auch an, wenn man auf einen neuen Rechner oder zu einem neuen DBMS wechselt. 15 Die Benutzer lassen sich so nicht sichern, hier sollte man am einfachsten die Datei mit den SQL-Statements aufheben. Unter Linux dient unixODBC [6] als ODBC-Schicht. Bevor man auf eine Datenbank zugreifen kann, muß diese in der Konfigurationsdatei odbc.ini beschrieben werden. Am einfachsten ruft man (als root) das Konfigurationsprogramm (g)ODBCConfig auf. In diesem Programm muß zuerst ein Treiber für PostgreSQL eingerichtet werden. Dies geschieht unter dem Punkt DriversAdd . Hier muß man die Namen von zwei Bibliotheken eintragen. Die eine wird als Driver bezeichnet, die andere als Setup. Für PostgreSQL sind dies die Bibliotheken libodbcpSQL.so und libodbcpSQLS.so, beide liegen unter /usr/local/lib. Nachdem mit dem Treiber die Verbindung mit einem DBMS definiert wurde, kommt die Verbindung zu einem DBS. Hier gibt es die Möglichkeit, unter System DNS eine Konfiguration für alle Benutzer einzurichten oder unter User DNS für einen Nutzer. Um einen systemweiten Zugriff einzurichten kommt man mit System DNSAdd zu einem Dialog, wo man den bereits eingerichteten Treiber selektiert und dann Ok drückt. In diesem Dialog kann man dann einstellen, welche Datenbank man abfragen möchte. Als nächstes kann man dann in Staroffice die Datenbank als Quelle einrichten. Unter DateiNeuDatenbank läßt sich ein Dialog zum Einrichten einer Datenbank öffnen. Um die Datenbank mit einem Textdokument nutzen zu können, wählt man unter BearbeitenDatenbank austauschen die gewünschte Datenbank aus. Danach kann man in das Dokument Verweise auf einzelne Spalten einfügen. Dies geht entweder mit EinfügenFeldbe- fehle. Oder man läßt sich mit Ansicht Aktuelle Datebank die Datenbank einblenden. Mit Drag’n Drop lassen sich dann die einzelnen Felder aus den Tabellenköpfen in das Dokument ziehen. Jetzt sollte auch klar sein, wofür die Nutzersicht mgaddr erzeugt wurde. Damit kann man bequem alle notwendigen Felder für Briefe handhaben, ohne über mehrere Tabellen arbeiten zu müssen. Ist man mit der Erstellung des Textes fertig, ruft man die Serienbrieffunktion erneut auf. Dort kann angegeben werden, ob die komplette Tabelle, oder bestimmte Zeilen benutzt werden sollen. Diese müssen vorher in der Datenbankansicht selektiert werden. Staroffice füllt dann die Daten aus der Datenbank in das Dokument ein und man kann recht bequem allen Mitgliedern eine neue Rechnung zukommen lassen. Der Funktionsumfang von Staroffice geht weit über das Beschriebene hinaus, neben einfachem Drucken lassen sich auch Dokumente abhängig von Datenbankeinträgen formatieren oder Serien-Emails erzeugen. Hier sei auf die Seite von Werner Roth verwiesen http://www. wernerroth.de/staroffice. 12 Selbst gemacht Ist Staroffice auch recht flexibel, was das Ausdrucken angeht, bleiben doch manchmal Wünsche offen oder man möchte die Datenbank in Verbindung mit dem Internet verwenden. Hierfür muß aus selbstgeschriebenen Programmen auf die Datenbank zugegriffen werden. Am einfachsten geht dies mit Skriptsprachen wie Tcl oder Perl. Es gibt wohl kaum ein DBMS ohne Tcl-Erweiterung, bei Adabas D wird diese flexible Skriptsprache sogar für die Systemwerkzeuge eingesetzt. Im Zusammenhang mit PostgreSQL kann man die Tcl-Erweiterung pgtcl verwenden. Diese befindet sich nach der Installation in dem Verzeichnis /usr/local/lib. Man kann sie entweder in tcl durch direktes Laden verwenden load /usr/local/lib/libpgtcl.so oder man legt (als Root) eine neue pkgIndex.tcl Datei dort an. /root# cd /usr/local/lib Directory: /usr/local/lib local/lib# tclsh % pkg_mkIndex . "*.so" % package require Pgtcl 1.3 Zuerst muß eine Verbindung mit einer Datenbank hergestellt werden, diese wird auch als Datenbankhandle bezeichnet. Dazu dient das Kommando pg_connect, dem eine Zeichenkette mit dem notwendigen Angaben übergeben wird. Das Datenbankhandle wird dann bei Abfragen benutzt, um das gewünschte DBS zu kennzeichnen. Damit lassen sich auch mehrere Verbindungen zu verschiedenen DBS in einem Skript verwenden. Die Abfrage benutzt normale SQL-Statements. Sie werden mit dem Kommando pg_exec an die Datenbank geschickt. Als Antwort bekommt man nicht das Ergebnis der Anfrage zurück, sondern ein Resulthandle, auch als Cursor bezeichnet. Mit diesem Cursor lassen sich dann die Ergebnisse der Abfrage, aber auch Informationen wie Fehlermeldungen oder Anzahl der Resultate abfragen. Hierzu dient das Kommando pg_result. Der Sinn dieses zweistufigen Verfahrens ist es, daß man so über mehrere 1000 Ergebnisse von mehreren Abfragen arbeiten kann. Würde man alle Ergebnisse direkt aus der Datenbank erhalten, könnte es für kleinere Systeme wegen der schieren Datenmenge zu Schwierigkeiten führen. Benötigt man die Ergebnisse einer Abfrage nicht mehr, sollte man den Cursor wieder Löschen. Das Kommando pg_result bietet verschiedene Möglichkeiten, die Ergbenisse oder Informationen über sie abzufragen, in dem folgenden Beispiel sind nur die wichtigsten vorgestellt. Die komplette Dokumentation über Pgtcl findet sich im Programmer’s Guide“. ” % package require Pgtcl 1.3 % set handle [pg_connect -conninfo ➥ "dbname=spielbank user=spielen ➥ password=testen"] pgsql4 % set cursor [pg_exec $handle ➥ "select name,vorname,id ➥ from mitglieder02"] pgsql4.1 % pg_result $cursor -status PGRES_TUPLES_OK % pg_result $cursor -numTuples 2 % pg_result $cursor -assign array1 array1 % parray array1 array1(0,id) = 1 array1(0,name) = Mustermann array1(0,vorname) = Hans array1(1,id) = 2 array1(1,name) = Musterfrau array1(1,vorname) = Hanna % pg_result $cursor -assignbyidx array2 array2 % parray array2 array2(Musterfrau,id) = 2 array2(Musterfrau,vorname) = Hanna array2(Mustermann,id) = 1 array2(Mustermann,vorname) = Hans % set num [pg_result $cursor -numTuples ] 2 % for {set i 0} {$i < $num} {incr i} { puts [pg_result $cursor -getTuple $i] } Mustermann Hans 1 Musterfrau Hanna 2 % pg_result $cursor -clear ;# Cursor ölschen % pg_disconnect $handle ;# Verbinung beenden Alternativ gibt es mit pg_select ein Kommando, mit dem man gleich über die erhaltenen Ergebnisse kursiert. % pg_select $handle ➥ "select name,vorname from mitglieder02" result ➥ { parray result puts "--Tupel--" } result(.command) = update result(.headers) = name vorname result(.numcols) = 2 result(.tupno) = 0 result(name) = Mustermann result(vorname) = Hans --Tupel-result(.command) = update result(.headers) = name vorname result(.numcols) = 2 result(.tupno) = 1 result(name) = Musterfrau result(vorname) = Hanna --Tupel-- Da SQL-Statements einfache Zeichenketten sind, lassen sie sich mit den Möglichkeiten von Tcl einfach erzeugen. Mit Vorsicht sollte man dabei mit Benutzereingaben umgehen. Eine falsch formatierte Datumsangabe bei der Abfrage oder Eingabe wird von dem DBMS mit einer Fehlermeldung bedacht. Diese kann man entweder an den Benutzer weiterreichen, damit er die Eingaben ändern kann. Noch besser ist es, dafür zu sorgen, daß die Eingaben nur in einem gültign Format vorliegen können. Unter Tk gibt es die Möglichkeit, Benutzereingaben in Entry-Widgets gleich bei der Eingabe zu überprüfen. Diese sind in der Dokumentation des Entrywidgets bei den Punkten -validate und -validatecommand erklärt. Einfacher machen es einem natürlich Widgets, deren Ausgabe man selber bestimmen kann wie bei den checkbuttons, dem tk optionMenu, der spinbox (Tk 8.4) oder der ComboBox (aus den BWidgets). Hier braucht keine Überprüfung mehr stattzufinden. Bücher und Verweise [1] A NDREAS H EUER , G UNTER S AAKE, Datenbanken: Konzepte und Sprachen, MITP-Verlag, Bonn, 2000 [2] Homepage von PostgreSQL.org PostgreSQL: http://www.de. [3] Homepage von Tcl und Tk sind bei Sourceforge http://tcl. sourceforge.org [4] Homepage von pgaccess: http://flex.ro/pgaccess/ [5] PostgreSQL User’s Guide, zu beziehen von [2] [6] Homepage von unixODBC: http://wwww.unixODBC.org [7] Homepage von tclodbc: projects/tclodbc/ http://sourceforge.net/ [8] Homepage von MySQL: http://www.mySQL.com Mit Hilfe von Tk lassen sich dann benutzerfreundliche Oberflächen für die Datenbank erstellen, die ganz an das Zielpublikum angepaßt werden können. Die Abbildung unten zeigt das Programm STGmitglieder. Es ist eine graphische Benutzeroberfläche für die Mitgliederverwaltung. Mit ihr lassen sich bequem die Daten einzelner Nutzer anlegen und ändern, einzelne Mitglieder suchen et cetera. [9] Tim Perdue: MySQL and PostgreSQL Compared, http:// www.phpbuilder.com/columns/tim20000705.php3 [10] Zwei- und dreibuchstabige Länderkennzeichnung, ftp://ftp. ripe.net/iso3166-countrycodes Über den Autor: Carsten Zerbst ist wissenschaftlicher Mitarbeiter an der Technischen Universtät Hamburg-Harburg. Arbeitsgebiet bis Ende des Jahres ist die Dienstintegration von Schiffen mit Hilfe von CORBA, danach sucht er neue Aufgaben. Die Skripte für diesen Vortrag sind unter http: //www.tu-harburg.de/˜skfcz/LT2001 zu bekommen.