freeX: Mit Perl auf Datenbanken zugreifen, Teil 5

Die ausschließlichen Vertriebsrechte an diesem Artikel liegen beim Computer- & Literaturverlag (C&L). Der Artikel darf nicht kopiert oder gar erneut in einer Zeitschrift oder einem Buch veröffentlicht werden ohne vorherige Erlaubnis von C&L. Der Verlag gestattet freundlicherweise die Veröffentlichung auf diesen Seiten. Wer öfter auf diesen Hinweis trifft, sollte sich überlegen, die Zeitschrift freeX zu abonnieren.

Perl hier, Perl da, Perl ist immer da!

 
Da man bei Verwendung von Perl-Programmen relativ schnell Ergebnisse sieht, ist die Sprache gut dafür geeignet, um einfache Abfragen oder Manipulationen auf Datenbanken durchzuführen.

 

1. DBI und DBD
2. Binär-Pakete
3. Dokumentation
4. Verbindung zur Datenbank
5. Zugriff auf die Daten
6. Ausgabe anpassen
7. Daten aufnehmen
8. Ressourcen

Wie im Artikel "PostgreSQL - Die Mutter der Datenbanken" weiter vorne in diesem Heft erwähnt, wird auf Datenbanksysteme normalerweise nicht direkt zugegriffen, sondern über Applikationen. Diese bieten mehr Komfort für die Benutzer und lassen sich speziell auf die Bedürfnisse dieser anpassen. Lediglich die Administratoren der Datenbanken werden für Wartungsarbeiten den direkten Weg wählen und die Applikation umgehen.

Für einfache oder schnell zu erstellende Übersichten und Reports eignet sich Perl sehr gut. Da Perl eine ordentliche Programmiersprache ist und viele Möglichkeiten für die Verknüpfung von Informationen und Formatierungen bietet, lassen sich ansprechendere Ausgaben erzeugen als mit dem Monitorprogramm der Datenbanken. Damit bietet Perl eine einfache Möglichkeit, zusätzliche Informationen aus Datenbanken zu extrahieren und ansprechend aufzubereiten.

Zum einen wurde für Perl eine saubere Modularisierung der verschiedenen Treiber erreicht, zum anderen gibt es Treiber für die meisten relationalen Datenbanksysteme. Zum Standard SQL ist damit ein weiterer hinzugekommen, der die Arbeit mit verschiedenen Datenbanken erheblich erleichtert (siehe nebenstehende Abbildung).

DBI und DBD

Die Anbindung an Datenbanksysteme geschieht mit Perl über das DBI-Modul und die die DBI-Schnittstelle (DBI steht für DataBase-Interface). Die Architektur ist mehrschichtig und erlaubt daher die einheitliche und parallele Kommunikation mit verschiedenen Datenbanksystemen. Das DBI-Modul selbst stellt nicht die Verbindung zum Datenbanksystem her, sondern beinhaltet lediglich zwei Schnittstellen: Zum einen die Schnittstelle zur Applikation über Perl-Objekte und -Funktionen und zum anderen die Schnittstelle zur nächst tieferen Schicht DBD (DBD steht für DataBase-Driver).

Während DBI lediglich eine Zwischenschicht für den Programmierer darstellt, existiert für jedes unterstütztes Datenbanksystem ein eigenes DBD-Modul. Dieses realisiert die eigentliche Anbindung an das Datenbanksystem. DBD-Module wurden für alle wichtigen Datenbanksysteme implementiert wie z.B. Adabas, IBM DB2, Informix, Ingres, mSQL, MySQL, Oracle, PostgreSQL, Sybase und weitere. Sogar ein allgemeiner Treiber für ODBC-Anbindungen ist vorhanden.

Welches DBD-Modul letztendlich geladen und verwendet wird, entscheidet das DBI-Modul selbständig, wenn eine Verbindung zur Datenbank aufgebaut werden soll. Der Anwender muß lediglich die benötigten Pakete zur Verfügung stellen und beim Verbindungsaufbeu die Datenbank entsprechend spezifizieren. Da der Anwender sich um die DBD-Module nicht kümmern muß, reicht es, wenn das DBI-Modul geladen wird:

   use DBI;

[Datenbankzugriffe]

Abbildung 1: Zugriff auf Datenbanken mit Perl

Binär-Pakete

Für die Datenbank-Schnittstelle DBI wird z.B. für die Debian-Distribution das Paket »libdbi-perl« benötigt. Das DBD-Modul für die Anbindung an PostgreSQL-Datenbanken heißt »libdbd-pg-perl«, für MySQL ist es »libdbd-mysql-perl« und so weiter. Für die kommerziellen Systeme muß der Quellcode des entsprechenden Moduls von einem CPAN-Server (Adresse siehe "Resources") geladen und anschließend selbst kompiliert werden. Die wenigstens Distributionen beinhalten diese.

Ein Perl-Modul wird mit »tar« ausgepackt und mit »make« compiliert. Der gesamte Vorgang samt Installation erfolgt mit den folgenden fünf Befehlen:

   tar xvfz DBD-Modul-Version.tar.gz
   cd DBD-Modul-Version
   perl Makefile.PL
   make
   make install

Mit dem Befehl »perl Makefile.PL« wird die Datei »Makefile« erst generiert, die zum Compilieren des Pakets benötigt wird.

Dokumentation

Alle verwendeten Module sind vernünftig dokumentiert. Mit dem Befehl »perldoc DBI« erhält man die Dokumentation zum DBI-Modul. Da es kein eigenes DBD-Modul gibt, sondern nur DBD::Pg, DBD::Oracle etc. erhält man die Dokumentation zu einem bestimmten DBD-Modul nur mit dem gesamten Namen, also »perldoc DBD::Pg« für die Schnittstelle zu PostgreSQL oder »perldoc DBD::Oracle« für die Schnittstelle zu Oracle. In der Dokumentation zum jeweiligen DBD-Modul sind die für dieses Modul spezifischen Routinen und Formate beschrieben. Sie gelten oft nicht für andere DBD-Module und sollten daher nur in Ausnahmefällen verwendet werden, da die Portabilität sonst verloren geht.

Verbindung zur Datenbank

Bevor auf die Datenbank zugegriffen werden kann, muß die Verbindung zum Datenbank-System hergestellt werden. Dieses geschieht mit der Routine »connect« aus dem DBI-Modul. Die Routine wird üblicherweise durch »DBI->connect()« aufgerufen und unterstützt bis zu vier Parameter. Meistens werden jedoch nur einer oder drei verwendet.

Der erste Parameter von »connect()« beschreibt die Datenbank, mit der das Modul die Verbindung aufbauen soll. Falls angegeben, beschreibt der zweite Parameter den zu verwendenden Benutzernamen und der dritte das zu verwendende Paßwort, um zur Datenbank zu verbinden. Das erste Argument zur Funktion »connect()« muß dabei folgende Angaben beinhalten:

Diese Angaben werden in einer einzigen Zeichenkette zusammengefaßt. Anhand dieses Parameters entscheidet das DBI-Modul, welcher DBD-Treiber für diese Verbindung geladen und vewendet werden soll. Wenn das Datenbanksystem auf dem gleichen Rechner läuft, auf dem auch das Perl-Programm ausgeführt wird, dann müssen weder Rechnername noch TCP-Port angegeben werden.

Der erste Parameter beschreibt die Datenbank als "Data Source Name" (»dsn«). Das Format dieses Parameters ist:

   dbi:datenbanksystem:datenbank[;optionen]

Obwohl der erste Teil »dbi« im Prinzip überflüssig ist, wird er vom DBI-Modul benötigt. Mit der Angabe des Datenbanksystems wird dem DBI-Modul mitgeteilt, welcher Datenbanktreiber bzw. welches DBD-Modul verwendet werden soll. Dieses wird automatisch nachgeladen. Hier wird »Pg« für PostgreSQL, »mysql« für MySQL, »Oracle« für Oracle etc. erwartet. Als »datenbank« wird der Name der zuverwendenden Datenbank im Datenbanksystem angegeben. Im Falle von PostgreSQL wird die Datenbank mit »dbname=name« angegeben.

Die Routine »connect()« gibt ein Datenbank-Handle zurück. In diesem sind alle wichtigen Informationen zur Datenbank gespeichert. Diese werden später benötigt, um SQL-Anweisungen zum Datenbanksystem zu schicken. Wenn keine Verbindung zur Datenbank aufgebaut wurde, wird logisch FALSCH zurückgegeben. Daher kann in einem Programm leicht überprüft werden, ob die Verbindung aufgebaut werden konnte oder nicht. Wenn dieses nicht der Fall war, dann macht es keinen Sinn, später SQL-Anweisungen abzusetzen. Das Programm sollte in dem Fall mit einer Fehlermeldung beendet werden.

Der folgende Auszug verbindet ein Perl-Programm mit einer PostgreSQL-Datenbank, die auf dem gleichen Rechner läuft wie das Perl-Skript. Der Name der Datenbank lautet »meinedb«

   use DBI;

   $dsn = "dbi:Pg:dbname=meinedb";
   $dbh = DBI->connect($dsn);
   if (!$dbh) {
       print "Zugriff verweigert!\n";
       exit (1);
   }

Wenn das Datenbanksystem auf einem anderen Rechner läuft, dann muß die Variable »$dsn« um die benötigten Angaben zum Rechnernamen und Port erweitert werden. Falls die Datenbank auf dem Rechner mit der IP-Nummer 192.168.10.21 läuft und auf den Port 5432 eingestellt wurde, sieht die Anweisung wie folgt aus:

   use DBI;

   $dsn = "dbi:Pg:dbname=meinedb;host=192.168.10.21;port=5432";
   $dbh = DBI->connect($dsn);
   if (!$dbh) {
       print "Zugriff verweigert!\n";
       exit (1);
   }

Wenn der Zugriff auf die Datenbank mit Benutzernamen und Paßwort geschützt ist, müssen diese ebenfalls angegeben werden. Bisher wurde kein Benutzername angegeben, dennoch wurde einer verwendet. Falls nicht anders angegeben, wird das Login des aktuellen Benutzers für die Verbindung zur Datenbank verwendet. Die Anmeldung mit Benutzername und Paßwort sieht wie folgt aus:

   use DBI;

   $dsn = "dbi:Pg:dbname=meinedb;host=192.168.10.21;port=5432";
   $dbh = DBI->connect($dsn, "login", "passwort");
   if (!$dbh) {
       print "Zugriff verweigert!\n";
       exit (1);
   }

Am Ende jeder Sitzung, bzw. wenn der Zugriff auf die Datenbank nicht mehr benötigt wird, sollte die Verbindung offiziell betrennt werden. Das Datenbank-Objekt wird daraufhin den internen Status zurücksetzen und die Netzwerkverbindung zur Datenbank ordentlich beenden.

   $dbh->disconnect if ($dbh);

Zugriff auf die Daten

Der Zugriff auf die eigentlichen Daten in der jeweiligen Datenbank erfolgt nun dreistufig:

  1. »prepare()«

    Mit diesem Befehl wird die SQL-Anweisung an eine interne Datenstruktur zur späteren Weiterbehandlung übergeben. Auf diese Weise können Platzhalter (»?«) verwendet werden. Genaues steht in der DBI-Dokumentation. Zurückgegeben wird ein State-Handle.

  2. »execute()«

    Die SQL-Anweisung, die jetzt mit dem State-Handle verbunden ist, wird an das Datenbanksystem weitergegeben. Zurückgegeben wird ein Return-Value, der mit dem Ergebnis der Anweisung verbunden ist.

  3. »fetch()«

    Tatsächlich handelt es sich hierbei um drei Funktionen: »fetchrow_array()«, »fetchrow_arrayref()« bzw. »fetchrow_hashref()«. Dieser Artikel beschränkt sich auf den ersten dieser Funktionen. Es wird jeweils eine gesamte Ergebniszeile zurückgeliefert. Diese Routine muß daher mehrfach wiederholt werden, am besten in einer Schleife, bis das Ergebnis logisch FALSCH ergibt.

[SQL-Abfragen]

Abbildung 2: DBI-Programmierung

Die SQL-Anweisung, die an das Datenbanksystem übertragen werden soll, ist genau die gleiche Anweisung, die auch im Monitor-Programm (für PostgreSQL ist das »psql«) eingegeben werden kann. Dort wird sie jedoch mit einem Semikolon oder »\g« abgeschlossen, das geschieht im DBI-Modul durch die Funktion »execute()«. Ein Semikolon ist hier nur dann erforderlich, wenn mehrere Anweisungen hintereinander geschrieben werden. Doch zunächst wird die SQL-Anweisung von der »prepare()«-Funktion vorbereitet.

Um eine Liste der Personen und Städte zu erhalten, wird folgende Anweisung in das Perl-Skript geschrieben:

   $query = "SELECT Vorname,Nachname,PLZ,Stadt FROM Adressen ORDER BY Vorname,Nachname";
   $sth = $dbh->prepare($query);

Als nächstes wird der SQL-Befehl an das Datenbanksystem übertragen und ausgeführt.

   $rv = $sth->execute;

Wenn das geklappt hat, wird ein Return-Value größer oder gleich 0 zurückgegeben. Dieser Wert entspricht der Anzahl Zeilen im Ergebnis der Anweisung. Dieser Wert wird anschließend überprüft. Ist er nicht größer als 0, dann gab es entweder ein Problem oder die Anweisung hat eine leere Antwort als Ergebnis. Eine leere Antwort ist dann legitim, wenn die SQL-Anweisung aus »UPDATE«-, »INSERT«- oder »DELETE«-Befehlen besteht oder wenn die »SELECT«-Anfrage keine Datensätze gefunden hat, die der angegebenen »WHERE«-Klausel entsprechen.

Da mit dem Ergebnis einer SQL-Anweisung nur dann weitergearbeitet werden kann, wenn es auch tatsächlich ein Ergebnis gibt, muß eine weitere Abfrage programmiert werden.

   if ($rv <= 0) {
       print "Keine Ergebniswerte!\n";
       exit (1);
   }

Erst wenn das Programm bis nach dieser Abfrage gekommen ist und in keiner bisherigen »if«-Verzweigung beendet wurde, darf das Ergebnis zeilenweise angefordert und verarbeitet werden.

In diesem Beispiel sollen die Zeilen einfach wieder ausgegeben werden, jedoch hübscher formatiert als das Monitor-Programm »psql« es könnte. Vor- und Nachnamen sollen zu einem Feld zusammengezogen werden und die Postleitzahl soll mit dem Städtenamen zusammengezogen werden.

Mit dem Befehl

   @row = $sth->fetchrow_array;

wird dem Array »@row« eine gesamte Zeile aus dem Ergebnis zugewiesen. Die Werte der einzelnen Felder werden dabei der Reihe nach den Elementen des Arrays zugewiesen. Die Reihenfolge entspricht genau der Reihenfolge der Felder in der »SELECT«-Anweisung (siehe oben). Die einzelnen Elemente des Arrays werden wie in den bisherigen Folgen der Perl-Serie mit »$row[nr]« referenziert.

Die Schleife soll solange ausgeführt werden, bis »fetchrow_array()« ein leeres Array zurückgibt. Erst dann wurden alle Zeilen des Ergebnisses gelesen. Die Schleife sieht somit wie folgt aus:

   while (@row = $sth->fetchrow_array) {
       printf " %-40s %d %s\n", $row[0] . " " . $row[1], $row[2], $row[3];
   }

Die »printf«-Anweisung formatiert die Zeilen ansprechend. Es werden keine unnützen senkrechte Striche ausgegeben. Hier fällt jedoch ein Problem auf. Wenn keine Postleitzahl bekannt ist, wird »0« ausgegeben, was definitiv falsch ist. Wenn keine PLZ angegeben wurde, soll auch keine ausgegeben werden. Um das zu erreichen, bedient man sich gerne eines Perl-Tricks. Da die Typen von Variablen automatisch von Perl konvertiert werden, können Variablen sowohl als Zahlen als auch als Zeichenketten verstanden werden. Wenn die PLZ nicht angegeben wurde, entspricht sie der leeren Zeichenkette. Daher liefern die folgende Zeilen das entsprechende Ergebnis, solange kein Städtename angegeben wurde. Code-Fragment

   while (@row = $sth->fetchrow_array) {
     printf " %-40s %s\n", $row[0] . " " . $row[1], $row[2] . " " . $row[3];
   }

Häufig wird aufgrund zum einfacheren Verständnis die Methode »fetchrow_hashref« verwendet, die anstelle des Arrays von oben eine Referenz auf ein Hash bzw. assoziatives Array zurückliefert. Die Indexwerte entsprechen dabei den Namen der beteiligeten Spalten. Der Quellcode läßt sich dadurch später erheblich leichter wieder verstehen, da auf Anhieb klar ist, welche Spalten wo verarbeitet werden. Das obige Beispiel sieht umgeschrieben dann wie folgt aus:

   while ($row = $sth->fetchrow_hashref) {
     printf " %-40s %s\n", $row->{'Vorname'} . " " . $row->{'Nachname'},
         $row->{'PLZ'} . " " . $row->{'Stadt'};
   }

Wenn das Ergebnis einer »SELECT«-Anfrage mehrere Zeilen enthält, das Programm jedoch nur den ersten Datensatz auswertet, bemängelt der Datenbank-Treiber diesen Umstand mit einer Bemerkung über die noch bereitstehenden Informationen. In solchen Fällen wird dem State-Handle mitgeteilt, daß die noch bereitstehenden Informationen nicht mehr benötigt werden. Das geschieht mit der speziellen Methode »finish«.

   $sth->finish;

Wenn beim Programmieren bereits feststeht, daß nur eine bestimmte Anzahl an Zeilen gelesen werden, sollte das der Datenbank direkt mitgeteilt werden. In der Sprache SQL ist dafür extra der Appendix »LIMIT n« vorgesehen, der an eine Abfrage angehängt wird. Darüberhinaus läßt sich zudem der Offset mit »OFFSET m« angeben.

Ausgabe anpassen

Sind in der Datenbank jedoch auch Datensätze enthalten, die einen Städtenamen, jedoch keine Postleitzahl enthalten, dann muß die »printf«-Zeile geändert werden. Bisher wird immer zwischen dem dritten und dem vierten Element des Arrays »@row« ein Leerzeichen eingefügt. Dieses Leerzeichen ist nun Stein des Anstoßes, denn dadurch wird ein Leerzeichen vor den Städtenamen eingefügt, auch wenn keine Postleitzal angegeben wurde (siehe Abbildung).

Gesucht wird daher eine Möglichkeit, das Leerzeichen nur dann einzufügen, wenn eine Postleitzahl vorhanden ist. Dazu wird sich einer verkürzten Schreibweise des »if-then-else«-Konstrukts bedient. Anstatt eine Funktion zu schreiben, die nur eine Verzweigung enthält und abhängig von der Bedingung einen Text zurückliefert, kann man in Perl auch eine kleine einzeilige Anweisung schreiben. Statt

   if (foo) {
     return "1";
   } else {
     return "2";
   }

formuliert man übrigens in Perl (und in C übrigens auch) gerne mit dem ternären Operator

   (foo?"1":"2")

Der Programmcode wird dadurch erheblich kompakter und kann auch leichter zu verstehen sein. Dieser Operator bietet sich immer dann an, wenn eine Verzweigung innerhalb einer Funktion oder Berechnung benötigt wird und die einzelnen Zweige sehr kurz sind und den gleichen Ergebnistyp haben.

Vor dem Fragezeichen steht die Bedingung wie bei einer normalen Verzweigung. Nach dem Fragezeichen werden zwei Angaben erwartet: der Then-Zweig und der Else-Zweig. Sie werden durch einen Doppelpunkt voneinander getrennt. Beide Zweige sollten vom gleichen Typ sein, also in beiden Fällen Zeichenketten, Ganzzahlen, Arrays o.ä. zurückgeben.

In diesem Fall wird eine Verzweigung benötigt, die ein Leerzeichen ausgibt, wenn eine Postleitzahl vorhanden ist, und nichts sonst. Wenn eine Postleitzahl angegeben ist, ist die Länge der entsprechenden Zeichenkette größer als 0. Damit muß folgender Code eingebaut werden:

   (length($row[2])>0?" ":"")

Wenn die Länge der Postleitzahl kleiner oder gleich 0 ist, wird die leere Zeichenkette »""« zurückgegeben und zwischen die beiden Zeichenketten »$row[2]« und »$row[3]« gesetzt. Zusammen sieht die Schleife nun wie folgt aus:

   while (@row = $sth->fetchrow_array()) {
     printf " %-40s %s\n", $row[0] . " " . $row[1], $row[2]
        . (length($row[2])>0?" ":"") . $row[3];
   }

Damit erhält man folgendes Perl-Programm, das alle Namen und Städte, sortiert nach Vorname und Nachname, ausgibt.


   use DBI;

   $dsn = "dbi:Pg:dbname=joey";
   $dbh = DBI->connect($dsn);
   if (!$dbh) {
       print "Zugriff verweigert!\n";
       exit (1);
   }

   $query  = "SELECT Vorname,Nachname,PLZ,Stadt FROM Adressen ORDER BY Vorname,Nachname";
   $sth = $dbh->prepare($query);

   $rv = $sth->execute;

   if ($rv <= 0) {
       print "Keine Ergebniswerte!\n";
       exit (1);
   }

   print " Name                                     Ort\n";
   print "------------------------------------------------------------------\n";
   while (@row = $sth->fetchrow_array) {
     printf " %-40s %s\n", $row[0] . " " . $row[1], $row[2]
        . (length($row[2])>0?" ":"") . $row[3];
   }

   $dbh->disconnect;

Daten aufnehmen

Der schreibende Zugriff auf eine Datenbank erfolgt genauso. Ein Perl-Programm kann auf die gleiche Weise Daten in die Datenbank einspielen oder modifizieren. Sollen z.B. weitgehende Modifikationen durchgeführt oder zusätzliche Informationen aufgenommen werden, so können sie mit Perl berechnet, zusammengestellt und anschließend an die Datenbank übergeben werden.

Da Perl Eingaben von der Tastatur lesen kann, werden auf diese Weise sehr einfache Eingabe-Programme für Datenbanken realisiert. Die Eingabe wird mit folgender Anweisung gelesen:

   $input = ;

Da so immer auch ein Zeilenumbruch gelesen wird, muß dieser anschließend wieder abgeschnitten werden, was mit der Funktion »chop()« geschieht:

   chop ($input);

Es ist jetzt ein leichtes, z.B. zuerst die eingelesene Variable zu untersuchen und Aktionen auszulösen anstatt sie direkt in die Datenbank aufzunehmen. So läßt sich leicht implementieren, daß bei der Eingabe eines Fragezeichens »?« eine Abfrage auf der Datenbank durchgeführt wird und alle möglichen oder bisher eingetragenen Werte eines Feldes angezeigt werden.

Durch Verknüpfung mit dem GTK-Modul oder anderen Modulen mit Bindungen an ein grafisches Widget-Set lassen sich auf diese Weise zudem schnell ansprechende grafische Benutzerschnittstellen realisieren.

Ressourcen

Martin Schulze
Quelle: freeX 2/01