Entwurf, Realisierung und Betrieb einer temporalen Erweiterung von

Werbung
Fakultät für Elektrotechnik und Informatik
Institut für Praktische Informatik
Fachgebiet Datenbanken und Informationssysteme
Entwurf, Realisierung und Betrieb
einer temporalen Erweiterung
von relationalen Datenbanken
Masterarbeit
im Studiengang Informatik
Marcell Salvage
Matrikelnummer: 2946190
Prüfer: Prof. Dr. Udo Lipeck
Zweitprüfer: Dr. Hans Hermann Brüggemann
Betreuer: Prof. Dr. Udo Lipeck
20.04.2015
Inhaltsverzeichnis
1 Einleitung
1.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.2 Ziel und Aufbau . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2 Grundlagen
2.1 Temporale Daten . . . .
2.1.1 Zeitmodelle . . .
2.1.2 Zeitpunkte . . . .
2.1.3 Zeitintervalle . .
2.1.4 Zeitspannen . . .
2.1.5 Zeitdimensionen .
2.2 PostgreSQL . . . . . . .
2.2.1 Extensions . . . .
2.2.2 Datentyp Range .
2.3 Bison & Flex . . . . . .
1
1
2
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
4
4
4
5
6
12
12
13
13
16
19
3 Temporale Tabellen
3.1 Valid Time-Tabellen . . . . . . . . . . . .
3.1.1 Anforderungskatalog: Constraints .
3.1.2 Anforderungskatalog: Anfragen . .
3.1.3 Anforderungskatalog: Modifikation
3.2 Transaction Time-Tabellen . . . . . . . . .
3.3 Bitemporale Tabellen . . . . . . . . . . . .
3.4 Herausforderungen & Probleme . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
24
24
25
28
30
34
36
36
4 Temporale Datenbanksprachen
4.1 TSQL2 . . . . . . . . . . . . . . .
4.2 SQL:2011 . . . . . . . . . . . . .
4.2.1 Valid Time-Tabellen . . .
4.2.2 Transaction Time-Tabellen
4.2.3 Bitemporal-Tabellen . . .
4.3 VTSQL2+ . . . . . . . . . . . . .
4.4 Evaluation . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
38
38
41
42
45
48
48
52
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
III
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
5 Entwurf und Implementierung
5.1 SQL-Erweiterung . . . . . . . . . . . .
5.1.1 DDL/DML-Kommandos . . . .
5.1.2 Anfragen . . . . . . . . . . . . .
5.2 Funktionen der PostgreSQL Extension
5.2.1 Allen-Relationships-Funktionen
5.2.2 Funktionen für DDL/DML . . .
5.2.3 Trigger . . . . . . . . . . . . . .
5.2.4 Anfrage-Hilfsfunktionen . . . .
5.3 Übersetzung der SQL-Erweiterung . . .
5.3.1 Parser . . . . . . . . . . . . . .
5.3.2 Abstract Syntax Tree (AST) . .
5.3.3 Übersetzung . . . . . . . . . . .
5.4 PHP-Interface . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
54
55
55
58
59
60
61
62
66
67
68
68
69
78
6 Beispieldatenbank Modulkatalog
6.1 Datentyp Semester . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.2 Studiengänge und Kompetenzbereiche . . . . . . . . . . . . . . . . . . . .
6.3 Lehrveranstaltungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
81
82
83
85
7 Fazit & Ausblick
87
A PostgreSQL-Extension:
Datentyp Semester
89
Abbildungsverzeichnis
95
Tabellenverzeichnis
97
Literaturverzeichnis
99
IV
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Kapitel 1
Einleitung
Entwurf, Realisierung und Betrieb einer temporalen Erweiterung; das sind 3 Aufgaben,
welche historisch bereits viele Male angegangen wurden. In der Einleitung soll zunächst
mithilfe der Motivation dargestellt werden, warum vorliegende Ausarbeitung dieses Thema
aufgreift. Nach der Motivation wird die Struktur in kurzen Sätzen dargestellt. Dadurch
wird ersichtlich, in welchem Kapitel die Aufgaben aufgegriffen und gelöst werden.
1.1
Motivation
Die Vergangenheit und Zukunft gehören zu Datenbanken wie die Gegenwart, jedoch
liefern die üblichen relationalen DBMS kaum Unterstützung, temporale Datenbanken
zu pflegen. Versuche, temporale Elemente in den SQL-Standard zu integrieren, gab es
schon vor 20 Jahren, genauer genommen seit 1994. Es wurde eine Initiative gegründet,
angeführt von Snodgrass, um SQL mit temporalen Elementen zu erweitern. 1992 gab es
bereits Diskussionen in der Community und nach einem Vorschlag von Snodgrass wurde
ein Komitee gegründet, welches mit der Entwicklung von TSQL21 beauftragt wurde. Die
Sprache versank etwas in Vergessenheit, bis der SQL-Standard SQL:2011 das Thema
erneut aufgegriffen hat, jedoch gibt es immer noch keine Einigung über die tatsächliche
Realisierung der einzelnen Bestandteile. Viele Unternehmen speichern historische Daten
auf ihre eigene Weise, so dass Interoperabilität zwischen den Daten ausgeschlossen ist. Es
besteht ein großer Markt für die Verwaltung temporaler Daten aller Art, insbesondere,
wenn sie transparent und über einen langen Zeitraum geschehen soll.
Ein Beispielszenario für temporale Daten ist, wie in der Literatur oft erwähnt, eine Datenbank über Beschäftigungsverhältnisse. Beschäftigungsverhältnisse verändern
sich über einen Zeitraum, Mitarbeiter wechseln ihre Position im Unternehmen und es
verändern sich Arbeitsumstände, wie Gehalt oder auch Beziehungen wie der Manager
einer Abteilung, der für diverse Angestellte verantwortlich ist. Ein anderes Szenario
fernab der gängigen Literatur, auf das in dieser Ausarbeitung eingegangen wird, ist
die Verwaltung von Veranstaltungen in Studiengängen in einem Online-Modulkatalog.
Dort wird festgehalten, welche Veranstaltungen in dem aktuellen Semester angeboten
1
Eine SQL-92 Erweiterung, näher beschrieben in Kapitel 4.1
1
werden, einschließlich ihrer Eigenschaften, wie Semesterwochenstunden, Leistungspunkte,
Beschreibung, Lehrkraft und vielen weiteren. Jedoch interessiert häufig nicht nur der
aktuelle Zustand. Studenten in späteren Semestern referenzieren ältere Veranstaltungen,
spätestens bei ihrem Abschluss auf dem Abschlusszeugnis. Oft wechseln angebotene
Veranstaltungen oder sie verschwinden gar vollständig. Trotzdem dürfen Informationen
über sie nicht verloren gehen, sie bleiben historisch relevant. Außerdem interessieren
Veranstaltungen aus dem vorherigen Semester, wenn Nachholtermine für Prüfungen
angeboten werden. Andere Beispiele für temporale Daten sind Data Warehouses und
das Bankenwesen. Dort werden sie auch regelmäßig für Analyseverfahren angefragt und
ausgewertet.
Diese Ausarbeitung befasst sich mit den Forschungsergebnissen der letzten Jahre im
Zusammenhang mit aktuellen Erkenntnissen und Technologien, um eine Schnittstelle zu
erstellen, welche die genannte Erfassung und Verwaltung temporaler Daten vereinfacht
und unterstützt. Zusätzlich soll sie ohne große Schwierigkeiten in ein bestehendes Datenbanksystem integriert werden können und Abwärtskompatibilität zu der bisherigen
Schnittstelle gewährleisten. So soll es einfach möglich sein, ein bisheriges Schema mit
wenigen Modifikationen zu temporalisieren und, wenn nötig, temporale Daten abzufragen,
wenn diese benötigt werden. Das gewährleistet höchste Kompatibilität mit bekannten
Schnittstellen und trotzdem Flexibilität. Dafür werden PostgreSQL-Extensions verwendet,
welche mit einem Parser eine abwärtskompatible Erweiterung von SQL lesen und mit der
Extension Informationen austauschen.
1.2
Ziel und Aufbau
Nach der Einleitung im ersten Kapitel werden im zweiten Kapitel die nötigen Grundlagen
erläutert, die eine temporale Datenhaltung erst möglich machen. Zu den Grundlagen
gehören zunächst Grundbegriffe der temporalen Datenhaltung wie die existierenden
Zeitmodelle, verschiedene Zeittypen wie beispielsweise Zeitpunkte und Zeitintervalle
sowie die Zeitdimensionen, das heißt Valid Time und Transaction Time. Zusätzlich
werden in den nachfolgenden Unterkapiteln die verwendeten Technologien beschrieben,
PostgreSQL sowie die Entwicklung von Extensions und Bison und Flex für die Erstellung
eines Parsergenerators.
Das dritte Kapitel bespricht die verschiedenen Arten von temporalen Tabellen und
welche Probleme und Herausforderungen dazukommen, wenn die Entscheidung für eine
temporale Datenbasis getroffen wird. Anhand von Beispielen werden temporale Aktionen
gezeigt und die Lösung in SQL implementiert dargestellt. Das gilt als Grundlage für die
im Kapitel 5 gezeigte Implementierung.
Im vierten Kapitel werden vergangene temporale SQL-Erweiterungen des SQLStandards betrachtet, um eine Basis für die in der Ausarbeitung entwickelte SQLErweiterung zu schaffen. Dafür werden Beispiele für übliche Aktionen dargestellt und
anschließend die Sprachen in ihrer Funktionalität evaluiert. Die Erkenntnisse aus diesem
Kapitel fließen in die Entwicklung der SQL-Erweiterung dieser Ausarbeitung mit ein.
Anschließend wird im fünften Kapitel der Entwurf der drei Bestandteile der ent-
2
wickelten DB-Erweiterung erklärt. Als erstes wird der Aufbau der Erweiterung des
SQL-Standards vorgestellt. Dafür wird gezeigt, welche neuen Schlüsselwörter hinzugekommen sind und wie die neue Semantik der einzelnen SQL-Anfragen ist. Danach wird
die PostgreSQL-Extension entworfen, welche die benötigten Funktionen und Trigger
erklärt. Dabei wird dargestellt, welche Methoden in welchem Teil der SQL-Erweiterung
benutzt werden und anhand von Beispielen ihre Funktionalität beschrieben. Im letzten
Teil des Kapitels wird die Übersetzung der SQL-Erweiterung in Standard-SQL anhand
von Beispielen gezeigt. Dafür wird der benötigte abstrakte Syntaxbaum und wie er in
der Übersetzung benutzt wird beschrieben.
Im sechsten Kapitel findet eine Testimplementierung einer temporalen Datenbank mit
Hilfe der entwickelten Bausteine statt. Dafür wird der momentan vorhandene Modulkatalog der Fakultät mit den entwickelten Konzepten neu aufgebaut. Anschließend werden
die Unterschiede analysiert und die Daten überführt. An die entstehende Datenbank
werden analog passende Anfragen gestellt, die den Anforderungen an den Modulkatalog
angepasst sind.
Den Abschluss der Arbeit bildet das Fazit, welches die Ergebnisse zusammenfasst
und auf mögliche weitere Bestandteile der Erweiterung von SQL hinweist.
3
Kapitel 2
Grundlagen
Im folgenden Kapitel werden nötige Grundlagen für den Aufbau der Erweiterung beschrieben. Dabei ist es wichtig zunächst die Bestandteile von temporalen Daten im Allgemeinen
kennen zu lernen, um den Aufbau von temporalen Datenbanken und Datenbanksprachen
verstehen zu können. Anschließend wird eine kleine Einführung in PostgreSQL mit Fokus
auf Extensions und dem Datentyp Range gegeben.
2.1
Temporale Daten
Temporale Daten sind Informationen, welche mit Zeitangaben abgespeichert werden.
Dieser Vorgang, Daten mit einem Zeitstempel zu versehen und anschließend mit dieser
Angabe abzuspeichern, klingt vorerst einfach. Dabei entstehen jedoch zusätzliche Anforderungen und Probleme. Nachfolgend wird zunächst die Zeit im Allgemeinen erklärt; welche
Formen von Zeit existieren? Anschließend werden die verschiedenen Zeitdimensionen
betrachtet. Dadurch entsteht ein Überblick der Arten von temporalen Tabellen, die in
der Literatur besprochen werden.
2.1.1
Zeitmodelle
Zeitmodelle sind ein Bestandteil von temporalen Informationen. Sie beschreiben die
Beschaffenheit von Zeit in den Daten. In [TYT11] wird zwischen drei verschiedenen
Modellen unterschieden. Das Modell, welches der Realität am nächsten kommt, ist das
kontinuierliche Modell. Dort ist jeder Zeitpunkt mit einer reellen Zahl dargestellt. Dieses
Modell hat eine hohe Genauigkeit, ist jedoch dank der Gegebenheit von Computern
immer mit Verlust verbunden. In der Realität wird dieses Modell selten eingesetzt. Im
diskreten Modell wird die Zeit in Chronons (näher beschrieben in Kapitel 2.1.2) unterteilt,
zum Beispiel Kalendermonate oder Jahre. So gibt es in diesem Modell für jedes Chronon
genau einen Abschnitt. Ein drittes Modell ist das nicht-temporale Modell, welches Daten
mit Zeit beinhaltet, die nicht durch ihre Zeit identifiziert werden, zum Beispiel das
Geburtsdatum. In temporalen Datenbanken beschäftigen wir uns mit dem diskreten
Modell, da es sich für die Abbildung von Daten mit temporalen Eigenschaften am besten
4
eignet und Anfragen sich logischer gestalten lassen, wie man in den nächsten Kapiteln
über die Zeitpunkte und Zeitintervalle sehen kann.
2.1.2
Zeitpunkte
Zeitpunkte, wie in [JW10] beschrieben, sind eine Momentaufnahme der Zeit. Sie beschreiben ein Chronon in der Zeitlinie. Verschiedene Zeitpunkte können nicht nur unterschiedliche Punkte in der Zeitlinie darstellen, es gibt auch Optionen, die bestimmen wie fein die
Zeit abgebildet wird. Diese Feinheit der Zeit bezeichnet man als Granularität. Beispiele
für solche Granularitäten sind Tage, Kalendermonate oder Jahre. So wäre ein Zeitpunkt
2012 mit der Granularität Jahr behaftet, 2012-04-04 12:30:50 hätte die Granularität
Sekunde. Die kleinste darstellbare Zeit, die sich mit einer angegebenen Granularität
darstellen lässt, heißt Chronon. So ist das Chronon vom gegebenen Zeitpunkt 2012 genau
ein Jahr. Dieses Jahr lässt sich nicht teilen, das heißt, wenn zwei Ereignisse in 2012
passiert sind, lassen sie sich nicht mit unterschiedlichen Zeitpunkten in der Granularität
Jahr darstellen. Es existieren auch Granularitäten, die nur in bestimmten Domänen
sinnvoll sind. So wird im Modulkatalog die Granularität eines Semesters gewählt, da
die Gültigkeit der einzelnen Elemente sich innerhalb eines Semesters nicht verändern.
Weitere Beispiele für ungewöhnliche, aber dennoch mögliche Granularitäten, wären eine
Dekade (10 Jahre) oder ein Trimester, welches auch im akademischen Raum benutzt
wird.
Zu den Zeitpunkten mit Granularitätsangaben kommt zudem noch das Problem
der Konvertierung zwischen verschiedenen Granularitäten. In welcher Beziehung stehen
zwei Zeitpunkte mit unterschiedlicher Granularität? Ist der Zeitpunkt 2014-03-20 in der
Granularität Tag vor oder nach dem Zeitpunkt 2014 mit der Granularität Jahr? Auch
die Frage, wie man eine Konvertierung bei kleiner werdender Granularität vornimmt,
kommt dabei auf. Wenn man genau definieren würde, dass bei der Konvertierung von
Jahr zu Tag der Zeitpunkt 2012 den Zeitpunkt 2012-01-01 repräsentieren würde, wäre
das vorher genannte Problem gelöst. Verschiedene DBMS verfahren in diesem Fall jedoch
unterschiedlich. Interessant wird es auch bei ungewöhnlichen Granularitäten, wie dem
zuvor genannten Semester, da der Semesteranfang sowohl von Hochschule zu Hochschule,
als auch von Jahr zu Jahr unterschiedlich ist. Es gibt somit keine genaue Einteilung
und es kann nicht mit Sicherheit gesagt werden, dass ein Semester exakt 6 Monate nach
dem letzten startet. Es ist nicht einmal klar, an welchem Wochentag es startet. In dem
Fall müsste man eine Interoperabilität mit Zeitpunkten mit anderen Granularitäten wie
beispielsweise einem Tag ausschließen.
Doch das ist nicht das einzige Problem. Zusätzlich kommen noch die Zeitzonen hinzu,
die insbesondere beim Austausch von Daten mit einer Granularität von Stunden und
feiner eine große Rolle spielen. So gelten zwischen Zeitzonen verschiedene Konventionen
und es existieren verschiedene Annahmen über die Zeit in einem Jahr, wie die Sommerzeit
und Winterzeit. Des Weiteren enthalten Zeitberechnungen wesentlich größere Probleme
als man zunächst annimmt Als Beispiel sei ein astronomisches Element in einer Datenbank
5
vermerkt, von dem wir wissen, dass es nach exakt einem Sonnenjahr1 erneut auftreten
wird. So würde es nicht reichen, lediglich das Jahr um eins zu erhöhen, da ein Sonnenjahr
länger als ein Jahr, approximiert etwa 365.242190417 Tage, lang ist. Zeitberechnungen
sind genau deshalb nicht trivial und man sollte bei der Implementierung solcher Systeme
auf domänenspezifische Anforderungen an die Zeit Rücksicht nehmen. Beispielsweise
sollte man sich beim Austausch zwischen Zeitzonen auf eine Zeitzone festlegen, die in
der Datenbank physikalisch gespeichert wird und bei der Ausgabe bei Bedarf zurück
konvertiert wird.
2.1.3
Zeitintervalle
Zeitintervalle eignen sich für die Beschreibung von zeitlich verankerten Zeiträumen, die
Vergangenheit, Gegenwart oder Zukunft umfassen. Sie beinhalten einen Anfangs- und
einen Endzeitpunkt. Dabei gibt es 4 verschiedene Arten, ein Zeitintervall darzustellen; [],
[), (] und (), wobei eine runde Klammer ein offenes Ende symbolisiert, das heißt,
dass der angegebene Zeitpunkt nicht im Intervall enthalten ist. Analog stehen eckige
Klammern für ein geschlossenes Ende, welches den angegeben Zeitpunkt miteinbezieht.
Als Beispiel würde das Zeitintervall [2014-01-01,2015-01-01) das ganze Jahr
2014 beschreiben, wohingegen [2014-01-01,2015-01-01] das ganze Jahr 2014 mit
zusätzlich dem 01.01.2015 beschreiben würde.
Für diese Ausarbeitung sind [) oder (]-Intervalle am besten geeignet, da sie die Darstellung von mehreren benachbarten Intervallen vereinfachen. Bei der Intervalldarstellung
gilt: wenn der Endzeitpunkt vom ersten Intervall identisch mit dem Anfangszeitpunkt
des zweiten Intervalls ist, lassen sich beide zu einem zusammenfassen. So ergeben die
Intervalle [2014-01-01,2014-06-01) und [2014-06-01,2015-01-01) zusammengefasst [2014-01-01,2015-01-01). Dieses Beispiel zeigt ein weiteres Problem
der []-Notation; ergeben zwei benachbarte Intervalle ein zusammengefasstes Intervall oder
existieren zwischen ihnen noch relevante Zeitpunkte? Es lässt sich nicht mit Bestimmtheit sagen, dass [2014-01-01,2014-05-31] und [2014-06-01,2015-01-01]
zusammengefasst [2014-01-01,2015-01-01) ergeben, da zwischen den Intervallen
noch Zeit liegt. [) oder (]-Intervalle erleichtern somit Operationen für den Zusammenschluss von Intervallen und die Temporalisierung von Daten. Zur Vereinfachung wird
in den folgenden Kapiteln ausschließlich [) verwendet, obwohl (] analog funktioniert.
Bis zum Ende der Zeit, oder auch 9999-12-31
In Zeitintervallen wird zusätzlich ein Konstrukt benötigt, das einen Zeitraum beschreibt,
welcher sich von einem festen Zeitpunkt aus erstreckt und bis an das Ende der Zeit
reicht. Das heißt, jeder Zeitpunkt ab dem Startzeitpunkt ist in diesem Intervall enthalten.
Dieses Konstrukt wird benötigt, wenn ein Zustand eines Objektes beschrieben wird,
von dem nicht bekannt ist, wann er ungültig ist. Es ist nicht bekannt, wann sich dieses
Objekt verändern wird. Darüber hinaus ist nicht bekannt, ob sich das Objekt jemals
1
Zeitraum, innerhalb dessen die Erde alle Jahreszeiten durchläuft
6
verändern wird. Deshalb wird ein Endzeitpunkt eingeführt, der eben dies bedeuten
soll. In der Literatur wird dieser Endzeitpunkt unterschiedlich genannt; in [JW10] wird
9999-12-31 benutzt, das heißt das Ende dieses Dekamillenniums. In [TYT11] wird dieser
Endzeitpunkt mit UC gekennzeichnet. Auch der Begriff FOREVER tritt in einigen Werken
auf. In PostgreSQL ist es möglich, den Start- und/oder Endzeitpunkt leer zu lassen, um
ebendies zu erreichen.
Now
Now beschreibt den Zeitpunkt, in dem die Funktion aufgerufen wird. So würde der
Zugriff auf Now am 2015-04-20 um 13:50:34 genau diesen Zeitpunkt ausgeben und eine
Sekunde später den selben Zeitpunkt um eine Sekundenzahl erhöht. Die Funktion ist
also vom Zeitpunkt des Aufrufes abhängig. Verschiedene DBMS nennen diese Funktion
unterschiedlich, so ist in PostgreSQL ein Now() üblich, der SQL Standard beschreibt
die Funktion mit CURRENT_TIMESTAMP und diversen anderen Funktionen seit der
Standardversion SQL2008. So gibt CURRENT_TIMESTAMP den momentanen Zeitpunkt
mitsamt Zeitzonen-Informationen zurück. Zusätzlich existiert CURRENT_DATE, welches
das Datum ohne Tageszeitangabe zurückgibt. Die Funkion LOCAL-TIME stellt dagegen
den Zeitpunkt ohne Zeitzonen-Informationen dar. In älteren DBMS nannte man diese
Funktion auch getdate().
Die Now-Funktion wird in der temporalen Datenhaltung für mehrere Aspekte benötigt.
So müssen in der Transaktionszeit bei einer erfolgreich abgeschlossenen Transaktion die
Tabellenspalten, die verändert oder gelöscht wurden, archiviert werden. Dabei wird bei
der Transaktionszeit angegeben, dass die Tabellen nun nur noch bis Now gelten und
gegebenenfalls die neuen Spalten zum Zeitpunkt der Transaktion ab dem Zeitpunkt Now
gelten. Auch bei der Gültigkeitszeit benötigt man das, wenn man ausdrücken will, dass
ein bestimmter Umstand ab jetzt nicht mehr gilt.
Die bereits im Kapitel für die Zeitintervalle 2.1.3 besprochenen Zeitzonen stellen auch
für Now ein Problem dar. So muss, je nach Implementierungsentscheidung, Now stets in
eine vorher definierte Zeitzone umgewandelt werden, um mit dem Rest der Datenbank
konform zu sein, insbesondere wenn die gewählte Granularität sehr fein ist.
Allen-Relationen zwischen Zeitintervallen
Allen-Relationships wurden zuerst 1983 im Artikel [All83] von James F. Allen erläutert.
Sie beschreiben die Beziehungen zwischen Zeitintervallen auf einer gemeinsamen Zeitlinie.
In der Tabelle 2.1 werden die 11 Beziehungen erläutert. Dabei sind t1 und t2 Zeitintervalle. Die linke Spalte zeigt eine Funktion mit zwei Zeitintervallen als Argumente, die
einen Wahrheitswert zurückgibt. Die rechte Spalte beschreibt, wann die Funktion den
Wahrheitswert true oder false zurückgibt.
In der Abbildung 2.1 findet sich eine grafische Erklärung der Beziehungen. Auf dieser
Grafik sind auch die Paare der Relationen R, S mit R(t1 , t2 ) ⇔ S(t2 , t1 ) zu erkennen. So
gilt, dass Before mithilfe des Vertauschens von Argumenten After darstellen kann. Es
7
gilt somit Before(t1 , t2 ) ⇔ After(t2 , t1 ). Analog gilt dasselbe für die anderen gegenübergestellten Beziehungen. Equals ist kommutativ, also Equals(t1 , t2 ) ⇔ Equals(t2 , t1 ).
In der Abbildung 2.2 wird eine Einteilung mittels einer Baumstruktur vorgenommen.
Dabei sind die Blätter des Baums die in der Tabelle 2.1 vorgestellten Relationships
(jeweils eine übersetzbare Relationship pro Paar). Die restlichen Knoten sind von [JW10]
zusätzlich hinzugefügte allgemeine Beziehungen zwischen Zeitintervallen. Entweder sie
schneiden sich und besitzen somit eindeutige gemeinsame Punkte, oder sie schneiden
sich nicht und es besteht kein gemeinsamer Punkt. Bei zwei nicht schneidenden gibt es
außerdem die Unterscheidung zwischen berührenden und nicht berührenden Intervallen.
Bei geschnittenen Intervallen gibt es die Unterscheidung, ob sie auch noch nicht gemeinsame Punkte haben. Wenn sie nur noch gemeinsame Punkte haben, kann zusätzlich
unterschieden werden, ob sie identisch sind und eines der Intervalle lediglich ein Teil des
anderen ist. Bei Teilintervallen wird außerdem danach unterschieden, ob sie am Anfang
oder Ende des Intervalls oder irgendwo in der Mitte liegen.
Before(t1 , t2 )
After(t1 , t2 )
During(t1 , t2 )
Contains(t1 , t2 )
Overlaps(t1 , t2 )
Overlapped-by(t1 , t2 )
Meets(t1 , t2 )
Met-by(t1 , t2 )
Starts(t1 , t2 )
Started-by(t1 , t2 )
Finishes(t1 , t2 )
Finished-by(t1 , t2 )
Equals(t1 , t2 )
t1 tritt vor t2 auf und die Zeitintervalle haben keine gemeinsamen Zeitpunkte
t2 tritt vor t1 auf und die Zeitintervalle haben keine gemeinsamen Zeitpunkte
Intervall t1 startet nach dem Anfangszeitpunkt von t2 und
endet vor dem Endzeitpunkt von t2
Intervall t2 startet nach dem Anfangszeitpunkt von t1 und
endet vor dem Endzeitpunkt von t1
Intervall t1 startet vor dem Anfangszeitpunkt von t2 und endet
zwischen dem Anfangs- und Endzeitpunkt von t2
Intervall t2 startet vor dem Anfangszeitpunkt von t1 und endet
zwischen dem Anfangs- und Endzeitpunkt von t1
Endzeitpunkt von t1 ist identisch mit dem Anfangszeitpunkt
von t2
Endzeitpunkt von t2 ist identisch mit dem Anfangszeitpunkt
von t1
t1 und t2 haben den gleichen Anfangszeitpunkt und t1 endet
vor t2
t1 und t2 haben den gleichen Anfangszeitpunkt und t2 endet
vor t1
t1 und t2 haben den gleichen Endzeitpunkt und t1 startet nach
t2
t2 und t1 haben den gleichen Endzeitpunkt und t2 startet nach
t1
t2 und t1 haben den gleichen Anfangs- und Endzeitpunkt
Tabelle 2.1: Die 13 Relationen zwischen Zeitintervallen nach James F. Allen
8
Abbildung 2.1: Darstellung der Relationen in Tabelle 2.1 (Grafik entnommen aus [TYT11])
Relationen zwischen Punkt und Intervall
Neben den Relationen zwischen Zeitintervallen sind auch Relationen zwischen Zeitpunkten
und Intervallen für die Entwicklung der temporalen Datenbank von Interesse. Diese sind
beschrieben in [TYT11]. Dabei sind diese Relationen eine Spezialisierung der gegebenen
13 Relationships nach Allen. Ein Punkt ist lediglich ein Intervall mit der Größe eines
Chronons seiner Granularität. Das heißt ein Intervall [2014-03-20, 2014-03-21)
oder auch [2014-03-20, 2014-03-20], gegeben die Granularität eines Tages, ist
gleichbedeutend mit dem Zeitpunkt 2014-03-20. Wenn nun die 13 Relationships auf ein
Intervall mit einer Größe > 1 angewendet werden, bleiben nur noch wenige übrig, da
andere niemals erfüllt werden können. So gibt es kein Overlaps und Overlapped-by, da
dafür ein Intervall außerhalb und innerhalb des anderen vorhanden sein muss. Das ist
bei einem Intervall mit der Größe 1 nicht möglich. Started-by und Finished-by haben
ebenfalls keinen Nutzen mehr, da ein Zeitpunkt nicht von einem Intervall gestartet
beziehungsweise abgeschlossen werden kann. Contains ist zusätzlich ausgeschlossen, da
ein größeres Intervall nicht in ein kleineres passt. Zudem ist Equals ausgeschlossen, da
die beiden Intervalle nicht die gleiche Größe besitzen können. In der Tabelle 2.2 sind
9
Abbildung 2.2: Taxonomie der Allen-Relationships (Grafik entnommen aus [JW10])
alle übrigen Relationships vermerkt. Dabei fällt auf, dass nun auch einige Relationships
durch die Einschränkung eines Intervalls identisch sind; so ist Meets dasselbe wie Starts
und analog Met-by dasselbe wie Finishes. In der Abbildung 2.3 findet sich eine grafische
Veranschaulichung, ähnlich wie bei der Abbildung 2.1, für die Relationen zwischen den
Intervallen.
Diese werden benötigt, um Anfragen an die Datenbank zu stellen. Eine häufige Anfrage an eine Valid Time-Tabelle ist: Welche Daten galten zum Zeitpunkt t? Dafür eignet
sich During, da es genau das beschreibt; ist der Zeitpunkt im gegebenen Zeitintervall
enthalten? Wenn er enthalten ist, so war der Datensatz zu diesem Zeitpunkt gültig und
kann ausgegeben werden. Andere Anfragen benutzen die beschriebenen Relationships
analog; auch bei Transaction Time-Tabellen. So lässt sich die Frage Welche Datensätze
wurden exakt zum Zeitpunkt t ungültig gemacht? mithilfe der Finishes-Relation ausdrücken. Weitere Einsatzzwecke für die Relationen finden sich im Kapitel Entwurf und
Implementierung.
Before(p, t)
After(p, t)
Meets(p, t)
Starts(p, t)
Met-by(p, t)
Finishes(p, t)
During(p, t)
Punkt p ist vor dem Anfangszeitpunkt von t
Punkt p ist nach dem Endzeitpunkt von t
Der Anfangszeitpunkt von t und Punkt p sind identisch
Der Endzeitpunkt von t und Punkt p sind identisch
Punkt p ist zwischen dem Anfangs- und Endzeitpunkt von t
Tabelle 2.2: Temporale Relationen zwischen Zeitpunkt und Zeitintervall
10
Abbildung 2.3: Darstellung der Relationen in Tabelle 2.2 (Grafik entnommen aus [TYT11])
Relationen zwischen zwei Punkten
Die Relationen lassen sich noch weiter spezialisieren, indem man die Einschränkungen aus
dem vorherigen Unterkapitel auf beide Intervalle anwendet, so dass beide Intervalle die
Größe 1 haben. Dadurch entsteht eine Gruppe von Relationen zwischen zwei Zeitpunkten.
Diese sind recht mühelos zu verstehen, da sie identisch zu den üblichen Vergleichsoperatoren sind; =, <, >. Sie lassen sich ähnlich wie die Relationen aus dem vorherigen
Unterkapitel ableiten, indem in die Relationen zwei Intervalle der Länge 1 eingesetzt
werden. In der Tabelle 2.3 sind die drei Relationen analog beschrieben. Dabei sind p und
q jeweils Zeitpunkte. Die Abbildung 2.4 zeigt eine grafische Veranschaulichung.
Before(p, q)
After(p, q)
Equals(p, q)
Punkt p ist vor dem Punkt q
Punkt p ist nach dem Punkt q
Punkt p und Punkt q sind identisch
Tabelle 2.3: Temporale Relationen zwischen zwei Zeitpunkten
Abbildung 2.4: Darstellung der Relationen in Tabelle 2.3 (Grafik entnommen aus [TYT11])
11
2.1.4
Zeitspannen
Eine Zeitspanne beschreibt einen nicht verankerten Zeitraum wie 1 Tag oder 4 Wochen.
Mit ihnen lässt sich der Unterschied zwischen zwei Zeitpunkten feststellen oder aus
einem bestehenden Zeitpunkt ein neuer Zeitpunkt erstellen. So ergibt der Zeitpunkt
2014-04-01 mit der Zeitspanne 1 Tag den Zeitpunkt 2014-04-02. Im SQL-92 wurde
ein Datentyp verabschiedet, der einer Zeitspanne entsprechen sollte; das INTERVAL.
Das sei nicht mit dem zuvor besprochenen Zeitintervall zu verwechseln. Der Datentyp
wurde mit einer ganzen Reihe anderer Datentypen eingeführt, beispielsweise dem DATE,
TIME und TIMESTAMP, alles Datentypen, welche die Abspeicherung von Zeit über den
Standard ermöglichen.
2.1.5
Zeitdimensionen
In der temporalen Datenhaltung existieren unterschiedliche Zeitdimensionen, die jeweils
eine andere Bedeutung im Kontext der Speicherung, Verwaltung und Abfrage der Daten
haben. Zuerst vorgestellt wurden die Begriffe der Zeitdimension im Artikel [SA86].
Die Implementierung und Verwaltung der verschiedenen Zeitdimensionen und ihrer
Kombinationen unterscheidet sich untereinander erheblich und muss deshalb gesondert
betrachtet werden.
Valid Time
Valid Time, auch Application Time oder Effective Time2 in Englisch oder Gültigkeitszeit
in Deutsch, beschreibt die Gültigkeit von Objekten in der entworfenen Welt. Valid Time
beschreibt das, was wir glauben, was in der modellierten Welt zu den abgebildeten Zeiten
wahr ist. Bei dem Entwurf einer Datenbank, wo sich Objekte und ihre Beziehungen über
die Zeit hinweg verändern, ist oft die Vergangenheit und Zukunft genauso interessant wie
die Gegenwart. Daher werden Objekte mit ihrer Valid Time abgespeichert und das heißt
auch, dass sie nun mehrmals in einer Tabelle, für verschiedene Zeiträume, vorkommen
können.
Transaction Time
Transaction Time, auch System Versioned oder Asserted Versioning in Englisch oder
Transaktionszeit in Deutsch, wird, ähnlich wie die Valid Time, in Tabellen mithilfe eines
Versionsstempels an den Datensätzen abgebildet. Sie bedeuten jedoch etwas anderes; sie
bilden ab, wie die Datenbank zu den angegeben Zeitpunkten beschaffen war. Transaktionszeit erläutert demnach, was wir glaubten, was für die modellierte Welt wahr war. Bei
der Valid Time ist es möglich, auch zukünftige und vergangene Datensätze zu editieren,
um Annahmen zu korrigieren oder zu erweitern, wohingegen dies bei der Transaktionszeit
nicht erlaubt ist. Die vergangenen Datensätze werden erschaffen, wenn aktuelle verändert
werden. Eine Ausnahme stellt dabei die in [JW10] beschriebene Asserted Versioning
2
in [JW10]
12
dar, welche auch DEFERRED TRANSACTION zulässt. Damit werden Transaktionen
bezeichnet, deren Zeitintervall in der Zukunft liegen. Dies ist von Nutzen, wenn wir
abbilden wollen, dass sich unser Wissen in der Zukunft verändert, jedoch die aktuellen
Daten nicht geändert werden dürfen, auch wenn sie nicht zwangsweise korrekt sind.
Bitemporal
Bitemporal heißen Tabellen, die sowohl die Valid Time als auch die Transaction Time
eines Objekts abspeichern.
2.2
PostgreSQL
PostgreSQL ist ein quelloffenes objekt-relationales Datenbanksystem, entwickelt seit
01.05.1995. Die Software wird mit C entwickelt und wird unter der selbst entwickelten
PostgreSQL-Lizenz vertrieben. Die vorliegende Ausarbeitung wird PostgreSQL in der
Version 9.2 verwenden. PostgreSQL wurde auf einige Betriebssysteme portiert, darunter
die üblichen Linux-Distributionen, Microsoft Windows und andere Unix-Klone wie Mac
OS X3 und BSD-Derivate. Das Projekt wird auch im kommerziellen Betrieb zahlreich
verwendet, zum Beispiel im U.S. State Department und bei Red Hat4 .
Zum Support von SQL:2011, welches im Kapitel ?? näher betrachtet wird, steht in
der Dokumentation von PostgreSQL 9.3.5 folgende Bemerkung im Kapitel Appendix D.
SQL Conformance 5 :
PostgreSQL supports most of the major features of SQL:2011. Out of 179
mandatory features required for full Core conformance, PostgreSQL conforms
to at least 160. In addition, there is a long list of supported optional features.
It might be worth noting that at the time of writing, no current version of
any database management system claims full conformance to Core SQL:2011.
Es fehlen jedoch alle im Kapitel 4.2 genannten Features. Periods lassen sich dennoch
mithilfe des Range-Datentyps simulieren.
2.2.1
Extensions
PosgreSQL ermöglicht die Programmierung von selbst definierten Funktionen und Datentypen, die in SQL benutzt werden können. Dabei werden verschiedene Programmiersprachen unterstützt, darunter C, welches die Programmiersprache ist, mit der
PostgreSQL implementiert wurde. Zusätzlich bietet PostgreSQL mithilfe von Modulen
weitere Programmiersprachen für die Entwicklung von Erweiterungen an: PL/pgSQL,
einer Sprache, die Oracles PL/SQL ähnelt, PL/TCL, PL/Python und PL/Perl. Diese
3
Seit Mac OS X 10.7 Lion sogar die Standard-Datenbank für die Server Edition und installiert auf
allen Desktop Versionen.
4
siehe http://www.postgresql.org/about/users/
5
http://www.postgresql.org/docs/9.3/static/features.html
13
Sprachen werden im Kontext der Erweiterungen prozedurale Programmiersprachen genannt und werden durch zusätzlich installierte Module gelesen, übersetzt und verarbeitet,
um für die Entwicklung zur Verfügung zu stehen. In dieser Ausarbeitung werden SQL,
C und PL/pgSQL als Programmiersprachen benutzt. Diese haben den Vorteil, dass
sie keinen zusätzlichen Interpreter auf dem Server benötigen und damit leichter auf
verschiedene PostgreSQL-Installationen zur Verfügung zu stellen sind, da C, SQL und
PL/pgSQL auf jedem PostgreSQL-System ab der Version 9.0 verfügbar sind.
Nachdem einige logisch zusammenhängende6 Funktionen, Objekte und Operatoren
geschrieben wurden, bietet PostgreSQL die Möglichkeit, diese Objekte in eine Extension
zusammenzufassen. Das hat den Vorteil, dass sie mit wenigen Befehlen auf eine andere
PostgreSQL-Installation übertragen werden kann. Man kann sie zudem leichter dokumentieren und warten und auch besser in ihren Rechten bei der Installation einschränken.
Zusätzlich ist es möglich die definierte Extension mitsamt ihrer Definitionen mit einem
einzigen Statement entfernen.
Für eine Extension werden zunächst mindestens zwei Dateien benötigt, eine Datei in
der Form <name>-<version>.sql und eine weitere in der Form <name>.control.
Diese beiden werden in das Extension-Verzeichnis von PostgreSQL verschoben und
können anschließend mit CREATE EXTENSION <name>; geladen werden. Mit einem
DROP EXTENSION <name>; kann die Extension entfernt werden. Um die Installation
zu vereinfachen, bietet sich ein Makefile an.
Das folgende Beispiel stammt aus der offiziellen Dokumentation von PostgreSQL.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
CREATE TYPE pair AS ( k text, v text );
CREATE OR REPLACE FUNCTION pair(anyelement, text)
RETURNS pair LANGUAGE SQL AS ’SELECT ROW($1, $2)::pair’;
CREATE OR REPLACE FUNCTION pair(text, anyelement)
RETURNS pair LANGUAGE SQL AS ’SELECT ROW($1, $2)::pair’;
CREATE OR REPLACE FUNCTION pair(anyelement, anyelement)
RETURNS pair LANGUAGE SQL AS ’SELECT ROW($1, $2)::pair’;
CREATE OR REPLACE FUNCTION pair(text, text)
RETURNS pair LANGUAGE SQL AS ’SELECT ROW($1, $2)::pair;’;
CREATE OPERATOR ˜> (LEFTARG = text, RIGHTARG = anyelement,
PROCEDURE = pair);
CREATE OPERATOR ˜> (LEFTARG = anyelement, RIGHTARG = text,
PROCEDURE = pair);
CREATE OPERATOR ˜> (LEFTARG = anyelement,
RIGHTARG = anyelement, PROCEDURE = pair);
CREATE OPERATOR ˜> (LEFTARG = text, RIGHTARG = text,
PROCEDURE = pair);
Quelltext 2.1: Datei: pair–1.0.sql
6
Logisch zusammenhängende Objekte sind beispielsweise eine Objektdefinition mitsamt selbstentwickelter Operatoren und Hilfsfunktionen
14
1
2
3
4
# pair extension
comment = ’A key/value pair data type’
default version = ’1.0’
relocatable = true
Quelltext 2.2: Datei: pair.control
1
2
3
4
5
6
EXTENSION = pair
DATA = pair−−1.0.sql
PG CONFIG = pg config
PGXS := $(shell $(PG CONFIG) −−pgxs)
include $(PGXS)
Quelltext 2.3: Datei: Makefile
In der pair-1.0.sql-Datei wird ein neuer Typ Pair erzeugt, der ein Paar aus
Text-Variablen als eine Einheit darstellt, das heißt ein Zweier-Tupel aus Texten. Diese
Art von Typen nennt man Composite-Types, weil sie genau das darstellen; eine Zusammensetzung aus Variablen. Für diesen Typ sind zusätzlich noch vier Funktionen
in SQL definiert worden, die als Konstruktor gelten. Mit ihnen lassen sich neue Paare
erzeugen. In den Funktionsargumenten findet man den Typ anyelement. Dies ist ein
Pseudo-Typ7 , der polymorphe Programmierung erlaubt. Damit lassen sich Funktionen
programmieren, die für mehr als einen bestimmten Datentyp gelten. Es wird erst zur
Laufzeit überprüft, ob die übergebenen Argumente mit den benutzten Operationen in der
definierten Funktion kompatibel sind und gegebenenfalls eine Fehlermeldung geworfen. Es
existieren noch weitere Pseudo-Types wie anyarray und anyrange; letzteres wird in
dieser Ausarbeitung mehrmals benutzt. Die definierten Funktionen werden anschließend
in der Datei zu überladenen Operatoren zugewiesen. Das erleichtert den Umgang mit
dem Datentyp.
In der pair.control-Datei ist ein Kommentar über die Extension vermerkt, sowie
die neueste stabile Version. Das Argument relocatable gibt an, ob sich eine Extension
ohne Probleme in ein anderes Schema verschieben lässt. Das ist nur möglich, wenn
die definierten Objekte in der Extension unabhängig vom gegebenen Schema sind. In
diesem Beispiel ist dies der Fall, dementsprechend gilt relocatable = true. Weitere
Angaben sind in der Dokumentation vermerkt.
Die dritte Datei ist der Makefile, der mithilfe der PostgreSQL-Mechanismen pg_config
die Installation von Extensions vereinfacht.
Extensions eignen sich damit für die Verteilung von Objekten mit ihren Funktionen
zwischen verschiedenen PostgreSQL-Systemen. Es ermöglicht dabei eine einfache Installation und Deinstallation. Außerdem bietet der Mechanismus noch weitere Optionen für
vereinfachte Updates an, so dass Abwärtskompatibilität gewährleistet wird. So lassen
sich mehrere Versionen einer Extension auf dem System installieren und die Datenbank
oder das Schema kann angeben, welche Version zwingend benötigt wird.
7
http://www.postgresql.org/docs/9.3/static/datatype-pseudo.html
15
2.2.2
Datentyp Range
Range ist ein in PostgreSQL integrierter Datentyp zur Darstellung von Intervallen. Diese
Intervalle haben dabei einen Start- und Endpunkt. Es ist möglich, Intervalle in den im
Kapitel 2.1.3 vorgestellten Möglichkeiten anzugeben, das heißt [], [), (] und ().
Der zugrundeliegende Datentyp kann dabei unterschiedlich sein, in PostgreSQL selbst
sind sechs Typen vordefiniert:
int4range Intervall aus Ints
(ganze Zahlen zwischen -2147483648 und +2147483647)
int8range Intervall aus Bigints
(ganze Zahlen zwischen -9223372036854775808 und 9223372036854775807)
numrange Intervall aus Numerics
(reelle Zahlen zwischen 131072 Vorkommastellen und 16383 Nachkommastellen)
tsrange Intervall aus Zeitstempeln ohne Zeitzonenangabe
tstzrange Intervall aus Zeitstempeln mit Zeitzonenangaben
daterange Intervall aus Daten
Es lassen sich mit CREATE TYPE <name> AS RANGE (<Typbeschreibung>)
weitere Range-Typen aus bestehenden Basisdatentypen definieren, da die bereits angebotenen Range-Typen nicht für jeden Einsatzzweck geeignet sind. So existieren in
temporalen Datenbanken mehr Granularitäten als die Zeitstempel und das Datum; es
existiert beispielsweise die Granularität Monat, welche mit den gegebenen Typen nicht
dargestellt werden können. In diesem Fall lassen sich selbst definierte Range-Datentypen
nutzen.
Es gibt hierbei verschiedene Möglichkeiten, eine Range zu erstellen; die erste Möglichkeit ist, die entsprechende Konstruktorfunktion aufzurufen. Diese heißt identisch
zur Bezeichnung der Range und nimmt mindestens 2, jedoch höchstens 3 Argumente
entgegen. Das erste Argument ist die untere Grenze des Intervalls, das zweite die obere
Grenze. Bei der Angabe von NULL wird der Start- oder Endpunkt auf unendlich gesetzt.
Im dritten optionalen Argument lässt sich angeben, von welcher Form das Intervall ist,
das heißt, ob der Start oder das Ende geschlossen oder offen ist. Standardmäßig hat es
die Form [). So erstellt der Aufruf int8range(3, 6) ein Intervall zwischen 3 und 6,
wobei 6 nicht enthalten ist. Dieser Aufruf ist identisch mit int8range(3, 6, ’[)’).
Für ein Intervall, in dem die 6 enthalten ist, wäre ein Aufruf mit int8range(3, 6,
’[]’) nötig. Neben dieser Möglichkeit lässt sich auch die Syntax :: von PostgreSQL
verwenden. Der ::-Operator konvertiert alles vor :: in den Typ, der nach :: angegeben
wird. So wird mit 2::float der Integer 2 in einen float 2.0 umgewandelt. Dasselbe
funktioniert auch mit Range, da dies eine Konvertierung von Strings zu Range ermöglicht.
So erstellt der Befehl ’[3, 6)’::int8range das zuerst genannte Beispiel. PostgreSQL übernimmt zusätzlich noch eine Konvertierung von [] zu [), falls der Datentyp es
16
zulässt, er also diskret ist. So wird ’[3, 6]’::int8range automatisch zu [3, 7),
wohingegen dasselbe Beispiel mit numrange statt int8range nicht dazu in der Lage
wäre, da es keine direkt benachbarte Zahl über 6 gibt, die dasselbe ausdrücken würde.
Wenn eine Range erstellt wird, können der Anfangs- und Endzeitpunkt weggelassen
werden. Sie definiert ein Intervall, bei dem entweder alle Punkte vor dem Endpunkt
oder alle Punkte nach dem Startpunkt im Intervall enthalten sind. Möglich ist auch ein
Intervall, in dem sowohl Anfangs- als auch Endpunkt weggelassen werden. Dieses Intervall
enthält alle möglichen Elemente, abhängig vom definierten Range-Typ. Es ist auch
möglich, statt dem weggelassenen Start- und Endpunkt ein infinity oder -infinity
anzugeben, wenn der Datentyp der Range so eine Notation enthält. Dabei gibt es jedoch
Unterschiede; so sind [today,] und [today,) identisch, [today,infinity] und
[today,infinity) aber unterschiedlich, da im letzteren der Wert infinity enthalten
ist, in dem davor jedoch nicht.
Für die Ausarbeitung sind insbesondere tsrange, tstzrange und daterange
interessant, da sie ein wie in Kapitel 2.1.3 besprochenes Zeitintervall darstellen und
zusätzlich noch Operationen und Indexe anbieten. So übernimmt PostgreSQL viele
Vorgänge, die sonst mühsam mithilfe von mehreren Spalten realisiert werden müssen,
wie beispielsweise den Schnitt von zwei Intervallen.
Beispiel
Es folgen nun einige Beispiele. Zunächst wird eine Tabelle erstellt, die eine Daterange
erstellt. Dabei wird ein übliches Beispiel einer Employee-Tabelle genommen, die eine
Validtime-Historie enthält. Zusätzlich existieren als Spalten der Vor- und Nachname
sowie das Jahresgehalt.
1 CREATE TABLE employee (
2 e id SERIAL,
3 first name TEXT,
4 last name TEXT,
5 salary NUMERIC,
6 vt DATERANGE,
7 PRIMARY KEY (e id, vt)
8 );
Quelltext 2.4: Employee-Tabelle mit Daterange für Validtime
Zu beachten ist dabei, dass e_id und vt den Primärschlüssel bilden, da in dieser
Tabelle mehrmals dasselbe Objekt auftreten kann, das jedoch zu unterschiedlichen Zeiträumen gilt. Nun werden einige Daten in die Tabelle eingefügt; zwei Mitarbeiter mitsamt
ihres Gehaltes, bei einem Mitarbeiter soll jedoch das Gehalt ab heute etwas höher sein.
1
2
3
4
5
INSERT INTO employee
(e id, first name, last name, salary, vt) VALUES
(1, ’Max’, ’Mustermann’, 2000, daterange(’2013−01−01’,NULL)),
(2, ’Susi’, ’Kaufgern’, 2000, ’[2013−01−01,today)’::daterange),
(2, ’Susi’, ’Kaufgern’, 3000, ’[today,)’::daterange);
Quelltext 2.5: Inserts in employee
17
Dabei sind die zwei verschiedenen Arten der Definition von Ranges zu beobachten. In
den Ranges selbst wird zusätzlich die Variable today verwendet. Diese gibt das aktuelle
Datum zurück. Nun noch eine einfache Anfrage, welche alle Mitarbeiter zurück gibt, die
momentan gültig sind.
1 SELECT e id, first name, last name, salary
2 FROM employee
3 WHERE vt @> date(’today’);
Quelltext 2.6: Snapshot von employee
Dafür wird der @>-Operator verwendet, welche überprüft, ob ein Objekt in einem
Intervall vorhanden ist. Damit lässt sich ausdrücken, ob der jetzige Zeitpunkt im Intervall
enthalten ist.
PostgreSQL bietet auch eine einfache Syntax zur Definition von Zeitpunkten an. So
lässt sich ein Datum-Intervall zwischen heute und in 12 Tagen leicht ausdrücken.
1 SELECT daterange(’today’, (now() + ’12 day’)::DATE);
2
3 Ausgabe: [2014−11−28,2014−12−10)
Quelltext 2.7: Range und Intervall
Liste der Operationen
PostgreSQL bietet mit dem Range-Datentyp noch eine Reihe nützlicher Funktionen und
Operatoren an, die sich auch für die Verarbeitung von Ranges mit Zeitpunkten eignen.
Mit ihnen lassen sich die Allen-Relationships effizient programmieren, da die Operatoren
mit optimiertem C implementiert und dabei auch Indexe beachtet wurden. In der Tabelle
2.4 sind die vordefinierten Operatoren zu entnehmen. Zusätzlich zu den Operatoren bietet
PostgreSQL noch Funktionen an, die es ermöglichen, auf die einzelnen Elemente der
Range zuzugreifen. Diese Funktionen sind in der Tabelle 2.5 vorgestellt.
18
Operator
=
<>
<
>
<=
>=
@>
@>
<@
<@
&&
<<
>>
&<
&>
-|+
*
-
Beschreibung
Gleich
Ungleich
kleiner als
größer als
kleiner gleich
größer gleich
enthält range
enthält element
range ist enthalten in
element ist enthalten in
überlappen
strikt links von
strikt rechts von
erweitert nicht rechts von
erweitert nicht links von
angrenzend
union
intersection
difference
Beispiel
int4range(1,5) = ’[1,4]’::int4range
numrange(1.1,2.2) <>numrange(1.1,2.3)
int4range(1,10) <int4range(2,3)
int4range(1,10) >int4range(1,5)
numrange(1.1,2.2) <= numrange(1.1,2.2)
numrange(1.1,2.2) >= numrange(1.1,2.0)
int4range(2,4) @>int4range(2,3)
int4range(1,55) @>42
int4range(2,4) <@ int4range(1,7)
42 <@ int4range(1,7)
int8range(3,7) && int8range(4,12)
int8range(1,10) << int8range(100,110)
int8range(50,60) >> int8range(20,30)
int8range(1,20) &<int8range(18,20)
int8range(7,20) &>int8range(5,10)
numrange(1.1,2.2) -|- numrange(2.2,3.3)
numrange(5,15) + numrange(10,20)
int8range(5,15) * int8range(10,20)
int8range(5,15) - int8range(10,20)
Ausg.
t
t
t
t
t
t
t
t
t
f
t
t
t
t
t
t
[5,20)
[10,15)
[5,10)
Tabelle 2.4: Range-Operatoren
2.3
Bison & Flex
GNU Bison ist ein quelloffener Parsergenerator, welcher mit dem älteren Yacc kompatibel
ist.8 Mit Bison lassen sich kontextfreie Grammatiken in einen Parser kompilieren. Dieser
Parser kann in den Programmiersprachen C, C++ oder Java erzeugt werden. Bison
wurde als Parsergenerator für dieses Projekt benutzt, weil ein Teil der Erweiterung in
C geschrieben ist, und Bison sich in Projekten, die in C geschrieben sind, bewährt hat.
So wird es von Ruby, PHP und von PostgreSQL selbst benutzt. Das Projekt wird Bison
benutzen, um die SQL-Erweiterung in einen abstrakten Syntaxbaum (kurz AST) zu
parsen und anschließend den AST in SQL zu übersetzen.
Zusammen mit Bison wird häufig flex benutzt, um einen Lexer9 für den Parser zu
erzeugen. flex übersetzt eine Definitionsdatei in einen Lexer in C, welcher die von Bison
benötigte yylex-Funktion zur Verfügung stellt. Die yylex-Funktion soll dabei das nächste
Token von einer Datei zurückgeben.
8
siehe https://www.gnu.org/software/bison/
Lexikalischer Scanner oder auch Tokenizer; zerlegt die Quelldatei in Token, das heißt in zusammenhängende Elemente
9
19
Funktion
lower(anyrange)
upper(anyrange)
isempty(anyrange)
lower inc(anyrange)
upper inc(anyrange)
lower inf(anyrange)
upper inf(anyrange)
Beschreibung
Startpunkt
Endpunkt
Intervall leer?
Links geschlossen?
Rechts geschlossen?
Startpunkt unendlich?
Endpunkt unendlich?
Beispiel
lower(numrange(1.1,2.2))
upper(numrange(1.1,2.2))
isempty(numrange(1.1,2.2))
lower inc(numrange(1.1,2.2))
upper inc(numrange(1.1,2.2))
lower inf(’(,)’::daterange)
upper inf(’(,)’::daterange)
Erg.
1.1
2.2
false
true
false
true
true
Tabelle 2.5: Range-Funktionen
Beispiel
In dem Beispiel wird ein Parser für eine Programmiersprache definiert, die arithmische
Ausdrücke der Form 1+1 liest und direkt auswertet. Dabei wird zunächst ein Lexer
benötigt, welcher die verschiedenen Tokens des Parsers definiert.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
%option noyywrap
%{
#include <stdlib.h>
#include <stdio.h>
#include ”calc.tab.h”
%}
%%
/∗ integers ∗/
[0−9]+ {
yylval.intval = atoi(yytext);
return NUM;
}
/∗ End of line and expression ∗/
[\n] { return END; };
/∗ operators ∗/
[+−/∗()] { return yytext[0]; }
/∗ skip whitespace and tabs ∗/
[ \t] { }
/∗ unknown characters produce error messsage ∗/
. { yyerror(”unknown character”); }
%%
Quelltext 2.8: Datei: calc.l
Die Definition eines Lexers in flex besteht aus drei Teilen, die jeweils mit %% getrennt
sind. Im Quelltext 2.8 von Zeile 1-8 ist der erste Block. Er enthält zunächst einen Abschnitt
20
welcher mit %{ eingeleitet und mit %} geschlossen wird. In ihm werden die benötigten
Header-Dateien und damit auch Bibliotheken aufgezählt, die für den Lexer benötigt
werden. In Zeile 5 ist der Header, der von Bison erstellt wird, eingebunden. Im zweiten
Block, welcher von der Zeile 9 bis zum Ende des Quelltextes reicht, werden die Regeln
der Tokens aufgezählt. Die Definition eines jeden Tokens passiert in zwei Teilen: zunächst
ein regulärer Ausdruck, welcher beschreibt, wie der Token aufgebaut ist. Als zweites ein
Codeblock, welcher aufgerufen wird, wenn der Lexer auf den Token trifft. Die Regel dieses
Lexers enthält diesmal 5 Definitionen, wovon jedoch nur 3 tatsächlich Token sind. Die
erste Definition ist ein Token, welcher ganze Zahlen repräsentiert. Eine ganze Zahl besteht
aus mindestens einer Zahl. Im Codeblock wird der Text dieses Tokens via atoi in eine
ganze Zahl umgewandelt und anschließend einer Variable, auf die Bison zugreifen kann,
zugewiesen. Nun wird noch der Typ des Tokens zurückgegeben, damit Bison zwischen
den Tokens unterscheiden kann. Die nächsten beiden Definitionen sind analog, nur dass
diesmal kein Wert benötigt wird, da auf diese Tokens nur symbolisch in Bison zugegriffen
wird. Die vorletzte Definition überspringt lediglich alle Tabs und Leerzeichen, da diese
im Parsen nicht signifikant sind. (Das ist nicht in jeder Programmiersprache der Fall;
so symbolisieren in der Programmiersprache Python die Leerzeichen, in welchem Block
sich der Code befindet, ähnlich wie { und } in anderen Programmiersprachen wie C und
Java). Die letzte Definition sammelt jedes andere Zeichen auf um eine Fehlermeldung
auszuwerfen. Diese Definition hilft beim Debuggen des Lexers. Der dritte Block ist
in diesem Fall leer; in ihm würde man zusätzliche Funktionen definieren, welche zu
der erzeugten C-Datei hinzugefügt werden. Es ist möglich, dort eine Main-Funktion
hinzuzufügen, falls man nur einen Lexer benötigt. In diesem Fall wird die Main-Funktion
jedoch in der Bison-Datei definiert.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
%{
#include <stdio.h>
int yyerror(const char ∗p) {
fprintf(stderr, ”%s\n”, p); // print the error message
}
%}
%union {
int intval;
};
%type <intval> expr
%token <intval> NUM
%token END
%left ’+’ ’−’
%left ’∗’ ’/’
%start input
%%
Quelltext 2.9: Datei: calc.y (Block 1)
Der Übersichtlichkeit halber wird die Bison-Datei aufgespalten. Im ersten Block
(Quelltext 2.9) ist ähnlich wie bei der Lexer-Definition eine Aufzählung aller verwendeten
Bibliotheken sowie einer Hilfsmethode yyerror in einem Codeblock angegeben. In Zeile 7-9
werden die Hilfsvariablen für den Lexer genannt. Anschließend werden noch die Typen,
21
Tokens und Operatoren definiert. Die Typendefinition bestimmt den Rückgabewert der
Regeln. Via %left wird die Operatorenreihenfolge definiert. Die Reihenfolge der Regeln hier
bestimmt auch, welche Operatoren zuerst ausgeführt werden, wenn der Parser ausgeführt
wird. Da + und - zuerst angegeben sind, werden diese zuerst geparst und damit als letztes
ausgeführt. Damit erreichen wir, dass Punkt- vor Strichrechnung evaluiert passiert. Mit
%start wird bestimmt, welche Regel im Parser zuerst ausgeführt wird.
1 input: /∗ nil ∗/ | input line ;
2
3 line: END | expr END {
4
printf(”=> %d\n”, $1);
5
};
6
7 expr: NUM {
8
$$ = $1;
9
} | expr ’+’ expr {
10
$$ = $1 + $3;
11
} | expr ’−’ expr {
12
$$ = $1 − $3;
13
} | expr ’∗’ expr {
14
$$ = $1 ∗ $3;
15
} | expr ’/’ expr {
16
$$ = $1 / $3;
17
} | ’−’ expr {
18
$$ = −$2;
19
} | ’(’ expr ’)’ {
20
$$ = $2;
21
};
Quelltext 2.10: Datei: calc.y (Block 2)
Im zweiten Block im Quelltext 2.10 werden die Regeln der kontextfreien Grammatik
angegeben. Die Codeblöcke hinter den Regeln werden ausgeführt, wenn der Parser die
Regel parst. Da expr den Typ int besitzt, kann die Regel line am Ende das Ergebnis von
expr als int via printf ausgeben. Es wäre auch möglich, mit den Codeblöcken einen AST
anzulegen.
1 %%
2 int main() {
3
return yyparse();
4 }
Quelltext 2.11: Datei: calc.y (Block 3)
Der letzte Block im Quelltext 2.11 ermöglicht es, ähnlich wie schon bei flex, Funktionen
in C anzugeben. In diesem Fall soll die Main-Funktion angegeben werden, da der Parser
bereits alles tut, was für das Beispielprogramm benötigt wird. Die Main-Funktion führt
lediglich die yyparse() Funktion aus, welche den Parsevorgang startet, indem es von der
Standardeingabe liest. Nun können die Bison-Datei und Flex-Datei mit dem Bison- und
dem Flex-Befehl kompiliert werden. Dabei sollte beim Bison-Befehl die Zusatzoption
-d angegeben werden, um zusätzlich einen Header anzulegen. Dieser Header ist für den
22
Lexer notwendig. Anschließend können die erzeugten C-Dateien mit einem C-Compiler
wie gcc kompiliert werden. Die Main-Datei stammt dabei von der C-Datei von Bison.
Wenn das kompilierte Programm ausgeführt wird, liest es von der Standardeingabe
Ausdrücke, die der Parser dann auswertet. Dabei wird automatisch während des Parsevorgangs via den Codeblöcken der Ausdruck ausgewertet und via dem printf() Befehl
in Zeile 4 vom Quelltext 2.10 ausgegeben. Durch die Definition der Regeln input und
line können auch weitere Ausdrücke angegeben werden, die in der line-Regel ausgegeben
werden. Im Quelltext 2.12 ist eine Beispielausführung des Programms zu sehen.
1
2
3
4
5
6
100
=> 100
1+2∗2
=> 5
(1+2)∗2
=> 6
Quelltext 2.12: Ausführung des Beispielparsers
23
Kapitel 3
Temporale Tabellen
Temporale Tabellen sind eine konkrete Realisierung der in 2.1.5 beschriebenen Zeitdimensionen. Sie unterscheiden sich von nicht temporalen Tabellen in einigen Punkten und
erzeugen dadurch neue Probleme, die es zu lösen gilt. Einige Lösungen dieser Probleme
sind mithilfe des Buchs [Sno00] entwickelt worden. In den folgenden Abschnitten werden
Beispiele für die verschiedenen Arten von temporalen Tabellen vorgestellt. Die Basis
stellt dabei das folgende Datenbankschema dar.
ANG (Nr, Name, Gehalt)
ARBEITET (Angestellter → ANG, Projekt → PROJEKT)
PROJEKT (Name, Budget, Projektleiter → ANG)
Nr
1
2
Name
Max
Bob
Gehalt
2500
2600
Ang.
1
1
2
Prj.
P1
P2
P2
Name
P1
P2
Budget
100
200
PLeiter
1
2
Tabellen 3.1: Beispielausprägungen
Die drei Relationen besitzen eine Beispielausprägung, die in den Tabellen 3.1 dargestellt
wird. Dieses Schema besitzt dabei noch keine temporalen Tabellen; wir werden sie in
den folgenden Abschnitten mit temporalen Attributen erweitern. Anschließend werden
übliche zeitbezogene Anfragen ausgeführt und die Daten manipuliert.
3.1
Valid Time-Tabellen
Im Kapitel 2.1.5 wurde die Valid Time als Konzept vorgestellt. Konkret im Datenbankmodell wird sie mit Zeitstempeln umgesetzt. Um eine Tabelle mit einer Valid TimeZeitdimension zu erweitern, fügen wir einen Zeitraum zu den Tupeln in der Datenbank
hinzu. Dafür wird die Datenbank um zwei (Anfangszeitpunkt und Endzeitpunkt) oder,
falls das Datenbanksystem einen Datentyp für Zeiträume besitzt, eine Spalte erweitert.
Die Datenbank muss zusätzlich noch notieren, welche Intervallnotation die Zeiträume
24
benutzen (siehe Kapitel 2.1.3). Diese temporale Tabelle und auch die gesamte Datenbank
benutzt die [)-Notation. Nun wird die Tabelle ARBEITET aus dem Beispielschema um
Valid Time erweitert. Dafür wird eine Spalte hinzugefügt, welche den im Kapitel 2.2.2
vorgestellten Range-Datentyp verwendet.
Ang.
1
1
2
Prj.
P1
P2
P2
VT
[2013-01-01,)
[2013-01-01,)
[2013-01-01,)
Tabelle 3.2: Relation ARBEITET mit Valid Time
In der Tabelle 3.2 ist nun die Beispielausprägung mit zusätzlicher Gültigkeitszeit
versehen. Jeder Tupel ist seit 2013-01-01 gültig und der Endzeitpunkt ist leer, demnach
gelten die Tupel für immer. Alle drei Tupel sind somit zum jetzigen Zeitpunkt gültig (vorausgesetzt, die Datenbank wird nach dem Zeitpunkt 2013-01-01 betrieben). Die Tabelle
hat demzufolge keine zusätzlichen Informationen zu seinem vorherigen Zustand. Jetzt ist
es jedoch möglich geworden, die Geschichte eines Mitarbeiters und seiner Projektzugehörigkeit aufzuzeichnen. Für diese Definition gibt es nun bestimmte Anforderungen, die ein
gutes temporales Datenbanksystem erfüllen sollte, um dem User maximale Flexibilität
im Umgang mit zeitbehafteten Daten zu geben.
3.1.1
Anforderungskatalog: Constraints
Eine Anforderung die auch bei nicht-temporalen Tabellen existiert sind Constraints,
welche die Ausprägungen der Tabellen einschränken; so existieren Bedingungen, welche
garantieren, dass für ein eindeutig identifiziertes Objekt nicht mehrere Ausprägungen
existieren oder das ein referenziertes Objekt via Fremdschlüsselbedingung existiert. Diese
Constraints gelten in dieser Form für temporale Tabellen nicht mehr so wie vorher. Zuerst
werden die Primärschlüssel betrachtet.
Angenommen, vom Zeitraum 2012-01-01 bis 2013-01-01 sei der Mitarbeiter 1 im
Projekt P1 tätig gewesen. Diese Daten würden jedoch das PRIMARY KEY-Constraint
der nicht-temporalen Version verletzen, da nun der Schlüssel (1, P1) zweimal in der
Ausprägung auftaucht. Das führt zum ersten Problem der temporalen Tabellen: Temporale
Schlüssel.
Temporaler Schlüssel
In relationalen Datenbanken erlauben Primärschlüssel eine eindeutige Identifizierung
eines Tupels über einen spezifizierten Wert. Dafür muss in den Objekten der Welt, die
über die Datenbank festgehalten werden, eine Eigenschaft gefunden werden, die bei einer
Instanz eines Objekts einzigartig ist. Diese Logik ist jedoch bei temporalen Tabellen nicht
mehr anzuwenden. Dort gilt für eine Tabelle, dass zu einem Zeitpunkt nicht mehr als eine
Ausprägung zu einem Objekt existieren darf. Der bestehende Primary Key-Constraint
löst dieses Problem nicht, wie das zuvor genannte Beispiel zeigt.
25
Naiv wäre die Lösung, den Zeitraum als Primärschlüssel hinzuzufügen. Das würde
jedoch das genannte Problem nicht lösen, sondern nur einige Spezialfälle ausschließen.
So wäre, wenn nur der Zeitraum Teil des Schlüssels ist, die Ausprägung in Tabelle 3.3
erlaubt.
Ang.
1
1
1
2
Prj.
P1
P1
P2
P2
VT
[2013-01-01,)
[2013-06-01,2014-01-01)
[2013-01-01,)
[2013-01-01,)
Tabelle 3.3: Ausprägung, die den temporalen Schlüssel verletzt
Jedoch gibt es zu dem Zeitraum [2013-06-01, 2014-01-01) für den Primärschlüssel
(1, P1) zwei Tupel, was den temporalen Schlüssel verletzt. Demnach gilt, dass eine
Operation, welche die obige Ausprägung erzeugen würde, abgelehnt werden muss. Es
muss demnach bei jeder Datenbankoperation überprüft werden, ob bereits ein Datensatz
mit dem gleichen nicht-temporalen Schlüssel existiert, wo sich die Zeitpunkte schneiden.
Wenn dies der Fall ist, muss die Operation abgelehnt werden, da sie sonst die Integrität
der temporalen Tabelle verletzen würde. Eine Möglichkeit, dieses Constraint zu erzwingen
ist via DBMS-Mitteln wie Trigger und Assertions.
Temporale referentielle Integrität
Nun soll zusätzlich zu der ARBEITET-Relation die Geschichte der Angestellten aufgezeichnet werden, da die Gehaltsänderungen verfolgt werden sollen. Dabei passiert dasselbe
wie zuvor bei der ARBEITET-Relation. Das Resultat ist in der Tabelle 3.4 zu sehen.
Nr
1
2
Name
Max
Bob
Gehalt
2500
2600
VT
[2013-01-01,)
[2013-01-01,)
Tabelle 3.4: Relation ANGESTELLTE mit Valid Time
Nun soll zur Tabelle ARBEITET noch etwas Historie hinzugefügt werden. So hat
der Mitarbeiter 2 von 2012-01-01 bis 2013-01-01 beim Projekt P1 gearbeitet. Danach
wurde er jedoch aus dem P1 entlassen und hat das Projekt P2 zugewiesen bekommen.
Die Tabelle 3.5 zeigt diese temporalen Daten.
Dadurch entsteht jedoch ein neues Problem; im dritten Tupel der ARBEITETRelation wird der Angestellte 2 zum Zeitraum [2012-01-01, 2013-01-01) referenziert.
Dieser Angestellte hat aber laut der Datenbank zu diesem Zeitpunkt gar nicht existiert!
Das führt zum nächsten Problem im Entwurf der Datenbank: Temporale referentielle
Integrität.
Demnach muss eine Überprüfung stattfinden, dass zu jedem Zeitpunkt, in dem eine
fremde Tabelle referenziert wird, auch tatsächlich ein Objekt mit dem entsprechenden
26
Ang.
1
1
2
2
Prj.
P1
P2
P1
P2
VT
[2013-01-01,)
[2013-01-01,)
[2012-01-01,2013-01-01)
[2013-01-01,)
Tabelle 3.5: Mitarbeiter 2 wurde versetzt
Primärschlüsselattribut existiert. Das heißt, dass bei jeder Veränderung der referenzierten
Tabelle sowie der referenzierenden Tabelle eine Überprüfung stattfinden muss. Falls
die veränderten Daten dieser Bedingung nicht entsprechen, so muss die Operation, wie
bei dem temporalen Schlüssel, abgelehnt werden. Eine solche Überprüfung kann wie
der temporale Primärschlüssel durch einen Trigger geklärt werden. Wie diese Trigger
programmiert werden und in einem dynamischen Kontext funktionieren, wird im Kapitel
5 gezeigt.
Temporale Vollständigkeit
Als drittes Constraint auf temporalen Tabellen existiert die Vollständigkeit. Sie garantiert,
dass ein Objekt über die Zeit hinweg niemals Lücken enthält, demnach zwischen dem
ersten Anfangszeitpunkt und dem letzten Endzeitpunkt kein Zeitpunkt existiert, der
nicht in einem der Tupel des Objektes enthalten ist. So würde die folgende Ausprägung
der Tabelle 3.6 ANGESTELLTE den Constraint verletzen.
Nr
1
2
2
Name
Max
Bob
Bob
Gehalt
2500
2600
2800
VT
[2013-01-01,)
[2013-01-01,2014-01-01)
[2014-06-01,)
Tabelle 3.6: Relation ANGESTELLTE mit Valid Time
Bob ist definiert vom 2013-01-01 bis zum Ende der Zeit, jedoch existiert eine
Lücke zwischen dem 2014-01-01 bis 2014-06-01. Eine gültige korrigierte Ausprägung
der ANGESTELLTE-Relation ist in Tabelle 3.12 zu sehen. Dort ist die Lücke gefüllt.
Nr
1
2
2
2
Name
Max
Bob
Bob
Bob
Gehalt
2500
2600
2700
2800
VT
[2013-01-01,)
[2013-01-01,2014-01-01)
[2014-01-01,2014-06-01)
[2014-06-01,)
Tabelle 3.7: Relation ANGESTELLTE mit Valid Time
27
Coalesce
Als letzte mögliche Integritätsbedingung der temporalen Tabellen wird das Coalescing
betrachtet. Falls zeitlich benachbarte Daten die gleichen Tupelwerte besitzen, sollen
sie zu einem Tupel verschmolzen werden, der den gesamten Zeitraum umfasst. Falls
demnach nun die Ausprägung 3.8 existiert, wird sie automatisch zu der Ausprägung 3.9
transformiert.
Nr
1
2
2
2
Name
Max
Bob
Bob
Bob
Gehalt
2500
2600
2600
2600
VT
[2013-01-01,)
[2013-01-01,2014-01-01)
[2014-01-01,2014-06-01)
[2014-06-01,)
Tabelle 3.8: Relation ANGESTELLTE mit Valid Time
Nr
1
2
Name
Max
Bob
Gehalt
2500
2600
VT
[2013-01-01,)
[2013-01-01)
Tabelle 3.9: Relation ANGESTELLTE mit Valid Time
3.1.2
Anforderungskatalog: Anfragen
Neben den besprochenen Unterschieden im Entwurf einer Valid Time-Tabelle und den
Constraints gibt es auch Unterschiede in den Anfragen. Da Anfragen an nicht temporale
Tabellen immer von einem jetzt gültigen Zustand ausgehen, ist es nicht möglich, die alten
Anfragen zu übernehmen. In diesem Kapitel soll anhand von Beispielen repräsentiert
werden, welche Arten von Anfragen existieren und wie sie sich von den nicht-temporalen
Varianten unterscheiden.
Temporale Schnappschussanfrage
Als Anfang sei die Anfrage aus dem Quelltext 3.1 gegeben.
1 SELECT ∗
2 FROM ang;
Quelltext 3.1: Nicht temporale Anfrage an ANG
Zuvor hat die Anfrage lediglich die beiden Mitarbeiter mit ihrem jetzigen Gehalt
ausgegeben. Nach der Transformation in eine Valid Time-Tabelle ist nun die Ausgabe
nicht mehr wie gewünscht der jetzige Zustand, sondern die Vergangenheit des Mitarbeiters
Max ist zusätzlich mit angegeben. Wenn eine Anfrage für den jetzigen Zustand gewünscht
ist, so muss dies zusätzlich angegeben werden.
28
1 SELECT Nr, Name, Gehalt
2 FROM ang a1
3 WHERE a1.vt <@ now();
Quelltext 3.2: Anfrage für momentane Angestellte
Besonders ist im Quelltext 3.2 auf die Zeile 3 zu achten, da der <@-Operator verwendet
wird, welcher überprüft, ob ein Element in einem gegebenen Intervall vorhanden ist.
Wenn der jetzige Zeitpunkt im Zeitraum VT existiert, ist der Tupel jetzt gültig und
damit bei einer Schnappschussanfrage auszugeben.
Temporaler Join
Bei einem temporalen Join wird, im Unterschied zu einem gewöhnlichen Join, die Zeit
eines jeden Objekts berücksichtigt. So gilt, wenn zwei Tupel aus temporalen Tabellen
durch die gegebene Join-Bedingung in Beziehung stehen, dass zusätzlich beachtet werden
muss, ob beide einen gemeinsamen Zeitraum haben und wenn, wie dieser aussieht.
Als Beispiel wird der natürliche Verbund zwischen ARBEITET und ANG betrachtet.
Angenommen, Bob habe 2014 eine Gehaltserhöhung erhalten (siehe Tabelle 3.10).
Nr
1
1
2
2
Name
Max
Max
Bob
Bob
Gehalt
2000
2500
2600
3000
VT
[2010-01-01,2013-01-01)
[2013-01-01,)
[2010-01-01,2014-01-01)
[2014-01-01,)
Tabelle 3.10: Zweite Gehaltserhöhung von Max
Beim temporalen Verbund von ARBEITET und ANG würde dadurch der Tupel, der
Bob dem Projekt 2 zuweist, in zwei Tupel aufgeteilt werden: zunächst ein Tupel, der
im Zeitraum [2013-01-01, 2014-01-01) gilt und Bob mit dem Gehalt 2600 notiert und
ein Tupel im Zeitraum [2014-01-01, FOREVER), der Bob mit einem Gehalt von 3000
notiert. Diese Aufspaltung ist in diesem Fall nötig, da für die Zeiträume verschiedene
Daten durch den Verbund entstanden sind. Für den Zeitraum [2010-01-01, 2013-01-01)
existieren in der resultierenden Tabelle keine Einträge, da es in diesen Zeitpunkten keine
Zuweisung von Angestellten zu Projekten gab.
Nr
1
1
2
2
2
Name
Max
Max
Bob
Bob
Bob
Gehalt
2500
2500
2600
2600
3000
Prj.
P1
P2
P1
P2
P2
VT
[2013-01-01,)
[2013-01-01,)
[2012-01-01,2013-01-01)
[2013-01-01,2014-01-01)
[2014-01-01,)
Tabelle 3.11: Temporaler Join zwischen ANG und ARBEITET
29
Die dazugehörige Anfrage ist im Quelltext 3.3 zu sehen.
1 SELECT a1.nr, a1.name, a1.gehalt, a2.projekt, a1.vt ∗ a2.vt
2 FROM ang a1, arbeitet a2
3 WHERE a1.vt && a2.vt
Quelltext 3.3: Temporaler Join
Not Exists
Zusätzlich zum Join existiert der Anti-Join, der mit Hilfe eines NOT EXISTS in der
Where-Klausel dargestellt wird. Auch da muss die Gültigkeitszeit beachtet werden. Als
Beispiel sei nach allen Projektnamen gefragt, in denen Bob nicht mitarbeitet. Der naive
Ansatz wäre ein SQL-Befehl wie im Quelltext 3.4.
1
2
3
4
5
6
7
SELECT ar.projekt
FROM arbeitet ar
WHERE NOT EXISTS (
SELECT ∗
FROM ang an
WHERE an.nr = ar.angestellter AND an.name = ’Bob’
)
Quelltext 3.4: Nicht temporales NOT EXIST
Die Ausgabe dieser Anfrage wäre jedoch leer, was nicht dem temporalen Sinn entspricht.
Tatsächlich wäre die Ausgabe mit der Gültigkeitszeit betrachtet wie in der Tabelle 3.12
beschrieben, da Bob im Projekt P1 seit 2013 nicht mehr mitwirkt. Die Anfrage funktioniert
analog zum temporalen Join mit vier Fällen, die vereinigt werden.
Prj.
P1
VT
[2013-01-01,)
Tabelle 3.12: Projekte, in denen Bob nicht mitwirkt.
Eine Umsetzung des NOT EXISTS, welche keine längliche UNION-Verkettung benötigt, ist im Kapitel 5.3 zu finden. Dort werden Funktionen benutzt, die zuvor in der
Implementierung definiert werden.
3.1.3
Anforderungskatalog: Modifikation
Zusätzlich ändern sich auch DML-Anweisungen bei temporalen Tabellen, da das traditionelle DELETE, UPDATE und INSERT auf Tupeln basiert und die Zeitstempel nicht
beachtet. So bestehen die einzelnen Befehle unter Umständen aus mehreren Schritten.
Es lassen sich weiterhin die üblichen DML-Anweisungen ausführen, jedoch sind
diese gänzlich losgelöst von der Zeit; sie arbeiten tupelbasierend. Dadurch entstehen
Ergebnisse, welche unter Umständen nicht gewünscht sind. Es werden nun zeitbehaftete
Modifikationen betrachtet, welche zu den üblichen Angaben noch eine Zeitangabe besitzen,
für die sie angewendet werden sollen.
30
Temporales Delete
Das temporale Löschen löscht nicht in jedem Fall Tupel, häufig verändert es nur abhängig
vom angegebenen Zeitrahmen die Zeitattribute. Die unterschiedlichen Möglichkeiten,
die bei einem temporalen Löschvorgang auftreten können, umfassen unter Umständen
mehrere traditionelle DML-Vorgänge. Es existieren vier mögliche Fälle, wie der Zeitraum
für den Löschvorgang und der Zeitraum des betroffenen Tupels im Verhältnis zueinander
stehen können. Diese vier Fälle müssen mitsamt Beispielen betrachtet werden, um später
bei der Implementierung die einzelnen Schritte untersuchen zu können.
Fall 1 Falls der angegebene Zeitraum einen betroffenen Tupel vollständig umfasst, gilt
es, den Tupel zu löschen. Das ist der einfachste der vier Fälle; in diesem Fall wird die
Operation ausgeführt, als sei der Löschzeitraum gar nicht angegeben.
Nr
1
1
2
2
Name
Max
Max
Bob
Bob
Gehalt
2000
2500
2600
3000
VT
[2010-01-01,2013-01-01)
[2013-01-01,)
[2010-01-01,2014-01-01)
[2014-01-01,)
Tabelle 3.13: Ausgangsausprägung für das temporale Löschen
Angenommen es existiert die Ausprägung 3.13 und es sollen temporal alle Angestellten
gelöscht werden, die Bob heißen und 2010-01-01 bis 2014-01-01 existieren. Dann würde
der dritte Tupel entfernt werden und die resultierende Ausprägung wäre die Tabelle 3.14.
Nr
1
1
2
Name
Max
Max
Bob
Gehalt
2000
2500
3000
VT
[2010-01-01,2013-01-01)
[2013-01-01,)
[2014-01-01,)
Tabelle 3.14: Bob im Zeitraum [2010-01-01,2014-01-01) wird gelöscht
Fall 2 Im Fall, dass der Zeitraum vor dem Anfangszeitpunkt des Tupels beginnt, jedoch
vor dem Endzeitpunkt des Tupels endet, wird der Tupel verkürzt. Das führt dazu, dass
kein Tupel tatsächlich gelöscht wird; es wird nur einer verändert. Die DELETE-Operation
ist in diesem temporalen Kontext demnach ein UPDATE. Sei die Ausprägung 3.14 vom
ersten Fall gegeben. Wenn nun alle Angestellten mit dem Namen Max für den Zeitraum
[2010-01-01,2011-01-01) gelöscht werden sollen, so würde das erste Tupel eine Änderung
erfahren, alle anderen Tupel wären unberührt und die Anzahl der Tupel würde sich nicht
ändern. Die Änderung ist dabei, dass der Anfangszeitpunkt des VT-Attributs geändert
wird. Das Ergebnis ist in der Tabelle 3.15 zu sehen.
31
Nr
1
1
2
Name
Max
Max
Bob
Gehalt
2000
2500
3000
VT
[2011-01-01,2013-01-01)
[2013-01-01,)
[2014-01-01,)
Tabelle 3.15: Max im Zeitraum [2010-01-01,2011-01-01) wird gelöscht
Fall 3 Analog gilt dasselbe für den umgekehrten Fall. Schneidet der betroffene Tupel
den Löschzeitraum auf dem rechten Rand, muss der Endzeitpunkt von VT verändert
werden. Beispiel für eine solche Operation sei das Löschen von Max im Zeitraum [201201-01,2013-01-01) (vergleiche Ausgabetabelle 3.16).
Nr
1
1
2
Name
Max
Max
Bob
Gehalt
2000
2500
3000
VT
[2011-01-01,2012-01-01)
[2013-01-01,)
[2014-01-01,)
Tabelle 3.16: Max im Zeitraum [2012-01-01,2013-01-01) wird gelöscht
Fall 4 Falls jedoch der Zeitraum innerhalb der Gültigkeitszeit des Tupels liegt, entsteht
eine Lücke. Es entstehen zwei neue Objekte. Demnach sind drei Schritte nötig, um den
temporalen Schlüssel nicht zu verletzten. Im ersten Schritt wird der betroffene Tupel
kopiert zwischengespeichert. Im zweiten Schritt wird der betroffene Tupel verändert,
indem der Endzeitpunkt von VT auf den Anfangszeitpunkt des zu löschenden Zeitraums
gesetzt wird. Als letzter Schritt wird der zwischengespeicherte Tupel eingefügt, wobei
jedoch der Anfangszeitpunkt auf den Endzeitpunkt des Löschzeitraums gesetzt wird.
Nr
1
1
2
Name
Max
Max
Bob
Gehalt
2000
2500
3000
VT
[2011-01-01,2012-01-01)
[2013-01-01,2014-01-01)
[2014-01-01,)
Tabelle 3.17: Max im Zeitraum [2014-01-01,2015-01-01) wird gelöscht (Schritt 2)
Gegeben sei die Ausprägung 3.16 des vorherigen Falls. Falls nun erneut Max gelöscht
werden soll, aber diesmal im Zeitraum [2014-01-01,2015-01-01), so tritt der besprochene
Fall ein. Nach dem ersten Schritt wird die VT vom zweiten Tupel zwischengespeichert,
um sie später einsetzen zu können. Anschließend wird der nächste Schritt ausgeführt,
um die Ausprägung in Form der Tabelle 3.17 zu erhalten. Dann wird der letzte Schritt
eingeführt und dadurch ein neuer Tupel eingefügt um die Ausprägung 3.18 zu erhalten.
32
Nr
1
1
1
2
Name
Max
Max
Max
Bob
Gehalt
2000
2500
2500
3000
VT
[2011-01-01,2012-01-01)
[2013-01-01,2014-01-01)
[2015-01-01,)
[2014-01-01,)
Tabelle 3.18: Max im Zeitraum [2012-01-01,2013-01-01) wird gelöscht (Schritt 3)
Temporales Update
Das temporale Update hat wie das temporale Delete vier Fälle. Diese vier Fälle haben
die gleichen Bedingungen wie beim temporalen Delete, die einzelnen Schritte sind jedoch
unterschiedlich.
Fall 1 Falls der Updatezeitraum den Zeitraum eines betroffenen Tupels komplett
umfasst, wird der Tupel aktualisiert ohne weitere Tupel hinzuzufügen.
Fall 2 & Fall 3 Im zweiten und dritten Fall sind wie beim temporalen Delete die
betroffenen Tupel entweder von vorne oder von hinten geschnitten und der Updatezeitrahmen liegt nicht zwischen dem Anfangs- und Endzeitpunkt der Gültigkeitszeit des
Tupels. Im Gegensatz zum temporalen Löschen müssen aber nun zwei Schritte erfolgen;
zunächst muss der Tupel zwischengespeichert werden und im betroffenen Tupel der
Zeitraum auf den Updatezeitraum gesetzt werden. Danach muss der zwischengespeicherte
unveränderte Tupel wieder eingefügt werden, jedoch so, dass der Zeitraum passt. Das
ist vom Fall abhängig. Im zweiten Fall muss der eingefügte Tupel den Anfangszeitpunkt
auf den Endzeitpunkt des Updatezeitrahmens setzen. Im dritten Fall wird jedoch der
Endzeitpunkt auf den Anfangszeitpunkt des Updatezeitrahmens gesetzt.
Gegeben sei die vorherige Ausprägung 3.18. Nun wird Bobs Gehalt für den Zeitraum
von [2014-01-01,2015-01-01) auf 3400 gesetzt. Dadurch entstehen im ersten Schritt die
Ausprägung 3.19 und im zweiten Schritt nach dem Einfügen des zwischengespeicherten
unveränderten Tupels die Ausprägung 3.20. Zu sehen ist demnach, dass sich die Anzahl
der Tupel durch das Update um eins erhöht hat.
Nr
1
1
1
2
Name
Max
Max
Max
Bob
Gehalt
2000
2500
2500
3400
VT
[2011-01-01,2012-01-01)
[2013-01-01,2014-01-01)
[2015-01-01,)
[2014-01-01,2015-01-01)
Tabelle 3.19: Update Gehalt von Bob auf 3400 im Zeitraum [2014-01-01,2015-01-01)
(Schritt 1)
33
Nr
1
1
1
2
2
Name
Max
Max
Max
Bob
Bob
Gehalt
2000
2500
2500
3400
3000
VT
[2011-01-01,2012-01-01)
[2013-01-01,2014-01-01)
[2015-01-01,)
[2014-01-01,2015-01-01)
[2015-01-01,)
Tabelle 3.20: Update Gehalt von Bob auf 3400 im Zeitraum [2014-01-01,2015-01-01)
(Schritt 2)
Fall 4 Im vierten Fall werden wie beim temporalen Delete mehrere Schritte benötigt.
Jedoch wird diesmal noch ein Schritt mehr gebraucht, da der zwischengespeicherte Tupel
zweimal eingefügt werden muss, nachdem der betroffene Tupel via Update verändert
wurde. Die Schritte sind demnach analog zu den vorherigen Fällen, der zweite Schritt
wird jedoch zweimal ausgeführt; jeweils einmal für den Zeitraum vor dem angegebenen
Updatezeitraum und ein weiteres Mal für den danach.
Temporales Insert
Ein temporales Insert besitzt keine Fallunterscheidung wie das temporale Update oder
Delete; es unterscheidet sich von einem üblichen Insert nur in der Tatsache, dass der
eingefügte Datensatz noch zeitliche Informationen in Form der Gültigkeitszeit beinhaltet.
Dabei muss beachtet werden, dass trotzdem die in den vorherigen Kapiteln definierten
Integritätsbedingungen gelten. Falls temporale Schlüssel verlangt sind, kann kein Datensatz mit identischen zeitinvarianten Schlüsseln eingefügt werden, falls sich die Zeiträume
schneiden. Falls temporale Vollständigkeit verlangt ist, muss der eingefügte Datensatz
entweder komplett neu sein oder einem bestehenden Datensatz folgen.
Nach der temporalen Einfügeoperation kann auch ein automatisches COALESCED
geschehen. Dadurch ist es möglich, einen bestehenden Datensatz mithilfe eines temporalen
INSERT zu verlängern und damit auf das UPDATE zu verzichten.
Es existieren keine Fallunterscheidungen, da beim temporalen INSERT keine bestehenden Tupel betroffen sein können. Entweder die Einfügeoperation war erfolgreich und
damit wurde ein weiterer Tupel hinzugefügt (außer COALESCE gilt und ein bestehender
Tupel wurde verlängert) oder eine der Integritätsbedingungen wurde verletzt und damit
war die INSERT-Operation nicht erfolgreich und wird einfach abgelehnt.
3.2
Transaction Time-Tabellen
Transaction Time-Tabellen werden ähnlich wie Valid Time-Tabellen ausgezeichnet. Dabei
besitzt jeder Tupel einen Zeitraum oder Zeitstempel. Doch im Gegensatz zu den Valid
Time-Tabellen sollen diese nicht manuell durch DML-Anweisungen verändert werden. Sie
sollen lediglich dazu dienen, die Geschichte der Datenbankausprägung zu repräsentieren.
34
So gilt es, dass die Tabelle die Zustände der Tupel vor der Modifikation nicht vergisst. Es
soll möglich sein, den Stand der Welt zu einem bestimmten Zeitpunkt zu rekonstruieren.
Es sind zwei Möglichkeiten genannt worden, Transaction Time-Tabellen auszuzeichnen.
Zunächst die Methode mit Zeitstempeln, die den Zeitpunkt aufzeichnet, ab dem ein
Datensatz gültig ist. Falls ein Tupel existiert, der das gleiche Objekt beschreibt, jedoch
einen neueren Zeitstempel hat, so gilt stets der neueste als aktuellster Datensatz. Ein
Beispiel für diese Variante ist in der Tabelle 3.21 zu sehen.
Nr
1
2
2
Name
Max
Bobb
Bob
Gehalt
2500
2600
2600
TT
2013-01-01 12:00
2013-01-01 12:00
2014-01-01 12:00
Tabellen 3.21: Transaction Time-Tabelle mit Zeitstempeln
So ist zu sehen, dass der Name von Bob zunächst falsch geschrieben wurde. Dieser
Fehler wurde 2014 korrigiert. Dabei muss bei der Tabellendefinition beachtet werden,
dass Nr nicht als Primärschlüssel ausreicht. TT muss als Schlüssel hinzugefügt werden,
damit die obigen Daten die Bedingungen nicht verletzen. Das Problem mit diesem
Ansatz ist, dass Löschoperationen nicht ohne Probleme aufgezeichnet werden können.
Falls sich herausstellt, dass ein Objekt fälschlicherweise hinzugefügt wurde, so gibt
es keine Möglichkeit, einen Zeitstempel anzusetzen, der Löschoperationen beschreibt.
Diese Variante ist demnach nur sinnvoll, wenn lediglich UPDATE-Operationen an Daten
aufgezeichnet werden sollen.
Eine vollständigere Methode ist es, ähnlich wie bei den Valid Time-Tabellen, Zeiträume zu verwenden. Dadurch ist es möglich, Lücken darzustellen und die tatsächliche
Ausprägungsvergangenheit der Tabellen zu beschreiben.
Nr
1
2
2
3
Name
Max
Bobb
Bob
Maxima
Gehalt
2500
2600
2600
2100
TT
[2013-01-01 12:00,)
[2013-01-01 12:00,2014-01-01 12:00)
[2014-01-01 12:00,)
[2012-01-01 12:00,2013-01-01 12:00)
Tabellen 3.22: Transaction Time-Tabelle mit Zeiträumen
Transaction Time-Tabellen werden mithilfe von Triggern auf den Tabellen realisiert. So
werden bei einer UPDATE-Operation die alten Datensätze, die betroffen sind, zunächst
kopiert und der Endzeitpunkt des Attributes TT des alten Tupels auf den jetzigen
Zeitpunkt und der Startzeitpunkt von TT des neuen Tupels ebenfalls auf den jetzigen
Zeitpunkt gesetzt. Dann wird die UPDATE-Operation dementsprechend auf die kopierten
Tupel durchgeführt, um die Operation zu beenden. Bei Löschoperationen werden alle
betroffenen Tupel als beendet gekennzeichnet, indem der Endzeitpunkt von TT auf den
jetzigen Zeitpunkt gesetzt wird.
35
Anfragen auf Transaction Time-Tabellen sind dabei relativ einfach, da Dinge wie
Coalesing und temporaler NOT EXIST keine Rolle spielen. Gefragt wird meist, was die
aktuelle oder vergangene Ausprägung der Datenbank ist. Das funktioniert ähnlich wie
schon bei den Valid Time-Tabellen, wenn dort nach den jetzt gültigen Objekten gefragt
wurde. Interessant ist auch die Geschichte der Datensätze. Eine Anfrage an die Tabelle
sortiert, nach den Zeiträumen erfüllt, diesen Zweck.
3.3
Bitemporale Tabellen
Bitemporale Tabellen repräsentieren eine Kombination aus beiden Zeitdimensionen
mithilfe der beschriebenen Methoden. Wenn davon ausgegangen wird, dass die Transaction
Time mithilfe von Zeiträumen dargestellt wird, so gilt, dass jeder Tupel nun zwei Zeiträume
enthält. Einer für die Transaktionszeit, der andere für die Gültigkeitszeit. So besitzen die
einzelnen Objekte nun zwei Dimensionen. Dadurch zeichnet die Transaction Time noch
zusätzlich Änderungen in den Gültigkeiten auf.
Nr
1
2
2
2
Name
Max
Bob
Bob
Bob
Gehalt
2500
2600
2600
2700
TT
[2013-01-01,)
[2013-01-01,2013-06-01)
[2013-06-01,)
[2013-06-01,)
VTE
[2013-01-01,)
[2013-01-01,)
[2013-01-01,2014-01-01)
[2014-01-01,)
Tabelle 3.23: Bitemporale Tabelle
In der Tabelle 3.23 ist die Geschichte von Bobs Gehalt aufgezeichnet. So wurde
2013-06-01 in die Tabelle eine Änderung für Bobs Gehalt eingetragen. 2014 verdient Bob
nun 2700. Dadurch wurde der alte Zustand mithilfe der Transaktionszeit gespeichert und
der neue Zustand mit den neuen Gültigkeitszeiten eingetragen.
3.4
Herausforderungen & Probleme
Der Betrieb temporaler Datenbanken erfordert spezielle Kenntnisse und Vorkehrungen,
die wiederum zu unbekannten Herausforderungen und Problemen im Betrieb führen
können.
1. Bestehende Anfragen haben nicht mehr die gleiche Semantik wie zuvor. Durch die
Änderung des Tabellenaufbaus sind neue Anfragen vonnöten, um die Bedeutung
wie vorher zu erreichen. Wenn ein SQL-Befehl zuvor den aktuellen Zustand der
Daten ausgegeben hat, so ist nun zusätzlich die Geschichte enthalten.
2. Temporale Anfragen sind komplexer und größer. Dadurch, dass die Struktur der
Tabellen sich verändert hat und Objekte über mehrere Tupel hinweg reichen, sind
neue Anfragen anders und häufig verschachtelt zu gestalten. So gilt beispielsweise
36
beim temporalen Join, dass mehrere Fälle abgedeckt werden müssen und anschließend mit einer Vereinigung gearbeitet werden muss, um das temporal logische
Ergebnis zu bekommen.
3. Es entsteht neuer Aufwand beim Einrichten der Datenbank, da bestimmte Vorgänge
nun von der Standardimplementierung des DBMS abweichen. So gilt, dass es neue
Integritätsbedingungen gibt und bestehende nun anders implementiert werden
müssen. Das heißt, dass für jede bestehende Tabelle neue Operationen geschrieben
und angewendet werden müssen.
4. Der Betrieb der Datenbank erfordert neue Methoden für die Änderungen der Daten.
Bisherige DML-Anweisungen sind nicht mehr direkt anwendbar und müssen unter
Umständen durch mehrere andere Anweisungen ersetzt werden.
5. Die Punkte 1-4 führen direkt dazu, dass neue Mitarbeiter nicht mehr mühelos
an eine bestehende Datenbank herangeführt werden können. So gilt, dass neue
Anfragen komplexer sind und der Betrieb anders funktioniert als bei konventionellen
Datenbanken. Dies führt zu Mehraufwand bei der Einführung neuer Mitarbeiter in
ein bestehendes Datenbankprojekt.
Diese neu entstandenen Probleme sollen innerhalb dieser Ausarbeitung gelöst werden.
Der Betrieb der Datenbank lässt sich durch das Einführen generischer Methoden als
Teil einer Erweiterung von PostgreSQL lösen. Die gestiegene Komplexität der Anfragen
und Verwaltung wird durch eine Erweiterung von SQL gelöst. Dafür werden zunächst
bekannte temporale Datenbanksprachen betrachtet, um aus bestehenden Fehlern zu
lernen und bestehende Elemente zu übernehmen.
37
Kapitel 4
Temporale Datenbanksprachen
Da im Rahmen der Ausarbeitung eine temporale Erweiterung von SQL entwickelt wird, ist
es hilfreich, eine Analyse der bestehenden temporalen Datenbanksprachen zu entwickeln.
Dabei werden TSQL2 von Snodgrass, die neuen temporalen Idiome vom SQL Standard
SQL:2011 sowie VTSQL2+ betrachtet. Ziel ist es, effiziente Problemlösungen zu entdecken
und bekannte Probleme bei der Entwicklung der eigenen Spracherweiterung zu vermeiden.
4.1
TSQL2
TSQL2 wurde definiert in [Sno95] und ist eine Erweiterung von SQL-92. Die Sprache
wurde entwickelt, nachdem Snodgrass 1992 vorgeschlagen hat, temporale Elemente in
SQL einzufügen. Im selben Jahr wurde ein Komitee gegründet, welches die Sprache
gestalten sollte. 1994 wurde die TSQL2 Language Specification [SAA+ 94a] veröffentlicht.
TSQL2 behandelt dabei sowohl Valid Time-, Transaction Time- als auch bitemporale
Tabellen. Im Standard [Sno95] ist die Unterstützung der Allen-Relationships angegeben.
Alle Tabellen bei TSQL2 werden implizit als Coalesced angenommen; es ist nicht möglich,
eine Tabelle ohne Coalescing anzulegen oder gar eine Anfrage zu gestalten, in der das
Ergebnis nicht automatisch Coalesced ist.
Im Folgenden wird ein Auszug aus der Sprache in Beispielen erläutert. Dabei soll
nicht die genaue Sprachdefinition betrachtet werden, da dies den Rahmen sprengen
würde, sondern lediglich ein Szenario mit temporalen Daten durchgespielt werden. Das
Beispielszenario wird dabei dem Artikel [SAA+94b] entnommen.
Beispiele
Das Beispiel beschreibt einen Medienplan, das heißt einen Werbeplan für US TV-Events.
Dieser Plan beschreibt, welche Werbung zu einem gegebenen Zeitpunkt in einem TVEvent gezeigt werden soll. Beispiele für solche Events sind berühmte Serien wie Roseanne
oder Sportevents wie der Superbowl1 .
1
Der Superbowl ist das Finale der amerikanischen Football Liga. Es ist das weltweit größte Sportevent
mit über 100 Millionen Zuschauern. Werbeplätze in diesem Ereignis sind heiß begehrt.
38
Zunächst wird eine Tabelle angelegt, welche die Kosten für Werbeverträge der verschiedenen TV-Events angibt. Dafür eignet sich eine Valid Time-Tabelle, die angibt,
welche Angebote für den angegeben Zeitraum gültig sind.
1 CREATE TABLE NBCShows
2 (ShowName CHARACTER ( 30 ) NOT NULL,
3 InsertionLength INTERVAL SECOND,
4 Cost INTEGER)
5 AS VALID STATE YEAR ( 2 ) TO NDCSeason ;
Quelltext 4.1: TSQL2-Statement für Valid Time-Tabelle
Der Quelltext 4.1 ist ein TSQL2-Statement, welches die Valid Time-Tabelle erstellt.
Es besteht aus drei Feldern, dem Namen der Show2 , der Werbelänge und den Kosten
in USD. Die Werbelänge ist dabei als INTERVAL-Datentyp mit der Granularität von
Sekunden gespeichert. Worin sich das Statement jedoch von einem üblichen SQL92Statement zum Erstellen einer Tabelle unterscheidet, ist die AS-Klausel in Zeile 5, welche
die Tabelle als Valid Time definiert. Bei der Definition müssen zwei Optionen angegeben
werden, zunächst die Range, welche die maximale Zeit angibt, die repräsentiert werden
kann, und die Granularität. In diesem Fall ist Range YEAR ( 2 ), das heißt 102 = 100
Jahre. Als Granularität wird ein eigener Kalendertyp angegeben, TSQL2 erlaubt solche
Definitionen. Es handelt sich dabei um die Season, in der Shows ausgestrahlt werden. Im
Fernsehbusiness ist es üblich, Events über eine Season auszustrahlen und sein Programm
in einer Season zu definieren, deswegen bietet sich hier der eigene Datentyp an. Nun
verwenden wir ein INSERT Statement, um Daten in die Valid Time-Tabelle einzufügen.
1 INSERT INTO NBCShows
2 VALUES (’Roseanne’,
3
INTERVAL ’30’ SECOND,
4
251000)
5 VALID TIMESTAMP ’Spring Season 1994’ ;
Quelltext 4.2: TSQL2 Insert Statement
Quelltext 4.2 unterscheidet sich von einem normalen INSERT-Statement, da es zusätzlich das VALID TIMESTAMP-Keyword beinhaltet. Mittels des Keywords wird angegeben,
in welchem Zeitraum der Eintrag gilt. In diesem Beispiel wird der selbst definierte
Kalender verwendet, um eine für die Domäne passende Zeitangabe anzugeben.
Für den Medienplan wird jedoch noch eine Tabelle benötigt, die den Plan definiert,
welche Werbespots in welchem Event laufen.
2
Feldname dem Beispiel entnommen, in der Tabelle werden jedoch auch Sportevents gespeichert.
39
1 CREATE TABLE NBC FB Insertion
2 (GameName CHARACTER ( 30 ),
3 InsertionWindow INTERVAL FootballSegment,
4 InsertionLength INTERVAL SECOND ( 3, 0 ),
5 CommercialID CHARACTER ( 30 ) )
6 AS VALID EVENT YEAR ( 2 ) TO HOUR
7
AND TRANSACTION ;
Quelltext 4.3: TSQL2 Valid Time- und Transaction Time-Definition
Der Quelltext 4.3 definiert eine Tabelle für den Einkauf von Werbeplätzen in einem
Football-Event. Sie besteht aus vier Feldern, dem Spielnamen, in welchem Segment des
Football-Spiels die Werbung ausgestrahlt wird, wie lang diese ist und schlussendlich den
Namen der Werbung. Zusätzlich wird, wie in Quelltext 4.1, die Tabelle als Valid Time
mit einer Range von 102 = 100 Jahren definiert, jedoch diesmal mit einer Granularität
von Stunden. Es existieren dennoch zwei Unterschiede zur anderen Tabelle. Erstens wird
sie als VALID EVENT definiert, was bedeutet, dass Valid Time nicht mit einem Intervall,
sondern mit einem Zeitpunkt, in diesem Fall einer genauen Stunde, definiert ist. Zweitens
soll sie zudem die Transaction Time dokumentieren. Die Tabelle ist demnach bitemporal.
Einfügen geschieht analog.
1 SELECT N.GameName
2 FROM NBC FB Insertion AS N N2
3 WHERE N.GameName = N2.GameName AND
4 N.CommercialID <> N2.CommercialID
Quelltext 4.4: TSQL2 List of football games broadcast by NBC that have two or more
commercials
Als nächstes kommen Beispielanfragen auf diese Tabellen. Die erste Anfrage im
Quelltext 4.4 gibt zunächst alle Football-Events von NBC zurück, welche zwei oder
mehr Werbeeinblendungen vermerkt haben. Zunächst fällt auf, dass keine Angaben zur
Transaktionszeit gemacht wurden; wenn sie fehlt, wird angenommen, dass der aktuelle
Zustand abgefragt wird und ältere Zustände werden nicht beachtet. Die Anfrage sieht
zunächst aus wie normales SQL, es gibt jedoch einen syntaktischen Unterschied; die
Tabelle hat NBC_FB_Insertion zwei Aliase. Dadurch wird implizit eine temporale
Selektion durchgeführt. Das heißt, Elemente aus N und N2 werden zusammengehängt
ausgegeben (joined), wenn sie zur gleichen Zeit gültig sind, ihre Valid Time demnach
Überschneidungen enthält. Als letzter Schritt wird eine implizite Valid Time-Projektion
ausgeführt. Neben dem Spielnamen werden zusätzlich die passenden Intervalle zurückgegeben. Zusammengefasst passieren drei Vorgänge bei dieser Anfrage: Transaktionszeit
behandelt, Valid Time-Selektion, Valid Time-Projektion.
1 SELECT SNAPSHOT ShowName,
2 CAST(VALID(N) TO INTERVAL DAY)
3 FROM NBCShows(ShowName) AS N
4 WHERE N.ShowName = ’Roseanne’
Quelltext 4.5: TSQL2 How long has the Roseanne show run?
40
Die Anfrage im Quelltext 4.5 zeigt, wie lange die Show Roseanne schon im amerikanischen Fernsehen läuft. Dabei werden drei neue Konstrukte benutzt. Zunächst das
Keyword SNAPSHOT, welches angibt, dass die Ausgabe keine Valid Time- oder Transactional Time-Tabelle sein soll. Damit wird die implizite Valid Time-Projektion vermieden
und, wenn die ausgegebene Tabelle in anderen Anfragen verwendet wird, stehen keine
temporalen Operationen zur Verfügung. In der TEXT-Klausel findet sich neben der Tabellenangabe eine Angabe eines Attributs in Klammern. Diese Angabe ermöglicht, das
implizite Coalescing auf Attribute einzuschränken. Im Gegenzug dazu können jedoch
nur diese Attribute der Tabelle benutzt werden. So gibt die Anfrage an, die Valid Time
nach ShowName zusammenzufassen. In der SELECT-Klausel findet sich als letztes ein
Cast, welche das Valid Time-Attribut in ein SQL-INTERVAL umwandelt (INTERVAL im
Sinne von einer Zeitspanne, nicht einem Zeitintervall). Ausgegeben von dieser Anfrage
wird eine Zeile mit zwei Spalten, eine davon Roseanne und die andere eine Zeitspanne in
Tagen. Wenn wir annehmen, dass Roseanne etwa neun Jahre ausgestrahlt wurde, wäre
die zweite Spalte ’3285 Days’.
1 SELECT ShowName, VALID CAST(BEGIN(VALID(A) AS DAY))
2 FROM NBCShows(ShowName) (PERIOD) AS A
3 WHERE CAST(VALID(A) AS INTERVAL YEAR) >=
4 INTERVAL ’ 2 ’ YEAR
Quelltext 4.6: TSQL2 List all shows broadcast by NBC that ran continuously for at least
two years, and indicate the day that they began that run
In der letzten Anfrage im Quelltext 4.6 des Beispielszenarios soll herausgefunden
werden, welche Shows auf NBC mindestens zwei Jahre liefen und zusätzlich ausgegeben
werden, wann die Ausstrahlung dieser Shows anfing. In der FROM-Klausel wird noch einmal
ein Attribut angegeben, nach welchem ein benutzerdefiniertes Coalescing stattfindet.
Zusätzlich wird das Argument (PERIOD) angegeben, welches die in der Tabellendefinition
angegebene Valid Time-Granularität überschreibt und erzwingt, dass die Valid Time nun
in einer Period mit einem Start- und Endzeitpunkt angegeben wird. In der WHERE-Klausel
wird die Valid Time in ein INTERVAL umgewandelt und mit dem INTERVAL ’2’ YEAR
verglichen. Als letzter Vorgang wird in der SELECT-Klausel anschließend der Startpunkt
der Valid Time mithilfe der BEGIN-Funktion extrahiert und in die Granularität DAY
umgewandelt. Die BEGIN-Funktion funktioniert nur auf Periods, deshalb die Umwandlung
der selbstdefinierten Granularität in das Period in der FROM-Klausel.
4.2
SQL:2011
Der SQL-Standard wird von ISO3 und IEC4 veröffentlicht. In der Dezember 2011 veröffentlichten Version des SQL-Standards, genannt SQL:2011, kamen eine Reihe neuer
Features im Bereich der temporalen Datenbanken hinzu. Anhand des Artikels [KM12]
sollen diese Features nun vorgestellt werden.
3
4
International Organization for Standardization
International Electrotechnical Commision
41
Zunächst das Feature PERIOD, welches als Eckstein der Unterstützung für die Verarbeitung von temporalen Informationen bezeichnet wird. Im Gegensatz zu vielen anderen
temporalen Datenbanksystemen, wird in SQL:2011 kein neuer Datentyp hinzugefügt, der
ein geordnetes Paar aus Zeitpunkten darstellt, sondern vielmehr kann bei der Definition
einer Tabelle als Metadaten angegeben werden, welches Felderpaar eine Periode darstellt.
Dabei kann definiert werden, ob die Felder als Valid Time oder als Transaction Time
klassifiziert sind. Diese Period hat bestimmte Eigenschaften, die sie anschließend erfüllen
muss. Der Anfangszeitpunkt muss stets vor dem Endzeitpunkt sein und die Period ist
als ein Closed-Open Intervall definiert, wie im Kapitel 2.1.3 besprochen. Der Vorteil an
diesem Ansatz ist, dass sich Periods leichter auf existierende Datenbanken anlegen lassen
können, insbesondere wenn sie bereits ein temporales System angelegt haben, welches
auf ein Intervallpaar von Feldern basiert.
SQL:2011 schlägt auch die Unterscheidung in drei Typen von Tabellen vor; Application
Time Period-Tabellen (auch Valid Time-Tabellen genannt), System Versioned-Tabellen
(Transaction Time-Tabellen) und die Kombination von beidem, die bitemporalen Tabellen.
Im SQL:2011 Standard sind jedoch nicht alle Fragen vollständig geklärt und für
zukünftige Versionen noch Vorschläge offen gelassen worden. Es gibt noch keinen Vorschlag
vom Standard zu Period-Normalisierung (auch bekannt als COALESCED Option). Es
fehlt auch eine native Unterstützung von Period Joins, das heißt Joins von verschiedenen
Tabellen unter temporalen Voraussetzungen, insbesondere Outer Joins, da sich Inner
Joins mithilfe des OVERLAPS-Operator simulieren lassen.
Im Gegensatz zu älteren Ansätzen hat SQL:2011 auch einige wesentliche Unterschiede. So verzichtet SQL:2011 komplett auf temporale Abwärtskompatibilität, um
die Speicherung der Daten transparent zu halten. Die Entwickler halten außerdem eine
Abwärtskompatibilität in Valid Time-Tabellen für sinnlos, da eine Anfrage an solch
eine Tabelle immer auch Informationen mit Historie ausgeben soll. Außerdem wird auf
Syntaxerweiterungen wie beispielsweise ein Präfix oder eine Anfrage verzichtet, da solche
Syntaxregeln in vergangenen Vorschlägen unklare Definitionen beinhalteten.
Im Folgenden sollen anhand eines Beispielszenarios die drei Tabellenarten mit Beispielanfragen erläutert werden. Daran lassen sich die Unterschiede in der Syntax und im
Aufbau der Tabellen im SQL:2011-Standard erkennen.
4.2.1
Valid Time-Tabellen
In SQL:2011 werden Valid Time-Tabellen Application Time Period-Tabellen genannt.
Dabei wird nun am Beispiel einer Mitarbeiter Tabelle die Gültigkeitszeitdefinition sowie
einige DDL-Statements und Anfragen veranschaulicht.
42
1 CREATE TABLE Emp(
2 ENo INTEGER,
3 EStart DATE,
4 EEnd DATE,
5 EDept INTEGER,
6 PERIOD FOR EPeriod (EStart, EEnd)
7 )
Quelltext 4.7: SQL:2011 Valid Time-Tabelle
Zunächst wird in Quelltext 4.7 die Tabelle Emp für Employee mit vier Spalten angelegt.
Primärschlüssel wurden bei dieser Definition mit Absicht weggelassen; sie werden im
Quelltext 4.11 behandelt. Die vier Attribute werden dabei exakt wie in älteren SQLStandards angelegt. Dazu kommt jedoch die Zeile 6. In ihr wird angegeben, dass nun zwei
der Attribute als eine PERIOD, das heißt ein Zeitintervall, dargestellt werden. Dabei sind
PERIOD FOR definierte Schlüsselwörter. Danach kommt der Name dieses Intervalls und
anschließend in Klammern die beiden Attribute. Die Reihenfolge ist dabei bedeutend, da
das erste Argument den Startpunkt des Zeitintervalls angibt und das zweite Argument
den Endpunkt. Dabei schränkt SQL:2011 ein, dass die Start- und Endzeitpunkte entweder
vom Datentyp Date oder Timestamp sein müssen. Diese Einschränkung unterscheidet
sich von TSQL2, wo eine eigene Granularität angegeben werden konnte.
Diese Art der Definition von Zeitintervallen wurde gewählt, um bestehende Tabellen
leichter mit temporalen Operatoren ausstatten zu können, ohne Zeilen zu entfernen
oder Daten konvertieren zu müssen. In dieser Ausarbeitung werden die beiden Attribute
zusammengefasst zu einer PostgreSQL Range (vergleiche Unterkapitel 2.2.2).
1 INSERT INTO Emp VALUES (
2 22217,
3 DATE ’2010−01−01’,
4 DATE ’2011−11−12’,
5 3
6 )
Quelltext 4.8: SQL:2011 Insert in eine Valid Time-Tabelle
ENo
22217
EStart
2010-01-01
EEnd
2011-11-12
EDept
3
Tabelle 4.1: Valid Time-Tabelle nach dem INSERT in 4.8
In Quelltext 4.8 werden nun Daten eingefügt. Dabei unterscheidet sich dieses Statement
nicht von einem INSERT-Statement aus den vorherigen Versionen von SQL. In der Tabelle
4.1 ist das Ergebnis der Einfügeoperation zu sehen.
43
1 UPDATE Emp
2 FOR PORTION OF EPeriod
3
FROM DATE ’2011−02−03’
4
TO DATE ’2011−09−10’
5 SET EDept = 4
6 WHERE ENo = 22217
Quelltext 4.9: SQL:2011 Update
ENo
22217
22217
22217
EStart
2010-01-01
2011-02-03
2011-09-10
EEnd
2011-02-03
2011-09-10
2011-11-12
EDept
3
4
3
Tabelle 4.2: Valid Time-Tabelle nach dem UPDATE in 4.9
Im Quelltext 4.9 ist ein UPDATE-Statement zu sehen. Dabei unterscheiden sich die
Zeilen 1, 5 und 6 nicht von einem normalen Update; sie würden das Department vom
Benutzer 22217 auf 4 ändern. Hinzugekommen bei SQL:2011 ist die FOR PORTIONSyntax. Mit ihr ist es möglich anzugeben, für welchen Zeitraum ein Update passieren soll.
Demnach soll das Department in diesem Beispiel für das Zeitintervall [2011-02-03,
2011-09-10) aktualisiert werden, die restliche Zeit soll gänzlich unberührt bleiben. Hinter dieser Operation steht nicht nur ein Vorgang; zunächst werden alle betroffenen Zeilen,
die in diesem Zeitraum gültig sind, lokalisiert. Danach gibt es mehrere Möglichkeiten, was
passieren kann. Wenn eine Zeile komplett im angegebenen Zeitraum gültig ist, wird diese
Zeile aktualisiert. Falls der angegebene Zeitraum jedoch nur einen Teil einer Zeile betrifft,
wie in diesem Beispiel, gilt [2011-02-03, 2011-09-10) DURING [2010-01-01
2011-11-12). Dann wird diese Zeile in zwei Zeilen kopiert, wobei bei der ersten Zeile
der Endzeitpunkt zum Anfangszeitpunkt des im Update angegebenen Zeitintervalls geändert wird und in der zweiten Zeile der Anfangszeitpunkt zum Endzeitpunkt des im
Update angegebenen Zeitintervalls geändert wird. Nun wird die aktualisierte Zeile mit
dem angegebenen Zeitintervall zwischen den beiden Zeilen eingefügt. Es existieren noch
weitere Sonderfälle, wie, wenn das angegebene Intervall am Ende der Gültigkeit eines
der Datensätze ist oder, wenn mehrere Zeilen betroffen sind. Die Tabelle 4.2 zeigt das
Ergebnis dieser Aktualisierung.
1 DELETE FROM Emp
2 FOR PORTION OF EPeriod
3
FROM DATE ’2011−02−03’
4
TO DATE ’2011−09−10’
5 WHERE ENo = 22217
Quelltext 4.10: SQL:2011 Delete
Das DELETE-Statement von Quelltext 4.10 funktioniert analog zum Update. Dabei
kann eine Lücke entstehen, das heißt das Zeitintervall muss nicht zwangsweise genau
44
ENo
22217
22217
EStart
2010-01-01
2011-09-10
EEnd
2011-02-03
2011-11-12
EDept
3
3
Tabelle 4.3: Valid Time-Tabelle nach dem DELETE in 4.10
einen Datensatz betreffen. Es kann auch mehrere Datensätze umfassen und dadurch kann
eine Lücke entstehen.
1 ALTER TABLE Emp
2 ADD PRIMARY KEY (ENo,
3 EPeriod WITHOUT OVERLAPS)
Quelltext 4.11: SQL:2011 Without Overlaps
Im Quelltext 4.7 wurde die Angabe eines Primary Keys mit Absicht weggelassen, um
ihn jetzt im Quelltext 4.11 zu definieren. Dabei ist wichtig zu beachten, dass ENo als
Primärschlüssel nicht ausreicht. Das ist auch schon an den Tabellen aus den vorherigen
Beispielen zu beobachten, dort existieren mehrere Zeilen für einen ENo-Wert. Die Integrität in Valid Time-Tabellen ist nicht, dass zu jedem Primärschlüssel nur eine einzige Zeile
existieren darf; es gilt, dass zu einem Primärschlüssel zu jedem Zeitpunkt nur ein Wert
existieren darf. Deswegen ist zusätzlich die EPeriod als Primärschlüssel angegeben. Dazu
kommt noch eine Option, die in SQL:2011 hinzugefügt wurde: WITHOUT OVERLAPS.
Diese Option schließt im Beispiel Daten aus, wo der angegebene Wert in ENo identisch
ist und die angegebenen Zeitintervalle in EPeriod überlappen. Das würde die temporale
Integrität verletzen, da dadurch zu gegebenem Zeitpunkt zwei Zeilen zu einem Objekt
existieren würden.
4.2.2
Transaction Time-Tabellen
Neben den Valid Time-Tabellen bietet SQL:2011 auch zusätzliche Konstrukte für Transaction Time-Tabellen an. Im Kontext vom SQL:2011 werden diese jedoch Sytem VersionedTabellen genannt. Es wird das Beispiel von der Valid Time-Tabelle im vorherigen Unterkapitel aufgegriffen.
1 CREATE TABLE Emp
2 ENo INTEGER,
3 Sys start TIMESTAMP(12) GENERATED ALWAYS AS ROW START,
4 Sys end TIMESTAMP(12) GENERATED ALWAYS AS ROW END,
5 EName VARCHAR(30),
6 PERIOD FOR SYSTEM TIME (Sys start, Sys end),
7 PRIMARY KEY (ENo)
8 ) WITH SYSTEM VERSIONING
Quelltext 4.12: SQL:2011 Transaction Time-Tabelle
Beim Anlegen der Tabelle ist im Quelltext 4.12 zu sehen, dass neue Konstrukte
in SQL:2011 existieren. Die Zeilen 1, 2 und 5 unterscheiden sich zunächst nicht von
der Syntax des vorherigen SQL-Standards, in den anderen Zeilen finden sich jedoch
45
Unterschiede. So hat diese Tabelle mit vier Spalten zwei Spalten, die als TIMESTAMP
definiert sind. Hinter den Spaltendefinitionen sind jedoch die Schlüsselwörter GENERATED
ALWAYS AS ROW START angegeben. Damit wird vermieden, dass der Benutzer diesen
Zeilen selbst beim INSERT einen Wert zuweist oder via UPDATE verändert. In der
Zeile 6 werden die beiden Spalten als PERIOD definiert, damit die PERIOD-Operatoren
für sie verfügbar sind. In der Zeile 8 wird die Tabelle WITH SYSTEM VERSIONING
definiert. Dadurch wird jede DDL-Operation auf der Tabelle überwacht und gegebenenfalls
bestehende Daten, wie bei Transaction Time üblich, archiviert bzw. aktualisiert. Dabei
genügt als Primärschlüssel die ENo, da SQL:2011 implizit die temporale Integrität für
Transaction Time-Tabellen gewährleistet.
In den nächsten drei DDL-Statements wird die Auswirkung der Transaction TimeTabellen beschrieben. Dabei sind die Statements identisch zu den Varianten in den alten
Standards. Jedoch ist das Ergebnis in der Tabelle durch die Transaction Time-Definition
anders. So wird bei einem UPDATE nicht lediglich eine einzige Zeile verändert, es werden
auch Zeilen hinzugefügt.
1 INSERT INTO Emp (ENo, EName)
2 VALUES (22217, ’Joe’)
Quelltext 4.13: SQL:2011 Transaction Time-Tabelle Insert
ENo
22217
Sys Start
2014-01-01 09:00:00
Sys End
9999-12-31 23:59:59
EName
Joe
Tabelle 4.4: Transaction Time-Tabelle nach dem INSERT in 4.13
Im Quelltext 4.13 wird eine INSERT-Operation durchgeführt. Dabei soll angenommen
werden, dass sie zum Zeitpunkt 2014-01-01 09:00:00 abgeschlossen ist. Die Tabelle 4.4 zeigt
das Ergebnis der Operation. Dabei ist zu beachten, dass im Statement keine Angaben
zu Sys Start und Sys End gemacht worden sind. Sie werden implizit, abhängig vom
Zeitpunkt der Operation, durchgeführt. Dadurch entsteht ein Sys Start mit dem Wert
2014-01-01 09:00:00, da dies der erste Zeitpunkt ist, zu dem dieser Zustand des Objekts
mit den entsprechenden Werten in der Datenbank abgespeichert war.
1 UPDATE Emp
2 SET EName = ’Tom’
3 WHERE ENo = 22217
Quelltext 4.14: SQL:2011 Transaction Time-Tabelle Update
ENo
22217
22217
Sys Start
2014-01-01 09:00:00
2014-11-01 12:00:00
Sys End
2014-11-01 12:00:00
9999-12-31 23:59:59
EName
Joe
Tom
Tabelle 4.5: Transaction Time-Tabelle nach dem UPDATE in 4.14
46
Nun wird im Quelltext 4.14 zum Zeitpunkt 2014-11-01 12:00:00 dieses Objekt aktualisiert. Der ursprünglich eingetragene Name war falsch, der richtige Name des Mitarbeiters ist Tom. Nun wird der alte Datenbestand nicht einfach verworfen, so wie es
bei Schnappschuss-Tabellen üblich ist, sondern in einer neuen Zeile archiviert. Deshalb
existieren für das Objekt 22217 nun zwei Zeilen in der Datenbank. Die erste Zeile stellt
dar, was über das Objekt zwischen den Zeitpunkten 2014-01-01 09:00:00 und 2014-11-01
12:00:00 angenommen wurde. Das zweite Objekt zeigt an, was seit 2014-11-01 12:00:00
bekannt ist und auch für die Zukunft als richtig angenommen wird. Diese Operation
besteht demnach aus zwei Versionen im Sinne der alten SQL-Standards. Zunächst wird
der bestehende Datenbestand aktualisiert, indem er nur noch bis zum aktuellen Zeitpunkt
gültig ist (Änderung bei Sys End). Dann wird eine neue Zeile eingefügt, welche die selben
Werte wie die alten enthält, mit dem Unterschied der Daten, die via UPDATE geändert
wurden. Diese gelten ab dem aktuellen Zeitpunkt und werden erstmal als für immer wahr
eingestuft.
1 DELETE FROM Emp
2 WHERE ENo = 22217
Quelltext 4.15: SQL:2011 Transaction Time-Tabelle Delete
ENo
22217
22217
Sys Start
2014-01-01 09:00:00
2014-11-01 12:00:00
Sys End
2014-11-01 12:00:00
2015-11-01 12:00:00
EName
Joe
Tom
Tabelle 4.6: Transaction Time-Tabelle nach dem DELETE in 4.15
Nun werden die Daten via dem Befehl in Quelltext 4.15 ein Jahr später gelöscht.
Dafür wird die aktuell gültige Datenzeile des Objekts als ab jetzt nicht mehr in der
Datenbank bekannt angegeben, indem der Wert von Sys End auf den aktuellen Zeitpunkt
gesetzt wird.
1 SELECT ENo,EName,Sys Start,Sys End
2 FROM Emp FOR SYSTEM TIME AS OF
3 TIMESTAMP ’2015−01−02 00:00:00’
Quelltext 4.16: SQL:2011 Anfrage
ENo
22217
Sys Start
2014-11-01 12:00:00
Sys End
2015-11-01 12:00:00
EName
Tom
Tabelle 4.7: Ergebnis nach Anfrage 4.16 an Transaction Time-Tabelle
Nun wurden in SQL:2011 Anfragen an Transaction Time-Tabellen erweitert. Wenn an
einem SELECT ... FROM ein FOR SYSTEM_TIME AS OF TIMESTAMP <Zeitstempel>
angehängt wird, so werden alle Zeilen zurückgegeben, welche vom Datenbanksystem zu
diesem Zeitpunkt als wahr empfunden wurden. Dadurch lässt sich durch die Tabelle die
Historie der Annahmen an die Datenbank verfolgen.
47
4.2.3
Bitemporal-Tabellen
SQL:2011 beschreibt auch die Unterstützung für Tabellen, die sowohl Valid Time als
auch Transaction Time enthalten, das heißt bitemporale Tabellen. Dabei wird als Beispiel
eine Kombination der vorherigen beiden Unterkapitel genutzt.
1 CREATE TABLE Emp(
2 ENo INTEGER,
3 EStart DATE,
4 EEnd DATE,
5 EDept INTEGER,
6 PERIOD FOR EPeriod (EStart, EEnd),
7 Sys start TIMESTAMP(12) GENERATED ALWAYS AS ROW START,
8 Sys end TIMESTAMP(12) GENERATED ALWAYS AS ROW END,
9 EName VARCHAR(30),
10 PERIOD FOR SYSTEM TIME (Sys start, Sys end),
11 PRIMARY KEY (ENo, EPeriod WITHOUT OVERLAPS),
12 ) WITH SYSTEM VERSIONING
Quelltext 4.17: SQL:2011 Bitemporal-Tabelle
Im Quelltext 4.17 wird die bitemporale Tabelle erstellt. Dabei wird in den Zeilen
3, 4, 6 und 11 die Valid Time-Unterstützung definiert, wobei die Zeilen 7-10 sowie die
Zeile 12 die Transaction Time zu der Tabelle hinzufügen. DDL-Statements und Anfragen
funktionieren auf solchen Tabellen analog zu den vorangegangenen Kapiteln.
4.3
VTSQL2+
VTSQL2+ ist eine SQL-Spracherweiterung basierend auf VTSQL2, welche in der Diplomarbeit Objekt-relationale Realisierung einer temporalen Datenbanksprache von Frank
Beier [Bei01] beschrieben wurde. Beier basierte seine Ausarbeitung auf eine ObjektRelationale Datenbank und erstellte ein Werkzeug zur Übersetzung und Ausführung von
VTSQL2 Kommandos in einer Datenbank. VTSQL2+ unterscheidet sich von VTSQL2
beispielsweise in der Syntax. Es verwendet nicht das Schlüsselwort VALIDTIME um eine
Anfrage mit einem temporalen Kontext zu beschreiben, sondern benutzt stattdessen das
Schlüsselwort SEQUENCED.
Beispiele
Die folgenden Beispiele betrachten lediglich die Anfragen und ignorieren die DDL/DML
Statements von VTSQL2+ , da diese schon effektiv im SQL:2011 Standard betrachtet
wurden. Zunächst wird eine Tabelle in der Datenbank angelegt und mit einer Beispielausprägung gefüllt.
48
1 CREATE VALIDTIME COALESCED TABLE ang(
2 Nr NUMBER PRIMARY KEY,
3 Name VARCHAR(20),
4 Gehalt NUMBER,
5 Chefnr NUMBER REFERENCES ang);
Quelltext 4.18: CREATE TABLE Klausel für die Beispiel Datenbank
Quelltext 4.18 zeigt eine Erweiterung der CREATE TABLE-Anweisung um zwei
Schlüsselwörter. VALIDTIME markiert die Tabelle als eine Valid Time-Tabelle und
fügt implizit ein weiteres Attribut VT hinzu, welches die Gültigkeitszeit des Objekts in
der Tabelle beschreibt. COALESCED heißt, dass der Zustand der Tabelle nach jeder
Veränderung automatisch COALESCED sein soll. Demnach darf die Tabelle niemals
in einem Zustand sein, in dem zwei zeitlich benachbarte Objekte mit der gleichen
Identifikation und Werten existieren. Die Beispielausprägung ist aus der Tabelle 4.8 zu
entnehmen.
Nr
12
12
12
12
13
13
13
27
27
Name
Mueller
Mueller
Mueller
Mueller
Meier
Meier
Meier
Boss
Boss
Gehalt
5000
5000
5500
5000
5300
5300
5400
5200
5700
ChefNr
12
27
27
27
13
27
27
27
27
VT
[01.03.2000,01.05.2000)
[01.05.2000,01.07.2000)
[01.07.2000,01.10.2000)
[01.10.2000,FOREVER)
[01.04.2000,01.08.2000)
[01.08.2000,01.11.2000)
[01.11.2000,FOREVER)
[01.05.2000,01.09.2000)
[01.09.2000,FOREVER)
Tabelle 4.8: Beispielausprägung einer temporalen Tabelle inVTSQL2+
Abwärtskompatible Schnappschuss Anfragen
Abwärtskompatibilität garantiert, dass SQL-Statements, die zuvor galten, weiterhin gültig
sind und behandelt werden, als sei nur der aktuelle Zustand der Objekte gefragt. Das
garantiert, dass Anwendungen wie bisher laufen, auch wenn eine temporale Funktionalität
hinzugefügt wurde.
1 SELECT ∗ FROM ang;
Quelltext 4.19: Temporal abwärtskompatible Schnappschuss Anfrage
In der Anfrage 4.19 wird nach allen Entitäten aus der Relation ang gefragt. Sie besitzt
keine spezielle Syntax und ist damit auch nicht temporal. Deswegen ist die Rückgabe
in der Tabelle 4.9 alle Entitäten, die zum jetzigen Zeitpunkt noch gültig sind. Dabei ist
noch zu beachten, dass aus der Ausgabe das Feld VT entfernt wurde. Die Anfrage ist
damit komplett von der Gültigkeitszeit entkoppelt. Dadurch entsteht Kompatibilität zu
alten Anfragen, die noch keine Gültigkeitszeit erwartet haben.
49
Nr
12
13
27
Name
Mueller
Meier
Boss
Gehalt
5000
5400
5700
ChefNr
27
27
27
Tabelle 4.9: Ergebnis von Anfrage 4.19
Sequentielle Anfragen
Nun kommt die spezielle Syntax der Anfragesprache hinzu. Zunächst wird das SEQUENCED Schlüsselwort betrachtet, welches sequentielle Anfragen erzeugt. Jede sequentielle Anfrage gibt genau eine VT-Spalte zurück, welche die Gültigkeit der Entität
beschreibt. Jede erwähnte Tabelle in der FROM-Klausel eines SEQUENCED SELECT
muss eine Gültigkeitstabelle sein, das heißt sie muss eine VT-Spalte besitzen.
1 SEQUENCED SELECT name FROM ang WHERE Gehalt>5250;
Quelltext 4.20: Sequentielle Anfrage
Die erste Anfrage 4.20 beschreibt wann jeder Angestellte 5250 Euro verdient hat. In
der Tabelle 4.10 sieht man die Ausgabe. Dabei ist zu beachten, dass Mueller nur vom
01.07.2000 bis 01.10.2000 die Bedingung erfüllt. Ohne das SEQUENCED Schlüsselwort
wäre demnach zum Zeitpunkt 01.01.2013 Mueller nicht Bestandteil der Ausgabe, da er
im Tupel 4 der Tabelle 4.8, welches zu dem Zeitpunkt gilt, nicht genug Geld verdienen
würde.
Name
Mueller
Meier
Meier
Meier
Boss
VT
[01.07.2000,01.10.2000)
[01.04.2000,01.08.2000)
[01.08.2000,01.11.2000)
[01.11.2000,FOREVER)
[01.09.2000,FOREVER)
Tabelle 4.10: Ergebnis von Anfrage 4.20
In der Tabelle 4.10 ist noch zusätzlich zu beobachten, dass zeitlich benachbarte Tupel
mit gleichen Werten existieren. Diese lassen sich mithilfe des COALESCE Schlüsselworts
zusammenfassen.
1 SEQUENCED COALESCE SELECT name FROM ang WHERE Gehalt>5250;
Quelltext 4.21: Sequentielle Anfrage mit COALESCE
Im Ausdruck 4.21 wird das COALESCE Schlüsselwort benutzt. Die Anfrage ist
ansonsten identisch zu der Anfrage 4.20. Die Ausgabe fasst die Tupel 2-4 zusammen, da
sie die gleichen Werte besitzen und zeitlich benachbart sind. Die endgültige Ausgabe ist
in der Tabelle 4.11 zu sehen.
50
Name
Mueller
Meier
Boss
VT
[01.07.2000,01.10.2000)
[01.04.2000,FOREVER)
[01.09.2000,FOREVER)
Tabelle 4.11: Ergebnis von Anfrage 4.21
Nun wird zu der Anfrage 4.21 zusätzlich eingeschränkt, für welchen Zeitraum die
Ausgabe gültig sein soll.
1 SEQUENCED [01.01.2000, 01.09.2000)
2 COALESCE SELECT name FROM ang WHERE Gehalt>5250;
Quelltext 4.22: Sequentielle Anfrage mit COALESCE in einem spezifizierten Zeitraum
So ist zwischen dem SEQUENCED und COALESCE ein Zeitraum mithilfe eines
Intervalls in der Anfrage 4.22 angegeben. Diese Angabe schränkt die Ausgabe ein. Kein
Tupel in der Tabelle 4.12 hat einen Zeitpunkt in VT, der außerhalb des angegebenen
Zeitraums gilt. Da in diesem auch Boss nicht gültig war (siehe 4.11), fehlt dieser bei der
Ausgabe komplett.
Name
Mueller
Meier
VT
[01.07.2000,01.10.2000)
[01.04.2000,01.09.2000)
Tabelle 4.12: Ergebnis von Anfrage 4.22
Da wie angesprochen eine SEQUENCED SELECT Anfrage immer genau eine VT
Spalte als Ausgabe enthält, kommt die Frage auf, was passiert, wenn zwei Tabellen in
der FROM-Klausel angegeben werden.
1 SEQUENCED SELECT ∗ FROM ang a, ang b;
Quelltext 4.23: Sequentielle Join
Nun wir die Tabelle ang mit einem CROSS JOIN zweimal in der FROM-Klausel
angegeben. In einem SEQUENCED JOIN werden jedoch nur zwei Tupel miteinander
gejoint, die auch zu gemeinsamen Punkten gültig waren. So ist in der Ausgabetabelle
4.12 zu sehen, wie Mueller und Meier miteinander gejoint werden, jedoch nur wenn sie
zur gleichen Zeit gültig waren. Zusätzlich wird in der VT-Spalte noch angegeben, zu
welchen Zeitpunkten dieser CROSS JOIN gültig ist.
Nicht-Sequentielle-Anfragen
Als dritte und letzte Methode, Anfragen in VTSQL2+ zu stellen, existieren die NONSEQUENCED Anfragen. Sie unterscheiden sich zu abwärtskompatiblen Anfragen, da sie
die VT-Spalte nicht ausblenden und alle Tupel, die zu diesem Moment nicht gültig sind,
nicht einfach weglässt, sondern sowohl die Spalte als auch alle Tupel zu jedem Objekt
51
a.Nr
...
12
12
12
12
12
12
...
a.Name
a.G
a.CN
b.Nr
b.Name
b.G
b.CN
VT
Mueller
Mueller
Mueller
Mueller
Mueller
Mueller
5000
5000
5500
5500
5000
5000
12
27
27
27
27
27
13
13
13
13
13
13
Meier
Meier
Meier
Meier
Meier
Meier
5300
5300
5300
5300
5300
5400
13
13
13
27
27
27
[01.04.2000,01.05.2000)
[01.05.2000,01.07.2000)
[01.07.2000,01.08.2000)
[01.08.2000,01.10.2000)
[01.10.2000,01.11.2000)
[01.11.2000,FOREVER)
Tabelle 4.13: Ergebnis von Anfrage 4.23 (Namen der Übersichtlichkeit halber gekürzt)
als normale Entitäten einer Relation behandelt. So lassen sich SEQUENCED Anfragen
mithilfe von NONSEQUENCED emulieren.
1 NONSEQUENCED SELECT ∗, a.VT, b.VT FROM ang a, ang b;
Quelltext 4.24: Nicht-Sequentielle-Anfrage
Zunächst sieht das Beispiel 4.24 so aus wie die Anfrage 4.23, vom Schlüsselwort
NONSEQUENCED abgesehen. Jedoch werden in der SELECT-Klausel darüber hinaus
die VT-Spalten von beiden Tabellen verlangt. Außerdem wird hier der CROSS JOIN traditionell durchgeführt, sodass lediglich alle existierenden Tupel ungeachtet vom Zeitraum
miteinander kombiniert werden. Mit Operatoren lässt sich anschließend die Anfrage aus
4.23 nachstellen. So lassen sich mithilfe von NONSEQUENCED komplexere Anfragen
aufstellen, die von den Anforderungen von SEQUENCED abweichen.
4.4
Evaluation
Es lassen sich nun die drei vorgestellten Sprachen vergleichen. Zunächst das etwas
betagtere TSQL2. Es besitzt eine etwas obskure Syntax und fragwürdige sowie komplexe
Ideen, was viel Interpretationsspielraum in der Implementierung ermöglicht. So sind alle
temporalen Tabellen darin stets COALESCED und die Syntax für das Anlegen neuer
temporaler Tabellen ist nicht intuitiv. Es ist wohl verständlich, dass diese Erweiterung
keinen Platz im SQL-Standard gefunden hat.
Der neue SQL-Standard hat gute Ansätze, ist jedoch noch nicht ausgereift. Er
beschäftigt sich hauptsächlich mit den DDL/DML-Ausdrücken und bietet nur eine
spezielle Syntax für Transaction Time-Tabellen, jedoch nicht für Valid Time-Tabellen.
Auch solche Fragen wie COALESCE sind durch den Standard nicht beantwortet. Das Ziel
war es wohl, einen schlanken Standard zu gestalten, der attraktiv für die Implementierung
der DBMS ist. Es existiert jedoch zum Zeitpunkt der Ausarbeitung noch kein DBMS,
welches diesen Teil des Standards vollständig implementiert.
Als letzte Sprache wurde die hauseigene Spracherweiterung VTSQL2+ eingeführt.
Sie besitzt eine klare und einfache Syntax und versucht Probleme zu lösen, die durch
temporale Daten entstehen. Sie besitzt mächtige Mittel mit den drei Typen der Anfragen,
52
die nach kurzer Einführung selbsterklärend sind. Elemente aus diesen Sprachen werden
sich hervorragend für die Entwicklung der eigenen Spracherweiterung eignen.
Nachdem die drei Sprachen nun ausführlich analysiert und evaluiert wurden, stellt
sich endgültig die Frage, welche Elemente der Sprache nun in die selbst entwickelte
SQL-Sprache fließen werden. Dabei kann TSQL2 schon komplett gestrichen werden,
da die Syntax antiquiert und nicht wie Teil einer modernen Spracherweiterung wirkt.
Da der SQL-Standard 2011 keine Spracherweiterung für temporale SELECT-Anfragen
bietet, eignet sich dafür die Syntax von VTSQL2+ . Es bleiben demnach DDL und DMLAusdrücke übrig. Für DML-Ausdrücke ist die Syntax vom Standard zwar sehr verbose,
jedoch auch klar und intuitiv zu verstehen. Für den Gewinn der Zukunftskompatibilität
eignen sich die DML-Statements vom SQL:2011-Standard demnach hervorragend. Bei
den DDL-Ausdrücken wirkt die Syntax von VTSQL2+ wesentlich ausgereifter, da sie
mit Schlüsselwörtern vor der Spaltendefinition arbeitet und somit die ursprüngliche
Syntax deutlich mehr intakt lässt. Der SQL-Standard verwendet dafür Schlüsselausdrücke
innerhalb der Spaltendefinition und wird deshalb nicht gewählt. Zusammenfassend kann
demnach bestimmt werden, dass mit leichten Modifikationen, die SELECT-Anfragen sowie
die DDL-Ausdrücke von VTSQL2+ gewählt werden, die DML-Ausdrücke jedoch vom
Standard übernommen werden. Dadurch wird die Zukunftskompatibilität gewährleistet
ohne auf eine klare Syntax zu verzichten.
53
Kapitel 5
Entwurf und Implementierung
Dieses Kapitel beschäftigt sich mit der Umsetzung und Pflege der vorher genannten
Erweiterung von SQL sowie der PostgreSQL-Extension. Zunächst wird die SQL-Sprache
beschrieben; dabei wird die Syntax und Semantik dieser Sprache vorgestellt. Dadurch
wird auch klar, welche Funktionen von der PostgreSQL-Extension benötigt werden. Diese
werden anschließend im nächsten Unterkapitel beschrieben. Diese enthält Trigger und
Funktionen, welche die Übersetzung der vorher genannten Sprache vereinfachen. Als
letztes Unterkapitel wird die Übersetzung der SQL-Erweiterung beschrieben. Dafür wird
der Bison/Flex-Parser vorgestellt und wie der davon erzeugte AST navigiert wird.
Abbildung 5.1: Architektur
Die formlose Abbildung 5.1 zeigt die Bestandteile der Software mitsamt der Abhängigkeiten. Daran sind die drei großen Teile der Architektur zu erkennen, welche nacheinander
in Unterkapiteln beschrieben werden. Die PostgreSQL-Extension kann dabei noch feiner
unterteilt werden, weil die Funktionen zu einzelnen Modulen zusammengefasst werden
können.
54
5.1
SQL-Erweiterung
Um den Betrieb der temporalen Datenbank zu vereinfachen, wurde im Rahmen der
Ausarbeitung eine Erweiterung für den SQL-Standard entwickelt. Dabei soll die Syntax
um Elemente ergänzt werden, welche die Kernkonzepte und Herausforderungen von
temporalen Daten behandeln. Im Kapitel 4 wurden bereits historische Versuche dokumentiert; hier sollen nun Konzepte von dem Kapitel entnommen und verändert in eine
neue Sprache zusammengefügt werden. Die im Kapitel 3 besprochenen Anforderungen
an temporale Tabellen sollen mithilfe dieser Erweiterung der Sprache sichtlich erfüllt und
die Bedienung dieser speziellen Art von Relation erleichtert werden.
Dafür werden alle veränderten SQL-Befehle zunächst auf die Änderungen der Syntax
betrachtet und anschließend anhand von Beispielen ihre Semantik erklärt, das heißt
welche Operationen auf der Tabelle ausgeführt werden. Beispiele zu der definierten SQLErweiterung lassen sich im späteren Kapitel 5.3 finden; dort ist auch gleichzeitig die
Übersetzung in traditionellem SQL angezeigt, welches die Semantik der Kommandos
noch verdeutlichen kann.
5.1.1
DDL/DML-Kommandos
Im Kapitel 3 war deutlich zu sehen, dass die Erstellung und Verwaltung von temporalen
Daten sich von traditionellen Tabellen unterscheidet; es existieren neue Integritätsbedingungen, welche bei den DDL-Kommandos beachtet werden müssen und DML-Kommandos
haben eine veränderte Semantik und benötigen unter Umständen einen Zeitraum, in dem
sie gelten. Zunächst wird CREATE TABLE betrachtet.
CREATE TABLE
CREATE TABLE ist um drei Arten erweitert worden, die VALIDTIME, VALIDTIME
COALESCED und die WITH SYSTEM VERSIONING-Varianten. Dabei können, wie
aus dem Syntaxdiagramm 5.2 entnommen werden kann, VALIDTIME und VALIDTIME
COALESCED mit WITH SYSTEM VERSIONING kombiniert werden. Alle diese zusätzlichen Schlüsselwörter sind optional; wenn sie weggelassen werden, wird die Tabelle
nicht-temporal definiert. Das heißt, dass sich das CREATE TABLE-Kommando abwärtskompatibel verhält und in dieser Variante kein anderes Verhalten aufweist. Dabei ist zu
beachten, dass die im Syntaxdiagramm angemerkten <Name> und <Columns> (Spaltendefinitionen) dabei analog zum traditionellen SQL funktionieren und keine speziellen
Mechanismen besitzen.
VALIDTIME Damit wird eine Tabelle mit Gültigkeitszeit ausgestattet; sie bekommt
demnach eine zusätzliche Spalte mit dem Namen VT. In ihr wird die Valid Time eines Tupels ausgezeichnet. Zusätzlich gilt, dass die Tabelle den Constraint für temporale Schlüssel
besitzt, demnach kann kein Objekt zweimal zur gleichen Zeit existieren (vergleiche Kapitel
3.1.1).
55
CreateTable:
VT:
TT:
Abbildung 5.2: Syntax-Diagramm für CREATE TABLE
VALIDTIME COALESCED Es gilt zunächst das gleiche wie bei VALIDTIME, jedoch
wird noch zusätzlich die COALESCED-Integritätsbedingung zu der Tabelle hinzugefügt,
die zeitlich benachbarte identische Tupel zu einem Tupel zusammenfügt.
WITH SYSTEM VERSIONING Zu der Tabelle wird noch eine Spalte TT hinzugefügt,
welche den Typ TSRANGE besitzt, der einem Intervall aus Zeitstempeln entspricht.
Zusätzlich wird noch ein Trigger hinzugefügt, der eben diese Transaktionszeit verwaltet
und bei der Veränderung der Daten durch DML-Kommandos die für die Transaktionszeit
notwendigen Operationen durchführt.
INSERT
INSERT besitzt keine spezielle Syntax und braucht demzufolge auch kein Syntaxdiagramm.
Die Gültigszeit eines Tupels wird lediglich beim einfügen wie ein normales Attribut
eingefügt. Alle Spezialfälle, wie das Zusammenfügen von benachbarten Tupeln oder
temporale Schlüssel werden mithilfe von Triggern gelöst, die bereits in CREATE TABLE
angelegt wurden. Das erspart die Änderung von vorhandenen INSERT-Statements in der
Software, die mit der Datenbank kommuniziert.
UPDATE
Die Syntax von UPDATE besitzt im Gegensatz zum traditionellem SQL ein neues Syntaxelement, im Syntaxdiagramm 5.3 als ForVT markiert. Dort kann angegeben werden, für
welchen Zeitraum das Update ausgeführt werden soll und damit endgültig, welche Tupel
betroffen werden. Falls das optionale ForVT weggelassen wird, verhält sich das Update
abwärtskompatibel wie beim traditionellen SQL auf Tupel-Ebene und beachtet die Gültigkeitszeit gar nicht. Dabei sind <Set> und <Where> identisch zum traditionellen SQL
56
definiert. Dabei besteht jede Zeitpunktangabe aus zwei Elementen, dem RangeType und
dem RangeElement, die nachfolgend näher vorgestellt werden.
Update:
ForVT:
Date:
Abbildung 5.3: Syntax-Diagramm für UPDATE
<RangeType> Der <RangeType> gibt an, von welchem Typ das nachfolgend angegebene Datum ist; das ist wichtig, damit die Anfrage die Granularität des Zeitpunktes
kennt und damit die Operationen entscheiden kann, die für die Anfrage benutzt werden.
Es ermöglicht damit eine größere Flexibilität von Granularitäten.
<RangeElement> Angabe eines Zeitpunktes in der vorher angegebenen Granularität.
Dabei ist zu beachten, dass das Element in einfachen Anführungsstrichen geschrieben
werden muss. Das gibt größere Freiheit bei Datentypen, die Elemente mit Leerzeichen
zulassen und erleichtert zusätzlich den späteren Übersetzungsvorgang.
Die Syntax ist äußerst verbose, jedoch abgesehen von der Datumsangabe in einfachen
Anführungsstrichen vollkommen abwärtskompatibel zum SQL-Standard. Deshalb existiert
auch eine Angabe der Spalte, welche die Gültigkeitszeit enthält. Da die SQL-Erweiterung
keine anderen Spaltennamen zulässt, ist in der Syntax fest das VT als Spaltenangabe
verankert. Das ist nur ein geringer Overhead bei der Programmierung für die zukünftige
Abwärtskompatibilität.
DELETE
Das DELETE funktioniert analog zu dem Update, jedoch existiert keine <Set>-Angabe.
Die Gültigskeitszeitangabe funktioniert allerdings gleich und es existiert ein abwärtskompatibler Modus, welcher auf Tupelebene arbeitet und die VT-Spalten vollkommen
ignoriert.
57
Delete:
ForVT:
Date:
Abbildung 5.4: Syntax-Diagramm für DELETE
5.1.2
Anfragen
Die SQL-Erweiterung hat die größten Änderungen in den SQL-Anfragen. Wie in VTSQL2+
existieren nun drei Arten von Anfragen: Schnappschussanfragen, SEQUENCED und
NONSEQUENCED. Die verschiedenen Varianten werden mit der Hilfe von Schlüsselwörtern vor der eigentlichen Anfrage als solche gekennzeichnet. Im Syntaxdiagramm 5.5 ist
die klein wirkende Änderung notiert. Dabei ist zu beachten, das innere Anfragen die beispielsweise in der FROM- oder WHERE-Klausel von <Sel> auch mit SEQUENCED und
NONSEQUENCED markiert werden können und den selben Regeln unterstehen wie eine
äußere Frage. Im Klartext bedeutet das, dass eine beliebige Verschachtelungstiefe von den
temporalen SELECT-Anfragen möglich ist. Insbesondere SEQUENCED-Unteranfragen
in der WHERE-Klausel benötigen eine besondere Semantik, da sie im Zusammenhang
mit der äußeren Anfrage stehen. in den folgenden Paragraphen werden die verschiedenen
Varianten der neuen SQL-Anfragen analysiert und Beispiele vorgebracht, um die Semantik
zu verstehen. Dafür ist es auch nötig, die abwärtskompatible Version zu betrachten.
Abbildung 5.5: Syntax-Diagramm für SELECT-Anfragen
Weder NONSEQUENCED noch SEQUENCED Falls eine Anfrage weder als
NONSEQUENCED noch als SEQUENCED markiert ist, gilt sie als abwärtskompatibel.
Das Verhalten der Anfrage ist dann abhängig von den referenzierten Tabellen. Falls
die Tabellen nicht temporal sind, wird die Anfrage einfach als solche übernommen und
58
ausgeführt. Wenn jedoch temporale Tabellen in der FROM-Klausel sind, so werden diese
als Schnappschuss referenziert. Das heißt, es werden nur die aktuell gültigen Objekte
betrachtet. Das führt dazu, dass in einem bestehenden System die existierenden SQLAnfragen nicht angepasst werden müssen.
NONSEQUENCED NONSEQUENCED verursacht, dass die in der folgenden SELECTAnfrage referenzierten Tabellen unverändert betrachtet werden, mitsamt ihrer VT- und
TT-Attribute. Wenn mehrere Tabellen referenziert werden, wird kein temporaler Join
ausgeführt. Im NONSEQUENCED-Modus lassen sich demnach die temporalen Daten auf
Tupelebene betrachten und dies ermöglicht, dass solche Operationen wie der temporale
Join im NONSEQUENCED-Modus nachprogrammiert werden können.
SEQUENCED SEQUENCED-Anfragen werden als Anfragen betrachtet, wo die Zeiträume
der temporalen Daten in jeder Aktion berücksichtigt werden. Die referenzierten Tabellen einer SEQUENCED-Anfrage müssen stets Gültigkeitszeit beinhalten. Das Ergebnis
einer SEQUENCED-Anfrage ist stets eine Tabelle mit einer Gültigkeitszeit. Das führt
dazu, dass SEQUENCED-Anfragen ineinander geschachtelt über FROM-Unteranfragen
legal sind, sogar wenn die FROM-Unteranfrage einen temporalen Join enthält, da SEQUENCED garantiert, das stets nur ein Attribut mit dem Namen VT, welches die
Gültigkeitszeit beschreibt, vorhanden ist.
<TIntv> An dieser Stelle lässt sich in einfachen Anführungsstrichen in Intervallnotation
angeben, für welchen Zeitraum die Tabelle abgefragt werden soll. Im Ergebnis sind dann
keine Tupel mehr vorhanden, die außerhalb des angegebenen Zeitraums gültig sind.
COALESCED COALESCED bedeutet, dass die Ausgabetabelle der Anfrage automatisch
COALESCED ist, das heißt benachbarte identische Tupel zu einem Tupel verschmolzen
werden.
5.2
Funktionen der PostgreSQL Extension
Nachdem die SQL-Erweiterung definiert wurde, ist in einigen der neu definierten Kommandos deutlich geworden, das neue Funktionen und Trigger nötig sind, welche es ermöglichen
neue temporale Daten hinzuzufügen, zu verändern, zu löschen oder anzufragen. Diese
Funktionen werden als Extension von PostgreSQL realisiert, da man diese leicht auf
neue Systeme installieren kann und sie schnell einsatzbereit sind. Dabei ist ein Großteil
der Funktionen in der PostgreSQL eigenen Sprache PL/pgSQL geschrieben. Lediglich
die Funktion, welche die SQL-Erweiterung von Kapitel 5.3 übersetzt, ist mithilfe von C
geschrieben.
Die Erweiterung besteht dabei aus vier verschiedenen Elementen; die Funktionen, welche die Allen-Relationships realisieren, DDL/DML-Funktionen, Anfrage-Hilfsfunktionen
und eine Übersetzungsfunktion. Zunächst sollen die Funktionen der Allen-Relationship
betrachtet werden, da sie die Grundlage für alle anderen Implementierungen bilden.
59
5.2.1
Allen-Relationships-Funktionen
Mithilfe der Operationen des Range-Datentyps lassen sich die 13 Allen-Relationships darstellen. Diese definierten Funktionen sind nützlich im allgemeinen Kontext der Anfragen
sowie für die Programmierung der folgenden Trigger und diversen anderen Funktionen, da
sie häufig Anfragen an Intervallpaare haben (beispielsweise ob zwei Intervalle benachbart
sind). In der Tabelle 5.1 sind die Übersetzungen der Relationen zwischen zwei Intervallen
beschrieben, in der Tabelle 5.2 die Übersetzung der Relationen zwischen Zeitpunkten
und Intervallen.
Before(t1 , t2 )
After(t1 , t2 )
During(t1 , t2 )
Contains(t1 , t2 )
Overlaps(t1 , t2 )
Overlapped-by(t1 , t2 )
Meets(t1 , t2 )
Met-by(t1 , t2 )
Starts(t1 , t2 )
Started-by(t1 , t2 )
Finishes(t1 , t2 )
Finished-by(t1 , t2 )
Equals(t1 , t2 )
t1 << t2
t1 >> t2
t1 <@ t2 AND t1 <> t2
t1 @> t2 AND t1 <> t2
t1 && t2 AND t1 < t2 AND upper(t1 ) < upper(t2 )
t1 && t2 AND t1 > t2 AND upper(t1 ) > upper(t2 )
t1 -|- t2 AND t1 < t2
t1 -|- t2 AND t1 > t2
lower(t1 ) = lower(t2 ) AND upper(t1 ) < upper(t2 )
lower(t1 ) = lower(t2 ) AND upper(t1 ) > upper(t2 )
lower(t1 ) > lower(t2 ) AND upper(t1 ) = upper(t2 )
lower(t1 ) < lower(t2 ) AND upper(t1 ) = upper(t2 )
t1 = t2
Tabelle 5.1: Übersetzung der 13 Relationships-Arten von Allen zu Operationen auf den
Range-Datentyp
Before(p, t)
After(p, t)
Meets(p, t)
Starts(p, t)
Met-by(p, t)
Finishes(p, t)
During(p, t)
p < lower(t)
p > upper(t)
p = lower(t)
p = upper(t)
p <- t
Tabelle 5.2: Übersetzung der Relationen zwischen Zeitpunkt und Zeitintervall
Es ist dabei überflüssig, eigene Methoden für den Vergleich zwischen Zeitpunkten
anzulegen. PostgreSQL bietet dafür bereits eigene Methoden an, die dafür verwendet
werden können.
Es ist zudem auch schwierig diese Methoden generisch anzulegen; PostgreSQL bietet
nur begrenzte Unterstützung für polymorphe Programmierung via den Pseudo-Typen
anyelement und dessen Spezialisierungen an (vergleiche Tabelle 5.3 1 ).
1
http://www.postgresql.org/docs/9.3/static/datatype-pseudo.html
60
Name
anyelement
anyarray
anynonarray
anyenum
anyrange
Beschreibung
Beliebiger Datentyp
Beliebiges Array
anyelement ohne Arrays
Beliebiges Enum
Beliebige Range
Tabelle 5.3: Übersetzung der Relationen zwischen Zeitpunkt und Zeitintervall
So sind die Funktionsdefinitionen der Allen-Relationships von der Polymorphie der
Pseudotypen abhängig. Bei den Operationen zwischen Zeitintervallen sind die Definitionen
mit zwei anyrange Datentypen verzeichnet. In den Relationen zwischen Zeitpunkten und
Zeitintervallen ist der Zeitintervall-Datentyp mit anyrange verzeichnet, wohingegen der
Zeitpunkt vom Typ anyelement ist. Wenn jetzt zusätzlich Funktionen für Punkt zu Punkt
Relationen verzeichnet wären, müssten ihre Eingabewerte den Datentyp anyelement
tragen. Dadurch würden sie die vorherigen Definitionen überdecken, da anyelement auch
Ranges beinhaltet. Es gibt keine Möglichkeit, eine Range aus dem Datentyp auszuschließen.
Dadurch entsteht die Einschränkung, dass die Allen-Relationships für Zeitpunktpaare nicht mit der passenden Benennung implementiert werden können. Da jedoch in
PostgreSQL passende Operatoren existieren, die den gleichen Zweck erfüllen, schränkt
das die Möglichkeiten zur Verarbeitung der Zeit nicht ein. Da nur drei Relationen von
Allen für Punktpaare beschrieben wurden (Before, After und Equals) und diese mit
logischen Vergleichsoperatoren (<, > und =) logisch zu ersetzen sind, ist dies auch keine
Einschränkung für die Nutzer.
5.2.2
Funktionen für DDL/DML
Da die SQL-Erweiterung es ermöglicht, DDL/DML im temporalen Kontext auszuführen,
werden Funktionen benötigt, welche die nicht-temporalen Versionen ergänzen, falls die
Anfrage das benötigt. So gilt, dass wenn ein DDL/DML-Kommando Syntax verwendet,
welche im jetzigen SQL-Standard nicht vorgesehen war, eine von den hier definierten
Funktionen aufgerufen werden muss, da die bereits vorhanden CREATE TABLE, DELETE und UPDATE in temporalen Datenbanken die benötigten Anforderungen nicht
erfüllen.
temporal create(tbl name TEXT, columns TEXT, vt BOOLEAN, coalesced
BOOLEAN, tt BOOLEAN)
Das temporal create führt als ersten Schritt stets ein CREATE TABLE der Form 5.1
aus. Dabei werden die TEXT-Variablen tbl name und columns in die CREATE TABLEAnweisung eingebunden und dadurch dynamisch eine Tabelle mit den Namen tbl name
und den in columns definierten Spalten erstellt.
61
Nun existieren noch drei weitere boolsche Argumente, welche den Typ der Tabelle
kennzeichnen. Falls vt wahr ist, wird die Tabelle mit zwei ALTER TABLE-Anweisungen
mit einem neuen Schlüsselattribut VT erweitert. Dieses ist vom Typ RANGE und
enthält die Gültigkeitszeit. Es sind zwei ALTER TABLE-Anweisungen notwendig, da
die erste Anweisung das Attribut hinzufügen muss und die zweite Anweisung den
Primärschlüssel-Constraint um VT erweitert. Anschließend bekommt die Tabelle einen
temporal unique trigger().
1 CREATE TABLE tbl name (
2 columns
3 )
Quelltext 5.1: temporal create CREATE TABLE-Anweisung
Falls coalesced wahr ist, wird der Tabelle der Trigger temporal coalesce trigger()
hinzugefügt. Das garantiert, dass zeitlich benachbarte identische Tupel zusammengefasst
werden. Ist tt wahr, so wird ähnlich wie bei VT ein neuer Primärschlüssel TT hinzugefügt, welcher die Transaktionszeit repräsentiert. Da das jedoch nicht ausreicht, um eine
Transaktionstabelle zu erstellen, muss noch der Trigger transaction time trigger() zu der
Tabelle hinzugefügt werden.
temporal delete(tbl name TEXT, vt anyrange, where text TEXT DEFAULT
’true’)
temporal update(tbl name TEXT, vt anyrange, set text TEXT, where text
TEXT DEFAULT ’true’)
Die Operationen temporal delete und temporal update funktionieren wie beschrieben im
Kapitel 3.1.3. Dabei sind die Argumente der Funktionen selbsterklärend. Die Löschfunktion besitzt ein optionales Argument mit der WHERE-Klausel, falls es benötigt wird. Die
UPDATE-Funktion hat ein Argument für die SET-Klausel und wie die Löschfunktion
ein Argument für die WHERE-Klausel.
5.2.3
Trigger
Jeder vorgestellte Trigger der temporalen Tabellen soll eine Integritätsbedingung erzwingen. Das heißt, dass diese Trigger durch eine Änderung der Daten aktiviert werden und
den Vorgang abbrechen, falls eine der Integritätsbedingungen verletzt wurde.
Die Herausforderung hierbei ist, dass diese Trigger nicht vollständig dynamisch erzeugt
werden. Wenn mit CREATE TRIGGER ein Trigger an eine Relation gebunden wird, so
wird bei jeder angegebenen Aktion auf dieser Relation eine Funktion aufgerufen, die beim
CREATE TRIGGER angegeben wird. Ein Beispiel für eine solche Aktion ist im Quelltext
5.2 zu sehen. Dabei ist zu sehen, dass bei jedem INSERT oder UPDATE von VT die
Funktion temporal_unique_trigger() für jede veränderte Zeile aufgerufen wird.
Dieser Aufruf wird dynamisch im temporal_create(...) erzeugt und aufgerufen,
wenn eine temporale Tabelle mit der entsprechenden Integritätsbedingung angelegt
werden soll.
62
1
2
3
4
CREATE TRIGGER ang valid time constraints trigger
BEFORE INSERT OR UPDATE OF vt ON ang
FOR EACH ROW
EXECUTE PROCEDURE temporal unique trigger();
Quelltext 5.2: CREATE TRIGGER-Beispiel
Jedoch wird die Funktion temporal_unique_trigger() nicht dynamisch erzeugt
und wurde einmal programmiert. Sie gilt damit als Blaupause für alle Trigger, die
diese Integritätsbedingungen überprüfen und erzeugen, vollkommen unabhängig von der
Tabelle, an der sie gebunden wird. Wie das funktioniert, wird im nächsten Abschnitt
erklärt. Zunächst wird erläutert, welche Integritätsbedingungen der Trigger erfüllt und
anschließend anhand eines Beispiels beim temporal_unique_trigger() wie das
Prinzip der Blaupause funktioniert, welches analog zu den anderen Triggern verwendet
wird.
temporal unique trigger() Der Trigger wird aktiviert, wenn entweder neue Daten in
die Tabelle eingefügt oder das Attribut VT eines Tupels verändert wird. Er verhindert, dass
zwei Tupel mit den gleichen Schlüsselattributen (ohne VT) überschneidende Zeitpunkte
besitzen. So wird verhindert, dass das UPDATE oder INSERT ausgeführt wird. Deswegen
triggert ein DELETE den Trigger nicht; die temporale Einzigartigkeit kann damit nicht
verletzt werden.
Um den Trigger zu implementieren, werden via der selbst entwickelten Hilfsmethode
get primary keys(tbl name TEXT) die Primärschlüssel ermittelt. Sie verwendet dafür
die PostgreSQL Katalogtabelle pg attribute.
Nun wird im Trigger via der NEW-Variable überprüft, ob bereits ein Datensatz mit
den Schlüsselattributen und überschneidenden Zeitpunkten existiert. Dabei muss darauf
geachtet werden, dass bei einem UPDATE der alte Datensatz ignoriert wird. Falls die
obige Bedingung erfüllt wird, so wird eine Exception geworfen und die Datenmanipulation
abgebrochen.
63
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
CREATE OR REPLACE FUNCTION temporal unique trigger() RETURNS trigger AS $$
DECLARE
breaks constraint boolean;
primary keys text[];
tt where TEXT := ’’;
BEGIN
primary keys := get primary keys(TG TABLE NAME);
primary keys := array remove(primary keys, ’vt’);
FOR i IN array lower(primary keys, 1)..array upper(primary keys, 1) LOOP
primary keys[i] := ’tbl.’ || primary keys[i] || ’ = ($1).’ || primary keys[i];
END LOOP;
IF (TG OP = ’INSERT’) THEN
EXECUTE ’SELECT true = any(SELECT ’ ||
concat ws(’ AND ’, VARIADIC primary keys) || ’ FROM ’ || TG TABLE NAME
|| ’ AS tbl ’ || ’WHERE tbl.vt && ($1).vt’ || ’)’
USING NEW
INTO breaks constraint;
ELSIF (TG OP = ’UPDATE’) THEN
EXECUTE ’SELECT true = any(SELECT ’ ||
concat ws(’ AND ’, VARIADIC primary keys) || ’ FROM ’ || TG TABLE NAME
|| ’ AS tbl ’ || ’WHERE tbl.vt && ($1).vt AND tbl.∗ <> $2’ || ’)’
USING NEW, OLD
INTO breaks constraint;
END IF;
IF breaks constraint THEN
RAISE EXCEPTION ’UNIQUE TEMPORAL CONSTRAINT VIOLATED’;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Quelltext 5.3: temporal unique trigger()
Im Quelltext 5.3 ist die Implementierung zu sehen. In der ersten Zeile ist zu sehen,
wie die Funktionsdefinition aussehen muss, wenn ein Trigger programmiert werden soll.
Die Funktion darf zunächst keine Argumente direkt entgegennehmen. Es ist jedoch
möglich, Argumente zu benutzen, indem die spezielle Variable TG_ARGV[] verwendet
wird. Beim CREATE TRIGGER-Aufruf können die Argumente wie bei jedem anderen
Funktionsaufruf auch übergeben werden, lediglich in der Prozedur selbst müssen sie über
die speziellen Variablen zugegriffen werden. Der Grund für die umständliche Definition
ist wohl historisch. Neben TG_ARGV[] existieren weitere spezielle Variablen, die beim
Aufruf implizit gesetzt werden. Interessant sind TG_OP, welche die Operation des Aufrufs
beschreiben, beispielsweise ein Update oder ein Insert, NEW welches bei einem INSERT
den eingefügten Tupel darstellt und bei einem UPDATE den veränderten Tupel, OLD
welches bei einem UPDATE den veränderten Tupel vor seiner Änderung darstellt und
beim DELETE den gelöschten Tupel annimmt und als letztes TG_TABLE_NAME welches
64
den Namen der Tabelle enthält, an welchen der Trigger gebunden ist. Mithilfe dieser
Variablen lässt sich die Blaupause erst erzeugen.
Die definierte Funktion hat als Rückgabetyp einen Trigger, obwohl in der Implementierung ein Tupel zurückgeliefert wird. Dieser Tupel ist abhängig von der Operation,
die den Trigger aufruft. So benötigt ein DELETE den Tupel, der gelöscht werden soll,
das UPDATE den veränderten Tupel und das INSERT den eingefügten. Falls NULL
zurückgegeben wird, so wird die Operation einfach nicht ausgeführt. Mit dieser Mechanik
ist es beispielsweise möglich, Tupel vor dem Einfügen zu manipulieren.
In den Zeilen 7-12 wird nun mithilfe der speziellen Variable TG_TABLE_NAME und
der Funktion get_primary_keys(<name>), welche in PostgreSQL so noch nicht
existierte und im Rahmen der Ausarbeitung hinzugefügt wurde, der Primärschlüssel der
Tabelle extrahiert und zu einem logischen Ausdruck zusammengefasst. In den Zeilen
14-26 wird dann bei einem INSERT oder UPDATE überprüft, ob der neu eingefügte
oder veränderte Tupel sich mit einem bereits in der Tabelle bestehenden Tupel schneidet.
Dafür wird mithilfe von EXECUTE und den speziellen Variablen dynamisches SQL
erzeugt, welches unabhängig von der Tabelle die gewünschte Anfrage ergibt. Anschließend
wird in den Zeilen 28-30 überprüft, ob die vorherigen Anfragen ein Ergebnis hatten und
falls das der Fall ist, wird die Aktion abgebrochen, ansonsten wie gewollt ausgeführt. Das
System ist extrem flexibel und benötigt, dass weniger Code dynamisch erzeugt werden
muss. Für die anderen Trigger funktioniert das analog.
temporal referential trigger() Um temporale referentielle Integrität zu implementieren, werden zwei verschiedene Trigger benötigt, da zwei Tabellen überprüft werden
müssen; die referenzierte und die referenzierende Tabelle. Dabei werden diesmal Parameter für die Trigger benötigt, die via TG ARGV abgefragt werden können. Diese werden
für die Angabe der Spalten benötigt, die referenziert werden. Die Implementierung richtet
sich dabei an den definierten Check aus dem Kapitel 3.1.1.
temporal coalesce trigger() Dieser Trigger richtet sich nach dem COALESCEDAttribut von VTSQL2, welches bestimmt, dass zeitlich benachbarte Daten mit ansonsten
identischem Tupelinhalt zeitlich verschmolzen werden. Dafür wird nach jedem INSERT
und UPDATE von VT, ähnlich wie beim temporal unique trigger(), überprüft, ob für
die veränderten Daten nun identische, zeitlich benachbarte Tupel existieren. Falls das
der Fall ist, wird der Tupel gelöscht und der Tupel aus NEW angepasst. Anschließend
wird der Algorithmus nochmal ausgeführt bis es keine benachbarten Daten mehr gibt.
Dadurch werden keine unnötigen Tupel angeschaut und die Methode bleibt effektiv.
transaction time trigger()
Der Trigger wird aktiviert, wenn ein beliebiges UPDATE oder DELETE auf der Tabelle
geschieht. Zunächst wird sowohl bei einem Update als auch bei einem Delete überprüft,
ob der betroffene Tupel den jetzigen Zeitpunkt enthält. Falls das nicht der Fall ist, so
geschieht bei diesem Tupel nichts. Bei einem Update kopiert es den Tupel, passt den
65
TT-Zeitstempel an und wendet anschließend die UPDATE-Veränderung an. Bei einem
DELETE werden bei allen beschriebenen Tupeln die TT-Zeitstempel abgeschlossen.
Dabei gibt es jedoch noch ein Problem; wenn im Trigger selbst DELETE oder
UPDATE verwendet wird, kommt es zu einer unendlichen Rekursion. Das lässt sich
umgehen, indem mit INSERT gearbeitet wird. So wird beim UPDATE lediglich eine
angepasste Kopie des OLD-Records via INSERT eingefügt und beim DELETE wird
vor dem Löschvorgang auch der angepasste OLD-Record eingefügt, sodass er zweimal
existiert, jedoch einer von ihnen gelöscht wird. Zusätzlich müssen die oben genannten
DDL/DML-Funktionen und Trigger der Valid Time-Tabellen angepasst werden, sodass
sie nur die Tupel beachten, welche den jetzigen Zeitpunkt im TT-Zeitraum besitzen.
5.2.4
Anfrage-Hilfsfunktionen
Anfrage Hilfsfunktionen werden bei der Übersetzung benutzt, um Operationen effizienter
und eleganter auszuführen.
coalesced(tbl name TEXT, attr TEXT)
Die Coalesced Funktion unterscheidet sich von den vorher genannten Funktionen. Die
vorherigen waren Aktionen, das heißt wenn sie ausgeführt worden sind, hat sich der
Datenbestand verändert. Coalesced ist jedoch eine Funktion, die im Rahmen einer Anfrage
aufgerufen werden soll. Ihr Rückgabewert ist ein SET OF RECORDS. Das bedeutet,
dass sie eine Tabelle zurückgibt.
1 SELECT ∗
2 FROM coalesced(
3 ’(SELECT name, vt
4 FROM ang
5 WHERE gehalt > 5250)’,
6 ’name’
7 ) AS (Name TEXT, vt DATERANGE)
Quelltext 5.4: Beispiel Coalesced
Im Quelltext 5.4 ist ein Beispiel für den Aufruf der Funktion gegeben. Benutzt wird
sie in der FROM-Klausel und das Ergebnis wird wie eine Tabelle behandelt. Es existieren
zwei Parameter. Der erste Parameter ist die Unteranfrage, welche am Ende Coalesced
sein soll. Der zweite Parameter sind die Spalten, nach denen zusammengefasst werden
soll. Da PostgreSQL zur Laufzeit die resultierende Tabelle nicht kennt, muss anschließend
im Alias-Abschnitt eine Tabellendefinition angegeben werden.
Der zweite Parameter und die Tabellendefinition sind dabei syntaktischer Overhead,
da beides aus der inneren Anfrage ermittelt werden kann. In der SQL-Erweiterung kann
dies ausgenutzt werden, um die Anfrage zu konstruieren.
66
relation split(vt anyrange, rel TEXT)
Die Funktion relation split spaltet die Tupel der Relation rel auf. Die Aufspaltung basiert
dabei auf dem ersten Argument vt. Ein Beispiel wäre, wenn die Funktion aufgerufen mit
relation split(’[2011-01-01, 2012-01-01)’, ’ang’) wird, wobei die Ausprägung von ang wie
in der Tabelle 5.4 angegeben wird.
Nr
1
1
2
2
Name
Max
Max
Bob
Bob
Gehalt
2000
2500
2600
3000
VT
[2010-01-01,2013-01-01)
[2013-01-01,)
[2010-01-01,2014-01-01)
[2014-01-01,)
Tabelle 5.4: Ausgangsausprägung für das temporale Löschen
Die Ausgabe der Funktion ist wie bei COALESCED ein Set aus Tupeln, welche in
diesem Fall so aussehen wie in Tabelle 5.5 angegeben.
Nr
1
1
1
1
2
2
2
2
Name
Max
Max
Max
Max
Bob
Bob
Bob
Bob
Gehalt
2000
2000
2000
2500
2600
2600
2600
3000
VT
[2010-01-01,2011-01-01)
[2011-01-01,2012-01-01)
[2012-01-01,2013-01-01)
[2013-01-01,)
[2010-01-01,2011-01-01)
[2011-01-01,2012-01-01)
[2012-01-01,2014-01-01)
[2014-01-01,)
Tabelle 5.5: Ausgangsausprägung für das temporale Löschen
Das ist besonders nützlich bei Unteranfragen in der WHERE-Klausel, da es erlaubt,
diese ungeachtet der Gültigkeitszeit zu betrachten.
5.3
Übersetzung der SQL-Erweiterung
Nachdem die Syntax und Semantik der SQL-Erweiterung definiert wurde, fehlt noch
die Übersetzung der SQL-Erweiterung in einen SQL-Dialekt, den PostgreSQL versteht.
Dafür werden die aus dem vorherigen Kapitel entwickelten Hilfsmethoden verwendet,
um eine einfache Übersetzung zu ermöglichen, die sich einfach verändern und entwickeln
lässt.
Der Vorteil, so viele Hilfsfunktionen der Extension zu verwenden, besteht darin, falls
zusätzliche Funktionalität benötigt wird oder ein Fehler sich eingeschlichen hat, dass
der komplexe Übersetzungsprozess nicht angefasst werden muss, sondern lediglich die
67
Funktion verändert werden muss und somit das Ergebnis der Übersetzung nicht anders
ist, jedoch die endgültige Ausführung sich verändert.
In diesem Kapitel wird zunächst der Parser und Lexer betrachtet, welcher die Sprache
liest. Anschließend wird der Übersetzungsvorgang zu einem AST betrachtet, welcher
den Zwischenschritt zur Transformation zu Standard-SQL darstellt. Als letzter Schritt
wird die Übersetzung von der erweiterten SQL-Syntax in Standard-SQL anhand von
Beispielen veranschaulicht.
5.3.1
Parser
Zunächst wird ein Lexer und Parser in Bison und Flex entwickelt, welcher aus dem
erweiterten SQL-Befehl zunächst einen AST generiert. Der Parser ist dabei eine Erweiterung des Beispielparser, welcher in [Lev09] zu finden ist. In dieser Lektüre wurde
als Beispiel ein rudimentärer SQL-Parser entwickelt. In der Ausarbeitung wurde dieser
vervollständigt, um einen Großteil des SQL-Standards zu parsen. Ansätze für die ASTGenerierung in diesem Beispielparser wurden bereits in einem anderen Projekt realisiert,
welches auf der Webseite 2 zu finden ist. Diese Ansätze wurden als Inspiration und Basis
genutzt, um die eigene AST-Generierung zu implementieren. Der generierte AST wird
anschließend benutzt, um eine mit PostgreSQL kompatible Anfrage zu generieren. Dafür
muss jedoch zunächst entschieden werden, welche Syntaxelemente in der Erweiterung
zum traditionellen SQL hinzukommen.
Die Entscheidung einen Parser mit Bison zu entwickeln entstand, weil einfache Mittel
wie reguläre Ausdrücke keine beliebige Rekursionstiefe erlaubt hätten. Damit wären
verschachtelte Anfragen wesentlich schwieriger zu übersetzen gewesen. Außerdem erlaubt
ein Parser leichtere Erweiterbarkeit mit Elementen und Bison wird auch von PostgreSQL
zum lesen der Anfragen benutzt.
5.3.2
Abstract Syntax Tree (AST)
Bison und Flex ermöglichen es, direkt beim parsen Code auszuführen. Dadurch lassen
sich Logging-Funktionalitäten hinzufügen, jedoch auch ein Übersetzer einfügen. Die
Übersetzung direkt beim Lesen auszuführen wäre jedoch nicht möglich gewesen, da
beim Übersetzungsvorgang eine Datenbankanbindung nötig ist. Die Datenbankanfragen
während des Parsevorgangs auszuführen wäre demnach weder performant, noch modular.
So lässt sich der AST gänzlich ohne Datenbankanbindung generieren und testen, was für
den Entwicklungsvorgang von Vorteil war.
In 5.5 ist ein Quelltext vermerkt, der in der Abbildung 5.6 als Syntaxbaum zu sehen
ist. Das navigieren der beliebig tiefen Knoten in beispielsweise dem Tables-Abschnitt
ist via dem Visitor-Pattern einfach umzusetzen und dafür geeignet, eine inkrementelle
Übersetzung vorzunehmen.
2
https://github.com/hoterran/sqlparser
68
Statement
Select
IsSequenced
True
Columns
name
gehalt
Tables
Where
ang
Bool
Comparison
Operator
Variable
Operator
Int
gehalt
>
5250
AND
Comparison
Variable
Operator
String
name
=
’Bob’
Abbildung 5.6: Beispiel AST
1 SEQUENCED SELECT name, vt
2 FROM ang
3 WHERE gehalt > 5250 AND name = ’Bob’
Quelltext 5.5: Beispiel Anfrage für AST-Umwandlung
5.3.3
Übersetzung
Die Übersetzung der erweiterten SQL-Syntax in PostgreSQL-kompatibles SQL passiert
mithilfe eines vorher kompilierten C-Moduls innerhalb des Extension-Ökosystems von
PostgreSQL. Dabei ist der Parser, welcher den Quellcode, wie zuvor erläutert, in den
AST umwandelt, vollkommen unabhängig von der Erweiterung.
Das C-Modul bietet eine Methode sql translate an, welche einen String als Parameter
anbietet. Dieser String soll die SQL-Anweisung mit der erweiterten Syntax beinhalten. In
der Abbildung 5.7 ist der Ablauf vom sql translate Aufruf bis zum übersetzten Ausdruck
zu verfolgen. Dabei wird zunächst der von Bison und Flex generierte Parser aufgerufen
und anschließend der AST durchlaufen und ein neuer SQL-Befehl zusammengesetzt.
Dieser wird anschließend zurückgegeben und an die Applikation weitergereicht. Zwischen
der Rückgabe des übersetzten Befehls und der Generierung des ASTs kann es zur Kommunikation mit dem PostgreSQL Server kommen, da manche der Befehle abhängig von
der Struktur der Tabelle sind. Im Folgenden werden anhand von Beispieltransformationen
gezeigt, wie der Aufbau der Befehle durch die C-Methode realisiert wurde.
69
Temporales Create
1 CREATE VALIDTIME COALESCED TABLE ang(
2 Nr INT,
3 Name TEXT,
4 Gehalt INT,
5 PRIMARY KEY (Nr)
6 )
Quelltext 5.6: Temporal Create vor der Transformation
1 SELECT temporal create(
2 ’ang’,
3 ’Nr INT, Name TEXT, Gehalt INT, PRIMARY KEY (Nr)’,
4 true,
5 true,
6 false
7 )
Quelltext 5.7: Temporal Create nach der Transformation
Die Übersetzung der temporalen CREATE-Anweisung besteht aus wenigen Schritten.
Zunächst wird die Zeile 1 aus dem Quelltext 5.7 zur Ausgabe hinzugefügt. Diese ist für
jeden VALIDTIME Create identisch, benötigt demnach keine Analyse der Eingabe. Dann
wird, wie in der Zeile 2 zu sehen ist, der Name in einfachen Anführungsstrichen mit
einem Komma am Ende angehängt. Anschließend werden die Spaltendefinitionen von
Zeile 2-5 in einfachen Anführungsstrichen hinzugefügt. Die überflüssigen Zeilenumbrüche
werden dabei entfernt. Die drei boolschen Parameter, die anschließend Teil der Operation
sind, bezeichnen den Typ der Tabelle. Der erste bestimmt, ob die Tabelle VALIDTIME
besitzt. Falls diese auf true gesetzt ist, kann der nächste Operator true oder false sein; er
zeigt an, ob die Tabelle COALESCED ist. Der letzte Parameter ist für die Transaction
Time zuständig. Falls die Tabelle WITH SYSTEM VERSIONING ausgezeichnet ist, so
gilt, dass der Parameter auf true gesetzt wird. Ansonsten wird er auf false gesetzt.
Die Übersetzung ist in einem SELECT verpackt, da dies die Syntax für Funktionenaufrufe in PostgreSQL ist. Das gleiche gilt für alle anderen Kommandos, welche die Daten
definieren oder manipulieren (abgesehen vom INSERT, welches keine spezielle Syntax
besitzt).
Temporales Update
1 UPDATE ang
2 FOR PORTION OF vt
3
FROM DATE ’2014-01-01’
4
TO DATE ’’
5 SET gehalt = 3000
6 WHERE name = ’Bob’
Quelltext 5.8: Temporales Update vor der Transformation
70
1 SELECT temporal update(
2 ’ang’,
3 ’[2014-01-01,)’::DATERANGE,
4 ’gehalt = 3000’,
5 ’name = ”Bob”’
6 )
Quelltext 5.9: Temporales Update nach der Transformation
Ein temporales Update besteht aus vier Elementen: dem Tabellennamen, der Zeitangabe, einer SET-Klausel und einer optionalen WHERE-Klausel. Die Tabelle, SET und
WHERE-Klausel werden in die temporal update Funktion eingesetzt. Dabei ist darauf
zu achten, dass einfache Anführungszeichen escaped werden müssen, das heißt sie müssen
gedoppelt werden, da sonst der String vorzeitig beendet und die Syntax für den Aufruf
der temporal update Funktion damit ungültig wird.
Die Zeitangabe ist zusammengesetzt aus zwei Zeitpunkten, die aus dem temporalen
Update zu extrahieren sind. Der Datentyp DATERANGE ist dabei auch mit angegeben. Wäre die Angabe FROM TS ’...’ TO TS ’...’ wäre der Typ TSRANGE, welches
einen Timestamp-Zeitraum darstellt. Das ermöglicht selbst-definierte Datentypen für
Zeitangaben in temporalen Updates zu verwenden. In diesem Fall ist die Angabe für den
Endzeitpunkt leer, das heißt das FOREVER als Endzeitpunkt gilt. Dieses Vorgehen ist
identisch zu der PostgreSQL-Syntax für Intervalle, die unendlich in die Zukunft reichen.
Temporales Delete
1 DELETE FROM ang
2 FOR PORTION OF vt
3
FROM DATE ’2014-01-01’
4
TO DATE ’2015-01-01’
5 WHERE name = ’Bob’
Quelltext 5.10: Temporales Update vor der Transformation
1 SELECT temporal delete(
2 ’ang’,
3 ’[2014-01-01,)’::DATERANGE,
4 ’name = ”Bob”’
5 )
Quelltext 5.11: Temporales Update nach der Transformation
Temporales Delete funktioniert analog zum temporalen Update. Bei der Transformation gibt es diesmal keine SET-Spalte, deshalb besitzt die resultierende Ausgabe einen
Parameter weniger.
71
Temporale Schnappschussanfrage
1 SELECT a.name, a.gehalt
2 FROM ang a
Quelltext 5.12: Abwärtskompatible Anfrage nach den aktuellen Angestellten
1 SELECT a.name, a.gehalt
2 FROM (
3 SELECT nr, name, gehalt
4 FROM ang
5 WHERE ’[now,now]’ <@ vt
6 ) AS a
Quelltext 5.13: Transformation von Anfrage 5.12
Die temporale Tabelle ang wird mit einer abwärtskompatiblen Schnappschussanfrage
abgefragt. Die Anfrage soll dabei die temporalen Elemente der Tabelle verwerfen und
lediglich die aktuell gültigen Tupel anzeigen. Die Gültigkeitszeit ist in der Ausgabe der
Anfrage nicht enthalten.
Bei der Transformation wird demnach zuerst überprüft, ob die angefragten Tabellen
temporal sind. Die Erweiterung erfragt dafür beim DBMS via der SPI-Schnittstelle, ob
die Tabelle ein VT-Attribut enthält. Wenn das der Fall ist, so wird diese Tabelle durch
eine innere Anfrage in der FROM-Klausel ersetzt. Diese innere Anfrage benutzt den
vorgestellten <@ Operator, um auf Daten einzuschränken, dessen VT den Zeitpunkt
NOW enthalten. Dabei wird speziell mit einem Intervall mit genau einem Element
gearbeitet, da das einen flexiblen Range-Datentyp für VT erlaubt. Wäre VT nicht vom
Typ DATERANGE sondern TSRANGE so wäre der Aufruf ’[now,now]’ <@ vt weiterhin
in Ordnung, da NOW auch ein valider Timestamp ist. Dabei besitzt die Unteranfrage
noch den gleichen Alias wie in der temporalen Schnappschussanfrage, damit die Felder
in SELECT-Klausel nicht umbenannt werden müssen. Im SELECT der Unteranfrage
werden alle Spalten außer VT ausgewählt, damit das Ergebnis vollkommen unabhängig
von der VALID TIME ist.
1 SELECT ang.name, ang.gehalt, projekt.name
2 FROM ang NATURAL JOIN projekt
Quelltext 5.14: Abwärtskompatible Anfrage mit einer nicht temporalen Tabelle
1 SELECT ang.name, ang.gehalt, projekt.name
2 FROM (
3 SELECT nr, name, gehalt
4 FROM ang
5 WHERE ’[now,now]’ <@ vt
6 ) AS ang NATURAL JOIN projekt
Quelltext 5.15: Transformation von Anfrage 5.14
Sei Projekt eine nicht temporale Tabelle; dadurch gilt bei der Anfrage, dass Projekt
nicht durch eine innere Anfrage ersetzt wurden muss. Zu sehen ist auch, wenn einer
72
temporalen Tabelle kein Alias vergeben wurde. Dann ist der Alias der inneren Anfrage
dieser temporalen Tabelle der Name eben dieser Relation. Dadurch werden komplizierte
Umformungen der SELECT-Klausel umgangen.
Sequenced Select
1 SEQUENCED SELECT name
2 FROM ang
3 WHERE Gehalt > 5250
Quelltext 5.16: Historische Anzeige aller Mitarbeiter die über 5250 verdienen
1 SELECT name, ang.vt vt
2 FROM ang
3 WHERE Gehalt > 5250;
Quelltext 5.17: Transformation von Anfrage 5.17
Bei einer sequentiellen Anfrage wird garantiert, dass die Ausgabetabelle eine Spalte VT mit der Gültigkeitszeit des Tupels enthält. Dadurch verändert sich bei einem
SEQUENCED SELECT an eine temporale Tabelle ohne Angabe eines Zeitrahmens
nicht viel. Lediglich die SELECT-Klausel wird um die Gültigkeitszeit erweitert. Dadurch
entsteht auch eine Bedingung für SEQUENCED SELECT: Jede in der FROM-Klausel
referenzierte Tabelle muss eine Spalte VT für die Gültigkeitszeit besitzen.
1 SEQUENCED SELECT a1.name a1name, a2.name a2name
2 FROM ang a1, ang a2
Quelltext 5.18: Valid Time Join
1 SELECT a1.name AS a1name, a2.name AS a2name, a1.vt ∗ a2.vt vt
2 FROM ang AS a1, ang AS a2
3 WHERE 1 = 1 AND a1.vt && a2.vt
Quelltext 5.19: Transformation von Anfrage 5.18
Falls zwei oder mehr Tabellen in der FROM-Klausel auftreten, wird ein temporaler
Join eingefügt. Dafür sind zwei Schritte nötig: zunächst muss in der WHERE-Klausel
sichergestellt werden, dass nur Tupel angezeigt werden, die gemeinsame Zeitpunkte
haben und in der SELECT-Klausel muss der Schnitt der beiden Zeiträume für den Tupel
angezeigt werden.
Um gemeinsame Zeitpunkte zu garantieren wird der &&-Operator verwendet. Dafür
werden alle möglichen Kombinationen aus den angegebenen Tabellen der FROM-Klausel
verwendet. Das 1 = 1 am Anfang der Bedingungen soll dabei die Programmierung
erleichtern, da dann in der jeder Zeile lediglich AND <Ausdruck> hinzugefügt werden
muss, ohne darauf zu achten, ob es sich um den ersten Eintrag handelt.
1 SEQUENCED SELECT a1.name a1name, a2.name a2name, a3.name a3name
2 FROM ang a1, ang a2, ang a3
Quelltext 5.20: Valid Time Join 2
73
1 SELECT a1.name AS a1name, a2.name AS a2name, a3.name AS a3name, a1.vt * a2.vt * a3.vt vt
2 FROM ang AS a1, ang AS a2, ang AS a3
3 WHERE 1 = 1 AND a1.vt && a2.vt AND a1.vt && a3.vt AND a2.vt && a3.vt;
Quelltext 5.21: Transformation von Anfrage 5.20
Die Angabe aller möglichen Kombinationen in der WHERE-Klausel ist leichter zu
sehen, wenn drei oder mehr Tabellen angefragt werden. Die möglichen Kombinationen
sind in diesem Fall a1, a2 & a1, a3 & a2, a3. Wenn alle drei Bedingungen gelten, so gibt
es auch keine Möglichkeit, dass die Ausgabe des *-Operators aus der SELECT-Klausel
leer ist.
1 SEQUENCED ’[2000-01-01, 2000-09-01)’ SELECT name
2 FROM ang
3 WHERE Gehalt > 5250
Quelltext 5.22: Anfrage mit SEQUENCED und Zeitraumangabe
1
2
3
4
5
6
7
SELECT name, tbl.vt ∗ ’[2000-01-01, 2000-09-01)’ vt
FROM (
SELECT name, ang.vt vt
FROM ang
WHERE Gehalt > 5250
) tbl
WHERE tbl.vt && ’[2000-01-01, 2000-09-01)’
Quelltext 5.23: Transformation von Anfrage 5.22
Falls beim Sequenced noch zusätzlich eine Zeitangabe angegeben wurde, so wird
zunächst die Anfrage übersetzt, als würde sie fehlen. Anschließend wird die resultierende
Anfrage in der FROM-Klausel als innere Anfrage verpackt, im Quelltext 5.23 in den
Zeilen 3-5 zu sehen, und im äußeren Teil wird via der WHERE- und SELECT-Klausel
die Angabe auf den angegeben Zeitraum eingeschränkt. Dadurch bleibt die Übersetzung
transparent und für weitere Verschachtlungen leichter zu überblicken.
Dabei ist anzumerken, dass die in der erweiterten SQL benutzte SELECT-Klausel
zweimal benutzt werden muss; in der äußeren und inneren Anfrage.
1 SEQUENCED COALESCED SELECT name
2 FROM ang
3 WHERE Gehalt > 5250
Quelltext 5.24: Anfrage mit Coalesced
1 SELECT ∗ FROM coalesced(
2 ’(SELECT name, ang.vt vt FROM ang WHERE Gehalt > 5250)’,
3 ’name’
4 ) AS (name text, vt daterange);
Quelltext 5.25: Transformation von Anfrage 5.24
74
COALESCED funktioniert ähnlich wie SEQUENCED: es wird zunächst die Anfrage
unabhängig vom COALESCED übersetzt und anschließend mit einer inneren Anfrage
benutzt. In diesem Fall wird die vorgestellte COALESCED-Funktion verwendet. Es muss
beachtet werden, dass beim Einfügen der inneren Anfrage als Zeichenkette die einzelnen
Anführungsstriche erneut verdoppelt werden müssen, da sonst die Zeichenkette zu früh
abgebrochen wird.
Der zweite und dritte Parameter wird via der SPI-Bibliothek ermittelt, indem die
innere Anfrage einmal ausgeführt wird, ohne tatsächlich Zeilen aus dem Ergebnis abzufragen, sondern die Spalten und ihren Datentyp zu ermitteln. Für den zweiten Parameter
werden die Spaltennamen ohne die VT-Spalte eingefügt. Für die Tabellendefinition darf
die Valid Time-Spalte jedoch nicht fehlen, da sie Teil der Ausgabetabelle ist. Durch die
Kommunikation mit dem PostgreSQL-Server ist es damit möglich, diese Daten implizit
durch den gegebenen erweiterten SQL-Ausdruck weiterzugeben. Das erspart Benutzern
eine explizite Angabe, wie es vor der Einführung der Spracherweiterung der Fall war.
1 SEQUENCED ’[2000-01-01, 2000-09-01)’ COALESCED SELECT <-name
2 FROM ang
3 WHERE Gehalt > 5250
Quelltext 5.26: Anfrage mit Coalesced
1 SELECT ∗ FROM coalesced(
2 ’(SELECT name, tbl.vt ∗ ’’[2000-01-01, 2000-09-01)’’ vt
3 FROM (
4
SELECT name, ang.vt vt
5
FROM ang
6
WHERE Gehalt > 5250
7 ) tbl
8 WHERE tbl.vt && ’’[2000-01-01, 2000-09-01)’’)’,
9 ’name’
10 ) AS (name text, vt daterange);
Quelltext 5.27: Transformation von Anfrage 5.26
Falls sowohl COALESCED als auch eine Zeitangabe existiert, so resultiert dies in einer
doppelt verschachtelten Anfrage. Zunächst wird die Anfrage ohne das COALESCED oder
die Zeitangabe zu beachten übersetzt. Das Resultat davon wird in die Zeiteinschränkung
verpackt und das Ergebnis davon wird anschließend in der COALESCED-Funktion
verpackt. Dadurch entsteht eine längliche Anfrage, die jedoch, wenn vernünftig eingerückt,
für einen Menschen verständlich aufgebaut ist.
Das Verschachteln der Anfragen bei Zeitangaben und COALESCED hat weitere
Vorteile. So muss bei der Anpassung der Transformation unabhängig von den beiden
Faktoren nicht beachtet werden, ob man die Kompatibilität von ihnen durch das Hinzufügen von speziellen Spalten oder die Änderung der Übersetzungsschritte bricht. Die
Übersetzungsschritte sind damit voneinander unabhängig.
75
Nonsequenced Select
1 NONSEQUENCED SELECT ∗
2 FROM ang a, ang b
3 WHERE a.name = ’Mueller’ AND b.name = ’Meier’
Quelltext 5.28: Anfrage mit Coalesced
1 SELECT ∗
2 FROM ang AS a, ang AS b
3 WHERE (a.name = ’Mueller’ AND b.name = ’Meier’)
Quelltext 5.29: Transformation von Anfrage 5.28
Nichtsequentielle Anfragen werden transformiert, indem das NONSEQUENCEDSchlüsselwort entfernt wird. Falls verschachtelte Anfragen existieren, die komplexere
Transformationen benötigen, werden diese vorher behandelt und eingefügt.
Verschachtelte Anfragen & Unteranfragen
Die bisher vorgestellten Transformationen lassen sich auch mithilfe von regulären Ausdrücken und Suchen/Ersetzen-Operationen realisieren. Somit besteht bis jetzt nur wenig
Vorteil, mit dem Parser den AST zu generieren und ihn zu navigieren um Ausdrücke wie
die einfache Erweiterbarkeit zu übersetzen. Jedoch ist SQL keine reguläre Sprache. Es
existieren kontextfreie Grammatiken um SQL zu erzeugen, damit ist SQL kontextfrei. Das
heißt, es existieren auch Ausdrücke, die sich nicht mit Wortersetzungen trivial übersetzen
lassen.
1 NONSEQUENCED SELECT x.Nr, x.Name
2 FROM (
3 SEQUENCED COALESCED SELECT a.Nr, a.Name
4 FROM ang a, ang b
5 WHERE a.ChefNr=b.Nr AND a.Gehalt>b.Gehalt
6 )x
Quelltext 5.30: Anfrage mit Coalesced
1 SELECT x.Nr, x.Name
2 FROM (
3 SELECT ∗
4 FROM coalesced(
5
’(SELECT a.Nr, a.Name, a.vt ∗ b.vt vt
6
FROM ang AS a, ang AS b
7
WHERE (a.ChefNr = b.Nr AND a.Gehalt > b.Gehalt) AND a.vt && b.vt)’,
8
’Nr, Name’
9 ) AS (nr int4, name text, vt daterange)
10 ) AS x;
Quelltext 5.31: Transformation von Anfrage 5.30
76
So existieren in SQL verschachtelte Ausdrücke. In FROM-Klauseln lassen sich beispielsweise SELECT-Anfragen schreiben, die wiederum FROM-Klauseln mit den gleichen
Regeln besitzen. Die Rekursionstiefe dieser Ausdrücke ist beliebig.
Die Übersetzung umgeht dieses Problem, indem er jeden AST, der eine SELECTAnfrage repräsentiert, auch wie eine SELECT-Anfrage behandelt. Das heißt, jede SELECTAnfrage wird zunächst identisch übersetzt und dementsprechend gespeichert und eingefügt.
Im Beispielquelltext 5.30 zu 5.31 ist das zu sehen; dort ist in der FROM-Klausel ein
SEQUENCED SELECT anzutreffen. Wenn bei der Navigation des AST die innere Anfrage behandelt wird, ist die Übersetzung identisch zu der äußeren. In diesem Fall ist
sie COALESCED, demnach findet dort eine Verschachtelung in die coalesced-Funktion
statt. Diese Unteranfrage resultiert in einer temporalen Tabelle. Dadurch ist es möglich,
verschachtelte SEQUENCED-Anfragen zu entwerfen, da sie als Voraussetzung haben,
dass alle referenzierte Tabellen temporal sind.
1 SELECT name
2 FROM (
3 SEQUENCED SELECT name, gehalt
4 FROM (
5
SEQUENCED SELECT nr, name, gehalt
6
FROM ang
7 ) a1
8 ) a2
Quelltext 5.32: Anfrage mit Coalesced
1 SELECT name
2 FROM (
3 SELECT name, gehalt
4 FROM (
5
SELECT name, gehalt, a1.vt vt
6
FROM (
7
SELECT nr, name, gehalt, ang.vt vt
8
FROM ang
9
) AS a1
10 ) AS a2
11 WHERE ’[now,now]’ <@ vt
12 ) AS a2
Quelltext 5.33: Transformation von Anfrage 5.32
Im Quelltext 5.32 ist eine dreifach verschachtelte Anfrage zu sehen. Dabei ist die
äußerste eine abwärtskompatible Schnappschussanfrage. Das Resultat ist dadurch in 5.34
eine vierfach geschachtelte Anfrage, da die äußere Schnappschussanfrage die Tiefe um
eins erhöht. Es ist zusätzlich zu beachten, dass bei der Schachtelung der Alias nicht
verloren gehen darf, da ansonsten die Spalten aus der SELECT-Klausel ungültig werden.
77
Temporales NOT EXISTS
1
2
3
4
5
6
7
SEQUENCED SELECT a.name, a.gehalt
FROM ang a
WHERE NOT EXISTS (
SEQUENCED SELECT ∗
FROM ang b
WHERE b.gehalt > a.gehalt
)
Quelltext 5.34: Temporales NOT EXISTS
1
2
3
4
5
6
7
8
9
10
11
12
13
SELECT nr, name, gehalt, chefnr, a.vt vt
FROM (
SELECT nr, name, gehalt, chefnr, relation split(vt, ’ang AS b’) vt
FROM ang AS a
) AS a
WHERE 1 = 1
AND NOT EXISTS (
SELECT nr, name, gehalt, chefnr
FROM ang AS b
WHERE 1 = 1
AND a.gehalt > b.gehalt
AND a.vt <@ b.vt
);
Quelltext 5.35: Transformation von Anfrage 5.34
Das temporale NOT EXISTS bedient sich der relation split-Funktion aus der PostgreSQLExtension um die Tupel der Ausgangsrelation in kleinere Teile aufzuspalten, sodass bei
Not Exists nicht darauf geachtet werden muss, wie die Daten innerhalb der inneren
Anfrage in temporal in Beziehung stehen.
5.4
PHP-Interface
Das Interface für die Bedienung der temporalen Erweiterung, in diesem Fall via PHP
und PDO, ist durch die Programmierung trivial. Es muss dafür gesorgt werden, dass eine
gegebene SQL-Anweisung in die Funktion der Datenbankerweiterung eingefügt wird und
das Ergebnis, welches lediglich eine Zeichenkette oder ein Fehler sein kann, wird von der
Sprache entgegengenommen, um anschließend wie üblich ausgeführt zu werden. Dieser
Ablauf ist im Sequenzdiagramm 5.7 zu sehen.
In PHP heißt das, dass eine neue Klasse erstellt wird, die von PDO erbt. Sei sie in
diesem Fall TemporalPDO genannt. Dadurch lässt sich in einer bestehenden Operation
jeder Gebrauch von PDO einfach durch TemporalPDO ersetzen, ohne das sich die
Funktionalität der Applikation ändert, selbst wenn PHP und PDO ein Update erhalten.
Anschließend werden drei neue Funktionen benötigt, welche nun SQL in erweiterter
Syntax erwarten; temporalQuery, temporalExec und temporalPrepare. Dabei basieren
diese auf den Namensgebern, die bereits in PDO existieren; query, exec und prepare. Wenn
78
:Applikation
:TemporalPDO
:PostgreSQLServer
:Extension
:SQLParser
RequestData()
Prepare(raw tsql)
return tsql
sql translate(tsql)
sql translate(tsql)
GenAST(tsql)
return ast
TranslateLoop
WalkAST()
opt
GetInfo(tbl)
return info
return part sql
return sql
return sql
Execute(sql)
return tbl data
return tbl data
Abbildung 5.7: Sequenzdiagramm für die Übersetzung der temporalen SQL-Erweiterung
79
eine Zeichenkette mit dem erweiterten SQL an diese Funktionen weitergereicht wird, so
wird sie vorbereitet, indem er escaped (das heißt jedes einfache Anführungszeichen wird
verdoppelt) und anschließend in die Form SELECT sql translate(<Anweisung>) gebracht wird.
Das Ergebnis dieser Query, eine Tabelle mit einer Spalte und lediglich einem Eintrag, dem
übersetzten Befehl, wird anschließend an den nicht temporalen PDO Befehl weitergeleitet
und das Ergebnis zurückgereicht. Dadurch bleibt die Flexibilität von PDO erhalten,
wie beispielsweise das prepare, welches ermöglicht eine Anweisung mit unterschiedlichen
Parametern mehrmals auszuführen.
Ein Wrapper in dieser Form ist in PHP weniger als 100 Codezeilen und realisiert die
Aufgabe vollständig. Ähnliche Software lässt sich trivial auch in anderen Programmiersprachen realisieren. Der Wrapper muss anschließend nicht mehr verändert werden, selbst
wenn sich die Transformation selbst verändert. Damit ist es nicht nötig den Wrapper zu
aktualisieren, falls es ein Update von der Software gibt. Es sind lediglich Änderungen an
der Datenbank nötig, was das Ausliefern der Software erleichtert.
80
Kapitel 6
Beispieldatenbank Modulkatalog
Nun soll das entwickelte Projekt praktisch in einem bestehenden temporalen System
eingesetzt werden. Dafür sollen die bereits vorhandenen temporalen Elemente durch die
neu entwickelten ersetzt werden. Nachdem die Struktur der Datenbank umgewandelt
wurde, werden Anfragen an die alte und neue Datenbank gestellt und verglichen, ob die
SQL-Erweiterung kürzere und elegantere Anfragen entworfen hat.
Dafür wird zunächst ein neuer Datentyp mithilfe einer weiteren PostgreSQL-Extension
eingeführt, da die bestehende Datenbank Semester als Granularität verwendet. Diese
Granularität ist nicht standardmäßig bei PostgreSQL mitgelierfert und soll als weiterer
Datentyp realisiert werden.
Abbildung 6.1: ER-Modell für die Studiengänge und Kompetenzbereiche
Nachdem der Grundsatz für die Zeitintervalle gelegt wurde, werden zwei Teilmengen
der bestehenden Datenbankdefinition mitsamt ihrer Daten entnommen. In der Abbildung
6.1 ist ein Entity-Relationship-Modell für die Studiengänge und Kompetenzbereiche zu
erkennen. Dabei besitzen diese beiden Entitäten im Modulkatalog noch wesentlich mehr
Attribute, welches im Modell via ... markiert ist, jedoch sind diese zur Übersichtlichkeit
halber weggelassen worden.
81
Abbildung 6.2: ER-Modell für die Lehrveranstaltungen
In der Abbildung 6.2 sind die Lehrveranstaltungen aus dem Modulkatalog vermerkt.
Diese verwendet, wie zuvor für die Kompetenzbereiche, erneut Semester als Granularität.
6.1
Datentyp Semester
Der Modulkatalog benutzt für seine Gültigkeitszeittabellen die Granularität Semester,
welche nicht in PostgreSQL integriert ist. Um effizient mit dem entwickelten Projekt
zu arbeiten, wird ein neuer Datentyp mithilfe einer PostgreSQL-Extension eingeführt
und für die neue Modulkatalogs-Datenbank zukünftig verwendet. Wenn diese Datenbank
die entsprechenden Operationen unterstützt, die auch beispielsweise DATE oder TIME
unterstützt, lässt sich die Erweiterung so benutzen, wie sie schon für DATE benutzt
wurde.
Um den neuen Datentyp zu realisieren, wird ein Modul in C programmiert, welches
zunächst beschreibt, wie der Speicheraufbau aussehen soll und wie der neue Datentyp
gelesen und ausgegeben wird. In diesem Fall besteht eine Semesterangabe aus 2 Elementen:
Zunächst die Angabe, ob ein Sommer- oder Wintersemester gemeint ist und anschließend,
in welchem Jahr dieses Semester stattfindet. Dabei ist zu beachten, dass Wintersemester
zwar über zwei Jahre stattfinden, jedoch nur das erste Jahr bei der Angabe zu speichern ist,
da das zweite Jahr eine redundante Information ist. Also ist ein Bit für die Kennzeichnung
von Winter- oder Sommersemester und ein Integer für die Angabe eines Jahres für die
Speicherung notwendig.
PostgreSQL definiert die Ausgabe und Eingabe von Daten über Zeichenketten, demnach muss danach definiert werden, wie die Beschaffenheit von Zeichenketten für Semesterangaben ist. In der Entität Semester, welche in den ER-Modellen 6.2 und 6.1 zu sehen ist,
steht die Beschreibung im Attribut Lang. Ein Beispiel ist demnach Wintersemester
2012/2013 und Sommersemester 2015. Also müssen diese Zeichenketten gelesen
und analysiert werden, ob es sich um ein Sommer- oder Wintersemester handelt und in
welchem Jahr es stattfindet. Die Ausgabe findet analog statt.
Für die vollständige Definition des Datentyps fehlen jedoch noch die Operatoren,
da er sonst nicht innerhalb von PostgreSQL-Ranges eingesetzt werden kann. Ohne die
Range-Funktionalität kann der Datentyp nicht als Granularität eingesetzt werden und
ist damit für die Datenbank nicht zu gebrauchen. Die Operatoren, die dafür benötigt
82
werden, sind die Vergleichsoperatoren, um eine chronologische Sortierung zu ermöglichen:
<, <=, =, >, >=, sowie eine CMP-Funktion, welche zwei Variablen vom erstellten
Datentyp entgegennimmt, und entweder -1, 0 oder 1 ausgibt, je nachdem ob die erste
übergebene Variable kleiner, identisch oder größer ist als die zweite. Diese definierten
Operatoren müssen anschließend noch zu einer Operatorengruppe zusammengefasst
werden, die bezeichnet, dass der Datentyp in einem binären Baum nun einsortiert werden
kann. Dadurch ist auch gegeben, dass der Datentyp in einem Intervall verwendet werden
kann. Der neue Datentyp heißt semester und der neue Range-Datentyp heißt analog
semesterrange. Beide werden wie alle anderen Datentypen in PostgreSQL verwendet.
Der Quelltext des Datentyps als C- und PL/pgSQL-Modul sind im Anhang A zu finden.
6.2
Studiengänge und Kompetenzbereiche
Abbildung 6.3: ER-Modell für die Studiengänge und KB mit neuem Datentyp Semester
Der neue Datentyp ermöglicht ein kompakteres ER-Modell, wie in der Abbildung
6.3 zu sehen ist. Statt vier Relationships zu einem Entity wird das nun durch zwei
Attribute gelöst, welche die Gültigkeitszeit der Objekte mithilfe von semesterrange
als Intervalle darstellt.
1
2
3
4
5
6
7
CREATE TABLE modtemp.studiengang
AS
SELECT sg.id, sg.name, sg.fachbereich,
semesterrange(semester(sem1.lang), semester(sem2.lang), ’[]’) vt
FROM modkat.studiengang sg
JOIN modkat.semester sem1 ON (sg.gueltig von = sem1.id)
JOIN modkat.semester sem2 ON (sg.gueltig bis = sem2.id);
Quelltext 6.1: CREATE TABLE Studiengang
Im Quelltext 6.1 ist ein CREATE TABLE-Aufruf für den neuen Studiengang angegeben. Dabei wird die AS-Syntax verwendet, welche es ermöglicht aus einem SELECT
eine Tabelle zu erstellen. Dadurch wird vermieden, die einzelnen Spalten anzugeben,
83
da diese ja schon durch die ursprüngliche Tabelle gegeben sind. In der Zeile 4 wird die
Semesterrange mithilfe des Lang-Attributs der Semester-Tabellen erzeugt. Dafür sind
zwei JOINs notwendig, da sonst die Semesterbeschreibungen fehlen würden. Anhand
dieses Befehls ist auch bereits zu sehen, wie der neue Datentyp bei den Anfragen hilft.
Aus der Anfrage 6.2, welche lediglich die Studiengänge mit der zugehörigen Gültigkeitszeit
als lange Semesterbeschreibung anzeigt, wird damit einfach 6.3.
1 SELECT sg.id, sg.name, sg.fachbereich, sem1.lang, sem2.lang
2 FROM modkat.studiengang sg
3 JOIN modkat.semester sem1 ON (sg.gueltig von = sem1.id)
4 JOIN modkat.semester sem2 ON (sg.gueltig bis = sem2.id);
Quelltext 6.2: Studiengang Beispielanfrage 1
1 SELECT sg.id, sg.name, sg.fachbereich, sg.vt
2 FROM modtemp.studiengang sg;
Quelltext 6.3: Studiengang Beispielanfrage 2
Für Kompetenzbereiche passiert die Transformation wie im Quelltext 6.4.
1
2
3
4
5
6
7
CREATE TABLE modtemp.kb
AS
SELECT kb.id, kb.name, kb.min lp, kb.max lp, kb.bemerkung, kb.sg,
semesterrange(semester(sem1.lang), semester(sem2.lang), ’[]’) vt
FROM modkat.kb kb
JOIN modkat.semester sem1 ON (kb.gueltig von = sem1.id)
JOIN modkat.semester sem2 ON (kb.gueltig bis = sem2.id);
Quelltext 6.4: CREATE TABLE Kompetenzbereiche
Da die beiden Tabellen nicht mithilfe temporal_create() angelegt wurden, fehlen
jedoch noch wichtige Bestandteile. So müssen noch die Primärschlüssel definiert werden
und die Trigger an die Relationen gebunden werden. Für die Relation Studiengang ist
das im Quelltext 6.5 zu sehen, für die Relation Kompetenzbereich ist das analog möglich.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ALTER TABLE modtemp.studiengang ADD PRIMARY KEY(id, vt);
CREATE TRIGGER studiengang temporal unique trigger
BEFORE INSERT OR UPDATE OF vt ON modtemp.studiengang
FOR EACH ROW
EXECUTE PROCEDURE temporal unique trigger();
CREATE TRIGGER studiengang coalesce trigger
BEFORE INSERT OR UPDATE ON modtemp.studiengang
FOR EACH ROW
EXECUTE PROCEDURE temporal coalesce trigger();
CREATE TRIGGER kb referential trigger
AFTER INSERT OR UPDATE OR DELETE ON modtemp.kb
FOR EACH ROW
EXECUTE PROCEDURE temporal referential trigger(’modtemp.kb’, ’sg’, ’modtemp.studiengang’, ’id’);
Quelltext 6.5: CREATE TABLE Kompetenzbereiche
84
Zusätzlich zu den Integritätsbedingungen wurde noch ein referentieller temporaler Fremdschlüssel via Trigger in den Zeilen 13-16 eingefügt. Das Attribut sg der
Tabelle modtemp.kb ist damit ein Fremdschlüssel auf das Attribut id der Tabelle
modtemp.studiengang.
Jetzt kann auf den Tabellen die im Kapitel 5 vorgestellten Funktionen sowie die
erweiterte SQL-Syntax angewendet werden. So kann nun ein sequentieller Join ausgeführt
werden, wie im Quelltext 6.6 und 6.7 beschrieben.
1 SEQUENCED SELECT sg.name, kb.name
2 FROM modtemp.studiengang sg
3 JOIN modtemp.kb kb ON (sg.id = kb.sg)
Quelltext 6.6: Sequentieller Join in erweitertem SQL
1 SELECT sg.name, kb.name, sg.vt ∗ kb.vt vt
2 FROM modtemp.studiengang AS sg
3 INNER JOIN modtemp.kb AS kb ON (sg.id = kb.sg)
4 WHERE 1 = 1 AND sg.vt && kb.vt;
Quelltext 6.7: Übersetzung von Aufruf in Quelltext 6.6
Wäre der Aufbau wie vorher, wären vier zusätzliche Joins nötig um die lange Semesterbeschreibung zu bekommen. Außerdem wären wesentlich mehr Bedingungen nötig,
um zu überprüfen, wie die voneinander abhängigen Gültigskeitszeiten erfüllt sind und
die Ausgabe anzupassen. Das zeigt, wie viel komfortabler die neue Syntax tatsächlich ist.
6.3
Lehrveranstaltungen
Abbildung 6.4: ER-Modell für die Lehrveranstaltungen mit neuem Datentyp Semester
Die Transformation von Lehrveranstaltungen passiert analog zu Studiengang und
Kompetenzbereich, wie am ER-Modell 6.4 zu sehen ist. Lehrveranstaltungen wurde als
weiteres Beispiel aus dem Modulkatalog entnommen, um ein Problem mit der temporalen
Datenhaltung aus dem Modulkatalog zu verdeutlichen. Wie im vorherigen ER-Modell
6.2 zu sehen ist, war dort nur die ID der Primärschlüssel. So ergibt das, dass zusammenhängende Objekte nicht unter einem Zeitinvarianten-Schlüssel gespeichert wurden,
sondern bei jeder Änderung ein komplett neues Objekt erschaffen wurde. Das führt dazu,
dass zusammenhängende Objekte nicht einfach zueinander zugeordnet werden können.
Damit ist die Entwicklung stark eingeschränkt, da ein großer Bestandteil der temporalen
Datenhaltung die Verwaltung der Historie von einzelnen Objekten ist. Es muss demnach
85
eine Möglichkeit gefunden werden, zueinander gehörende Objekte zu finden und ihnen
einen gemeinsamen zeitinvarianten Schlüssel zu geben.
1
2
3
4
5
6
7
8
9
10
11
UPDATE modtemp.lv lv1
SET id = (
SELECT lv2.id
FROM modtemp.lv lv2
WHERE lv1.name = lv2.name AND lv1.id < lv2.id
)
WHERE exists (
SELECT lv2.id
FROM modtemp.lv lv2
WHERE lv1.name = lv2.name AND lv1.id < lv2.id
);
Quelltext 6.8: Zusammengehörende Objekte in Lehrveranstaltungen
Eine naive Lösung ist im Quelltext 6.8 zu sehen, wo lediglich alle Veranstaltungen, die
den gleichen Namen tragen, zueinander zugeordnet werden. Dafür wird eine UPDATEAnweisung benutzt, welche eine Unteranfrage in der SET-Klausel verwendet. Dieser
Ansatz hat sieben der 677 Lehrveranstaltungen zu einem anderen zugeordnet. In den
Lehrveranstaltungen hat sich die Anzahl der Semesterwochenstunden geändert. Jedoch
finden sich dadurch keine Veranstaltungen, bei denen sich der Name verändert hat. Es
gibt bessere Methoden für das Matching von Daten, jedoch ist das eher Thema einer
anderen Ausarbeitung. Diese Relation zeigt jedoch die Nachteile einer unzureichenden
temporalen Verwaltung und die Vorteile des Ansatzes aus der Ausarbeitung.
Der gesamte Modulkatalog ließe sich mithilfe des vorgestellten Projekts nun umsetzen
und die Anfragen an diese Datenbank effizienter und lesbarer gestalten.
86
Kapitel 7
Fazit & Ausblick
In dieser Ausarbeitung wurden historische und aktuelle Entwicklungen der temporalen
Datenhaltung analysiert und anschließend ein eigener Versuch gestartet, temporale
Datenhaltung in Form von Gültigkeits- und Transaktionszeit zu realisieren und zu
verwalten. Dabei ist klar geworden, dass dieses Unterfangen schon lange ein Thema in
den Datenbanken darstellt; frühere Versuche neue Sprachelemente zu SQL hinzuzufügen
scheiterten, obwohl sie von führenden Forschern auf dem Gebiet der Datenbankentwicklung
geleitet wurden, da einfach keine Akzeptanz entstanden ist. Dabei besteht Bedarf, wenn
sogar hauseigene Software im Fachbereich temporale Datenhaltung benötigt, um einfach
und effektiv Anfragen im Thema der Lehrveranstaltungen mit ihren Eigenschaften und
Modulen über Jahre hinweg zu verwalten.
Dabei wurden zunächst die Grundlagen besprochen und dazu reicht es nicht, sich
auf die Datenbanken zu beschränken. So sind temporale Daten auch ein Thema, welches
Gebiete umfasst, die nicht in die traditionelle Datenbankentwicklung fallen. Es fallen
fragen auf wie die Verwaltung von Zeit und ihre Granularität und welche Probleme
auftreten, wenn ungewöhnliche Granularitäten verwendet werden. Doch stellen temporale
Daten auch die traditionellen Eigenschaften von Tabellen auf den Kopf; so gilt nicht mehr,
dass jedes Objekt durch einen Tupel repräsentiert wird. Dadurch sind neue Probleme
entstanden, wie die Repräsentation der Tupel bei einer Anfrage und wie die Datenhaltung geregelt werden soll. Wenn Veränderungen an den Daten vorgenommen werden, so
entstehen neue Vorgänge, die sich stark von den üblichen UPDATE/DELETE/INSERTAnweisungen unterscheiden. Auch neue Integritätsbedingungen sind entstanden, welche
neue Hürden für die Entwicklung geworfen haben; die bekannten Integritätsbedingungen
von SQL, wie der Primärschlüssel, eignen sich nicht ausreichend um die neuen Integritätsbedingungen zu entwickeln. Via Trigger mussten neue Mechanismen zu den Tabellen
hinzugefügt werden, welches sich als schweres Unterfangen erwiesen hat, da dies abhängig
von der Tabelle passieren muss. Der Trigger muss zur Laufzeit wissen, welche Tabelle der
Ursprung des Triggers ist und dementsprechend, abhängig von der Beschaffenheit eben
dieser, unterschiedliche dynamische Anfragen stellen, um die Integritätsbedingungen,
wie den temporalen Schlüssel, zu garantieren. Es entstehen neue Anforderungen an die
Tabellen und wie sie verwaltet werden und auch wie sie angefragt werden. Was zum
größten Kapitel dieser Ausarbeitung führt; die Analyse bestehender SQL-Erweiterung und
87
die anschließende Entwicklung einer eigenen für die Verwaltung der genannten temporalen
Daten.
Dabei sind neue Probleme entstanden. Eine Erweiterung für SQL zu beschreiben
erfordert Wissen über die bestehende Syntax und für die Konstruktion der Syntax für
die Erweiterung zusätzlich noch Wissen über historische Ansätze, um bekannte Fehler zu
vermeiden. Im Rahmen der Übersetzung sind dabei verschiedene Probleme entstanden:
So war es nötig, zur Laufzeit dynamisch Eigenschaften über die Tabelle zu erfahren. Eine
Kommunikation zwischen der Datenbank und der Tabelleneinheit war nötig. Es muss
auch darauf geachtet werden, dass nicht zu häufig kommuniziert wird, da es sonst bei der
Übersetzung zu einem Engpass kommen kann. Eine andere große Herausforderung war
das temporale NOT EXISTS, das heißt die temporale Differenz. Eine saubere Syntax zu
entwerfen, welche effizient und ohne Einschränkungen übersetzt werden kann, erforderte
einige verworfene Ansätze, bis sich schlussendlich für die Variante mit den aufgespaltenen
Tupeln entschieden wurde. Auch gab es Probleme, bei der Übersetzung jedes Detail
zu beachten, was auch schlussendlich zum Ausblick führt: was für Möglichkeiten zur
Erweiterung existieren?
So ist die SQL-Erweiterung nicht vollständig mit dem jetzigen SQL-Standard kompatibel. Es fehlen UNION, INTERSECT und EXCEPT zwischen zwei SELECT-Anweisungen
vollständig und werden nicht korrekt gelesen und übersetzt. Im temporalen Kontext
könnte auch Funktionalität hinzugefügt werden: Temporale Aggregierungen wie ein SUM,
welches die Zeiten gewichtet und ein korrektes Ergebnis im temporalen Kontext anzeigt,
fehlen vollkommen, aber könnten nachgerüstet werden. Sie benötigen neue Übersetzungsvorgänge, falls eine solche Funktion in einem SEQUENCED SELECT auftritt. EXISTS
und NOT EXISTS in WHERE-Klauseln bei UPDATE/DELETE-Anweisungen sind in der
momentanen Implementierung strikt nicht-temporal und beachten nicht den temporalen
Zusammenhang zwischen einer SEQUENCED-Unteranfrage und der Tabelle, auf die sich
die Anweisung bezieht. Als letzte mögliche Erweiterung wäre es interessant, das Projekt
noch einmal zu besuchen, wenn der temporale Teil des SQL:2011-Standards tatsächlich
von PostgreSQL implementiert wird. Die jetzige Syntax ist nicht vollständig kompatibel
und bestehende Anfragen der Erweiterung müssten leicht korrigiert werden, damit sie
gänzlich ohne den selbst konzipierten Übersetzer laufen. So wäre eine weitere Aufgabe,
den jetzigen Übersetzer vollständig kompatibel zum Standard zu gestalten, mitsamt der
Syntax für DDL-Statements und der Möglichkeit, die Attribute für die Gültigkeits- und
Transaktionszeit selbst zu benennen. Das würde eine starke Veränderung der Software
zugrunde haben, würde aber stärkere Flexibilität im System ermöglichen.
So gilt es, die bestehenden Erfahrungswerte über die temporale Datenhaltung weiterhin
zu bewahren und zu erweitern. Temporale Daten werden nicht auf einmal verschwinden;
die Frage nach der Vergangenheit, Gegenwart und Zukunft wird im Zeitalter der Big
Data nur noch bedeutender.
88
Anhang A
PostgreSQL-Extension:
Datentyp Semester
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include
#include
#include
#include
<string.h>
”postgres.h”
”utils/rangetypes.h”
”fmgr.h”
PG MODULE MAGIC;
Datum
Datum
Datum
Datum
Datum
Datum
Datum
Datum
Datum
semester in(PG FUNCTION ARGS);
semester out(PG FUNCTION ARGS);
semester abs lt(PG FUNCTION ARGS);
semester abs le(PG FUNCTION ARGS);
semester abs eq(PG FUNCTION ARGS);
semester abs ge(PG FUNCTION ARGS);
semester abs gt(PG FUNCTION ARGS);
semester abs cmp(PG FUNCTION ARGS);
semesterrange canonical(PG FUNCTION ARGS);
typedef enum {
winter,
summer
} SemesterSeason;
typedef struct Semester {
int year;
SemesterSeason season;
} Semester;
PG FUNCTION INFO V1(semester in);
Datum
semester in(PG FUNCTION ARGS)
{
char ∗str = PG GETARG CSTRING(0);
char season str[128];
89
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
int year;
Semester ∗semester;
semester = (Semester ∗) palloc(sizeof(Semester));
if (strcmp(str, ”now”) == 0) {
semester−>season = summer;
semester−>year = 2015;
PG RETURN POINTER(semester);
}
if (sscanf(str, ”%s %d”, season str, &year) != 2) {
ereport(ERROR, (errcode(ERRCODE INVALID TEXT REPRESENTATION),
errmsg(”invalid input syntax for semester: \”%s\””, str)));
}
if (strcmp(season str, ”Wintersemester”) == 0) {
semester−>season = winter;
} else if (strcmp(season str, ”Sommersemester”) == 0) {
semester−>season = summer;
} else {
ereport(ERROR, (errcode(ERRCODE INVALID TEXT REPRESENTATION),
errmsg(”invalid input syntax for semester: \”%s\””, str)));
}
semester−>year = year;
PG RETURN POINTER(semester);
}
PG FUNCTION INFO V1(semester out);
Datum
semester out(PG FUNCTION ARGS)
{
Semester ∗semester = (Semester ∗) PG GETARG POINTER(0);
char ∗result;
result = (char∗)palloc(128);
if (semester−>season == summer) {
sprintf(result, ”Sommersemester %d”, semester−>year);
} else {
sprintf(result, ”Wintersemester %d/%d”, semester−>year, semester−>year+1);
}
PG RETURN CSTRING(result);
}
static int
semester abs cmp internal(Semester ∗ a, Semester ∗ b)
{
if (a−>year < b−>year ||
(a−>year == b−>year && a−>season == summer && b−>season == winter))
return −1;
if (a−>year > b−>year ||
(a−>year == b−>year && a−>season == winter && b−>season == summer))
90
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
return 1;
return 0;
}
PG FUNCTION INFO V1(semester abs lt);
Datum
semester abs lt(PG FUNCTION ARGS)
{
Semester ∗a = (Semester ∗) PG GETARG POINTER(0);
Semester ∗b = (Semester ∗) PG GETARG POINTER(1);
PG RETURN BOOL(semester abs cmp internal(a, b) < 0);
}
PG FUNCTION INFO V1(semester abs le);
Datum
semester abs le(PG FUNCTION ARGS)
{
Semester ∗a = (Semester ∗) PG GETARG POINTER(0);
Semester ∗b = (Semester ∗) PG GETARG POINTER(1);
PG RETURN BOOL(semester abs cmp internal(a, b) <= 0);
}
PG FUNCTION INFO V1(semester abs eq);
Datum
semester abs eq(PG FUNCTION ARGS)
{
Semester ∗a = (Semester ∗) PG GETARG POINTER(0);
Semester ∗b = (Semester ∗) PG GETARG POINTER(1);
PG RETURN BOOL(semester abs cmp internal(a, b) == 0);
}
PG FUNCTION INFO V1(semester abs ge);
Datum
semester abs ge(PG FUNCTION ARGS)
{
Semester ∗a = (Semester ∗) PG GETARG POINTER(0);
Semester ∗b = (Semester ∗) PG GETARG POINTER(1);
PG RETURN BOOL(semester abs cmp internal(a, b) >= 0);
}
PG FUNCTION INFO V1(semester abs gt);
Datum
semester abs gt(PG FUNCTION ARGS)
91
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
{
Semester ∗a = (Semester ∗) PG GETARG POINTER(0);
Semester ∗b = (Semester ∗) PG GETARG POINTER(1);
PG RETURN BOOL(semester abs cmp internal(a, b) > 0);
}
PG FUNCTION INFO V1(semester abs cmp);
Datum
semester abs cmp(PG FUNCTION ARGS)
{
Semester ∗a = (Semester ∗) PG GETARG POINTER(0);
Semester ∗b = (Semester ∗) PG GETARG POINTER(1);
PG RETURN INT32(semester abs cmp internal(a, b));
}
PG FUNCTION INFO V1(semesterrange canonical);
Datum
semesterrange canonical(PG FUNCTION ARGS)
{
RangeType ∗r = PG GETARG RANGE(0);
TypeCacheEntry ∗typcache;
RangeBound lower;
RangeBound upper;
bool empty;
Semester ∗semester;
typcache = range get typcache(fcinfo, RangeTypeGetOid(r));
range deserialize(typcache, r, &lower, &upper, &empty);
if (empty)
PG RETURN RANGE(r);
if (!lower.infinite && !lower.inclusive) {
semester = (Semester ∗)lower.val;
if (semester−>season == summer) {
semester−>season = winter;
} else {
semester−>season = summer;
semester−>year++;
}
lower.inclusive = true;
}
if (!upper.infinite && upper.inclusive) {
semester = (Semester ∗)upper.val;
if (semester−>season == summer) {
semester−>season = winter;
92
191
192
193
194
195
196
197
198
199 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
} else {
semester−>season = summer;
semester−>year++;
}
upper.inclusive = false;
}
PG RETURN RANGE(range serialize(typcache, &lower, &upper, false));
CREATE FUNCTION semester in(cstring)
RETURNS semester
AS ’semester4psql’
LANGUAGE C IMMUTABLE STRICT;
CREATE FUNCTION semester out(semester)
RETURNS cstring
AS ’semester4psql’
LANGUAGE C IMMUTABLE STRICT;
CREATE TYPE semester (
internallength = 16,
input = semester in,
output = semester out,
alignment = int
);
CREATE FUNCTION semester abs lt(semester, semester) RETURNS bool
AS ’semester4psql’ LANGUAGE C IMMUTABLE STRICT;
CREATE FUNCTION semester abs le(semester, semester) RETURNS bool
AS ’semester4psql’ LANGUAGE C IMMUTABLE STRICT;
CREATE FUNCTION semester abs eq(semester, semester) RETURNS bool
AS ’semester4psql’ LANGUAGE C IMMUTABLE STRICT;
CREATE FUNCTION semester abs ge(semester, semester) RETURNS bool
AS ’semester4psql’ LANGUAGE C IMMUTABLE STRICT;
CREATE FUNCTION semester abs gt(semester, semester) RETURNS bool
AS ’semester4psql’ LANGUAGE C IMMUTABLE STRICT;
CREATE OPERATOR < (
leftarg = semester, rightarg = semester, procedure = semester abs lt,
commutator = > , negator = >= ,
restrict = scalarltsel, join = scalarltjoinsel
);
CREATE OPERATOR <= (
leftarg = semester, rightarg = semester, procedure = semester abs le,
commutator = >= , negator = > ,
restrict = scalarltsel, join = scalarltjoinsel
);
CREATE OPERATOR = (
leftarg = semester, rightarg = semester, procedure = semester abs eq,
commutator = = ,
restrict = eqsel, join = eqjoinsel
93
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
);
CREATE OPERATOR >= (
leftarg = semester, rightarg = semester, procedure = semester abs ge,
commutator = <= , negator = < ,
restrict = scalargtsel, join = scalargtjoinsel
);
CREATE OPERATOR > (
leftarg = semester, rightarg = semester, procedure = semester abs gt,
commutator = < , negator = <= ,
restrict = scalargtsel, join = scalargtjoinsel
);
CREATE FUNCTION semester abs cmp(semester, semester) RETURNS int4
AS ’semester4psql’ LANGUAGE C IMMUTABLE STRICT;
CREATE OPERATOR CLASS semester abs ops
DEFAULT FOR TYPE semester USING btree AS
OPERATOR 1 < ,
OPERATOR 2 <= ,
OPERATOR 3 = ,
OPERATOR 4 >= ,
OPERATOR 5 > ,
FUNCTION 1 semester abs cmp(semester, semester);
CREATE FUNCTION semester hash(s semester) RETURNS int
AS $$
BEGIN
RETURN hashtext(text(s));
END;
$$ LANGUAGE plpgsql;
CREATE OPERATOR CLASS semester abs hash op
DEFAULT FOR TYPE semester USING hash AS
OPERATOR 1 = ,
FUNCTION 1 semester hash(semester);
CREATE TYPE semesterrange;
CREATE FUNCTION semesterrange canonical(semesterrange)
RETURNS semesterrange
AS ’semester4psql’
LANGUAGE C IMMUTABLE STRICT;
CREATE TYPE semesterrange AS RANGE (
subtype = semester,
canonical = semesterrange canonical
);
94
Abbildungsverzeichnis
2.1
2.2
2.3
2.4
Darstellung der Relationen in Tabelle 2.1 (Grafik entnommen aus [TYT11])
Taxonomie der Allen-Relationships (Grafik entnommen aus [JW10]) . . .
Darstellung der Relationen in Tabelle 2.2 (Grafik entnommen aus [TYT11])
Darstellung der Relationen in Tabelle 2.3 (Grafik entnommen aus [TYT11])
9
10
11
11
5.1
5.2
5.3
5.4
5.5
5.6
5.7
Architektur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Syntax-Diagramm für CREATE TABLE . . . . . . . . . . . . . . . . . . .
Syntax-Diagramm für UPDATE . . . . . . . . . . . . . . . . . . . . . . .
Syntax-Diagramm für DELETE . . . . . . . . . . . . . . . . . . . . . . .
Syntax-Diagramm für SELECT-Anfragen . . . . . . . . . . . . . . . . . .
Beispiel AST . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Sequenzdiagramm für die Übersetzung der temporalen SQL-Erweiterung
54
56
57
58
58
69
79
6.1
6.2
6.3
6.4
ER-Modell
ER-Modell
ER-Modell
ER-Modell
81
82
83
85
für
für
für
für
die
die
die
die
Studiengänge und Kompetenzbereiche . . . . . . . .
Lehrveranstaltungen . . . . . . . . . . . . . . . . . .
Studiengänge und KB mit neuem Datentyp Semester
Lehrveranstaltungen mit neuem Datentyp Semester .
95
.
.
.
.
Tabellenverzeichnis
2.1
2.2
2.3
2.4
2.5
Die 13 Relationen zwischen Zeitintervallen nach James F. Allen
Temporale Relationen zwischen Zeitpunkt und Zeitintervall . . .
Temporale Relationen zwischen zwei Zeitpunkten . . . . . . . .
Range-Operatoren . . . . . . . . . . . . . . . . . . . . . . . . .
Range-Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
8
10
11
19
20
3.1
3.2
3.3
3.4
3.5
3.6
3.7
3.8
3.9
3.10
3.11
3.12
3.13
3.14
3.15
3.16
3.17
3.18
3.19
24
25
26
26
27
27
27
28
28
29
29
30
31
31
32
32
32
33
3.21
3.22
3.23
Beispielausprägungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Relation ARBEITET mit Valid Time . . . . . . . . . . . . . . . . . . . .
Ausprägung, die den temporalen Schlüssel verletzt . . . . . . . . . . . . .
Relation ANGESTELLTE mit Valid Time . . . . . . . . . . . . . . . . .
Mitarbeiter 2 wurde versetzt . . . . . . . . . . . . . . . . . . . . . . . . .
Relation ANGESTELLTE mit Valid Time . . . . . . . . . . . . . . . . .
Relation ANGESTELLTE mit Valid Time . . . . . . . . . . . . . . . . .
Relation ANGESTELLTE mit Valid Time . . . . . . . . . . . . . . . . .
Relation ANGESTELLTE mit Valid Time . . . . . . . . . . . . . . . . .
Zweite Gehaltserhöhung von Max . . . . . . . . . . . . . . . . . . . . . .
Temporaler Join zwischen ANG und ARBEITET . . . . . . . . . . . . .
Projekte, in denen Bob nicht mitwirkt. . . . . . . . . . . . . . . . . . . .
Ausgangsausprägung für das temporale Löschen . . . . . . . . . . . . . .
Bob im Zeitraum [2010-01-01,2014-01-01) wird gelöscht . . . . . . . . . .
Max im Zeitraum [2010-01-01,2011-01-01) wird gelöscht . . . . . . . . . .
Max im Zeitraum [2012-01-01,2013-01-01) wird gelöscht . . . . . . . . . .
Max im Zeitraum [2014-01-01,2015-01-01) wird gelöscht (Schritt 2) . . . .
Max im Zeitraum [2012-01-01,2013-01-01) wird gelöscht (Schritt 3) . . . .
Update Gehalt von Bob auf 3400 im Zeitraum [2014-01-01,2015-01-01)
(Schritt 1) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Update Gehalt von Bob auf 3400 im Zeitraum [2014-01-01,2015-01-01)
(Schritt 2) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Transaction Time-Tabelle mit Zeitstempeln . . . . . . . . . . . . . . . .
Transaction Time-Tabelle mit Zeiträumen . . . . . . . . . . . . . . . . .
Bitemporale Tabelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.1
4.2
4.3
Valid Time-Tabelle nach dem INSERT in 4.8 . . . . . . . . . . . . . . . .
Valid Time-Tabelle nach dem UPDATE in 4.9 . . . . . . . . . . . . . . .
Valid Time-Tabelle nach dem DELETE in 4.10 . . . . . . . . . . . . . .
43
44
45
3.20
96
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
33
34
35
35
36
4.4
4.5
4.6
4.7
4.8
4.9
4.10
4.11
4.12
4.13
Transaction Time-Tabelle nach dem INSERT in 4.13 . . . . . . . . . . .
Transaction Time-Tabelle nach dem UPDATE in 4.14 . . . . . . . . . . .
Transaction Time-Tabelle nach dem DELETE in 4.15 . . . . . . . . . . .
Ergebnis nach Anfrage 4.16 an Transaction Time-Tabelle . . . . . . . . .
Beispielausprägung einer temporalen Tabelle inVTSQL2+ . . . . . . . . .
Ergebnis von Anfrage 4.19 . . . . . . . . . . . . . . . . . . . . . . . . . .
Ergebnis von Anfrage 4.20 . . . . . . . . . . . . . . . . . . . . . . . . . .
Ergebnis von Anfrage 4.21 . . . . . . . . . . . . . . . . . . . . . . . . . .
Ergebnis von Anfrage 4.22 . . . . . . . . . . . . . . . . . . . . . . . . . .
Ergebnis von Anfrage 4.23 (Namen der Übersichtlichkeit halber gekürzt)
46
46
47
47
49
50
50
51
51
52
5.1
Übersetzung der 13 Relationships-Arten von Allen zu Operationen auf den
Range-Datentyp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Übersetzung der Relationen zwischen Zeitpunkt und Zeitintervall . . . .
Übersetzung der Relationen zwischen Zeitpunkt und Zeitintervall . . . .
Ausgangsausprägung für das temporale Löschen . . . . . . . . . . . . . .
Ausgangsausprägung für das temporale Löschen . . . . . . . . . . . . . .
60
60
61
67
67
5.2
5.3
5.4
5.5
97
Literaturverzeichnis
[All83]
J. F. Allen. Maintaining Knowledge About Temporal Intervals. Commun.
ACM, 26(11), 1983, 832–843. ISSN 0001-0782. URL http://doi.acm.
org/10.1145/182.358434.
[Bei01]
F. Beier. Objekt-relationale Realisierung einer temporalen Datenbanksprache.
Diplomarbeit, Leibniz Universität Hannover, 2001.
[Bet10]
C. Bettini. Time Granularities in Databases, Data Mining, and Temporal
Reasoning. Springer-Verlag, Berlin, Heidelberg, 2010. ISBN 3642086349,
9783642086342.
[JW10]
T. Johnston, R. Weis. Managing Time in Relational Databases: How to
Design, Update and Query Temporal Data. Morgan Kaufmann Publishers
Inc., San Francisco, CA, USA, 2010. ISBN 0123750415, 9780123750419.
[KM12]
K. Kulkarni, J.-E. Michels. Temporal Features in SQL:2011. SIGMOD Rec.,
41(3), 2012, 34–43. ISSN 0163-5808. URL http://doi.acm.org/10.
1145/2380776.2380786.
[Lev09]
J. Levine. flex & bison: Text Processing Tools. O’Reilly Media, 2009. ISBN
0596155972.
[Mal09]
E. Malinowski. Advanced Data Warehouse Design: From Conventional to
Spatial and Temporal Applications. Springer-Verlag, Berlin, Heidelberg, 2009.
ISBN 3642093833, 9783642093838.
[SA86]
R. Snodgrass, I. Ahn. Temporal Databases. Computer, 19(9), 1986, 35–
42. ISSN 0018-9162. URL http://dx.doi.org/10.1109/MC.1986.
1663327.
[SAA+ 94a] R. T. Snodgrass, I. Ahn, G. Ariav, D. Batory, J. Clifford, C. E. Dyreson,
R. Elmasri, F. Grandi, C. S. Jensen, W. Käfer, N. Kline, K. Kulkarni, T. Y. C.
Leung, N. Lorentzos, J. F. Roddick, A. Segev, M. D. Soo, S. M. Sripada.
TSQL2 Language Specification. SIGMOD Rec., 23(1), 1994, 65–86. ISSN
0163-5808. URL http://doi.acm.org/10.1145/181550.181562.
98
[SAA+ 94b] R. T. Snodgrass, I. Ahn, G. Ariav, D. Batory, J. Clifford, C. E. Dyreson,
R. Elmasri, F. Grandi, C. S. Jensen, W. Käfer, N. Kline, K. Kulkarni, T. Y. C.
Leung, N. Lorentzos, J. F. Roddick, A. Segev, M. D. Soo, S. M. Sripada. A
TSQL2 Tutorial. SIGMOD Rec., 23(3), 1994, 27–33. ISSN 0163-5808. URL
http://doi.acm.org/10.1145/187436.187449.
[Sno95]
R. T. Snodgrass. The TSQL2 Temporal Query Language. Kluwer Academic
Publishers, Norwell, MA, USA, 1995. ISBN 0792396146.
[Sno00]
R. T. Snodgrass. Developing Time-oriented Database Applications in SQL.
Morgan Kaufmann Publishers Inc., San Francisco, CA, USA, 2000. ISBN
1-55860-436-7.
[TCG+ 93] A. U. Tansel, J. Clifford, S. Gadia, S. Jajodia, A. Segev, R. Snodgrass,
Hg. Temporal Databases: Theory, Design, and Implementation. BenjaminCummings Publishing Co., Inc., Redwood City, CA, USA, 1993. ISBN
0-8053-2413-5.
[TYT11]
Y. Tang, X. Ye, N. Tang. Temporal Information Processing Technology and
Its Applications. Springer Publishing Company, Incorporated, 2011. ISBN
3642149588, 9783642149580.
99
Erklärung
Hiermit versichere ich, dass ich die vorliegende Arbeit und die zugehörige Implementierung
selbstständig verfasst und dabei nur die angegebeben Quellen und Hilfsmittel verwendet
habe.
Hannover, 20.04.2015
Marcell Salvage
100
Herunterladen