Typisierte Dateien und das Datenbankprinzip mit QBASIC


Datentabellen

Wenn schon Strukturvariablen und Dateien für sich alleine jeweils ein sehr mächtiges Programmierwerkzeug darstellen, welches Instrument entsteht erst dann, wenn beide zusammen benutzt werden! Wenn wir dann noch das dritte neue Werkzeug, Zeiger, mit verwenden, dann haben wir das zusammen, was vom Prinzip her die Grundlage mehr als Dreiviertel aller heutigen professionellen Softwaresysteme darstellt: Datenbanken. Von der Datenbank, die Websites oft zugrundeliegt (z.B. MySQL) über Geschäftsdatenbanken (z.B. Oracle) bis zum Wirtschaftssystem a la SAP R/3.

Das Prinzip davon zu verstehen, davon sind wir gar nicht soweit entfernt wie Sie vielleicht denken. Dass wir mit QBASIC soweit kommen, liegt auch daran, dass BASIC in seiner Blütezeit auf PC's oft für kleinere Business-Programme und Datenbanken als Programmiersprache benutzt und von Microsoft, dem BASIC-Hersteller daher entsprechend aufgerüstet wurde.

Datenbanken lassen sich bei weitem nicht nur für Geschäftsanwendungen benutzen. Sie sind genauso in der Fotoarchivierung, in der Statistik oder in Spielen gut anwendbar. Aber zunächst einmal noch einen Schritt zurück zu typisierten Dateien.

Nehmen wir an, wir haben eine umfangreiche Datenstruktur erstellt, wie z.B. adresstyp aus dem vorigen Kapitel, aber adresstyp enthalte noch Tel-Nummer, Fax-Nummer, Email, Homepage und einiges mehr. Nun will ich alles in eine Datei rausschreiben. Natürlich kann ich das machen, indem ich jedes Element einzeln mit einer PRINT-Anweisung als Text rausschreibe. Für viele Zwecke mag das auch ganz nützlich sein, wenn ich die rausgeschriebenen Daten z.B. mit einer Tabellenkalkulation weiter bearbeiten möchte. Aber wenn ich die Daten einfach nur auf der Festplatte zwischenspeichern möchte, dann will ich nicht jedes einzelne Element meiner Struktur anfassen müssen. Gibt es nicht eine Möglichkeit, einfach PRINT #1,adresse hinzuschreiben? Und dann schreibt BASIC alles raus, was zu adresse gehört? Gibt es. Ist auch nicht kompliziert.

Datensätze

In so einem Fall schreibt BASIC ein Binary. Wobei dieses Binary in Blöcke fester Länge aufgeteilt wird. Jeder Block speichert eine Strukturvariable, also z.B. eine Adresse. BASIC muss lediglich wissen, wie gross so ein Block ist, wieviel Bytes er umfasst. Das ermitteln wir mit der Funktion LEN, genauso wie bei Strings. Haben wir z.B.

TYPE typbeispiel
  a AS INTEGER
  b AS INTEGER
END TYPE

so ist LEN(typbeispiel)=4.

Solche Blockteile von Dateien heissen in der EDV "Datensätze". Ein Datensatz ist immer der Inhalt eines "TYPES", ein Bündel aus Werten, das einer Informationseinheit zugeordnet wird, z.B. einer Person, einem Produkt, einem Zeitpunkt, einem Foto usw.

Jetzt öffnen wir die Datei, dieses mal mit dem Schlüsselwort "RANDOM". Aha, das kennen wir schon: "Random Access" = Wahlfreier Zugriff: OPEN "my.dat" FOR RANDOM AS #1. Wir bekommen also gleich noch den wahlfreien Zugriff auf die Datei als Beigabe.

Das reicht allerdings noch nicht. Nun müssen wir BASIC noch angeben, welche Grösse ein Datensatz hat. Das machen wir mit dem Schlüsselwort LEN ganz am Ende der OPEN-Anweisung:

OPEN "my.dat" FOR RANDOM AS #1 LEN=LEN(typbeispiel)

Das war auch schon das Schwierigste. Nun geben wir unsere Variablen aus. Da wir wahlfreien Zugriff haben, müssen wir bei der Ausgabe mitangeben, in welchen Datensatz wir die Variable schreiben wollen. Falls wir die Datei neu anlegen, werden wir natürlich ganz vorne beginnen. Den entsprechenden Befehl kennen wir schon: PUT. Damit haben wir in BINARY-Files einzelne Bytes geschrieben. Jetzt schreiben wir ganze Datensätze. Das Ganze könnte z.B. so aussehen:

TYPE ta
  a AS INTEGER
  b AS INTEGER
END TYPE

DIM a1 AS ta

OPEN "R:\a.dat" FOR RANDOM AS #1 LEN = LEN(ta)

FOR i% = 0 TO 9
  a1.a = i% * 5: a1.b = i% * 8
  PUT #1, i% + 1, a1
NEXT i%

CLOSE(1)

Das Einlesen funktioniert entsprechend mit GET. Der Modus "RANDOM" bedeutet nicht nur wahlfreien Zugriff, es bedeutet auch, dass wir gleichzeitig Lese- und Schreibzugriff haben. Wir müssen also, wenn wir die Datensätze nach dem Schreiben wieder lesen wollen, die Datei nicht dazwischen schliessen und wieder öffnen.

TYPE ta
  a AS INTEGER
  b AS INTEGER
END TYPE

DIM a1 AS ta

OPEN "R:\a.dat" FOR RANDOM AS #1 LEN = LEN(ta)

FOR i% = 0 TO 9
  a1.a = i% * 5: a1.b = i% * 8
  PUT #1, i% + 1, a1
NEXT i%

FOR i% = 0 TO 9
  GET #1, 10 - i%, a1
  PRINT i%, a1.a, a1.b
NEXT i%

CLOSE (1)

Eine solche Sequenz von Datensätzen, wie wir sie im Beispiel geschrieben und gelesen haben, nennt man im allgemeinen in der EDV eine "Datentabelle". In vielen Fällen werden Datentabellen in typisierten Dateien gespeichert, d.h. eine Datentabelle entspricht einer Datei. Es kann aber auch sein, dass viele Datentabellen in einem File gespeichert werden, wie z.B. bei MS Access.

Schlüssel und Relationen

Wir wollen nun ein neues Programm erstellen. Und zwar ein Super-Fotoalbum. Nicht einfach ein normales Fotoalbum, in dem man die Bilder und vielleicht noch eine Zeile Text sieht. Sondern eins, bei dem man bei Bedarf die Namen der fotografierten Leute, ihre Beziehung zum Fotografen, ihre Adressdaten, Geschichten zum Foto und die technischen Daten der verwendeten Kamera abfragen kann.

Das klingt zunächst weniger nach technischer Revolution, als nach sehr viel Arbeit: Zu jedem Foto muss alles erfasst werden: Die Namen der Leute, die Beziehungen, die Adressen, die Geschichten, die technischen Daten. Oh jeh. Das kann bei ein paar tausend Fotos schnell ziemlich anstrengend werden.

Die technische Revolution besteht darin, dass das gar nicht soviel Arbeit ist, wie man meinen könnte, wenn man s.g. Relationen benutzt. Wie man so etwas nutzt und programmiert, damit beschäftigt sich dieser Abschnitt,anhand einer Mini-Version unseres Albums.

Was ist eine Relation?

Nehmen wir an, wir erstellen zunächst zwei Tabellen. Eine enthält die Dateinamen der Fotos. Die andere enthält die Daten zu den Personen: Namen, Beziehung zum Fotografen ("Cousin" oder "Schulfreund") und die Adresse. Dann müsste ja eigentlich nur noch bei den Fotos notiert werden, welcher der Personen in welchem Foto erscheint. Technisch gesprochen: Welche der Datensätze aus der Personentabelle welchem Datensatz der Fototabelle zugeordnet werden muss. So ein Zuordnung nennt man Relation.

Schlüssel

Ich muss also in der Fototabelle nur so eine Art Zeilennummer speichern, die auf den richtigen Datensatz in der Personentabelle verweist. Und da auf Fotos mehrere Personen abgebildet sein können, müssen auch mehrere "Zeilennummern" abspeicherbar sein. Eine solcher Verweis auf "Zeilennummern", bzw. genauer Datensatznummern stellt aber nichts anderes dar als das, was wir schon vor einigen Kapiteln kennengelernt haben: Zeiger. Wir müssen also einfach Zeiger auf Datensätze abspeichern. Und ein solcher Zeiger auf einen Datensatz heisst in der Datenbanksprache Sekundärschlüssel. Die Frage ist jetzt nur noch, wie man die Adresse selbst, also die Datensatznummer in der Datenbanksprache bezeichnet. Na, dreimal dürfen Sie raten: Richtig, Primärschlüssel. Das Ganze sieht also dann so aus:

Fototabelle

PrimärschlüsselDateinameSekundärschlüssel1Sekundärschlüssel2
0C:\fotos\2005\sommer\a1.bmp01
1C:\fotos\2005\sommer\a2.bmp-1-1
2C:\fotos\2005\sommer\a3.bmp0-1
3usw.


Personentabelle

Primärschlüssel
NameAdresseBeziehung
0Peter Meier12345 WolldingenSchulfreund
1Susanne Müller54321 StrickdingenEx-Freundin
2usw.

Das ist dann schon der ganze Zaubertrick. In einem Satz: Wir speichern bei den Fotos noch Datensatznummern anderer Tabellen, die noch weitere Informationen zum Foto enthalten.

Die -1 im Sekundärschlüssel ist ein s.g. Missing-Code. Er zeigt an, dass hier kein Schlüssel vorhanden ist. Er wurde deshalb negativ gewählt, weil er bei der Verwendung in einem Array oder als Datensatznummer zu einem Laufzeitfehler führt. Somit bemerkt man beim Testen mit Sicherheit, wenn einmal auf Schlüssel zugegriffen wird, die gar nicht existieren dürfen.

Datenbank

Und das Ganze zusammen, also Datentabellen und die Relationen, die sie verknüpfen, nennt man eine Datenbank.

Nun dürfte auch klar sein, wie wir das Ganze auf Geschichten erweitern können: Nehmen wir an, es gibt noch ein Urlaubstagebuch. In Tabellenform steht zu jedem Tag des Urlaubs ein Texteintrag. Ein Datensatz enthält also als Elemente (ein Element nennt man in der Datenbanksprache ein "Feld"), also als Felder das Datum und einen String mit dem Tagebuchtext dieses Tages. In der Fototabelle müssen wir dann nur einen Verweis auf den richtigen Tagebuch-Datensatz unterbringen.

Übung:

Nun müssen wir allerdings gut organisieren: Welcher Schlüssel in der Fototabelle gehört zu welcher Tabelle? Überlegen Sie sich einmal eine TYPE-Struktur für die Fototabelle, so dass hier nichts durcheinanderkommt!

Umsetzung

Es gibt nun zwei Art und Weisen, wie wir so etwas in QBASIC umsetzen können. Erste Methode: Wir speichern die Datentabellen in Arrays. Zweite Methode: Wir speichern die Datentabellen in Dateien. Die erste Methode würde ich aus Platzgründen nicht empfehlen. Ein Array kann in QBASIC maximal 64K Umfang haben. Ausserdem muss dann für jede Operation die gesamte Datentabelle in den Hauptspeicher gelesen werden. Keine gute Idee. Damit bleiben also die typisierten Dateien. Wir brauchen also zwei Programme: Ein Einleseprogramm, mit dem wir die Fotoeinträge und Personeneinträge (und Tagebucheinträge) erfassen können. Und das eigentlich Fotoalbum-Programm, in dem wir die Fotos anschauen. Natürlich können wir beide Funktionen auch in ein und demselben Programm unterbringen.

Die Schlüssel stellen die Datensatznummern dar, die wir ins 2. Argument hinter PUT und GET schreiben. Das Erfassungprogramm sollte uns allerdings die Arbeit abnehmen, die Datensatznummern zu verwalten. Es sollte also z.B. fragen: "Welche Personen befinden sich auf dem Foto?". Wir geben dann ein: "Peter Meier" und "Susanne Müller". Das Programm sucht dann in der Personentabelle im Namensfeld, ob dort solche Einträge zu finden sind und speichert im positiven Fall die entsprechenden Datensatznummern bei den Fotos ab.

Grosse Übung

Nun machen Sie sich ans Werk: Erstellen Sie das Fotoalbum-Programm! Zumindest eine Minimalversion davon. Ach ja: Sie können mit QBASIC natürlich nicht so ohne Weiteres jpg-Dateien darstellen. Es gibt viele Freeware-Programme, z.B. dieses hier, mit dem sie ein paar Fotos ins bmp-Format konvertieren können. Oder Sie lassen in einem eigenen Fenster mittels dem SHELL-Befehl das entsprechende Foto anzeigen. Das ist mit Sicherheit sogar komfortabler, für Sie wie auch für den Benutzer.