DHVLab-Ausbildungsvorhaben "Erstellung einer interaktiven Karte" im Rahmen des Vertiefungskurses "Begräbniskultur in der Frühen Neuzeit" (Prof. Dr. Mark Hengerer), Wintersemester 2016/2017
Folgende DHVLab-Komponenten werden verwendet:
Datengrundlage:
Brigitta LAURO, Die Grabstätten der Habsburger. Kunstdenkmäler einer
europäischen Dynastie. Wien 2007
→ Personenregister samt Begräbnisstätten (25 Seiten à durchschnittlich
22 Personeneinträge = ca. 550 Einträge1)
Ziel:
→ Sie erlernen Grundlagen der Datenaufnahme und -verarbeitung samt
anschließender Visualisierung und damit grundlegende technische
Kompetenzen für die Anwendung auf Ihre geisteswissenschaftliche
Fragestellungen.
Die Kursleiter setzten gemeinsam mit einigen Teilnehmern das Projekt
nach Semesterende fort. Die aktuelle Version der Karte ist auf folgender
Seite abrufbar:
http://www.habsburg.gwi.uni-muenchen.de/2
Bedeutung von IT-Grundkenntnissen für Geisteswissenschaftler/-innen:
An dieser Stelle seien ein paar, zufällig
ausgewählte Jobangebote des einschlägigen Portals
HSozKult angeführt:
⇒ Das Projekt "IT for
All" im Rahmen der Förderlinie "Digitaler Campus Bayern"
setzt hier an (vgl. Projektbeschreibung).
Im Rahmen des Projekts entsteht das DHVLab1,
eine interaktive Lehr- und Lernumgebung, die im Rahmen
dieses Kurses zum Einsatz kommt.
Aufgaben:
Hinweise zur Benutzung:
Karten eignen sich in besonderer Weise dazu, komplizierte Sachverhalte anschaulich darzustellen1. Wenngleich eine Karte zunächst einmal eine zweidimensionale Darstellung eines dreidimensionalen Raumes ist - was notwendigerweise eine gewisse Reduktion mit sich führt - stellt sie für die historische Forschung eine komplementäre Ebene dar, die Historikern die Möglichkeit bietet, Wissenssammlungen ansprechend zu visualisieren2. Diese geographische Dimension rückt in den vergangenen Jahren durch die digitalen Möglichkeiten verstärkt in den Fokus der Wissenschaft3, die sich bis dato den Vorwurf gefallen lassen musste, die "spatial dimension" zu vernachlässigen4.
Dass die kartographische Visualisierung (historischer) Informationen immer beliebter wird - und es somit sinnvoll erscheint, den Weg dorthin zu erlernen - soll anhand einiger ausgewählter Beispiele gezeigt werden:
→ all diese Projekte basieren auf strukturierten Datensammlungen5. Manche von Ihnen verwenden zur Darstellung die Google Maps API, andere das Pendant Open Street Map.
Ergebnisse der Diskussion mit den Kursteilnehmern:
Fazit: Es handelt sich um eine reichhaltige Datenquelle, die durch die Begrenzungen des Mediums 'gedrucktes Buch' jedoch nicht in der Intensität ausgeschöpft werden kann, wie es für den Historiker wünschenswert wäre. Die strukturierte Aufnahme der Forschungsdaten, ihre Anreicherung um Metadaten und zusätzlichen Informationen sowie die Vernetzung mit bestehenden Online-Angeboten verspricht eine erhebliche Steigerung des Mehrwerts der Datensammlung. Neben dem Zugewinn an Interaktivität, Benutzerfreundlichkeit und visuell ansprechender Gestaltung können aus den genannten innovativen Features nicht zuletzt neue wissenschaftliche Fragestellungen erwachsen.
Unser Kursziel ist es, in gedruckter Form vorliegende Daten in strukturierter Form zu erfassen und für die spätere Weiterverarbeitung in eine Datenbank einzupflegen. Eine Möglichkeit wäre es, die entsprechenden Buchseiten mit einem OCR-Programm zu erfassen und die damit gewonnenen Daten im Anschluss aufzubereiten. Die Einführung in die Benutzung einer OCR-Software war jedoch im Rahmen dieses Kurses nicht möglich, daher wurden die Daten händisch mit einem Tabellenkalkulationsprogramm erfasst. Ob händisch oder maschinell - die Aufgabe der anschließenden Datenstrukturierung bleibt unverändert.
"Alle Arten von Information können auf Tabellen heruntergebrochen werden"
Bevor wir uns der Datenaufnahme zuwenden, seien an dieser Stelle ein
paar grundlegende Gedanken zu unserem Gegenstand, den Forschungsdaten,
angeführt.
Forschungsdaten
Naiver Umgang mit Forschungsdaten: Leider finden sich gerade in den Geisteswissenschaften bis heute zahlreiche Beispiele für einen 'naiven' Umgang mit Daten, d.h. ein werkzeugzentriertes Arbeiten überwiegt ein datenorientiertes Denken. Beispielsweise werden Bibliographien in Hausarbeiten zumeist in einer Word-Datei in Textform gesammelt, seltener in einem hierfür geeigneten Literaturverwaltungsprogramm wie Zotero oder Citavi. Dies bringt folgende negative Begleiterscheinungen mit sich:
→ Für weiterführende Informationen sei auf die Einführung in die Datenaufnahme mit Calc verwiesen.
"Wer Calc kann, kann auch Excel" Die Datenaufnahme erfolgt mit i.d.R. mit einem Tabellenkalkulationsprogramm wie Calc oder Excel. Calc, die kostenlose Alternative zu Excel, steht seinem kostenpflichtigen Pendant in der Funktionalität ebenbürtig gegenüber. Es findet im DHVLab Anwendung, da sich das Projekt für den Einsatz non-proprietärer Software ausspricht (Stichwort: Unabhängigkeit bestimmter Herstellerformate). → Öffnen Sie Calc im Virtuellen Desktop wie folgt: Startbutton -> Anwendungen -> Büroprogramme → In Calc können Dateien als ods- (Open Document Spreadsheet) oder xls-Format abgespeichert werden; das Öffnen mit Excel (und umgekehrt) ist problemlos möglich. → Für den späteren Import der Daten in die Datenbank bietet sich die Verwendung des Formats CSV (Comma Separated Values) an. Wichtig: Für den Import kann immer nur EINE Tabelle einbezogen werden, d.h. es muss für jede Tabelle eine neue CSV-Datei angelegt werden!
Bereits vor der Datenaufnahme in Calc/Excel ist es unabdingbar, dass man sich Gedanken zum Datenschema macht. Wie soll die spätere Datenbank strukturiert sein? Welche Erkenntnisinteressen verfolge ich mit den aufzunehmenden Forschungsdaten? (= reflektiertes Arbeiten)
Die erarbeitete Liste der Begräbnisstätten der Habsburger stellt eine große Bereicherung für die wissenschaftliche Beschäftigung mit der Geschichte der habsburgischen Dynastie dar. Diese wissenschaftliche Leistung soll in keiner Weise in Abrede gestellt werden. Über die, dem Medium "gedrucktes Buch" geschuldeten Nachteile der Auflistung wurde bereits weiter oben mit den Teilnehmern diskutiert. Hinzu kommen kleinere inhaltliche Inkonsistenzen bzw. Unsicherheiten, bei deren Erfassung in Form einer Datenbank sich die Frage nach der Strukturierung im Besonderen stellt. Die Diskussion mit den Teilnehmern führte u.a. zu folgenden Beobachtungen:
→ Für uns Geisteswissenschaftler birgt dieses strukturierte Arbeiten
mit Daten viele Vorteile, u.a. dass man sich mit allen Problemfällen
auseinandersetzen muss und verbindliche Kriterien mit einhergehender
Entscheidungsfindung geschaffen werden müssen.
→ Dabei gilt
stets: Auswahlkriterien gut dokumentieren. Inhaltliche Entscheidungen
müssen als solche erkennbar sein. Ihre Dokumentation ist wichtig, da das
Wissen darüber ansonsten (z. B. bei einem Personalwechsel) schnell
verloren gehen kann.
→ Neben der Dokumentation der
Datenstruktur muss auch die einbezogene Datenbasis genau beschrieben
werden, um signifikante und vertrauensvolle Werte anbieten zu können.
(z. B.: "Von den in der Datenbank erfassten Personen werden nur 80%
ausgegeben, da bei den anderen 20% keine Geodaten ermittelt werden
konnten.")
Ein kleines Übungsbeispiel zum Einstieg:
(// Übungsdatei einbinden)
Lösungsvorschläge
(//Abbildung der vorangehenden, bereinigten Calc-Datei einbinden)
Bei der vorangehenden Übung haben wir damit begonnen, Inhalte sauber
strukturiert abzulegen und Informationen auf verschiedene
Tabellenspalten sinnvoll aufzugliedern.
Vorbereitend zu
dieser Sitzung, sollten die Teilnehmer einen Abschnitt aus der Liste
nach Lauro (ges. 10 Personen) sinnvoll strukturieren. Im Folgenden
werden drei der Einsendungen als ausgewählte Beispiele besprochen:
Datei 1:
Datei 2:
Datei 3:
→ Für umfassende Informationen sei auf "Das relationale Datenmodell I" verwiesen.
In den gezeigten Beispielen
gibt es gute Ansätze zur Strukturierung der Forschungsdaten. Jedoch
tritt zuweilen eine noch recht "geisteswissenschaftliche Vorstellung"
von Tabellen zutage, d.h. es gilt noch stärker zu verstehen, warum Daten
strukturiert in verschiedenen Tabellen und Spalten abgelegt werden
sollten und auf welche Art und Weise dies in einer relationalen
Datenbank erfolgt.
>Warum werden Daten in
verschiedenen Tabellen abgelegt?
Zur Einbindung der Informationen in andere Tabellen
werden eindeutige Schlüssel (ID) verwendet bzw. eigene Tabellen
angelegt, die nur zur Zuordnung verschiedener Objekte und Merkmale
dienen. Die Verknüpfung erfolgt stets über die ID, die jeden Datensatz,
d.h. jede Zeile einer Tabelle eindeutig identifizierbar macht.
Ein guter Ansatzpunkt um festzustellen, an welcher
Stelle ein weiteres Aufsplitten von Informationen sinnvoll sein könnte,
ist das Sortieren nach bestimmten Informationen. In manchen der oben
angeführten Beispiele wäre es zum Beispiel nicht möglich, nach den
Geburts- oder Sterbejahren zu sortieren, wenn sich diese in derselben
Spalte wie der Personenname befindet. Dadurch wird auch die
Quantifizierbarkeit der Informationen negativ beeinflusst, da z.B. nicht
danach gefiltert werden kann, wie viele Personen im Zeitraum XXXX-YYYY
gestorben sind.
Um sich Klarheit über die
Datenbasis und ihre sinnvolle Strukturierung noch vor der Erstellung der
späteren Datenbank zu verschaffen, hilft die Anlage eines sogenannten
Entity-Relationship-Modells (ERM). Dieses wird in der folgenden Sitzung
theoretisch erklärt und praktisch anhand der besprochenen Beispieldaten
umgesetzt.
In der vorangehenden Sitzung wurde die Bedeutung für die strukturierte und normalisierte Erfassung von Forschungsdaten dargelegt. Die Anlage eines Entity-Relationship-Modells (ERM) unterstützt in der konzeptionellen Phase eines Datenbankprojekts die Erstellung des Datenbank-Designs.
→ Ein Datenmodell gibt Antworten auf diese Fragen. Als theoretische Grundlage eines Datenbanksystems beschreibt ein Datenmodell, auf welche Weise Daten in einem Datenbanksystem abgespeichert und verarbeitet werden sollen. Es ist leicht verständlich, daher dient es als gute Kommunikationsbasis zwischen Anwendern und Entwicklern. Dadurch hat sich das ER-Modell zum De-facto-Standard für die Datenmodellierung etabliert. Es besitzt folgende grundlegende Komponenten:
Objekte können unterschiedliche Arten von Beziehung zueinander besitzen:
Zur Anlage eines ERM eignet sich der Diagrammeditor Dia (Start →
Anwendungen → Grafik). Dort können auf intuitive Weise die oben
genannten Bestandteile eines ERM (Rechtecke, Rauten, Kreise) sowie
Verbindungslinien erstellt werden. Diese können mit beschreibenden
Texten versehen werden (Objektbezeichner, Beziehungsarten). Änderungen
können durch Verschieben der einzelnen Elemente leicht erfolgen,
Verknüpfungen zwischen Elementen bleiben dabei erhalten. Neue Objekte
oder Verknüpfungen lassen sich jederzeit ergänzen. Das erstellte ERM
kann anschließend als Bilddatei (Datei → Diagramm exportieren) im
PNG-Format exportiert werden.
Gemeinsam mit den Teilnehmern
wurde auf Grundlage der Beispieldaten der vergangenen Sitzung ein ERM in
Dia erstellt.
Beschreibung:
Aus diesem ERM ergeben sich demnach folgende Tabellen, die
gleichzeitig auch bereits einen Eindruck von unserer anzulegenden
Datenbank vermitteln:
Diskussion der Modellierung inhaltlicher Aspekte in den einzelnen Tabellen:
Zur Teamorganisation und zur Sammlung von Ideen, Informationen und Problemfällen wurde zunächst eine Arbeitsumgebung auf dh-lehre eingerichtet. Ergänzt wurde dies in der Folge durch ein Google Doc, in dem alle zentralen Aspekte durch die Teilnehmer gesammelt und beschrieben wurden. Dieses Dokument dient in der Folge auch als Grundlage für Ausarbeitung einer Dokumentation des Datenbankprojekts im Rahmen der Veröffentlichung.
In den vorangehenden Sitzungen wurden Grundlagen zur Modellierung von
Forschungsdaten und ihrer Strukturierung (ER-Diagramm) vermittelt.
Nachdem unsere Daten nun in sauberer Form in Calc-Tabellen vorliegen,
können wir in einem nächsten Schritt die Datensammlungen in eine
Datenbank importieren.
Eine theoretische Einführung in
relationale Datenbanken findet sich im entsprechenden Abschnitt des
Handbuchs1. Es wird empfohlen,
zunächst dort
einen Blick hineinzuwerfen, bevor Sie die Lektüre fortsetzen.
Für die Verwaltung und Verarbeitung unserer anzulegenden Datenbank
verwenden wir die grafische Benutzeroberfläche phpMyAdmin.
Mit der Anmeldung im DHVLab wurde für Sie automatisch eine
persönliche Datenbank angelegt. In dieser Datenbank können Sie ab sofort
Tabellen anlegen, Daten importieren/exportieren und modellieren. Um
diese Schritte möglichst komfortabel durchführen zu können, empfiehlt
sich die Verwendung der Benutzeroberfläche phpMyAdmin. Der Zugriff auf
phpMyAdmin ist über den Browser des Virtuellen Desktops möglich. Öffnen
Sie hierzu den Browser und geben Sie in die Adresszeile ein2:
web.dmz.dhvlab.fo/sql
Melden Sie sich wie
gewohnt mit Ihren Benutzerdaten an. Erkunden Sie zunächst die
Benutzeroberfläche. Einen nützliche Beschreibung finden Sie im
zugehörigen Abschnitt des Handbuchs3.
Beachten Sie stets, in welcher Navigationsebene Sie sich aktuell
befinden, bevor Sie auf "Importieren" klicken. Die zu importierende
Datei wird genau dort eingefügt. Wählen Sie eine Datei aus, die Sie
importieren möchten. Wählen Sie dann das Format der ausgewählten Datei
aus. Wenn Sie als Dateiformat CSV auswählen, müssen Sie bei den nun
angezeigten "Formatspezifischen Optionen" noch spezifizieren, durch
welches Zeichen die einzelnen Spalten in der Datei getrennt werden (z.B.
",", ";", "\t"). Um dies zu ermitteln, werfen Sie zuvor mit einem
Texteditor einen Blick in die CSV-Datei. Mit Klick auf "OK" schließen
Sie den Import der Tabelle ab. Sie wird an der entsprechenden Stelle in
der linken Auflistung angeführt.
Anwendungsfall:
Wählen Sie nun die eben importierte Tabelle an, um Sie wiederum aus der Datenbank zu exportieren. Klicken Sie anschließend auf "Exportieren" in der operativen Ebene. Bei "Art des Exports" können Sie es in der Regel "Schnell - nur notwendige Optionen anzeigen" belassen. Bei "Format" können Sie zwischen einer Reihe an Ausgabeformaten wählen. Sinnvollerweise speichern Sie Ihre Ausgabe entweder als SQL-File oder als CSV und wählen Ihren persönlichen Ordner als Speicherort.
Es gibt neben dem Import von Tabellen natürlich auch die Möglichkeit,
diese unmittelbar in phpMyAdmin anzulegen. Dies erfolgt auf zwei
verschiedene Arten, entweder manuell über die intuitive
Benutzeroberfläche oder durch Eingabe des entsprechenden SQL-Befehls5.
Anwendungsfall "via
Benutzeroberfläche" (Ortstabelle):
Anwendungsfall "SQL" (Grabstätten):
CREATE TABLE grabstaette (grabstaette_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(550), ort_id INT(11), kategorie_id INT(11), geokoordinate1 DECIMAL(10,7), geokoordinate2 DECIMAL(10,7), abbildung VARCHAR(550), wikipedia VARCHAR(550));
Sie können in Ihre neu angelegte Tabelle nun nach Belieben neue
Datensätze hinzufügen, bestehende überarbeiten oder löschen. Dies
erfolgt wiederum über die intuitiv gestaltete Benutzeroberfläche.
Anwendungsfall "Datensatz einfügen":
Übungsaufgaben
Im vorangehenden Abschnitt haben wir uns mit der Oberfläche des DBMS phpMyAdmin und ihren zentralen Funktionen (Import/Export, Anlegen neuer Tabellen, Einfügen und Bearbeiten von Datensätzen) vertraut gemacht. Dabei sind wir an einer Stelle bereits mit der Datenbanksprache SQL in Kontakt gekommen. Im Folgenden werden wir uns mit den Grundzügen dieser für uns sehr wichtigen Sprache auseinandersetzen1. Die Abfragen/Befehle der Datenbanksprache SQL lassen sich in drei Kategorien unterteilen:
Mit DDL können Veränderungen verschiedener Art an der bestehenden
Tabellenstruktur vorgenommen werden. Hierfür kommt das Statement "ALTER"
zum Einsatz, beispielsweise um eine Tabellenspalte zu ergänzen oder zu
löschen:
~ ALTER TABLE personen_sqluebung ADD sterbejahr_alt
varchar(4) AFTER sterbejahr
→ Die Tabelle
'personen_sqluebung' wird um eine Spalte namens 'sterbejahr_alt' mit
einer Länge von vier Zeichen ergänzt. ~ ALTER TABLE personen_sqluebung
DROP COLUMN sterbejahr_alt
→ Die Spalte 'sterbejahr_alt'
wird aus der Tabelle 'personen_sqluebung' entfernt.
Die Datenbearbeitungssprache DML ermöglicht uns das Auswählen
(SELECT), Einfügen (INSERT INTO), Ändern (UPDATE) und Löschen (DELETE
FROM/TRUNCATE TABLE) von Daten innerhalb von Tabellen. Der "SFW"-Block
(SELECT, FROM, WHERE) bzw. die "WHERE-Klausel" erlauben eine Selektion
einzelner Zeilen aus einer Tabelle, deren Inhalte einem bestimmten
Muster entsprechen3.
Anwendungsfall 1:
~ Geben Sie Funktion, Name und Geburtsjahr
aller Personen aus, die zwischen 1200 und 1500 geboren wurden.
→ SELECT funktion, name, geburtsjahr FROM personen_sqluebung WHERE
geburtsjahr >= "1200" AND geburtsjahr <= "1500"
Anwendungsfall 2:
~ Geben Sie Funktion, Name und
Geburtsjahr aller Personen aus, die zwischen 1200-1209 und 1500 geboren
wurden.
→ SELECT funktion, name, geburtsjahr FROM
personen_sqluebung WHERE geburtsjahr >= "120_" AND geburtsjahr <=
"1500"
_ genau ein beliebiges Zeichen trunkieren
% beliebig große Anzahl an Zeichen trunkieren
Anwendungsfall 3:
~ Geben Sie Funktion, Name und
Geburtsjahr aller Personen aus, die zwischen 1200 und 1500 geboren
wurden, sortiert nach dem Geburtsjahr in absteigender Reihenfolge:
→ SELECT funktion, name, geburtsjahr FROM personen_sqluebung WHERE
geburtsjahr >= "1200" AND geburtsjahr <= "1500" ORDER BY
geburtsjahr DESC
Ohne den Zusatz DESC
erfolgt
die Sortierung immer aufsteigend (ASC
)
Anwendungsfall 4:
~ Verwendung von sog.
"Korrelationsnamen", wenn man Spaltennamen mit alternativem Namen
ausgeben möchte. Dies ist insbesondere dann sinnvoll, wenn man mehrere
Tabellen miteinander verknüpft, die ähnliche oder gleiche Spaltennamen
besitzen (z.B. "name" → "Herrschername")
→ SELECT funktion
"Titel", name "Herrschername", geburtsjahr FROM personen_sqluebung WHERE
geburtsjahr >= "1200" AND geburtsjahr <= "1500"
~ Einfügen eines oder mehrerer Datensätze in eine bestehende
Tabelle:
→ INSERT INTO `personen_sqluebung` (`funktion`,
`name`) VALUES (Fürst, Franz), (Kaiserin, Sissi)
Es müssen
nicht alle Tabellenfelder befüllt werden; die Felder, welche befüllt
werden sollen, müssen genannt werden.
~ Import
von Datensätzen aus einer Tabelle 1 in eine Tabelle 2:
→
INSERT INTO tabelle2 (id, spalte2, spalte3) SELECT id, spalte2, spalte3
FROM tabelle1 WHERE id < 2;
~ Aktualisierung eines Wertes in einer bestehenden Tabelle:
→ UPDATE `personen_sqluebung` SET geburtsjahr = "1837" WHERE name
= "Sissi"
~ UPDATE, mehrere Spalten betreffend: → UPDATE
`personen_sqluebung` SET geburtsjahr = "1837", sterbejahr = "1900" WHERE
name = "Sissi"
~ Löschen von Inhalten, beispielsweise nach einem fälschlicherweise
ausgeführten INSERT INTO-Statement, bei dem in der Folge alle Einträge
ab einer bestimmten ID gelöscht werden müssen:
→ DELETE FROM
`personen_sqluebung` WHERE id_person > 29
Zur Anwendung des Erlernten bearbeiten Sie bitte folgende SQL-Übungsaufgaben:
Bitte senden Sie zur Überprüfung des Lernerfolges die hierfür benötigten SQL-Abfragen Ihrem Kursleiter zu.
→ SELECT * FROM personen_sqluebung WHERE geschlecht = 'w'
→ SELECT * FROM personen_sqluebung WHERE name LIKE 'R%'
→ SELECT * FROM personen_sqluebung WHERE function = 'König' OR
function = 'Kaiser'
Ergänzung zu
Abfrageergebnissen:
→ INSERT INTO `personen_sqluebung`(`name`, `funktion`,
`geschlecht`, `geburtsjahr`, `sterbejahr`) SELECT `herrscher`, `titel`,
`geschlecht`, `geburtsjahr`, `todesjahr` FROM `personen_zusatz`
Hinweis: Es ist nicht zwingend erforderlich, die
Tabelle zunächst in die eigene Datenbank zu kopieren. Es können
Datensätze auch aus einer anderen Datenbank (hier: Kursdatenbank)
importiert werden; hierzu muss die Datenbank entsprechend gekennzeichnet
werden, was durch Datenbankname.Tabellenname (durch einen Punkt
verbunden) erfolgt.
→ INSERT INTO
`personen_sqluebung`(`name`, `funktion`, `geschlecht`, `geburtsjahr`,
`sterbejahr`) SELECT `herrscher`, `titel`, `geschlecht`, `geburtsjahr`,
`todesjahr` FROM `lab_grabdenkmaeler.personen_zusatz`
→
UPDATE `personen` SET name = "Rudolf I." WHERE id_person = 5
→ DELETE FROM personen_sqluebung WHERE id_person > 26
Bisher haben wir immer nur mit einer Tabelle gearbeitet. Das Potential relationaler Datenbanken kommt aber erst zur Geltung, wenn man die vorher fein säuberlich getrennten Daten nun wieder in Verbindung setzt - und das erfolgt mit sogenannten Joins1. Bildlich gesprochen werden mehrere Tabellen nebeneinander gelegt. Wir unterscheiden verschiedene Arten von Joins:
Anwendungsfall "INNER JOIN":
~ Sie möchten die
Grabstätten samt ihrer zugehörigen Orte in einer Tabelle ausgeben,
jedoch nur den Namen der Grabstätte, ihre Geokoordinaten, den Ortsnamen
und den Staat.
→ SELECT name, geokoordinate1,
geokoordinate2, ortsname, staat FROM grabstaette INNER JOIN ort ON
(grabstaette.id_ort = ort.id_ort)
Anwendungsfall "LEFT JOIN":
Bei einem LEFT JOIN werden alle
Datensätze der "linken" Tabelle ausgegeben, auch wenn sie in der
"rechten" Tabelle keine Entsprechung aufweisen. Die Datensätze der
rechten Tabelle werden dagegen nur mit ausgegeben, wenn sie eine
Entsprechung aufweisen, ansonsten wird das entsprechende Feld mit NULL
ausgegeben. ~ Sie möchten sich alle Grabstätten anzeigen lassen und die
zugehörigen Orte, sofern diese bereits vorhanden sind. Fälle, in denen
der Ort nicht vorhanden ist, werden mit NULL ausgegeben. Damit können
Sie sich einen Überblick verschaffen, wo noch nach Orten gesucht werden
muss.
→ SELECT grabstaette.name, ort.ortsname FROM
grabstaette LEFT JOIN ort ON grabstaette.id_ort = ort.id_ort
Anwendungsfall "RIGHT JOIN":
Beim RIGHT
JOIN verhält es sich gegensätzlich zum LEFT JOIN, d.h. hier wird die
Haupttabelle "grabstaette" mit der Tabelle ort "rechts herum" verbunden.
Hier werden also alle Datensätze der rechten Tabelle (ort) angezeigt und
fehlende der linken Tabelle (grabstaette) mit NULL ausgefüllt.
~ Sie möchten sich alle Orte anzeigen lassen und die zugehörigen
Grabstätten, sofern diese bereits vorhanden sind. Fälle, in denen die
Grabstätte nicht vorhanden ist, werden mit NULL ausgegeben.
→ SELECT grabstaette.name, ort.ortsname FROM grabstaette LEFT JOIN ort
ON grabstaette.id_ort = ort.id_ort2
Anwendungsfall "OUTER JOIN":
Beim OUTER JOIN handelt es sich
um eine Kombination aus einem LEFT- und einem RIGHT JOIN. Es werden alle
Datensätze beider Tabellen ausgegeben, bei Übereinstimmung werden sie
verknüpft, ansonsten wie gehabt mit NULL wiedergegeben. Da SQL keinen
genuinen OUTER JOIN unterstützt, muss mit einem UNION-Statement
nachgeholfen werden (= Verknüpfung des Ergebnisses zweier Abfragen) ~
Sie möchten sich alle Grabstätten und alle Orte in einer Tabelle
ausgeben lassen, auch wenn diese keine jeweilige Verknüpfung zueinander
vorweisen können.
→ SELECT grabstaette.name, ort.ortsname
FROM grabstaette LEFT JOIN ort ON grabstaette.id_ort = ort.id_ort UNION
SELECT grabstaette.name, ort.ortsname FROM grabstaette RIGHT JOIN ort ON
grabstaette.id_ort = ort.id_ort
In manchen Fällen erweitern wir das Angebot über die Angaben bei
Lauro hinaus, beispielsweise wenn dort nur eine Abtei oder Kirche
angegeben ist, nicht aber wo diese gelegen ist (zum Beispiel "Stift
Rein").
Damit wir nicht Gefahr laufen, dass Einträge doppelt
erfasst werden, wurden davon betroffene Fälle im Team-Dokument gesammelt
(d.h. neben der oben angeführten Überprüfung muss ergänzend hier ein
Blick hinein geworfen werden, um Redundanzen zu vermeiden).
Zur Vertiefung des Erlernten werden wir im Folgenden eine etwas
komplexere Datenbankabfrage über mehrere Tabellen erarbeiten. Ziel
unserer Beispielabfrage ist es die Grabstätten mit den meisten darin
befindlichen Habsburgern zu ermitteln. Dabei sollen nur Bestattete
berücksichtigt werden, die ausschließlich an einem Ort liegen.
Die Verbindung der Personen mit ihren Grabstätten
erfolgt über die Verknüpfungstabelle "begraben". Zum besseren
Verständnis gehen wir Schritt für Schritt vor.
SELECT personen.name, personen.funktion, begraben.id_grabstaette, begraben.bestattungsart, begraben.komplett FROM personen INNER JOIN begraben ON personen.id_person = begraben.id_person;
SELECT personen.name, personen.funktion, begraben.bestattungsart, begraben.komplett, grabstaette.name, grabstaette.geokoordinate1, grabstaette.geokoordinate2 FROM personen INNER JOIN (begraben INNER JOIN grabstaette ON begraben.id_grabstaette = grabstaette.id_grabstaette) ON personen.id_person = begraben.id_person
SELECT personen.name, personen.funktion, begraben.bestattungsart, begraben.komplett, grabstaette.name, ort.ortsname, grabstaette.geokoordinate1, grabstaette.geokoordinate2 FROM personen INNER JOIN (begraben INNER JOIN (grabstaette INNER JOIN ort ON grabstaette.id_ort = ort.id_ort) ON begraben.id_grabstaette = grabstaette.id_grabstaette) ON personen.id_person = begraben.id_person
SELECT personen.name, personen.funktion, begraben.bestattungsart, begraben.komplett, grabstaette.name, ort.ortsname, grabstaette.geokoordinate1, grabstaette.geokoordinate2 FROM personen INNER JOIN (begraben INNER JOIN (grabstaette INNER JOIN ort ON grabstaette.id_ort = ort.id_ort) ON begraben.id_grabstaette = grabstaette.id_grabstaette) ON personen.id_person = begraben.id_person WHERE bestattungsart = "1"
SELECT personen.name, personen.funktion, begraben.bestattungsart, begraben.komplett, grabstaette.name, ort.ortsname, grabstaette.geokoordinate1, grabstaette.geokoordinate2 FROM personen INNER JOIN (begraben INNER JOIN (grabstaette INNER JOIN ort ON grabstaette.id_ort = ort.id_ort) ON begraben.id_grabstaette = grabstaette.id_grabstaette) ON personen.id_person = begraben.id_person WHERE bestattungsart = "1" AND komplett = "1"
Merke: Bei zunehmender Komplexität der Abfrage nimmt die Zahl der Ergebnisse entsprechend ab.
SELECT personen.name, personen.funktion, begraben.bestattungsart, begraben.komplett, grabstaette.name, COUNT(*) "Anzahl", ort.ortsname, grabstaette.geokoordinate1, grabstaette.geokoordinate2 FROM personen INNER JOIN (begraben INNER JOIN (grabstaette INNER JOIN ort ON grabstaette.id_ort = ort.id_ort) ON begraben.id_grabstaette = grabstaette.id_grabstaette) ON personen.id_person = begraben.id_person WHERE bestattungsart = "1" AND komplett = "1" GROUP BY grabstaette.name
SELECT begraben.bestattungsart, begraben.komplett, grabstaette.name, COUNT(*) "Anzahl", GROUP_CONCAT(personen.name, personen.funktion), ort.ortsname, grabstaette.geokoordinate1, grabstaette.geokoordinate2 FROM personen INNER JOIN (begraben INNER JOIN (grabstaette INNER JOIN ort ON grabstaette.id_ort = ort.id_ort) ON begraben.id_grabstaette = grabstaette.id_grabstaette) ON personen.id_person = begraben.id_person WHERE bestattungsart = "1" AND komplett = "1" GROUP BY grabstaette.name
SELECT begraben.bestattungsart, begraben.komplett, grabstaette.name, COUNT(*) "Anzahl", GROUP_CONCAT(personen.name, ', ', personen.funktion SEPARATOR '\n') "Personen", ort.ortsname, grabstaette.geokoordinate1, grabstaette.geokoordinate2 FROM personen INNER JOIN (begraben INNER JOIN (grabstaette INNER JOIN ort ON grabstaette.id_ort = ort.id_ort) ON begraben.id_grabstaette = grabstaette.id_grabstaette) ON personen.id_person = begraben.id_person WHERE bestattungsart = "1" AND komplett = "1" GROUP BY grabstaette.name
SELECT begraben.bestattungsart, begraben.komplett, grabstaette.name, COUNT(*) "Anzahl", GROUP_CONCAT(personen.name, ', ', personen.funktion ORDER BY personen.name SEPARATOR '\n') "Personen", ort.ortsname, grabstaette.geokoordinate1, grabstaette.geokoordinate2 FROM personen INNER JOIN (begraben INNER JOIN (grabstaette INNER JOIN ort ON grabstaette.id_ort = ort.id_ort) ON begraben.id_grabstaette = grabstaette.id_grabstaette) ON personen.id_person = begraben.id_person WHERE bestattungsart = "1" AND komplett = "1" GROUP BY grabstaette.name ORDER BY Anzahl DESC
SELECT begraben.bestattungsart, begraben.komplett, grabstaette.name, COUNT(*) "Anzahl", GROUP_CONCAT(personen.name, ', ', personen.funktion ORDER BY personen.name SEPARATOR '\n') "Personen", ort.ortsname, grabstaette.geokoordinate1, grabstaette.geokoordinate2 FROM personen INNER JOIN (begraben INNER JOIN (grabstaette INNER JOIN ort ON grabstaette.id_ort = ort.id_ort) ON begraben.id_grabstaette = grabstaette.id_grabstaette) ON personen.id_person = begraben.id_person WHERE bestattungsart = "1" AND komplett = "1" GROUP BY grabstaette.name Having Anzahl > 3 ORDER BY Anzahl DESC
Ergebnisabfrage unmittelbar über die
phpMyAdmin-Oberfläche visualisieren:
Damit wir die Daten, die wir in den vergangenen Sitzungen strukturiert erfasst haben, in Kartenform visualisieren können, benötigen wir verschiedene Komponenten1:
Hinweis: Selbstverständlich ist es auch möglich, alle
oben genannten Informationen in der HTML-Datei unmittelbar zu vereinen
und auf das Anlegen mehrerer Dateien zu verzichten. Sobald jedoch
bestimmte Elemente nicht nur in einer, sondern in mehreren Seiten
eingebunden werden sollen, ist es sinnvoller, diese einmal an zentraler
Stelle für alle Dateien abzulegen2.
Auf dem Virtuellen Desktop legt jeder Teilnehmer im Bereich
"Persönlicher Ordner" einen Ordner "habsburg" an; dieser Ordner
simuliert unseren Webspace (normalerweise auf dem Webserver). In diesem
Ordner legen wir zwei Unterordner an, zum einen "css" für Cascading
Stylesheet, zum anderen "js" für JavaScript.
Anschließend öffnen wir einen Editor unserer Wahl (im DHVLab
beispielsweise Kate oder Sublime 2) und erstellen drei neue
Textdateien:
Im Folgenden wenden wir uns der Erstellung der HTML-Datei zu:
<!DOCTYPE html>
<html lang=" de">
<head>
<!-- Das ist unser ein Kommentar, den der
Browser nicht ausgibt-->
<!-- Head-Bereich, enthält
Metainformationen über die Webseite (werden nicht im Browser ausgegeben) -->
<title>Habsburger Grabdenkmäler</title>
<!-- Seitentitel, im Browserfenster, Leesezeichen, Suchmaschinen -->
<meta charset="utf-8">
<!-- Mit utf-8 können Sonderzeichen direkt genutzt werden (im Deutschen z.B.
Umlaute ö, ä, ü) -->
<link rel="stylesheet" type="text/css" href="css/style.css">
<!-- Datei style.css mit Informationen zur Gestaltung unserer HTML-Seite -
befindet sich im Ordner css -->
<script type="text/javascript"
src="http://maps.googleapis.com/maps/api/js?v=3.exp"></script>
<!-- Einbinden der Google Maps API -->
<script type="text/javascript" src="js/map.js"></script>
<!--
Einbinden unserer JavaScript-Datei, die die Funktionen zu unserer Karte enthalten
wird. -->
</head>
<body>
<!-- Umfasst den Inhalt, der im Browser ausgegeben wird -->
<h1>Die Grabstätten der Habsburger</h1>
<h2>Ein Datenbankprojekt des Hauptseminars, WiSe 2016/17</h2>
<!-- Überschriften vom Typ 1 und 2 -->
<div id="canvas"></div>
<!-- Das Google-Maps Layer wird mit einem div-Container eingebunden -->
<p align="center"> Ich bin ein Textblock mit einem
/> Zeilenumbruch
darin. Außerdem findet sich hier ein Link auf unsere <b>'echte'</b>
<a
href="http://www.habsburg.gwi.uni-muenchen.de/">Habsburg-Projektseite</a>.
Wenn wir möchten, dass sich der Link in einem neuen Fenster öffnet,
fügen wir noch das Attribut <i>target="blank"</i> hinzu: Wir verlinken
also auf erneut auf die <a target="blank"
href="http://www.habsburg.gwi.uni-muenchen.de/">Habsburg-Projektseite.</a></p>
<button onmousedown="init(); marker();">Marker</button>
<button onmousedown="line();">Linie</button>
<!-- Es werden zwei Schaltflächen erstellt, die unterschiedliche
Funktionalitäten besitzen. Bei Klick auf die erste Schaltfläche werden
die Marker gesetzt, bei der zweiten eine Verbindungslinie zwischen den
Markern gezogen. Ihre Funktionalität erhalten sie über
JavaScript-Funktionen, zu denen wir weiter unten kommen
werden.-->
</body>
Nun bearbeiten wir die erstellte CSS-Datei im Editor, um ausgewählten
Bereichen in unserer HTML-Datei Gestaltungsmerkmale zu übergeben. Wir
beschränken uns dabei auf einige wenige Merkmale:
html, body {
height: 100%;
margin: 0px;
padding: 0px;
}
#canvas {
width: 90%;
height: 80%;
margin: auto;
}
/* Ein Kommentar in CSS */
/* canvas ist
der Bereich für den Google Maps Layer. Das Doppelkreuz signalisiert,
dass es sich hier um ein bestimtes div-Element mit der ID "canvas"
handelt. */
h1 {
text-align: center;
}
h2 {
text-align: center;
color: green;
}
In diesem ersten Beispiel werden wir eine Karte erstellen, auf der
sich mehrere, verschiedenfarbige Marker befinden und diese mit einer
Linie verbinden. Sowohl die Marker als auch die Linien sollen nur dann
erscheinen, wenn der Benutzer auf den zugehörigen Button klickt.
onload = init;
//mit dem Laden der Seite soll das Initialisieren der Karte einhergehen.
var map, mapOptions
var muenchen = new google.maps.LatLng(48.149600, 11.574481)
var paris = new google.maps.LatLng(48.859836, 2.359529)
var frankfurt = new google.maps.LatLng(50.110556, 8.682222)
var m1, m2, m3
// Variablen können nebeneinander, durch Kommata getrennt, geschrieben werden.
// Wir weisen den geographischen Längen- und Breitenangaben Variablennamen zu
(muenchen, paris, frankfurt)
// m1, m2, m3 sind Variablen, die wir als Marker unten zu diesen Geodaten einsetzen
werden
// Zunächst einmal muss die Google Maps initalisiert werden.
function init() {
map = new google.maps.Map(
// Der Variable map wird die JavaScript-Klasse google.maps.Map zugewiesen.
// Wir legen eine neue Instanz dieser Klasse mithilfe des JavaScript-Operators new
an.
document.getElementById("canvas"),
// Referenz zum JavaScript-Objekt document.
mapOptions = {
//
Nun definieren wir mit der Variable mapOptions das Aussehen unserer Karte; es sind
kaum Grenzen gesetzt.
center: new google.maps.LatLng(48.149600, 11.574481),
zoom: 7,
streetViewControl:false,
scaleControl: true,
mapTypeControl:true,
mapTypeControlOptions: {
style: google.maps.MapTypeControlStyle.HORIZONTAL_BAR,
position: google.maps.ControlPosition.BOTTOM_CENTER
},
zoomControl:true,
zoomControlOptions: {
position: google.maps.ControlPosition.RIGHT_BOTTOM
},
mapTypeId: google.maps.MapTypeId.TERRAIN
}
);
}
// die verwendeten Optionen finden sich allesamt in der offiziellen Google Maps
API-Beschreibung:
// https://developers.google.com/maps/documentation/javascript/tutorial?hl=de
// Probiert die unterschiedlichen Möglichkeiten aus, indem ihr false durch true
ersetzt, andere Koordinaten einsetzt, eine andere Zoomstufe oder die in KAPITÄLCHEN
gesetzten Style-Informationen verändert.
function marker() {
// Eine weitere Funktion, die dem Setzen von Markern gilt.
m1 = new google.maps.Marker(
{ map: map, position: muenchen, animation: google.maps.Animation.DROP, icon:
"http://maps.google.com/mapfiles/ms/icons/blue-dot.png"
});
// Wir weisen der oben definierten Variable m1 die JavaScript-Klasse
google.maps.Marker zu.
// In geschweiften Klammern finden sich Anweisungen, was das Programm erledigen
soll:
// "Verwende die Karte "map", setze den Marker an die Position "muenchen" (also die
eingangs definierte Variable mit den Geokoordinaten). "Wende die Animation "Drop"
an, verwende ein blaues Icon. Die Standardfarbe ist rot -> siehe Frankfurt.
m2 = new google.maps.Marker(
{ map: map,
position: paris, animation: google.maps.Animation.DROP, icon:
"http://maps.google.com/mapfiles/ms/icons/green-dot.png"
});
m3 = new google.maps.Marker(
{ map: map, position: frankfurt, animation: google.maps.Animation.DROP
});
} function line() {
// Es gibt viele weitere Möglichkeiten der Visualisierung, z.B. die Verbindung
einzelner Marker mit Linien.
new google.maps.Polyline(
{
map: map, path: [muenchen, frankfurt, paris], geodesic: true
})
}
// wir binden diese Funktion über einen Button in die HTML-Datei ein.
Im folgenden wählen wir einen leicht veränderten Ansatz. Wir nehmen
an, dass durch ein vorgeschaltetes PHP-Skript Datensätze aus unserer
Datenbank in die JS-Datei in Form eines Arrays eingespeist wurden. Auf
diese Informationen wird im Anschluss dynamisch zugegriffen:
onload = init;
var map, mapOptions
var marker = []
var p = [
['Adelgunde von Bayern','Herzogin von
Modena','w','1823','1914','61234',48.205673,16.370173],
['Adelheid (Maria Adelaida)','Königin von
Sardinien','w','1855','231856',45.080833,7.767500]
];
// Vorteil: der komplette DB-Bestand wird zunächst abgerufen und in die Karte
eingebunden; dadurch sind alle eingebundenen Funktionen (z.B. eine Timeline) nach
einmaligem Laden durch den Benutzer auch offline benutzbar.
// Zunächst einmal muss die Google Maps initialisiert werden.
function init() {
map = new google.maps.Map(
// Der Variable map wird die JavaScript-Klasse google.maps.Map zugewiesen.
// Wir legen eine neue Instanz dieser Klasse mithilfe des JavaScript-Operators new
an.
document.getElementById("canvas"),
// Referenz zum JavaScript-Objekt document.
mapOptions = {
// Nun definieren wir mit der Variable mapOptions das Aussehen unserer Karte; es
sind kaum Grenzen gesetzt.
center: new google.maps.LatLng(48.149600, 11.574481),
zoom: 7,
streetViewControl:false,
scaleControl: true,
mapTypeControl:true,
mapTypeControlOptions: {
style: google.maps.MapTypeControlStyle.HORIZONTAL_BAR,
position: google.maps.ControlPosition.BOTTOM_CENTER
},
zoomControl:true,
zoomControlOptions: {
position: google.maps.ControlPosition.RIGHT_BOTTOM
},
mapTypeId: google.maps.MapTypeId.TERRAIN
}
);
for (var i in p){
marker[i] = new google.maps.Marker(
{map: map, position: new google.maps.LatLng(p[i][6], p[i][7]), opacity: 0.66, title:
p[i][0], id:i, label: '*'});
}
// For-Schleife wiederholt die Anweisung, solange die Bedingung (var i in p
vorhanden) zutrifft. Der nachfolgende Anweisungsblock wird beim Durchlauf
ausgeführt, in unserem Fall 2x.
// Bei den einzelnen Parametern wird jeweils die Position der Information im Array
angegeben (z.B. p[i][0] = Name der Person). Label fügt dem Marker ein Symbol
hinzu.
}