# MySQL und Perl

There cannot be a crisis next week. My schedule is already full.
Henry Kissinger, US-Außenminister 1973–1977

Obwohl es mittlerweile eher selten vorkommt, dass Perl im Zusammenhang mit MySQL verwendet wird, ist es dennoch möglich. Beliebter ist allerdings die Verwendung von PHP.

# Vorbereitungen

Eine Voraussetzung für den Aufbau einer Verbindung mit MySQL und das Senden von Anfragen an den Server ist, dass zwei bestimmte Perl-Module installiert sein müssen. Diese Module nennen sich DBI (Database Interface) und DBD-MySQL (Database Driver MySQL). Die Installation der beiden Module erfolgt über den PPM (Perl Package Manager). Sowohl unter Windows als auch unter Linux genügt es, eine Konsole zu öffnen und dort ppm und Enter einzugeben. Sie erhalten dann folgende Ausgabe:

PPM – Programmer's Package Manager version 3.0.1.  
Copyright (c) 2001 ActiveState SRL. All Rights Reserved.  
Entering interactive shell. Using Term::ReadLine::Stub as readline   
library.  
Profile tracking is not enabled. If you save and restore profiles   
manually, your profile may be out of sync with your computer.   
See 'help profile' for more information.  
Type 'help' to get started.  
ppm>

Nun sollten Sie zuerst nach der Datenbank-Schnittstelle DBI im Repository suchen. Geben Sie Folgendes ein:

ppm> search DBI

Daraufhin müssten Sie die folgende Ausgabe erhalten (oder eine ähnliche):

Searching in Active Repositories  
 ...  
   4. DBI [1.30] Database independent interface for Perl  
 ...

Nach der Eingabe von

ppm> install dbi

wird der Installationsvorgang gestartet. Dabei wird das Modul zuerst heruntergeladen und anschließend installiert.

Die gleiche Vorgehensweise gilt auch für das DBD-Mysql-Modul: zuerst suchen und dann den korrekten Namen bei der Installation angeben.

# Datenbankverbindung herstellen

Das Modul DBI stellt eine allgemein gültige Schnittstelle zwischen Perl und Datenbanken dar. Einige werden sicherlich die ODBC-Schnittstelle unter Windows kennen, die in etwa die gleiche Aufgabe erledigt. Beide sollen – vorzugsweise in einer einheitlichen Syntax – den Zugriff auf alle möglichen Datenbanksysteme regeln, so dass bei einem Wechsel des Datenbanksystems nach Möglichkeit nur der Treiber für das Datenbanksystem geändert werden muss.

Ohne einige Anweisungen, auf welche Art und Weise DBI mit dem Datenbanksystem kommunizieren muss, werden Sie leider nicht weit kommen. Deshalb muss also für jedes Datenbanksystem ein spezieller Treiber installiert werden. Für Perl und DBI sind dies die DBD-Treiber und – speziell zugeschnitten auf MySQL – der DBD::Mysql-Treiber.

Zuallererst müssen Sie natürlich das entsprechende Modul in Ihr Perl-Script einbinden. Dies erfolgt über die Anweisung

use DBI;

Das DBD-Modul muss an dieser Stelle nicht eingebunden werden, da sich das DBI-Modul selbstständig darum kümmert und lediglich die Angabe erwartet, welches Modul verwendet werden soll.

Der eigentliche Verbindungsaufbau erfolgt dann über die connect-Methode des DBI-Objekts, das drei Parameter erwartet.

resource DBI->connect(string datasource, string uid, string pwd [, hash attr])

Der Parameter datasource ist eine Zeichenkette, die den zu verwendenden Treiber und die Datenbank in einer bestimmten Schreibweise sowie den Hostnamen oder die IP-Adresse des Rechners erwartet, auf dem das Datenbanksystem läuft. Da der Parameter sehr lang werden kann, hat es sich eingebürgert, dafür einen extra Skalar zu definieren und diesen dann an die connect-Methode zu übergeben. Für einen Zugriff auf ein MySQL-Datenbanksystem, das auf dem lokalen Rechner läuft, und die Datenbank bspbuecher lautet die datasource-Zeichenkette:

$datasource = "DBI:mysql:database=bspbuecher;host=localhost";

Auch für den Benutzernamen, der im Parameter uid übergeben wird, und das Passwort, das im Parameter pwd übergeben wird, sollten Sie extra Skalare definieren. Dies hat den Vorteil, dass Sie nur an einer Stelle im Script die Daten ändern und der connect-Methode immer nur die Skalare übergeben müssen.

$uid = "user";  
$pwd = "password";

Diese Daten sind natürlich von der Konfiguration Ihres Systems abhängig und sollten durch die entsprechenden korrekten Daten ersetzt werden.

Zusätzlich zu den drei eben angesprochenen Parametern gibt es noch einen vierten optionalen Parameter. Dieser vierte Parameter enthält einen Hash mit verschiedenen Attributen, die angeben, wie sich das Script verhalten soll, wenn es während der Kommunikation mit der Datenbank zu Fehlern kommt.

Attribut Werte Erklärung
RaiseError 0 oder 1 Gibt an, ob das Programm beim Auftreten eines Fehlers abbrechen oder weiterlaufen soll. Wenn es abbrechen soll, lautet der Wert 1, andernfalls 0.
PrintError 0 oder 1 Gibt an, ob Fehlermeldungen ausgegeben werden sollen. 0 bedeutet nein, 1 bedeutet ja.
LongReadLen n Byte Gibt die maximale Länge der zu lesenden Datenfelder in n Byte an. Der Wert 0 deaktiviert das Attribut.
LongTruncOK 0 oder 1 Gibt an, ob zu lange Datenfelder abgeschnitten werden sollen. Wenn ja, muss der Wert 1 lauten, wenn nein, 0.

Tabelle 4.1: Mögliche Attribute für den Verbindungsaufbau

Um während des Programmierens die Fehler besser finden zu können, sollten Sie das Programm weiterlaufen lassen, wenn es auf einen Fehler trifft, und eine entsprechende Ausgabe erzeugen lassen.

%attributes = {'RaiseError'=>1,'PrintError'=>1};

Sobald die Verbindung zur Datenbank erfolgreich aufgebaut werden konnte, liefert die connect-Methode ein Zugriffshandle auf die Datenbank zurück. Für dieses Handle hat sich in Perl die Bezeichnung $dbh (database handle) eingebürgert.

Alles in allem sieht ein Script, das eine Verbindung zu einer Datenbank aufbauen soll, mindestens folgendermaßen aus:

use DBI;  
$datasource =   
  "DBI:mysql:database=bspbuecher;host=localhost";  
$uid = "user";  
$pwd = "password";  
%attributes = {'RaiseError'=>1,'PrintError'=>1};  
$dbh = DBI->connect($datasource,$uid,$pwd,%attributes);

Dies ist natürlich nur ein sehr rudimentäres Beispiel. So sollten Sie natürlich noch die Hashbang notieren und gegebenenfalls noch das CGI-Modul einbinden, damit Aufrufe über einen Webserver und Ausgaben im Browser erfolgen können.

Natürlich können Sie auch eine Verbindung wieder beenden. Dafür genügt der Aufruf der Methode disconnect des Datenbankhandles (ja, das Handle ist ein Objekt).

$dbh->disconnect();

# Anfragen stellen

Bevor Sie mit Perl eine Anfrage an eine MySQL-Datenbank stellen können, müssen Sie überprüfen, welche Art von Anfrage Sie stellen möchten. Dabei werden zwei Arten unterschieden:

  • Anfragen, die eine Ergebnismenge liefern, z. B. SELECT
  • Anfragen, die keine Ergebnismenge liefern, z. B. DELETE, INSERT etc.

Je nach eingesetzter Art müssen die SQL-Anweisungen mit einer unterschiedlichen Methode ausgeführt werden.

Alle Anfragen, die keine Ergebnismenge liefern, sollten Sie mittels der Methode $dbh->do ausführen. Zu diesen Anfragen gehören z. B. DELETE- oder INSERT-Anweisungen, die zwar Datensätze verändern, aber allerhöchstens die Anzahl der veränderten Datensätze liefern.

mixed $dbh->do(string sqlquery)

Die Methode do erwartet lediglich einen Parameter, und zwar die SQL-Anfrage in Form einer Zeichenkette. Je nachdem wie die Anfrage verlief, liefert die Methode dann einen von vier Werten zurück:

Wert Typ Erklärung
"0E0" String Dieser Wert wird zurückgegeben, wenn kein Datensatz verändert wurde. Mit der Anweisung $result+=0; kann der Wert in eine Zahl umgewandelt werden.
–1 Integer Dieser Wert wird zurückgegeben, wenn die Anzahl der veränderten Datensätze nicht ermittelt werden konnte. Die Anfrage wurde aber trotzdem erfolgreich ausgeführt.
undef Undefiniert Dieser Wert wird immer dann zurückgegeben, wenn die Anfrage nicht erfolgreich ausgeführt werden konnte.
n Integer Eine beliebige Zahl, die größer als 0 ist, steht für die Anzahl der durch die Anfrage betroffenen Datensätze.

Tabelle 4.2: Mögliche Rückgabewerte der Methode do

Ein Beispiel:

$result = $dbh->do("INSERT INTO bspbuecher (autor,titel,seiten,isbn,preis,verlag) VALUES ('Christian Wenz','JavaScript',624,'3–89842–234–8',39.90,1)");

Wurde der Datensatz erfolgreich hinzugefügt, würde die Methode nun den Wert 1 zurückgeben, wenn nur ein Datensatz eingefügt wurde. Da MySQL jedoch die Möglichkeit bietet, mehrere Datensätze in einem Rutsch einzufügen, würde der Rückgabewert der Anzahl der erfolgreich hinzugefügten Datensätze entsprechen. Wurde kein Datensatz eingefügt, lautet der Rückgabewert –1. Im Fall eines Fehlers, z. B. wenn die Anfrage falsch formuliert wurde, liefert die Methode einen undefinierten Wert zurück.

In MySQL ist es möglich, die ID des Datensatzes auszulesen, der hinzugefügt wurde. Dies ist jedoch nur bei Tabellen realisierbar, die eine Spalte besitzen, deren Optionen auf PRIMARY KEY und AUTO_INCREMENT gesetzt wurden. Der automatisch erzeugte Wert wird in einer Hashliste des dbh-Objekts hinterlegt, dessen Schlüsselname mysql_insertid lautet.

$ds_id = $dbh->{'mysql_insertid'};

Der Skalar $ds_id enthält nun den automatisch erzeugten Wert (in der Regel die ID) des hinzugefügten Datensatzes – dies aber nur dann, wenn wirklich ein Datensatz hinzugefügt wurde. Wichtig ist auch, dass nur ein einzelner Datensatz eingefügt werden darf, damit dies auch funktioniert. Wurde kein automatischer Wert erzeugt, lautet der Rückgabewert –1.

Alle Anfragen, die eine Ergebnismenge liefern (hauptsächlich die SELECT-Anweisungen), müssen mit den kombinierten Methoden prepare und execute ausgeführt werden.

Die prepare-Methode dient dazu, der Datenbank die Möglichkeit zu lassen, vor dem Ausführen einer SQL-Anweisung, die eine Ergebnismenge liefert, gewisse Vorbereitungen zu treffen. Obwohl MySQL im Gegensatz zu anderen Datenbanken keine Vorbereitungen treffen muss und auch nicht trifft, müssen Sie diese Methode vorher immer aufrufen, da die Methode ein so genanntes Statement-Handle zurückgibt. Es ist ein Objekt und wird normalerweise mit $sth bezeichnet.

object dbh->prepare(string sqlquery)

Die auszuführende SQL-Anweisung wird als Zeichenkette an die Methode prepare des dbh-Objekts übergeben. Konnte die Anweisung nicht erfolgreich vorbereitet werden, weil sie z. B. falsch formuliert ist oder Datenfelder oder Tabellen, auf die zugegriffen werden soll, nicht existieren, liefert die Methode einen undefinierten Wert zurück.

$sth = $dbh->prepare('SELECT * FROM bspbuecher');

Um nun die Anweisung ausführen zu können, müssen Sie die Methode execute des sth-Objekts aufrufen.

void sth->execute(void)

Weder erwartet diese Methode einen Parameter noch gibt sie irgendeinen Wert zurück. Sie dient lediglich dazu, die Anweisung auszuführen und MySQL anzuweisen, die Ergebnismenge bereitzustellen. Da das Objekt nur dann erzeugt wird, wenn die Anweisung erfolgreich ausgeführt worden ist, müssen Sie auch keinen Rückgabewert überprüfen.

$sth->execute();

Sobald Sie die Ergebnismenge ausgelesen und verarbeitet haben, müssen Sie die Methode finish aufrufen, um den belegten Speicher frei zu räumen und das sth-Objekt wieder zu löschen, damit Sie die nächste SQL-Anweisung ausführen können.

$sth->finish();

Alles zusammen könnte dann so aussehen:

use DBI;  
$datasource =   
  "DBI:mysql:database=bspbuecher;host=localhost";  
$uid = "user";  
$pwd = "password";  
%attributes = {'RaiseError'=>1,'PrintError'=>1};  
$dbh = DBI->connect($datasource,$uid,$pwd,%attributes);  
if(defined($dbh))  
{  
  $sth = $dbh->prepare('SELECT * FROM bspbuecher');  
  if(defined($sth))  
  {  
    $sth->execute();  
    # Ergebnisverarbeitung  
    $sth->finish();  
  }  
}  
$dbh->disconnect();

# Ergebnisverarbeitung bei SELECT-Anweisungen

Wie zuvor erwähnt wurde, müssen Sie nach dem Ausführen der Methode execute des Statement-Handles die Ergebnismenge verarbeiten. Auch hierfür gibt es verschiedene Methoden.

Sehr wichtig ist in diesem Zusammenhang die Methode rows des Statement-Handles. Diese Methode liefert die Anzahl der Datensätze der Ergebnismenge bzw. der Ergebnistabelle (Ergebnismengen werden intern als Tabelle organisiert).

Obwohl diese Methode eigentlich nicht nach SELECT-Abfragen verwendet werden soll, ist dies trotzdem möglich. Es gibt ansonsten keine andere Möglichkeit, im Voraus die Anzahl der Datensätze der Ergebnismenge zu ermitteln, ohne alle Datensätze auslesen zu müssen.

$count = $sth->rows();

Je nachdem, wie viele Datensätze in der Ergebnismenge existieren, wird in $count dann die entsprechende Anzahl gespeichert.

Achten Sie darauf, rows direkt nach execute aufzurufen, andernfalls erhalten Sie ein verfälschtes Ergebnis.

# ---[SNIP]---  
$sth = $dbh->prepare('SELECT * FROM bspbuecher');  
if(defined($sth))  
{  
  $sth->execute();  
  $count = $sth->rows();  
  print "Die Anfrage liefert $count Datensätze!";  
  $sth->finish();  
}  
# ---[SNAP]---

# Datensätze lesen

Der nächste Schritt sollte nun sein, die Datensätze auszulesen. Wie genau Sie das machen, hängt von Ihrem Gusto ab. Ich werde Ihnen an dieser Stelle eine der praktikabelsten Varianten zeigen, da diese unabhängig von der abgefragten Tabelle immer funktioniert und anwendbar ist.

Um einen Datensatz auf eine flexible Art und Weise auszulesen, wäre es am einfachsten, den Datensatz als Liste bzw. Array zu erhalten. Dafür können Sie die Methode fetch bzw. den Alias fetchrow_array verwenden. Die Methode gibt bei ihrem erstmaligen Aufruf den ersten Datensatz als Liste zurück. Nach jedem Aufruf setzt sie den internen Zeiger auf den nächsten Datensatz und gibt beim nächsten Aufruf diesen Datensatz zurück – so lange, bis ein Fehler auftritt oder alle Datensätze ausgelesen worden ist.

array sth->fetch(void)

Die Methode erwartet bei ihrem Aufruf keinerlei Parameter.

# ---[SNIP]---  
$sth = $dbh->prepare('SELECT * FROM bspbuecher');  
if(defined($sth))  
{  
  $sth->execute();  
  $count = $sth->rows();  
  print "Die Anfrage liefert $count Datensätze!\n";  
  print "<table>\n";  
  while(@row = $sth->fetch())  
  {  
    print "<tr>";  
    foreach $field (@row)  
    {  
      print "<td>$field</td>";  
    }  
    print "</tr>";  
  }  
  print "</table>";  
  $sth->finish();  
}  
# ---[SNAP]---

Dieses Beispiel würde alle Datensätze nacheinander als ganze Zeile in einer HTML-Tabelle ausgeben.

Anstatt das Feld eines Datensatzes, das in MySQL den Wert NULL besitzt, auch entsprechend zurückzugeben, gibt die Methode fetch den Wert als undefiniert zurück. Vor der Ausgabe sollten Sie also das gerade aktuelle Element daraufhin überprüfen, ob es einen Wert besitzt oder undefiniert ist.

# ---[SNIP]---  
while(@row = $sth->fetch())  
{  
  print "<tr>";  
  foreach $field (@row)  
  {  
    if(defined($field))  
    {  
      print "<td>$field</td>";  
    }  
    else  
    {  
      print "<td>NULL</td>";  
    }  
  }  
  print "</tr>";  
}  
print "</table>";  
# ---[SNAP]---

Alternativ können Sie die einzelnen Elemente der Liste mit $row[0], $row[1] usw. ansprechen. Dies setzt jedoch entweder voraus, dass Sie die genaue Anzahl der Spalten der Ergebnismenge kennen, oder aber eine for-Schleife, die von 0 bis zum letzten Element durchzählt. Besser, und vor allem einfacher ist es also, die Variante mit foreach zu verwenden.

Sollte die Ergebnismenge nur über einen einzigen Wert verfügen (d. h. eine Spalte und eine Zeile), wäre es mehr als unsinnig, diesen Wert mit ineinander verschachtelten Schleifen zu lesen und auszugeben. Ein solches Ergebnis könnte z. B. die SQL-Anweisung sein:

SELECT COUNT(*) FROM bspbuecher

Einfacher wäre dann folgende Variante:

# ---[SNIP]---  
$sth = $dbh->prepare('SELECT COUNT(*) FROM bspbuecher');  
if(defined($sth))  
{  
  $sth->execute();  
  $count = $sth->fetch();  
  print "Die Tabelle bspbuecher enthält $count Datensätze.";  
  $sth->finish();  
}  
# ---[SNAP]---

Dies ist übrigens ebenfalls eine Möglichkeit, die Anzahl der Datensätze einer SQL-Anweisung zu ermitteln. Dabei muss einfach die eigentliche SQL-Anweisung um die SQL-Funktion COUNT erweitert werden. Das Ergebnis der Anfrage enthält dann die korrekte Anzahl der Datensätze, die in der folgenden Anweisung dann abgefragt werden.

# ---[SNIP]---  
$sth = $dbh->prepare('SELECT COUNT(*) FROM bspbuecher');  
if(defined($sth))  
{  
  $sth->execute();  
  $count = $sth->fetch();  
  print "Die Tabelle bspbuecher enthält $count  
         Datensätze.";  
  $sth->finish();  
}  
$sth = $dbh->prepare('SELECT * FROM bspbuecher');  
if(defined($sth))  
{  
  $sth->execute();  
  print "<table>\n";  
  while(@row = $sth->fetch())  
  {  
    print "<tr>";  
    foreach $field (@row)  
    {  
      if(defined($field))  
      {  
        print "<td>$field</td>";  
      }  
      else  
      {  
        print "<td>NULL</td>";  
      }  
    }  
    print "</tr>";  
  }  
  print "</table>";  
  $sth->finish();  
}  
# ---[SNAP]---

Leider verlängert diese Art der Zählung der Datensätze den Quelltext enorm.

# Ergebnisverarbeitung bei Anweisungen, die keine Ergebnismenge liefern

Es gibt nicht viele Möglichkeiten, um die Datensätze einer Ergebnismenge auszulesen. Bei Anfragen, die keine Ergebnismenge liefern, sieht dies noch dünner aus. Ab und zu ist es jedoch auch bei solchen Anfragen wichtig herauszubekommen, was sich denn nun eigentlich geändert hat.

Ein Verfahren habe ich schon weiter vorn beschrieben: das Ermitteln des AUTO_INCREMENT-Wertes. Vorab sollte gesagt werden, dass die nachfolgend beschriebene Methode nur unter MySQL funktioniert und nicht auf andere Datenbanksysteme übertragen werden kann.

Wenn Sie in einer Tabelle eine Spalte definiert haben, die die beiden Optionen PRIMARY KEY und AUTO_INCREMENT zugewiesen bekommen hat, dann möchten Sie in den seltensten Fällen von Hand einen Wert für diese Spalte angeben. Im Gegenteil, denn die Kombination der beiden Optionen bedeutet so viel wie »automatisch generierter einmaliger Schlüssel«. Bei INSERT-Anweisungen werden Sie alle Felder bis auf das besagte Schlüsselfeld angeben. Um nun trotzdem den automatisch zugewiesenen Schlüssel ermitteln zu können, hält das dbh-Objekt die Eigenschaft mysql_insertid bereit.

Diesen Wert benötigen Sie dann, wenn Sie in einer anderen Tabelle eine Relation zu einem Datensatz einfügen möchten, oder um eine Meldung auszugeben, die die ID des neuen Datensatzes enthält.

# ---[SNIP]---  
$sql = "INSERT INTO bspbuecher (autor,titel,seiten,isbn,preis,verlag) VALUES ('Friedrich Esser','Java 2',656,'3–934358–66–7',34.90,1)";  
$n = $dbh->do($sql);  
if(defined($n))  
{  
  $ds_id = $dbh->{'mysql_insertid'};  
  print "Die ID des neuen Datensatzes lautet $ds_id.\n";  
}  
# ---[SNAP]---

Dieses Beispiel lässt sich natürlich noch verfeinern, demonstriert jedoch die Funktionsweise ganz gut.

Obwohl INSERT-, UPDATE- oder DELETE-Anweisungen keine wirkliche Ergebnismenge liefern, beeinflussen sie dennoch die Datensätze einer Tabelle. In vielen Fällen möchte man nun wissen, wie viele Datensätze effektiv von der ausgeführten Anweisung tangiert wurden.

Das Einzige, was Sie noch tun müssen, ist, den Rückgabewert der do-Methode auszuwerten. Da der Rückgabewert jedoch einer von drei verschiedenen Variablentypen sein kann, müssen Sie zunächst ein paar Schritte unternehmen. Zuerst müssen Sie überprüfen, ob der Rückwert unter Umständen undefiniert ist, und wenn nicht, müssen Sie die zurückgegebene Zahl auswerten. Das nachfolgende Beispiel ist so geschrieben, dass es ein Höchstmaß an Kompatibilität aufweist.

# ---[SNIP]---  
$sql = "INSERT INTO bspbuecher (autor,titel,seiten,isbn,preis,verlag) VALUES ('Friedrich Esser','Java 2',656,'3–934358–66–7',34.90,1)";  
$n = $dbh->do($sql);  
if(defined($n))  
{  
  $n+=0;  
  if($n == -1)  
  {  
    print "Es ist <b>unbekannt</b>, wie viele Datensätze  
           verändert wurden!";  
  }  
  else  
  {  
    print "Es wurden <b>$n</b> Datensätze verändert!";  
  }  
}  
else  
{  
  print "Während der Ausführung der Anweisung ist ein  
         Fehler aufgetreten!";  
}  
# ---[SNAP]---

Obwohl der Skalar $n sowohl eine Zeichenkette als auch einen Zahlenwert enthalten kann, aber auch undefiniert sein kann, funktioniert dieses Beispiel immer. Der Grund ist, dass zuerst überprüft wird, ob die Variable undefiniert ist. Wenn ja, ist ein Fehler aufgetreten und der else-Anweisungsblock wird ausgeführt, der eine entsprechende Meldung ausgibt. Innerhalb des if-Anweisungsblocks wird $n um den Wert 0 erhöht. Der Trick ist dabei folgender: Wenn $n die Zeichenkette 0E0 enthält, wird durch die arithmetische Operation versucht, die Zeichenkette in eine Zahl umzuwandeln. Da der Buchstabe E keine Zahl ist, wird nach dem ersten Zeichen abgebrochen, und $n trägt nach der Umwandlung den Wert 0. Da 0 + 0 = 0 ist, würde $n nun die korrekte Zahl der veränderten Datensätze enthalten. Sollte der Wert von $n jedoch –1 oder größer als 0 sein, wird einfach nur 0 hinzuaddiert, wodurch sich der eigentliche Wert nicht ändert.

In Tabelle 4.2 weiter oben finden Sie die möglichen Rückgabewerte der Methode do noch einmal übersichtlich dargestellt und erklärt.

# Datentypen

Da Perl und MySQL Datentypen wie Zeichenketten und Datums- und Zeitangaben unterschiedlich behandeln, sollte auf eine Reihe von Dingen geachtet werden.

# Zeichenketten

Zeichenketten in MySQL werden in einfachen Anführungsstrichen notiert. Doppelte Anführungszeichen, wie sie z. B. in Perl oder gar PHP erlaubt sind, können Sie in MySQL nicht verwenden. Ihnen bleibt bei der Zusammenstellung einer SQL-Anweisung also nur die Möglichkeit, die Zeichenkette in Perl mittels doppelter Anführungszeichen zu definieren, damit Sie innerhalb der Zeichenkette einfache Anführungszeichen verwenden können, um eine Zeichenkette markieren zu können. Der Vorteil ist, dass Sie dabei auch die automatische Auflösung der Variablennamen verwenden können.

$name = 'Galileo Press';  
$sql = "SELECT * FROM bspverlage WHERE name = '$name'";

Dieses Beispiel wird Ihnen keine Probleme bereiten, da die eigentliche SQL-Anweisung in doppelten Anführungszeichen notiert wurde. Hätten Sie einfache Anführungszeichen verwendet, müssten Sie diese innerhalb der Zeichenkette entwerten. Zudem würde dann die automatische Auflösung der Variablennamen nicht zur Verfügung stehen. Umgeschrieben würde dies folgendermaßen aussehen:

$name = 'Galileo Press';  
$sql = 'SELECT * FROM bspverlage WHERE name = \\''.$name.'\\'';

Dies sieht natürlich wesentlich unübersichtlicher als die vorherige Methode aus. Beide Versionen haben jedoch einen Nachteil, wie das nachfolgende Beispiel demonstriert:

$a_string = "That's tricky.";  
$sql = "INSERT INTO a_table VALUES ('$a_string')";

Auf den ersten Blick fällt der fast schon grob fahrlässige Fehler gar nicht auf, da die Syntax vollkommen korrekt ist. Erst wenn Sie die Zeichenkette betrachten, die an MySQL übergeben wird, ist der Fehler sichtbar. Die sieht nämlich so aus:

INSERT INTO a_table VALUES ('That's tricky')

Wenn MySQL eine solche Anweisung erhält, liefert es einen Fehler zurück, da es Probleme mit den drei einfachen Anführungszeichen hat. Nach dem zweiten Anführungszeichen erwartet es, dass die Zeichenkette zu Ende ist, es folgen aber trotzdem noch Zeichen, nämlich s tricky'. Ein solcher Fehler kann Ihnen zum einen beim Programmieren sehr schnell unterlaufen und zum anderen dann entstehen, wenn Sie einem Benutzer Eingaben erlauben, die in die Datenbank eingefügt werden sollen. Abhilfe schafft jedoch die Methode quote des dbh-Objekts. Diese Methode setzt vor alle Zeichen, die in MySQL Sonderzeichen sind, einen Backslash und entwertet die Zeichen somit. Die quotierte Zeichenkette gibt die Methode dann zurück.

string dbh->quote(string sql)

Das nachfolgende Beispiel quotiert zuerst die Zeichenkette, die in die SQL-Anweisung eingefügt werden soll.

$a_string = "That's tricky.";  
$a_string = $dbh->quote($a_string);  
$sql = "INSERT INTO a_table VALUES ('$a_string')";  
$n = $dbh->do($sql);

Es gibt zwar auch einige Perl-Funktionen, die diese Aufgabe übernehmen könnten. Doch von ihnen ist abzuraten, da sie alle Sonderzeichen entwerten, die sie finden können, darunter auch Zeichen wie +.

# Datums- und Zeitangaben

Perl verwendet für Datums- und Zeitangaben ein anderes Format als MySQL. Perl kennt nur das Format der Sekunden, die seit dem 01.01.1970 um 00:00:00 Uhr vergangen sind. MySQL hingegen kennt mehrere leicht unterschiedliche Formate. Insgesamt sind es fünf. Für Datumsangaben sind dies JJJJTTMM und JJJJ-TT-MM, wobei J für Jahr, T für Tag und M für Monat steht. Bei Zeitangaben akzeptiert es die Formate HHMMSS oder HH:MM:SS. Hier steht H für Stunden, M für Minuten und S für Sekunden. Natürlich ist auch die Kombination JJJJTTMMHHmmSS möglich.

Nun muss also eine Umwandlung der Formate stattfinden. Dafür stellt MySQL jedoch zwei Funktionen bereit, die wesentlich flexibler als die Perl-Pendants sind. Um eine Datums- und Zeitangabe von Perl nach MySQL zu übersetzen, verwenden Sie die Funktion FROM_UNIXTIME .

$timestamp = time();  
$sql =   
  "INSERT INTO a_table VALUES(FROM_UNIXTIME($timestamp))";

Der Perl-Zeitstempel wird nun in ein MySQL-kompatibles Format umgewandelt. Übrigens achtet MySQL in diesem Moment sogar darauf, ob die Spalte, in die der Wert eingefügt werden soll, vom Typ DATETIME, DATE oder TIME ist, und passt den Zeitstempel entsprechend korrekt an.

Eine Umwandlung mit der Perl-Funktion localtime ist zwar auch möglich, wird jedoch einfach zu lang, um sie im Quelltext wirklich effektiv einsetzen zu können. Sehen Sie sich dazu einfach noch einmal das Abschnitt 34.3 aus dem Perl-Teil dieses Buches an. Hauptverursacher des langen Quelltextes sind die Umwandlung von Stunden, Minuten und Sekunden in ein zweistelliges Format mit führender 0 und das richtige Zusammensetzen der Zeichenkette.

Um einen MySQL-Zeitstempel in ein Perl-kompatibles Format zu verwandeln, existiert die MySQL-Funktion UNIX_TIMESTAMP. Sie wandelt eine Datums- und Zeitangabe wieder in das Sekunden-Format um.

$sql = "SELECT UNIX_TIMESTAMP(date) FROM a_table";

Die Zeitangabe in der Tabelle wird nun wieder in das korrekte Perl-Format verwandelt. Zwar ließe sich auch diese Umwandlung mit Perl realisieren, aber auch dabei wird der Quelltext sehr schnell überaus lang, und es muss überprüft werden, ob der Zeitstempel vom Type DATETIME, DATE oder TIME ist.

# Fehlerbehandlung

Es ist nicht ausgeschlossen, dass es während der Arbeit mit einer MySQL-Datenbank zu einem Fehler kommt. Wenn Sie die Option 'RaiseError'=>1 setzen, kümmert sich das DBI-Modul selbstständig darum, eine Fehlermeldung auszugeben, und bricht das Programm sofort ab. Sie können jedoch eine eigene Fehlerbehandlung programmieren, die dadurch natürlich wesentlich flexibler und für Sie individueller wird.

Das DBI-Modul – so wie alle Objekte im Zusammenhang mit einer Datenbank, z. B. $dbh oder $sth – bietet zwei Methoden an, um auf einen Fehler zu überprüfen und die korrekte Fehlermeldung auszugeben. Dies sind die Methoden err und errstr. Die Methode err gibt entweder 0 zurück, wenn kein Fehler aufgetreten ist, oder eine andere Zahl, die dem Fehlercode entspricht. Die Methode errstr hingegen gibt eine zum Fehler passende Fehlermeldung aus.

Damit Sie jedoch eine eigene Fehlerbehandlung verwenden können, müssen Sie die automatische Fehlerbehandlung mit 'RaiseError'=>0 deaktivieren.

# ---[SNIP]---  
$dbh = DBI->connect($datasource,$uid,$pwd,%attributes);  
if(DBI->err() == 0)  
{  
  ...  
}  
else  
{  
  print "Fehler #".DBI->err().": ".DBI->errstr();  
}  
# ---[SNAP]---

Beim Verbindungsaufbau müssen Sie die Methoden des DBI-Moduls verwenden, da bei einem Fehler das dbh-Objekt noch nicht existiert. Ist die Rückgabe der Methode err gleich 0, dann ist kein Fehler aufgetreten, und der if-Anweisungsblock wird ausgeführt. Andernfalls wird der else-Anweisungsblock ausgeführt, der dann eine entsprechende Fehlermeldung erzeugt.

Das nachfolgende Beispiel soll verdeutlichen, dass die Methoden err und errstr abhängig vom Aufrufkontext sind, d. h., zu welchem Objekt die Fehler erzeugende Methode gehört.

# ---[SNIP]---  
$dbh = DBI->connect($datasource,$uid,$pwd,%attributes);  
if(DBI->err() == 0)  
{  
  $sth = $dbh->prepare('SELECT * FROM a_table');  
  $sth->execute();  
  if($sth->err() == 0)  
  {  
    ...  
    $sth->finish();  
  }  
  else  
  {  
    print "Fehler #".$sth->err().": ".$sth->errstr();  
  }  
  $dbh->disconnect();  
}  
else  
{  
  print "Fehler #".DBI->err().": ".DBI->errstr();  
}  
# ---[SNAP]---

Wenn der Fehler beim Ausführen einer anderen als der SELECT-Anweisung auftritt, müssen Sie die Methoden des dbh-Objekts aufrufen, um die Fehlerbehandlung realisieren zu können.

# Metainformationen

Metainformationen sind die Informationen, die detaillierte Auskunft über die Struktur einer Tabelle oder einer Spalte geben, z. B. die Anzahl der Spalten einer Tabelle oder die Anzahl der Dezimalstellen eines Zahlenwertes. All diese Informationen lassen sich mit dem sth-Objekt auslesen.

$sth->{'NUM_OF_FIELDS'} liefert die Anzahl der Spalten der Ergebnistabelle. Dieser Wert ist nur dann mit der Anzahl der Spalten der abgefragten Tabelle gleichzusetzen, wenn Sie ein Konstrukt wie SELECT * FROM a_table verwendet haben.

$sth->{'TYPE'} liefert die Typen der Spalten als Liste von Zahlen zurück. Die Reihenfolge der Zahlen der Liste entspricht der Reihenfolge der Spalten in der Tabelle, wodurch sich anhand des Zahlenwertes der Typ des Datenfeldes ermitteln lässt.

$sth->{'NAME'} liefert ein Array mit den Namen der einzelnen Spalten der Ergebnistabelle. Die Reihenfolge der Namen entspricht wiederum der Reihenfolge der Spalten in der Tabelle. Alternativ können Sie auch $sth->{'NAME_uc'} verwenden, um die Namen in Großbuchstaben zu erhalten, oder $sth->{'NAME_lc'}, um die Namen in Kleinbuchstaben zu erhalten.

$sth->{'NULLABLE'} speichert in einem Array, ob das Datenfeld der Spalte den Wert NULL enthalten darf oder nicht.

$sth->{'SCALE'} liefert ein Array mit Werten, die der maximalen Zeichenzahl des Feldes entsprechen.

$sth->{'PRECISION'} liefert ebenfalls wieder ein Array mit Werten, die angeben, wie viele Nachkomma- bzw. Dezimalstellen der Zahlenwert der Spalte besitzt.

# Zusammenfassung

  • Verbindungen zu MySQL-Datenbanken werden mit der Methode connect des DBI-Moduls ermöglicht.
  • Im datasource-Parameter muss dabei sowohl der Name der zu verwendenden Datenbank als auch des zu verwendenden Treibers angegeben werden.
  • SQL-Anweisungen mit Ergebnismenge werden mit Hilfe der Methoden prepare und execute ausgeführt.
  • SQL-Anweisungen ohne Ergebnismenge werden mit der Methode do ausgeführt.
  • Die Verarbeitung der Ergebnismenge erfolgt abhängig von der verwendeten Methode.
  • Eine eigene Fehlerbehandlung können Sie mit Hilfe der Methoden err und errstr realisieren.
  • Das sth-Objekt liefert unterschiedliche Metainformationen zur Ergebnistabelle.

# Fragen und Übungen

  1. Schreiben Sie ein Perl-Script, das die Felder autor, titel und preis aller in der Tabelle bspbuecher vorhandenen Datensätze als HTML-Tabelle ausgibt.
  2. Erweitern Sie das Perl-Script aus der vorherigen Aufgabe so, dass die Titel der Bücher als Hyperlinks ausgegeben werden. Sobald ein Benutzer auf eine der Verknüpfungen klickt, soll nur der gewählte Datensatz vollständig als HTML-Tabelle ausgegeben werden. Das Script, das den Datensatz ausgibt, müssen Sie nicht erzeugen.
  3. Ändern Sie das Script aus Aufgabe 1 so, dass der Benutzer die maximale Anzahl der anzuzeigenden Bücher durch die Angabe einer Zahl in der URI beschränken kann.