Vivisektion – Ein Einstieg

Der @derguenther ist vor ein paar Tagen bei mir in dieses Twitter reingerauscht und hat mich um Rat bezüglich Datenbanken gefragt. Guter Rat ist ja teuer, wenn man nur diffus weiß, was eigentlich die Frage ist, und deshalb hatte ich ihn eher spöttisch gefragt, wie „köheim“ seine Daten sind, oder ob er mir ein paar Zeilen aus Excel raushauen kann. Und siehe da – der Kollege macht das die ganze Zeit schon und hinterfragt seinen Weg, wie er zu Erkenntnissen findet, offen in seinem Blog.

Von dort habe ich mir seine Datenstruktur gezogen, kann jeder für sich selbst auch machen, und mal einen Blick darauf geworfen. Zwei Fragen hat er: (1) wie mache ich es, dass jeder Fund auch eine Belegstelle bekommen kann, und (2) wie schaffe ich eine gesunde Datenbasis für verschiedene Visualisierungen? 

(1) ist relativ leicht zu beantworten und gibt eine Steilvorlage für ein zentrales Thema: Normalisierung. Wir müssen die Daten so granular auflösen, dass wir zu jeder Information einen weiteren Bezug auf eine Belegstelle (etwa einer Seitenzahl, Blatt-Nr., oder ähnliches) und von dort zu einer Quelle (Literatur, Akte, etc.) herstellen kann. (2) soll dann folgen, im Prinzip ist das dann wieder eine Frage, wie man die Daten aus der Datenbank rausbekommt und in welche Art Visualisierung man sie wie übergibt. Themen für später, denn erst braucht es ein Datenmodell, dann eine Möglichkeit, Daten hineinzubekommen, und dann geht’s zurück ins Frontend.

Ausgangspunkt soll eine Tabelle von Personen sein, die bei Ermittlungen über die „Winzengruppe“ ins Visier der Gestapo geraten sind. Die Daten hat Christian hier in Google Docs gestellt: Link zur Personentabelle

Wir sehen hier einige sehr typische Dinge, zu denen Excel verleitet:

– Datenfelder, die in sich schlecht oder gar nicht normalisiert sind.

– Datenfelder, die polyvalent genutzt werden

– Datenfelder mit uneinheitlichen Null-Werten

– Datensätze, die durch mindestens einen dieser drei „Fehler“ schlechter oder gar nicht eindeutig abfragbar werden.

Was heißt das?

Normalisieren bedeutet, Informationen so lange voneinander zu trennen, sprich entweder in Attribute oder Relationen zu teilen, bis sie ausschließlich atomaren Wertebereich haben. Was das genau bedeutet, kann man ziemlich unverstehbar bei Wikipedia nachlesen, aber in den Tabellen nachvollziehen. Wichtig zu verstehen ist: Ein Feld „Name“ beinhaltet zwei Attribute (Vorname und Nachname) und mindestens ein Zeichen (Leerzeichen oder Komma und Leerzeichen), das man gar nicht speichern muss, weil es keinerlei Information trägt. Ein Name sollte auch bei einer sehr endlichen Zahl an Personen nicht als ID verwendet werden. Ein Telefonbuch genügt, um zu belegen, welche Zahl an Personen man mit dem uneindeutigen Bezeichner „Name“ meinen könnte. Bei Personen bietet es sich an, eine ID, in der Datenbanksprache Primärschlüssel zu wählen, die rein den Datensatz bezeichnet und sonst keinen inhaltlichen Bezug zu den Daten hat. Menschen neigen dazu, Namen zu vererben, Geburtstage haben endliche Werte, so dass man – um sicher Eindeutigkeit herzustellen – schon Attribute verbinden muss, was wiederum gegen die Normalisierungsregeln verstößt. Also:

CREATE TABLE IF NOT EXISTS `Tutorial_Personen` (
 `PersonId` int(16) unsigned NOT NULL AUTO_INCREMENT,
 `LastName` varchar(120) NOT NULL,
 `FirstName` varchar(120) NOT NULL,
 `Birthdate` date DEFAULT NULL,
 PRIMARY KEY (`PersonId`),
 KEY `LastName` (`LastName`,`FirstName`)
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=4 ;
Was ist das nun?
Dieser „Satz“ ist die erste Zeile Code des Projekts – in einer Sprache namens „Structured Query Language“ und hier im Dialekt für einen MySQL-Datenbankserver. Warum MySQL? Weil es a) in XAMPP gebündelt ist und daher zuverlässig bei jedem, der es lernen will, leicht installierbar ankommen kann. Und b) hat heute noch das billigste Hosting mindestens eine MySQL-Datenbank. Wer rausfindet, dass MySQL nichts für seine Zwecke ist, ist bereits so weit, dass er keine „Einsteigerhilfe“ mehr braucht – also bitte keine Debatte um Maria oder Couch oder was auch immer… wir sprechen eine Runde MySQL.
SQL ist eine relativ einfache Sprache und fast immer ähnlich natürlicher Sprache lesbar – sie teilt dem Datenbankserver mit, was er tun soll:
CREATE TABLE `Tutorial_Personen` (Lege eine Tabelle mit dem Namen Tutorial_Personen an).
Zwei Eigenheiten hier: Spalten- und Tabellennamen werden in Accents Graves eingefasst – nicht zwingend, aber das ist eine typische Fehlerquelle. Und als Tabellennamen habe ich ein Präfix („Tutorial_“) vor den eigentlichen Tabellennamen (Personen) gesetzt. Das hilft, Ordnung in der Datenbank zu halten.
Weiter im Text, die neuen Elemente jeweils fett hervorgehoben:
CREATE TABLE IF NOT EXISTS `Tutorial_Personen` – prüft, ob es die Tabelle schon gibt und führt die Anweisung nicht aus, falls die Prüfung negativ ausfällt. Man fängt damit ein Scheitern ab – statt eines Fehlers kommt nun eine einfach Rückmeldung zurück. Warum man das will? Wenn man mehrere Anweisungen in einer Gruppe gemeinsam ausführt, wird die Abarbeitung bei einem Fehler gestoppt. Deswegen fängt man vorhersehbare Fehler ab.
CREATE TABLE IF NOT EXISTS `Tutorial_Personen` (
 `PersonId` int(16) unsigned NOT NULL AUTO_INCREMENT,
 `LastName` varchar(120) NOT NULL,
 `FirstName` varchar(120) NOT NULL,
 `Birthdate` date DEFAULT NULL,
Nun kommen die einzelnen Tabellenspalten (Attribute) hinzu: Ich habe sie englisch benannt (denn jede Art von Programmierung drumherum wird eine Art Englisch sein, d.h. man bleibt in einer Sprache, wenn man Code liest. Und jeder Teil des zusammengesetzten Wortes beginnt mit einem Großbuchstaben und geht in Kleinbuchstaben weiter. Man nennt das CamelCasing (nach den Kamelhöckern) und es ist reine Konvention, aber es ist guter Stil, eine Schreibweise zu pflegen, z.B. alle Variablennamen in lowerCamelCase, alle übrigen Entitäten mit UpperCamelCase. Galt es in der Frühzeit des Programmierens noch als guter Stil, möglichst wenig Tipparbeit durch abgekürzte Variablen- und Entitätennamen zu erzeugen, schreibt man heute alle Bezeichnungen so ausführlich wie nötig, um Eindeutigkeit (einerseits des Namens innerhalb des Projekts, andererseits der Information über Inhalt und Funktion) herzustellen. Auf den Namen der Tabelle wird nicht mehr hingewiesen, denn wenn man in die Verlegenheit kommt, sich zwischen mehreren „FirstName“-Feldern zu entscheiden, wird man zuvor den Tabellennamen nennen müssen. Es gibt also eigentlich keinen Grund für eine Spalte wie PersonFirstName – außer, man benutzt diese Spalte als Foreign Key – Sekundär- oder Fremdschlüssel.
int(16) unsigned bedeutet: gültige Werte sind alle positiven (ohne Vorzeichen) ganzzahligen Werte bis zu 16 Stellen. AUTO_INCREMENT bedeutet, dass hier ein automatischer Zähler den Inhalt nummeriert (dadurch ist es nicht möglich, dem Feld selbst einen Wert kleiner als den Zähler – die Kardinalität – zuzuweisen). NOT NULL bedeutet: Das Feld darf nicht logisch null (also „nichts“) beinhalten, trifft also eine Festlegung, was passiert, wenn nichts in das entsprechende Feld eingetragen wird.  Es wird vielleicht klar, wenn ich die nächsten Werte hinzu nehme: Die nächsten beiden Felder sind Nach- und Vorname vom Typ VARCHAR (Character, varying. Zeichenkette variabler Länge) mit maximal 120 Zeichen Länge. Für Vor- und Nachnamen gewöhnlicher Menschen sollte das reichen, wer endlose Namen Adeliger eingeben muss, sollte längere Werte vorhalten. In beide Felder will ich leere Zellen nicht als Null behandelt wissen, denn Menschen haben selbst dann einen Vornamen, wenn ich ihn nicht kenne. Beim Geburtsdatum hingegen setze ich das Datum per Vorgabe zunächst auf Null (Default Null), falls kein Datum eingegeben wird. Anderenfalls könnten Fälle insbesondere beim Auslesen der Daten entstehen, in denen fälschlich 0.0.0000 oder 1.1.1970 angenommen werden. Das wäre nicht gut.
Der Rest der Abfrage sind Anweisungen, wie die Tabelle einzurichten ist, welche Schlüssel existieren, welche Felder indiziert werden sollen etc. Hier sind vier Optionen möglich: PRIMARY KEY – der Primärschlüssel: Später werden Relationen über diese Schlüssel hergestellt, insofern ist hier ein eindeutiger Bezeichner zu wählen – z.B. eine Kundennummer. INDEX ist eine Spalte, die später durchsucht werden soll. Es ist „höflich“, dem Server vorher zu sagen, auf welche Abfragen er sich vorbereiten soll und es wird essenziell wichtig, wenn man sich den Datenbankserver mit mehreren Nutzern teilt oder große Datenmengen oder komplexe Abfragen benutzt. UNIQUE bezeichnet Spalten, in denen sich kein Wert wiederholen darf. FULLTEXT bezeichnet Spalten, die für die Volltextsuche indiziert werden sollen. Die Volltextsuche vom MySQL ist aber etwas, das man eher nicht anwirft, wenn man es vermeiden kann 😉
Wir schreiben schnell noch ein paar Demo-Werte in die Tabelle:
INSERT INTO `Tutorial_Personen` (`PersonId`, `LastName`, `FirstName`, `Birthdate`) VALUES
 (1, 'Mustermann', 'Erika', '1900-12-25'),
 (2, 'unbekannt', '', '2015-05-11'),
 (3, '', '', NULL);
Einfach, weil ich die Syntax für Einfüge-Operationen zeigen will: INSERT INTO `TabellenName` (`Spalte1`, `Spalte2`…) VALUES (..), (…);
Die Zahl der Argumente muss dabei immer passen: Für alle Spalten, die im ersten Klammerausdruck stehen, muss später ein Wert stehen, und wenn es nur “ (zwei Hochkommata mit Nichts dazwischen, nicht Anführungszeichen!) ist. Und für jeden Wert, der nach Values folgt, muss vorher eine Spalte benannt worden sein.
Eine letzte Abfrage zum Feierabend:
SELECT * FROM `Tutorial_Personen`;
Und damit sollten alle Datensätze einmal angezeigt werden und wir können im nächsten Post überlegen, wie wir Christians übrige Personendaten unterbringen, die anderen Normalisierungsdefizite beheben und eine Runde bewusst gegen die Normalisierungsregeln verstoßen.

 

Eine Antwort auf „Vivisektion – Ein Einstieg“

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

*