- Fachgebiet Datenbanken und Informationssysteme

Werbung
Realisierung einer temporalen Erweiterung von SQL
auf einem objekt-relationalen
Datenbankmanagementsystem
Diplomarbeit im Studiengang Mathematik mit der Studienrichtung Informatik
an der Universität Hannover
vorgelegt von
Carsten Reinhard
Betreut von Prof. Dr. Udo Lipeck
Universität Hannover, Institut für Informatik
Fachgebiet Datenbanken und Informationssysteme
Zweitgutachter Prof. Dr. techn. Wolfgang Nejdl
Universität Hannover
Institut für Rechnergestützte Wissensverarbeitung
Hannover, 22. Dezember 1999
ii
Zusammenfassung
Die Zeit ist eine allseits vorhandene Komponente der realen Welt, und alle denkbaren Informationen unterliegen letztlich einer zeitlichen Veränderung. Es wundert daher nicht, daß in fast
allen vorhandenen Datenbanksystemen zeitbehaftete Daten verarbeitet werden. Dennoch ist die
gegenwärtige Unterstützung temporaler Aspekte in den verbreiteten Datenbanksprachen relativ
gering. Es hat daher in der Vergangenheit verschiedene Ansätze gegeben, neue Datenbanksprachen mit temporaler Funktionalität zu entwickeln. Auch für zukünftige Standards — etwa SQL3
— ist man darum bemüht, eine inhärente temporale Unterstützung zu vereinbaren.
In der vorliegenden Arbeit werden daher zunächst grundsätzliche Prinzipien zur temporalen Erweiterung von Datenbanksprachen erläutert. Diese sollen eine umfangreiche temporale
Unterstützung bei gleichzeitiger Aufwärtskompatibilität und leichter Erlernbarkeit der neuen
Sprache sicherstellen.
Weiterhin wird, anhand von Beispielen und durch die Beschreibung des zugrundeliegenden
temporalen Datenmodells, mit ATSQL2 eine prototypisch verfügbare temporale Erweiterung
von SQL vorgestellt, die auf Basis eines relationalen Datenbanksystems implementiert wurde.
Auf dieser Anfragesprache aufbauend wird eine neue temporale Erweiterung von SQL formuliert. Das Ziel ist dabei, in höherem Maße temporale Semantik zur Verfügung zu stellen als das
in der genannten Implementierung wegen der Verwendung eines rein relationalen Datenbanksystems der Fall gewesen ist.
Mit Hilfe der erst in jüngster Zeit kommerziell verfügbaren objekt-relationalen Möglichkeiten
ist trotz der umfangreicheren temporalen Funktionalität eine relativ einfache Übersetzung der
temporalen Anfragesprache erreichbar. Auf dieser Grundlage wird schließlich die neue temporale
Erweiterung von SQL unter Verwendung eines vorhandenen objekt-relationalen Datenbankmanagementsystems realisiert und als Prototyp implementiert.
iii
iv
Inhaltsverzeichnis
1 Einleitung
1.1 Problemstellung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.2 Gliederung der Arbeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2 Grundbegriffe
2.1 Temporale Bezeichnungen . . . . . . . . . .
2.1.1 Dimensionen der Zeit . . . . . . . .
2.1.2 Darstellung der Zeit . . . . . . . . .
2.2 Zeitstempel . . . . . . . . . . . . . . . . . .
2.2.1 Inhalt der Zeitstempel . . . . . . . .
2.2.2 Anwendung der Zeitstempel . . . . .
2.2.3 Temporale Beziehungen . . . . . . .
2.3 Temporale Erweiterung von Datenmodellen
2.3.1 Aufwärtskompatibilität . . . . . . .
2.3.2 Schnappschuß-Reduzierbarkeit . . .
2.3.3 Temporale Vollständigkeit . . . . . .
1
1
2
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
4
4
4
5
6
7
9
10
10
12
15
17
3 Temporale Erweiterungen von SQL
3.1 Der Sprachvorschlag ATSQL2 . . . . . . . . . . . .
3.1.1 Syntax . . . . . . . . . . . . . . . . . . . . .
3.1.2 Semantik . . . . . . . . . . . . . . . . . . .
3.2 Die neue temporale Erweiterung SQLTE . . . . . .
3.2.1 Grundlegende Konzepte . . . . . . . . . . .
3.2.2 Temporale Anfrageformulierung . . . . . . .
3.2.3 Temporale Datenmanipulation . . . . . . .
3.2.4 Temporale Datendefinition . . . . . . . . .
3.2.5 Präsentation temporaler Anfrageergebnisse
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
19
19
20
24
27
28
30
34
37
38
.
.
.
.
.
.
.
.
40
40
41
44
45
46
47
48
53
4 Planung und Entwurf der Implementation
4.1 Objekt-relationale Möglichkeiten . . . . . .
4.1.1 Kollektionen . . . . . . . . . . . . .
4.1.2 Referenzen . . . . . . . . . . . . . .
4.1.3 Methoden . . . . . . . . . . . . . . .
4.1.4 Oracle 8.1 vs. Oracle 8.0 . . . . . . .
4.2 Varianten des Speichermodells . . . . . . . .
4.2.1 Einfache Schachtelung . . . . . . . .
4.2.2 Doppelte Schachtelung . . . . . . . .
v
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
vi
INHALTSVERZEICHNIS
4.3
4.2.3 Festlegung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Schichtenarchitektur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5 Implementation
5.1 Programmübersicht . . . . . . . . . . . . . . .
5.2 Datenbankstrukturen . . . . . . . . . . . . . .
5.2.1 Grundlegende Vereinbarungen . . . . .
5.2.2 Objekte . . . . . . . . . . . . . . . . .
5.2.3 Funktionen . . . . . . . . . . . . . . .
5.2.4 Temporäre Tabellen . . . . . . . . . .
5.3 Programmbeschreibung . . . . . . . . . . . .
5.3.1 Globale Strukturen . . . . . . . . . . .
5.3.2 Ein- und Ausgabeverarbeitung . . . .
5.3.3 Hauptprogramm . . . . . . . . . . . .
5.3.4 Lexikalische Analyse . . . . . . . . . .
5.3.5 Syntaktische Analyse . . . . . . . . . .
5.3.6 Semantische Analyse und Übersetzung
5.3.7 Datenbankschnittstelle . . . . . . . . .
5.4 Testdatenbank . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
59
60
63
63
64
65
65
66
66
67
68
69
71
71
72
74
81
85
6 Ausblick
87
A Syntax der temporalen Erweiterung
89
B Dokumentation der Programmdateien
B.1 Datenbankstrukturen . . . . . . . . . . . . . . . . . . . . . . .
B.1.1 Hauptskript — sqlte.sql . . . . . . . . . . . . . . . . .
B.1.2 Datentyp Intervall — interval.sql . . . . . . . . . . . .
B.1.3 Datentyp Zeitstempel — stamp.sql . . . . . . . . . . .
B.1.4 Paketfunktionen — package.sql . . . . . . . . . . . . .
B.1.5 Deinstallationsskript — dropsqlte.sql . . . . . . . . . .
B.1.6 Test des komplexen Speichermodells — kommode.zip .
B.2 Übersetzungsprogramm . . . . . . . . . . . . . . . . . . . . .
B.2.1 Globale Strukturen — sqlte.h . . . . . . . . . . . . . .
B.2.2 Hauptprogramm — sqlte.pc . . . . . . . . . . . . . . .
B.2.3 Eingabeverarbeitung — eingabe.pc . . . . . . . . . . .
B.2.4 Ausgabeverarbeitung — ausgabe.pc . . . . . . . . . .
B.2.5 Lexikalische Analyse — scanner.pc . . . . . . . . . . .
B.2.6 Syntaktische Analyse — parser.pc . . . . . . . . . . .
B.2.7 Semantische Analyse und Übersetzung — codegen.pc
B.2.8 Datenbankschnittstelle — oracle.pc . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
91
91
91
92
92
93
94
95
95
95
95
96
97
98
100
103
106
Literaturverzeichnis
110
Abbildungsverzeichnis
113
Kapitel 1
Einleitung
1.1
Problemstellung
Der Titel legt es bereits nahe: Die vorliegende Arbeit beinhaltet den Entwurf und die Implementierung einer temporalen Erweiterung der Datenbanksprache SQL mit Hilfe eines objektrelationalen Datenbankmanagementsystems. Nachfolgend soll daher zunächst ein grober Überblick über die Begriffe der temporalen Erweiterung und des objekt-relationalen Datenbanksystems gegeben werden, eine präzisere Beschreibung der beteiligten Datenbanksprachen erfolgen,
sowie die Notwendigkeit der Zuhilfenahme eines vorhandenen DBMS erläutert werden.
Allen Datenbanksystemen und Anfragesprachen gemein ist die Möglichkeit, Informationen
über Zeit in irgendeiner Form abzulegen und zu verarbeiten. Üblicherweise geschieht dieses mit
Hilfe spezieller Datentypen, die es ermöglichen, z. B. Datums- oder Zeitangaben abzulegen. Eine temporale Erweiterung einer konventionellen (nicht-temporalen) Datenbanksprache verlangt
jedoch mehr, nämlich eine in der Sprache eingebaute Unterstützung zur Auswertung zeitabhängiger Informationen. Eine temporale Anfragesprache soll es dem Benutzer mit Interesse an zeitveränderlichen Daten ermöglichen, sich nicht mit der expliziten Auswertung einzelner temporaler
Attribute beschäftigen zu müssen. Wie aufwendig diese explizite Formulierung im Vergleich zu
der Benutzung einer leistungsfähigen temporalen Datenbanksprache ist, wird in [ZCF97, ch. 5]
anhand einer Fallstudie erläutert. Ebenso wird dort beschrieben, daß fast alle Datenbankapplikationen von zeitveränderlichen Daten betroffen seien und somit die Notwendigkeit einer solchen
datenbankseitigen temporalen Unterstützung belegt.
Präzise handelt es sich bei der hier zu erweiternden Datenbanksprache um den Sprachstandard SQL-92 [SQL92, MS93]. Da die Festlegung eines nachfolgenden Standards in Form von
SQL3 und insbesondere der temporalen Erweiterung in Form von SQL/Temporal [SQL99] als
Teil von SQL3 noch auf sich warten läßt, ist der Sprachvorschlag TSQL2 [SAA94a, SAA94b,
ZCF97, Myr97] bislang der wohl breiteste Konsens für eine temporale Erweiterung von SQL92.
Darüber hinaus wurde TSQL2 auf Basis der weiterentwickelten Version Applied TSQL2 [BJS95]
— im Gegensatz zu vielen anderen Sprachvorschlägen — bereits in verschiedenen Varianten
implementiert. Als aktuell verfügbare Implementationen sind vor allem Tiger [BJ96, Böh97]
und TimeDB 2.0 [Ste98a, Ste98b] zu nennen. Eine Übersicht über vorhandene Prototypen auch
anderer Anfragesprachen gibt [Böh95].
Weiterhin wurden auf der Grundlage von ATSQL2 Änderungsvorschläge für SQL/Temporal
unterbreitet [SBJ96a, SBJ96b, SBJ97], so daß der sich entwickelnde SQL3-Standard vermut-
1
2
KAPITEL 1. EINLEITUNG
lich entsprechende Übereinstimmungen aufweisen wird. Aus den genannten drei Gründen wird
sich die Spracherweiterung der vorliegenden Arbeit eng an den Veröffentlichungen zu ATSQL2
orientieren.
Die Implementierung einer temporalen Anfragesprache kann auf verschiedenen Ebenen erfolgen. Die Entwicklung von Datenbankapplikationen mit zeitveränderlichen Daten soll durch
die inhärente temporale Unterstützung der Sprache vereinfacht werden. Damit möglichst vielen, auch bereits vorhandenen Applikationen die zusätzliche Funktionalität zur Verfügung steht,
wäre es wünschenswert, diese auf einer niedrigen, DBMS-nahen Ebene anzusiedeln. Auch scheint
dieses im Sinne einer hohen Performance neuer Operationen sinnvoll. Leider kann in vorhandene DBMS nicht in dem benötigten Umfang eingegriffen werden. Andererseits erscheint es zu
zeitaufwendig, ein eigenes, selbst nur rudimentäres, DBMS zu entwickeln. Daher soll hier, wie
auch bei den o. g. Implementationen Tiger und TimeDB, auf ein vorhandenes kommerzielles
DBMS aufgesetzt werden. Kommandos der temporalen Datenbanksprache werden dabei in SQL
übersetzt und in der vorhandenen Datenbank ausgeführt. Auf diese Weise können alle wichtigen
Funktionen des zugrundeliegenden DBMS genutzt werden, wenngleich es den Verzicht auf die
zuvor beschriebenen Vorteile mit sich bringt.
Im Gegensatz zu den beiden genannten vorhandenen Implementationen, die auf der Basis der
Funktionalität relationaler DBMS arbeiten, sollen in dieser Arbeit die zusätzlichen Möglichkeiten
eines objekt-relationalen DBMS genutzt werden. Diese Systeme basieren nicht mehr auf dem
reinen relationalen Datenmodell, sondern sind um Konzepte der Objektorientierung erweitert
worden. Dabei sind die umgesetzten Konzepte unterschiedlich stark ausgeprägt. Hier soll das
kommerzielle DBMS Oracle in der Version 8 Verwendung finden, das mit der sogenannten objects
option u. a. die Möglichkeiten der benutzerdefinierten Objekttypen, Methoden, Referenzen und
kollektionswertigen Attributen zur Verfügung stellt. Eine Einführung der in Oracle gebotenen
objekt-relationalen Konzepte geben [HP98] und [Ora98a, Part IV].
Die Vorteile der Nutzung objekt-orientierter Konzepte in relationalen Datenbanken ergeben sich direkt aus den Stärken der objekt-orientierten Programmierung: Sie liegen u. a. in
der Möglichkeit der natürlichen Modellierung der realen Welt sowie einer leichten Wart- und
Erweiterbarkeit der entwickelten Programme. Durch die Nutzung der objekt-relationalen Erweiterungen sollen hier genau diese Vorteile auch bei der Implementierung einer temporalen
Anfragesprache erreicht werden. So können z. B. benutzerdefinierte Typen, kollektionswertige
Attribute und Referenzen zur leichten Verständlichkeit der Modellierung von temporalen Informationen beitragen. Die Möglichkeit der Kapselung von Objekten samt zugehöriger Methoden
sollte einer guten Wart- und Erweiterbarkeit zuträglich sein.
1.2
Gliederung der Arbeit
Im nachfolgenden Kapitel sollen zunächst einige Begriffe erläutert werden, die für den weiteren
Verlauf der Arbeit benötigt werden. Dazu gehören Konzepte, die vielen bisherigen Ansätzen von
temporalen Datenmodellen und temporalen Anfragesprachen gemein sind. Weiterhin werden
grundsätzliche Anforderungen zur temporalen Erweiterung von Datenmodellen beschrieben.
Im Kapitel drei wird der Sprachvorschlag ATSQL2 anhand von Anwendungsbeispielen und
durch Beschreibung des zugrundeliegenden Datenmodells vorgestellt. Darauf aufbauend wird
mit Hilfe der Anforderungen des zweiten Kapitels die neue Anfragesprache SQLTE entwickelt.
1.2. GLIEDERUNG DER ARBEIT
3
Nach der Festlegung der neuen temporalen Datenbanksprache wird im vierten Kapitel deren prototypische Implementation vorbereitet. Dazu werden erst allgemein objekt-relationale
Möglichkeiten von Oracle 8 vorgestellt. Weiterhin wird anhand zweier Speichermodell-Varianten
die konkrete Verwendbarkeit dieser Möglichkeiten geprüft. Es wird dann ein Architekturkonzept temporaler Spracherweiterungen beschrieben, um abschließend erste Vereinbarungen für
die darauffolgende Implementation zu treffen.
Im fünften Kapitel erfolgt die Beschreibung der eigentlichen Implementation. Dazu wird
zunächst eine Übersicht über das gesamte Programm gegeben. Danach werden die Strukturen
innerhalb der Datenbank erläutert und das Übersetzungsprogramm außerhalb der Datenbank
vorgestellt. Schließlich wird über erste praktische Erfahrungen mit der Anwendung des Prototypen auf umfangreicheren Datenmengen berichtet.
Wie üblich wird zum Schluß der Arbeit in Kapitel sechs ein Ausblick auf sinnvolle Erweiterungen gegeben. Es wird dabei sowohl auf denkbare Ergänzungen des Sprachumfangs wie auf
mögliche Weiterentwicklungen der Implementation eingegangen.
Kapitel 2
Grundbegriffe
Es sollen nun einige Begriffe rund um temporale Datenbanken erläutert werden, die als Grundlage für die nachfolgenden Kapitel benötigt werden. Es wird jeweils die Literaturstelle angegeben,
unter der detailliertere Informationen zu dem entsprechenden Begriff zu finden sind. Die Quellenangabe besagt also nicht notwendigerweise, daß die erstmalige Definition des jeweiligen Begriffs
auf den entsprechenden Autor zurückgeht.
2.1
2.1.1
Temporale Bezeichnungen
Dimensionen der Zeit
Die übliche Darstellung von Zeit in nicht-temporalen DBMS geschieht mit Hilfe von speziellen
Datentypen, z. B. DATE in Oracle mit den Informationen von Jahr, Monat, Tag, Stunden, Minuten und Sekunden. Diese Form wird als benutzerdefinierte Zeit (user-defined time) [Ste98a]
bezeichnet und steht dem Benutzer im allgemeinen auch in temporalen DBMS zur Verfügung.
Die benutzerdefinierte Zeit wird in Form von entsprechenden Attributen explizit vom Benutzer
eingerichtet und direkt unter seiner Regie abgefragt oder verändert. Dazu stehen arithmetische
Operationen und Umwandlungsfunktionen für die Zeit-Datentypen zur Verfügung. Es gibt jedoch
keinerlei inhärente Interpretation der benutzerdefinierten Zeitinformation durch das DBMS. Ein
Beispiel für ein solches explizites Attribut ist etwa das Attribut Geburtsdatum. Im Gegensatz dazu stehen implizite Attribute, d. h. zusätzliche Zeitinformationen, die vom temporalen
DBMS ohne direktes Einwirken des Benutzers vergeben werden und durch die temporale Funktionalität der Anfragesprache interpretiert werden.
Bei der Benutzung von Datenbanken ist es ein gängiges Vorgehen, Informationen nicht unmittelbar zum Zeitpunkt ihres Eintretens in der Datenbank abzulegen. So wird z. B. ein neuer
Angestellter einer Firma erst zwei Tage nach seinem Einstellungsdatum als Mitarbeiter in der
Datenbank aufgenommen, weil die zuständige Verwaltungskraft vorher Urlaub hatte. Ebenso ist
es denkbar, eine Information in die Datenbank einzutragen, noch bevor diese gültig wird, wenn
mit dem Eintreten eines Sachverhalts in der Zukunft fest gerechnet wird. Der neue Angestellte könnte z. B. bereits bei Vertragsabschluß einige Wochen vor dem Einstellungsdatum in die
Datenbank eingetragen werden.
Es sind daher in der Regel zwei Dimensionen der Zeit von Interesse: Erstens die Gültigkeitszeit (valid time) [ZCF97, ch. 5], nämlich die Zeitspanne, während der eine bestimmte Information
4
2.1. TEMPORALE BEZEICHNUNGEN
5
in der realen Welt zutraf, zutrifft oder zutreffen wird. Zweitens interessiert die Transaktionszeit (transaction time) [ZCF97, ch. 5 ], welche die Zeitspanne beschreibt, während der eine
bestimmte Information der realen Welt in der Datenbank dokumentiert war oder ist. Dabei wird
die Transaktionszeit ohne Einflußmöglichkeit des Benutzers direkt vom temporalen DBMS vergeben, so daß sich eine Dokumentations- oder sogar Kontrollfunktion ergibt. Außerdem kann so
die Transaktionszeit niemals in der Zukunft liegen.
Wird sowohl die Gültigkeitszeit als auch die Transaktionszeit unterstützt, so verwendet man
den Begriff bitemporal. Wird gar keine eingebaute temporale Unterstützung geboten (abgesehen von der benutzerdefinierten Zeit), ist die Bezeichnung nicht-temporal üblich. Der
Begriff Schnappschuß (snapshot) wird sowohl für temporale Datenbanken benutzt, wenn Informationen nur für einen bestimmten Zeitpunkt betrachtet werden, wie auch für nicht-temporale
Datenbanken, die naturgemäß die Anwendungswelt nur für einen einzigen Zeitpunkt abbilden.
Um Mißverständnisse zu vermeiden, soll hier auf die zuletzt genannte Verwendung des Begriffs
Schnappschuß zugunsten der Bezeichnung nicht-temporal verzichtet werden.
Datenbanken, die ausschließlich das Prinzip der Gültigkeitszeit unterstützen, werden auch als
historische Datenbanken (historical databases) bezeichnet. Wird in einer Datenbank nur die
Transaktionszeit benutzt, nennt man diese Rollback-Datenbank (rollback database) [Ste98a],
da dort die Datenablage nach dem Prinzip append-only funktioniert und daher stets auf alle
vorherigen Datenbankzustände zurückgegriffen werden kann.
Obwohl sich die vorliegende Arbeit an der in Abschnitt 1.1 erwähnten bitemporalen Anfragesprache ATSQL2 orientiert, wird der Umfang hier auf ein überschaubares Maß beschränkt,
indem der Schwerpunkt von vornherein auf die Unterstützung der Gültigkeitszeit gelegt wird.
Das heißt es wird hier auf die Realisierung der Transaktionszeit verzichtet, da die prinzipiellen temporalen Funktionen der Gültigkeitszeit zunächst von größerem Interesse erscheinen als
die eher dokumentarischen Aufgaben der Transaktionszeit. Trotzdem soll die Möglichkeit der
späteren Ergänzung der Anfragesprache nicht gänzlich außer Acht gelassen werden, so daß in
zukünftigen Projekten ggf. die Unterstützung der Transaktionszeit hinzugefügt werden kann.
Es wird daher im folgenden häufig nur mit den Begriffen gültig oder Gültigkeitszeit gearbeitet,
auch wenn sich die Aussage ebensogut auf allgemeine temporale Informationen und insbesondere auf die Transaktionszeit anwenden läßt. Auch ist oft eine Erweiterung auf bitemporale
Informationen denkbar.
2.1.2
Darstellung der Zeit
In [ZCF97, ch. 5.2] findet man verschiedene Möglichkeiten beschrieben, den Verlauf der Zeit
darzustellen. Hier soll stets das lineare Modell (linear model) verwendet werden. Durch das
Fortschreiten der Zeit von der Vergangenheit zur Zukunft ergibt sich dabei eine totale Ordnung
auf der Menge der Zeitpunkte. Diese Betrachtungsweise steht im Gegensatz zum verzweigenden
Modell (branching model), welches linear ist von der Vergangenheit bis zum aktuellen Zeitpunkt
und für die Zukunft verschiedene Zeitlinien zuläßt, die jeweils eine mögliche Folge von Ereignissen
darstellen.
Im linearen Zeitmodell kann man den Verlauf der Zeit durch verschiedene Dichten beschreiben. Man bezeichnet dabei das Zeitmodell als diskret (discrete), wenn die Menge der Zeitpunkte
isomorph zur Menge der natürlichen Zahlen ist, so daß jeder Zeitpunkt genau einen Vorgänger
und Nachfolger hat. Bei Isomorphie zu den rationalen oder reellen Zahlen bezeichnet man das
Modell als dicht (dense) — zwischen zwei Zeitpunkten läßt sich dann stets ein weiterer finden.
6
KAPITEL 2. GRUNDBEGRIFFE
Stetige Modelle (continuous models) sind isomorph zu den reellen Zahlen und somit dicht und
lückenlos.
Beim diskreten Zeitmodell repräsentiert jede natürliche Zahl einen unteilbaren Zeitabschnitt
beliebiger, aber fester Länge. Einen solchen Zeitabschnitt bezeichnet man als Chronon; er stellt
die kleinste darstellbare Zeiteinheit des diskreten Zeitmodells dar. Die Länge der Chronons wird
durch die Granularität des Modells bestimmt. Spricht man im diskreten Zeitmodell von einem
Zeitpunkt eines bestimmten Ereignisses, so meint man tatsächlich den zugehörigen Chronon,
d. h. man benennt nur den kleinsten im Modell vorhandenen Zeitabschnitt, währenddessen das
Ereignis eingetreten ist.
Ein Kalender stellt bestimmte Granularitäten zur Verfügung und ermöglicht die Übersetzung zwischen den einzelnen vorgesehenen Granularitäten. In SQL-92 wird z. B. eine Variante
des Gregorianischen Kalenders verwendet: Es existieren die Granularitäten Jahre, Monate, Tage,
Stunden, Minuten, Sekunden und Bruchteile von Sekunden sowie die jeweiligen Abbildungsfunktionen.
Sofern eine Unterstützung verschiedener Kalender vorhanden ist, wird sich die Auswahl eines
bestimmten Kalenders unmittelbar durch die Art der Anwendung ergeben. Die gewünschte Granularität der Einteilung hängt von der benötigten Genauigkeit ab: Während man im üblichen
Sprachgebrauch mit einer Minute als Chronon auskommen wird (z. B. ’der Unfall geschah um
12:34 Uhr ’), sind bei technischen Anwendungen sicher feinere Einteilungen vonnöten (z. B. ’der
Prozeß wurde um 12:34:56.7890 Uhr verdrängt’).
Wenngleich unser Zeitempfinden am ehesten dem der stetigen Modelle entspricht, soll in der
vorliegenden Arbeit stets das diskrete Zeitmodell Verwendung finden. Man folgt bei dieser Wahl
praktischen Gesichtspunkten, da man bei einer digitalen Abbildung der Zeitlinie letztlich auf eine
diskrete Darstellung angewiesen ist. Durch die freie Wahl der Granularität in Abhängigkeit der
Meßgenauigkeit der vorhandenen temporalen Informationen sollte diese Zeitdarstellung jedoch
stets den Anforderungen genügen.
Abschließend sei darauf hingewiesen, daß nachfolgend dem üblichen Sprachgebrauch folgend
stets von Zeitpunkten die Rede ist, obwohl tatsächlich derjenige Chronon gemeint ist, der den
entsprechenden Zeitpunkt überlappt.
2.2
Zeitstempel
Wenn man nicht-temporale Daten um eine Zeitinformation ergänzt, so bezeichnet man diese
zusätzliche Information als Zeitstempel (time stamp). Dabei können die Daten mehrere Zeitstempel erhalten, wenn mehrere Zeitlinien unterstützt werden, z. B. wird bei einem bitemporalen
DBMS jeweils ein Stempel für die Gültigkeitszeit und einer für die Transaktionszeit vergeben.
Zeitstempel werden üblicherweise mit Hilfe von impliziten Attributen realisiert.
Die Art der verwendeten Zeitstempel ist eine wesentliche Designentscheidung für ein temporales DBMS. Man unterscheidet daher die Art der Zeitstempel erstens durch ihren Inhalt, d. h.
man prüft, welchen Umfang die gestempelten Informationen haben. Zweitens interessiert es, auf
welche Einheiten von Daten die Zeitstempel jeweils angewendet werden, d. h. man unterscheidet
nach Umfang der gestempelten Daten.
Eine Übersicht über verschiedene in der Literatur vorgestellten Datenmodelle mit unterschiedlichen Zeitstempeln findet man z. B. in [ZCF97, ch. 5.4.3] oder [Ste98a, ch. 3].
2.2. ZEITSTEMPEL
7
i) Zeitpunkte
ii) Temporale Intervalle
iii) Temporale Elemente
iv) Temporale Mengen
4000 | 87
(null) | 91
4000 | 97
5000 | 98
4000 | [87, 90)
4000 | [97, 98)
5000 | [98, ∞)
4000 | [87, 90) ∪ [97, 98)
6000 | [98, ∞)
4000 | {87, 88, 89, 97}
6000 | {98, . . .}
Tabelle 2.1: Verschiedene Inhalte von Zeitstempeln am Beispiel einer Gehaltsentwicklung
2.2.1
Inhalt der Zeitstempel
Für den Inhalt der Zeitstempel gibt es viele vorstellbare Varianten; hier sollen vier wichtige
davon erwähnt werden. Der einfachste Zeitstempel besteht aus der Angabe eines einzigen Zeitpunktes, der z. B. den Beginn der Gültigkeit eines bestimmten Datums zu diesem Zeitpunkt
beschreibt. Es bedarf dann eines späteren Zeitpunktes, an dem sich das Datum ändert, um
auszudrücken, daß die Gültigkeitszeit des ersten Datums endet. Das geänderte Datum fungiert
dann praktisch als Nachfolger. Ist ein solcher nicht vorhanden und die Geschichte des Datums
trotzdem unterbrochen, kann man dieses mit Hilfe spezieller Nullwerte darstellen.
Wegen dieser Problematik kann ein Stempeln durch ein temporales Intervall (s. Definition
1) sinnvoll sein. Dabei bedarf es keiner Nachfolger, da die Zeitspanne durch einen Beginn- und
Endzeitpunkt festgelegt wird. Es werden rechts offene, links abgeschlossene Intervalle verwendet
(rola). Diese Darstellung wird in der Literatur häufig benutzt (z. B. [Ste98a, Sno00]), obwohl
im Sprachgebrauch eher abgeschlossene Intervalle üblich sind (z. B. ’die Praxis ist von Montag
bis Freitag geöffnet’). Sie hat jedoch — wie man leicht nachprüft — den Vorteil, daß die Mengenoperationen Vereinigung und Differenz für endliche rola-Intervalle stets wieder rola-Intervalle
liefern. Man erhält so eine einfache und schlüssige theoretische Grundlage, wenngleich man bei
der Verwendung des diskreten Zeitmodells (s. Abschnitt 2.1.2) ebensogut mit abgeschlossenen
Intervallen arbeiten könnte, indem man statt der offenen rechten Intervallgrenze den zugehörigen
Vorgänger einsetzt (z. B. [87, 90] statt [87, 91), falls ein Chronon einem Jahr entspricht).
Für die Darstellung eines Zeitraumes, der auf unbestimmte Zeit gültig ist, findet man in
der Literatur verschiedene Varianten. Wie in Tabelle 2.1 zu sehen ist, geschieht dieses bei der
Verwendung von Zeitpunkten relativ einfach: Besitzt ein Datum keinen Nachfolger, so ist es bis
auf weiteres gültig, d. h. im Beispiel ist das Gehalt in Höhe von 5000 bis zur nächsten Änderung
gültig, die in der Zukunft stattfinden wird. Die Darstellung ist in diesem Fall eindeutig, nur ihre
Interpretation steht dadurch leider noch nicht fest. Wenn der Angestellte z. B. anfragt ’Wie hoch
wird mein Gehalt nächstes Jahr sein?’, kann die Antwort 5000 oder keine Angabe lauten, je
nachdem, ob das System implizit einen Nachfolger (5000 | ∞) oder (5000 | now) zugrundelegt.
Bei temporalen Intervallen wird der o. g. Umstand durch einen künstlichen Endzeitpunkt
erreicht, ähnlich dem impliziten Nachfolger bei Stempeln mit Zeitpunkten. Entsprechend den
beiden Interpretationsmöglichkeiten existieren für diesen künstlichen Zeitpunkt im wesentlichen
zwei Varianten. Hier soll der Vorgehensweise von [Ste98a] gefolgt werden und durch den Endzeitpunkt ∞ (auch: forever ) die Tatsache ausgedrückt werden, daß ein Datum bis auf weiteres
gültig ist. Im obigen Beispiel würde der Angestellte also die Antwort 5000 erhalten. Dabei wird
jedem Nutzer klar sein, daß die Datenbank keine seherischen Fähigkeiten besitzt, sondern nur
Erwartungen der Anwender für die nächste Zukunft wiedergibt.
Häufig wird als künstlicher Endzeitpunkt now (auch: until changed ) benutzt, so z. B. in
8
KAPITEL 2. GRUNDBEGRIFFE
[Sno95, BJ96, TJB97, Sno00]. Dieses now beschreibt stets den aktuellen Zeitpunkt und wächst
mit der Zeit, so daß bei Intervallen, die auf now enden, keine Aussagen über die Zukunft getroffen werden. Im obigen Beispiel würde der Angestellte somit die Antwort keine Angabe erhalten.
Diese Darstellung erfordert jedoch vom temporalen DBMS eine ständige Übersetzung des künstlichen Zeitpunktes now in die aktuelle Systemzeit, während der Zeitpunkt ∞ einfach durch den
festen größtmöglichen Zeitpunkt des Systems dargestellt werden kann. Freilich darf — wie bereits
oben erwähnt — die Verwendung von ∞ nicht zu der fehlerhaften Folgerung verleiten, daß das
zugehörige Datum auch in der Zukunft gültig sein wird (s. [NA93]). Es handelt sich stets nur um
eine Erwartung oder Prognose. Der Einwand in [Sno95, ch. 20], daß z. B. ein Angestelltenverhältnis bis zum 31.12.9999 eine sehr optimistische Annahme sei, ist sicherlich berechtigt. Trotzdem
erscheint diese Darstellung vertretbar, da man den Aufwand der ständigen Übersetzung des
aktuellen Zeitpunktes (s. [TJB97]) vermeidet.
Definition 1 (temporales Intervall)
Es sei s ein Zeitpunkt einer festgelegten Granularität und e ebenfalls ein solcher Zeitpunkt mit
e > s oder ∞. Dann bezeichnet die Menge I = [s, e) = {t | s ≤ t < e} ein temporales Intervall.
Der wesentliche Vorteil temporaler Intervalle gegenüber Zeitpunkten als Zeitstempel ist die
knappe Darstellung von Mengen aufeinanderfolgender Zeitpunkte in direkter Einheit mit dem
eigentlichen Datum. Entwickelt man diesen Gedanken weiter, gelangt man zur Nutzung von
temporalen Elementen (s. Definition 2) als Zeitstempel, die eine endliche Vereinigung nicht
überlappender Intervalle zulassen. Ein Anwendungsbeispiel ist in Tabelle 2.1 dargestellt.
Weiterhin sollen die beteiligten Intervalle einander nicht berühren. Durch diese Einschränkungen ist die Definition hier etwas spezieller als in der Literatur üblich. Sie ist gewissermaßen
die gekürzte Darstellung der allgemeinen Version, bei der beliebige temporale Intervalle vereinigt
werden, auch wenn diese einander berühren oder sich überlappen. Durch die Vereinigungsoperation werden aber in beiden Fällen stets dieselben Zeitabschnitte beschrieben. Die gekürzte
Darstellung garantiert jedoch die Eindeutigkeit der Darstellung, d. h. ein Zeitabschnitt kann
nicht durch zwei verschiedene temporale Intervalle gemäß Definition 2 beschrieben werden.
Definition 2 (temporales Element)
Es seien I1 , . . . , In disjunkte temporale Intervalle ohne gemeinsame Berührpunkte. Dann bezeichnet die Menge T E = I1 ∪ . . . ∪ In ein temporales Element.
Die temporalen Elemente haben gegenüber Intervallen neben der erhöhten Ausdruckskraft
den Vorteil, daß sie — wie man leicht überprüft — gegen endliche Anwendung der mengentheoretischen Operationen Vereinigung und Differenz abgeschlossen sind, sie also nicht aus der
Menge der temporalen Elemente herausführen. Dieser Umstand erscheint für die Berechnung von
Anfrageergebnissen nützlich. Andererseits lassen sich beliebige temporale Elemente offensichtlich
nicht mehr atomar im Sinne der ersten Normalform (1NF) darstellen, was die Handhabung der
Zeitstempel unter Umständen komplizieren kann.
Eine weitere Form des Zeitstempels ist die der temporalen Menge, bei der einfach alle beteiligten Zeitpunkte (genauer: alle Chronons einer festgelegten Granularität) aufgezählt werden.
Da die Mengen nicht auf endlich viele Elemente beschränkt sind, ist die Darstellung von unendlich oft und periodisch wiederkehrenden Ereignissen möglich. Obwohl diese Art der Zeitstempel
für den Fall der endlichen Menge nur eine andere Darstellungsform des temporalen Elementes
2.2. ZEITSTEMPEL
9
ist, lassen sich auch dann durch spezielle Implementationen u. U. günstige Eigenschaften erzielen. So wäre es z. B. vorstellbar, eine endliche temporale Menge mit der Granularität eines
Tages für die Zeitspanne von 100 Jahren mit Hilfe eines nur fünf Kilobyte großen Bit-Feldes zu
implementieren.
In Tabelle 2.1 sind die vier vorgestellten Zeitstempel anhand der Entwicklung des Datums
Gehalt beispielhaft dargestellt.
2.2.2
Anwendung der Zeitstempel
Obwohl im Prinzip ein Stempeln aller möglichen Einheiten von Daten (einzelne Attribute, ganze
Tupel oder Objekte, sogar komplette Datenbanken) denkbar ist, wird der Umfang der gestempelten Daten für das relationale Datenmodell vorwiegend nach Zeitstempeln auf Tupeln (tuple
timestamping) und auf Attributen (attribute timestamping) unterschieden.
Bei Zeitstempeln auf Tupeln werden alle Tupel einer nicht-temporalen Relation durch einen
Stempel mit temporalen Informationen ergänzt. Ein solches erweitertes Tupel bezeichnet dann
z. B. den Umstand, daß alle expliziten Informationen des Tupels in der Anwendungswelt zu der
implizit angegebenen Zeit gültg waren, sind oder sein werden. Nachfolgend soll ht k V T i ein
Tupel mit den expliziten Attributen t und dem Zeitstempel V T bezeichnen.
Das wesentliche Problem bei Zeitstempeln auf Tupeln ist, daß sich die temporale Information
immer auf das gesamte Tupel bezieht. Verändert sich im Laufe der Zeit nur ein einziges Attribut des Tupels, während alle anderen unverändert bleiben, etwa eine Gehaltserhöhung in einer
Angestellten-Relation, so muß ein neues Tupel mit nur einem veränderten Attribut und neuem
Zeitstempel erzeugt werden. Das erzeugt Redundanzen innerhalb einer Relation und bewirkt,
daß die Geschichte eines Objektes der realen Welt über mehrere Tupel einer Relation verteilt
wird. Der letztgenannte Umstand wird in [GV85] als vertikale temporale Anomalie bezeichnet.
In Tabelle 2.2 wird die Problematik anhand eines Beispiels dargestellt. Um die Anschaulichkeit
zu erhöhen, werden dort temporale Intervalle als Zeitstempel verwendet, obwohl der Effekt vom
Inhalt des Zeitstempels unabhängig ist.
Der vertikalen temporalen Anomalie kann man z. B. durch Einführung der temporalen Normalform (time normal form, TNF) [NA93] begegnen, nach der Relationen aufgeteilt werden in
einen zeitunabhängigen Schlüssel und Attribute, die sich nur synchron ändern dürfen. Dieses
Vorgehen fördert jedoch die Ausprägung der horizontalen temporalen Anomalie [GV85]. Dabei
verteilt sich, infolge der Dekomposition entlang synchronem Änderungsverhalten der Attribute,
die Geschichte eines Objektes der realen Welt über mehrere Tupel verschiedener Relationen. Im
Beispiel der Angestellten-Datenbank würde man etwa die Relation mit den Attributen AngNr,
Name, Gehalt, Abteilung, Adresse und Telefon u. U. in fünf Relationen aufteilen müssen, wenn
man AngNr als temporal invariant annimmt, da sich die übrigen fünf Attribute im Zeitverlauf
offensichtlich unabhängig voneinander ändern können.
Bei Zeitstempeln auf Attributen wird zu jedem Attributwert die Zeitinformation ergänzt.
Ändert sich der Wert eines Attributs, so wird lediglich die neue Ausprägung samt neuer Zeitinformation abgelegt. Dieses wird anhand Tabelle 2.3 verdeutlicht. Dort sind dieselben Informationen
wie im vorangehenden Beispiel mit Hilfe von Attribut-Zeitstempeln dargestellt.
Verwendet man Zeitstempel auf Attributen, so entfallen die oben beschriebenen Probleme
der erhöhten Redundanz und der temporalen Anomalien. Andererseits bedarf es offensichtlich,
unabhängig vom Inhalt der verwendeten Zeitstempel, eines Datenmodells, das über die erste
10
KAPITEL 2. GRUNDBEGRIFFE
AngNr
Name
Gehalt
VT
12
12
13
Müller
Müller
Meier
4000
5000
6000
[84, 96)
[96, ∞)
[87, ∞)
Tabelle 2.2: Beispiel einer Relation mit Zeitstempeln auf Tupeln
AngNr
VT
Name
VT
Gehalt
VT
12
[84, 96)
Müller
[84, 96)
13
[87, ∞)
Meier
[87, ∞)
4000
5000
6000
[84, 96)
[96, ∞)
[87, ∞)
Tabelle 2.3: Beispiel einer Relation mit Zeitstempeln auf Attributen
Normalform hinaus geht (non first normal form: NFNF ). Das heißt es werden kollektionswertige
Attribute benötigt, da an die Stelle eines Attributes in nicht-temporalen Relationen endliche
Mengen von Paaren aus Attributausprägung und Zeitstempel treten.
2.2.3
Temporale Beziehungen
Wie in Abschnitt 2.2.1 gesehen, bieten temporale Intervalle die Möglichkeit, auf einfache Art
und Weise Mengen von aufeinander folgenden Zeitpunkten zu beschreiben. Bei einer temporalen
Anfragesprache bedarf es darüber hinaus Mittel, um Intervalle geeignet miteinander in Beziehung
zu setzen, d. h. sie bezüglich ihrer Lage miteinander zu vergleichen.
In [Ste98a] werden dazu dreizehn temporale Beziehungen von Zeitintervallen beschrieben.
Diese Beziehungen sind in Tabelle 2.4 dargestellt. Sie decken alle Möglichkeiten ab, wie sich zwei
temporale Intervalle zueinander verhalten können. Zeitintervalle entsprechen dabei der Definition
1. Zu einem Intervall I = [s, e) bezeichnen begin und end die zugehörigen Start- bzw. Endpunkte,
d. h. es gilt begin(I) = s und end(I) = e. Die Beziehung zwischen zwei Intervallen wird über die
Lage der Start- bzw. Endpunkte definiert.
Eine kleinere Menge temporaler Beziehungen ist in Tabelle 2.5 dargestellt. Dabei werden
nur fünf Beziehungen benötigt, um die gleiche Ausdruckskraft wie in Tabelle 2.4 zu erhalten.
Dieses wird erreicht, indem nicht nur Intervalle miteinander verglichen werden, sondern auch
Zeitpunkte mit Intervallen sowie Zeitpunkte untereinander. Läßt man bei der Vergleichsbildung
noch die Funktionen begin und end auf Intervallen zu, so erhält man das gewünschte Ergebnis.
In Tabelle 2.5 bezeichnen I1 und I2 also nicht nur Intervalle, sondern es können auch Zeitpunkte und insbesondere Endpunkte von Intervallen gemeint sein. Die Funktionen begin und
end erfahren dabei für die einelementige Menge eines Zeitpunktes tp eine einfache Erweiterung,
nämlich begin(tp) = end(tp) = tp.
2.3
Temporale Erweiterung von Datenmodellen
Nachdem bisher bereits wichtige Konzepte temporaler Datenbanken vorgestellt wurden, sollen
nachfolgend Voraussetzungen zur Erweiterung nicht-temporaler Datenmodelle um temporale
2.3. TEMPORALE ERWEITERUNG VON DATENMODELLEN
Temporale Beziehung
I1 bef ore I2
I1 af ter I2
I1 during I2
I1 contains I2
I1 overlaps I2
I1 overlapped by I2
I1 meets I2
I1 met by I2
I1 starts I2
I1 started by I2
I1 f inishes I2
I1 f inished by I2
I1 equals I2
Definition durch Endpunkte der Intervalle
end(I1 ) < begin(I2 )
end(I2 ) < begin(I1 )
(begin(I1 ) > begin(I2 ) ∧ end(I1 ) ≤ end(I2 ))∨
(begin(I1 ) ≥ begin(I2 ) ∧ end(I1 ) < end(I2 ))
(begin(I2 ) > begin(I1 ) ∧ end(I2 ) ≤ end(I1 ))∨
(begin(I2 ) ≥ begin(I1 ) ∧ end(I2 ) < end(I1 ))
begin(I1 ) < begin(I2 ) ∧ end(I1 ) > begin(I2 ) ∧ end(I1 ) < end(I2 )
begin(I2 ) < begin(I1 ) ∧ end(I2 ) > begin(I1 ) ∧ end(I2 ) < end(I1 )
end(I1 ) = begin(I2 )
end(I2 ) = begin(I1 )
begin(I1 ) = begin(I2 ) ∧ end(I1 ) < end(I2 )
begin(I1 ) = begin(I2 ) ∧ end(I2 ) < end(I1 )
begin(I1 ) > begin(I2 ) ∧ end(I1 ) = end(I2 )
begin(I2 ) > begin(I1 ) ∧ end(I1 ) = end(I2 )
end(I1 ) = end(I2 ) ∧ begin(I1 ) = begin(I2 )
Tabelle 2.4: Temporale Beziehungen von Intervallen nach [Ste98a]
Temporale Beziehung
Definition durch Endpunkte der Intervalle
I1 precedes I2
I1 overlaps I2
I1 meets I2
I1 contains I2
I1 equals I2
end(I1 ) < begin(I2 )
∃t ∈ I1 ∧ t ∈ I2
end(I1 ) = begin(I2 )
end(I1 ) ≤ begin(I2 ) ∧ end(I1 ) ≥ end(I2 )
end(I1 ) = end(I2 ) ∧ begin(I1 ) = begin(I2 )
Tabelle 2.5: Temporale Beziehungen von Intervallen und Zeitpunkten nach [Ste98a]
11
12
KAPITEL 2. GRUNDBEGRIFFE
Funktionalität beschrieben werden. Diese Voraussetzungen bilden bereits die Grundlagen der
Anfragesprache ATSQL2 (s. Abschnitt 3.1). Sie wurden u. a. in [BJ96] formuliert, um einen
möglichst nahtlosen Übergang von einem nicht-temporalen zu einem temporal erweiterten Datenmodell zu erreichen.
2.3.1
Aufwärtskompatibilität
Ein Datenmodell nach [BJ96] besteht aus zwei Komponenten, nämlich einer Menge von Datenstrukturen und einer Anfragesprache auf diesen Datenstrukturen. Formal notiert wird ein Datenmodell M mit der Menge DS (data structure) aller in M darstellbaren Datenbankzustände
und der Menge QL (query language) aller gültigen Ausdrücke der Anfragesprache, die auf einen
Datenbankzustand aus DS angewendet werden können. Weiterhin bezeichnet db einen Datenbankzustand und s einen Ausdruck aus der Anfragesprache.
Die folgende Definition fordert die rein syntaktische Aufwärtskompatibilität, d. h. es sollen im
erweiterten Modell alle Datenbankzustände und alle Ausdrücke des zugrundeliegenden Modells
vorhanden sein. Es wird somit gewährleistet, daß eine vorhandene Datenbank (basierend auf
dem alten Datenmodell) zusammen mit ihren Applikationen in eine neue temporale Datenbank
(basierend auf dem erweiterten Datenmodell) transferiert werden kann und die Applikationen
ausführbar bleiben.
Definition 3 (syntaktische Aufwärtskompatibilität) [BJ96]
Es seien M1 = (DS1 , QL1 ) und M2 = (DS2 , QL2 ) zwei Datenmodelle. Das Modell M1 ist
syntaktisch aufwärtskompatibel (syntactically upward compatible) zu dem Modell M2 gdw.
1. ∀db2 ∈ DS2 (db2 ∈ DS1 ) und
2. ∀s2 ∈ QL2 (s2 ∈ QL1 )
Bei dem oben beschriebenen Transfer von einer vorhandenen Datenbank zu einer neuen
temporalen Datenbank ist es nicht nur erwünscht, daß alte Applikationen ausführbar bleiben,
sondern natürlich auch, daß sie dasselbe Ergebnis liefern. Das heißt vorhandene Ausdrücke sollen
in Zukunft syntaktisch korrekt sein und auch semantisch die gleiche Bedeutung haben. Genau
dieses wird in Definition 4 gefordert. Dabei bezeichnet hhs(db)iiM das Ergebnis der Anwendung
eines Ausdrucks s aus QL auf den Datenbankzustand db aus DS und zwar innerhalb des Datenmodells M = (DS, QL).
Definition 4 (Aufwärtskompatibilität) [BJ96]
Es seien M1 = (DS1 , QL1 ) und M2 = (DS2 , QL2 ) zwei Datenmodelle. Das Modell M1 ist
aufwärtskompatibel (upward compatible) zu dem Modell M2 gdw.
1. M1 ist syntaktisch aufwärtskompatibel zu M2 und
2. ∀db2 ∈ DS2 (∀s2 ∈ QL2 (hhs2 (db2 )iiM2 = hhs2 (db2 )iiM1 ))
Bleibt man weiter bei dem oben beschriebenen Transfer, können nun transferierte Ausdrücke
der Anfragesprache weiterhin ausgeführt werden, und man wird damit dieselben Ergebnisse erzielen. Darüber hinaus ist es jedoch wünschenswert, auf dem neuen System die nicht-temporalen
2.3. TEMPORALE ERWEITERUNG VON DATENMODELLEN
13
Applikationen um neue temporale Funktionalität zu erweitern, ohne eine komplette Neuentwicklung vornehmen zu müssen. Es wird also eine Koexistenz von alten, nicht-temporalen Programmteilen und neuen, temporal erweiterten Programmteilen gewünscht.
Bei einer solchen Koexistenz soll natürlich in den temporalen Programmteilen bereits die
neue temporale Funktionalität anwendbar sein, d. h. man will sich dort nicht auf die Verwendung aufwärtskompatibler Ausdrücke beschränken. Man benötigt daher neue, temporal erweiterte Datenbankzustände, die sowohl die Anwendung aufwärtskompatibler Ausdrücke wie auch
die Anwendung temporaler Ausdrücke erlauben. Im Falle der Anwendung aufwärtskompatibler
Ausdrücke auf temporal erweiterte Datenbankzustände soll sich dabei wiederum das gleiche Ergebnis einstellen wie bei der Verwendung der Ausdrücke auf die ursprünglichen nicht-temporalen
Datenbankzustände.
Es soll daher T (db) denjenigen temporalen Datenbankzustand beschreiben, der sich für jeden
Datenbankzustand db eines nicht-temporalen Datenmodells ergibt, wenn man ihn um die zur
Erweiterung des Datenmodells benötigten temporalen Informationen ergänzt. Mit Hilfe dieses
Operators T wird in der nachfolgenden Definition nun formalisiert, daß jeder Ausdruck einer
nicht-temporalen Anfragesprache, angewandt auf den nicht-temporalen Datenbankzustand db,
dasselbe Ergebnis liefert, wie wenn er auf den zugehörigen temporalen Datenbankzustand T (db)
angewendet würde.
Es wird dabei unterschieden zwischen solchen Ausdrücken, die Anfrage-Operationen nach sich
ziehen (QL |query ) und solchen, die Änderungs-Operationen darstellen (QL |update ). Ergebnisse
beliebiger Operationen sind allgemein nicht vergleichbar, da eine aufwärtskompatible ÄnderungsOperation auf einem temporalen Datenbankzustand wieder einen temporalen Datenbankzustand
als Ergebnis liefert und dieselbe Änderungs-Operation auf dem zugehörigen nicht-temporalen
Datenbankzustand wiederum zu einem nicht-temporalen Datenbankzustand führt.
Betrachtet man den Operator T am Beispiel einer nicht-temporalen Relation und soll diese
im Rahmen der temporalen Erweiterung tupelweise mit temporalen Gültigkeitszeitintervallen
gestempelt werden, so könnte man jedes Tupel mit dem Intervall [current, ∞) versehen. Dabei
meint current den festen Zeitpunkt der Anwendung von T . Auf diese Weise wären alle Tupel
der neuen temporalen Relation vom Zeitpunkt der Erweiterung an bis auf weiteres gültig (s. a.
Abschnitt 2.2.1).
Definition 5 (temporale Aufwärtskompatibilität) [BJ96]
Es seien MT = (DST , QLT ) ein temporales Datenmodell und MS = (DSS , QLS ) ein nichttemporales Datenmodell. T sei der beschriebene Erweiterungsoperator und U = u1 , u2 , . . . , un
(n ≥ 0) mit ui ∈ (QLS |update ) eine Folge von Änderungs-Operationen. Das Modell MT ist
temporal aufwärtskompatibel (temporally upward compatible) zu dem Modell MS gdw.
1. MT ist aufwärtskompatibel zu MS und
2. ∀dbS ∈ DSS (∀U (∀qS ∈ (QLS |query )(hhqS (U (dbS ))iiMS = hhqS (U (T (dbS )))iiMT )))
Abbildung 2.1 stellt die Forderung der temporalen Aufwärtskompatibilität schematisch dar.
Jedes Rechteck steht dort für einen Schnappschuß-Datenbankzustand. In der oberen Zeile (nichttemporales Datenmodell MS ) sind dieses nicht-temporale Datenbankzustände. Es ist dort nur
der aktuelle Datenbankzustand vorhanden; die vorherigen Zustände gehen jeweils durch die
Änderungsoperation verloren, was die gestrichelten Rechtecke symbolisieren. In der unteren Zeile bildet die Folge von einzelnen Schnappschüssen einen temporalen Datenbankzustand, und
14
KAPITEL 2. GRUNDBEGRIFFE
aktueller
Schnappschuß
}
Zeit
nicht-temporale DB-Zustände
im Datenmodell MS
dbS
u1
...
un-2
un-1
un
U(dbS)
qS
identische Ergebnisse
T
qS
temporaler DB-Zustand
im Datenmodell MT
(
T(dbS)
u1
...
un-2
un-1
un
U(T(dbS))
) = U(T(dbS))
Abbildung 2.1: Ausführung einer temporal aufwärtskompatiblen Anfrage (nach [BJ96])
die Zwischenzustände bleiben erhalten. Bezüglich des Ergebnisses der Anfrage qS spielt es jedoch keine Rolle, ob diese auf den aktuellen, nicht-temporalen Datenbankzustand oder auf den
temporalen Datenbankzustand zum aktuellen Zeitpunkt angewendet wird.
Im Falle des obigen Beispiels der temporalen Erweiterung einer nicht-temporalen Relation
könnte sich die Reihe von Änderungsoperationen ui mit der abschließenden Anfrage qS wie folgt
darstellen: In der Relation Ang mit den Attributen AngN r, N ame und Gehalt sollen 1996 und
1998 zwei fünfprozentige Gehaltserhöhungen dokumentiert werden. Außerdem wird 1999 wegen
Einsparungsmaßnahmen eine Kündigung aller Mitarbeiter mit mehr als 4400 Monatsgehalt vorgenommen. In Abbildung 2.2 ist dieses Beispiel dargestellt (vgl. a. Abbildung 2.1). In der unteren
Zeile wurde die Relation frühzeitig mittels des Operators T in eine temporale Relation erweitert.
Da im weiteren Zeitverlauf jedoch nur aufwärtskompatible Sprachausdrücke (u1 , u2 , u3 ) verwendet wurden, liefert die aufwärtskompatible Anfrage qS zum Abschluß auf beiden Relationen
identische Ergebnisse1 .
Zu betonen ist bei der temporalen Aufwärtskompatibilität, daß der Operator T das alte Modell MS verläßt, d. h. T (dbS ) ist ein temporaler Datenbankzustand, und hinter den Ausdrücken
qS sowie ui verbergen sich neue Operationen mit inhärenter Interpretation temporaler Informationen, die lediglich dasselbe nicht-temporale Ergebnis wie im Modell MS liefern. Erst wenn eine
geeignete Interpretation von temporalen Datenbankzuständen für einzelne Zeitpunkte gefunden
wurde, können diese neuen Operationen mit Hilfe ihrer nicht-temporalen Gegenstücke definiert
werden.
Dieses geschieht üblicherweise mit dem Zeitscheiben-Operator τ , der einem temporalen Datenbankzustand zu einem bestimmten Zeitpunkt einen nicht-temporalen Datenbankzustand,
eben einen Schnappschuß, zuordnet. Damit ist τ die Umkehrung des Operators T . Die Anfrage qS (U (T (dbS ))) aus der Abbildung 2.1 kann dann durch sein nicht-temporales Gegenstück
1
Bei genauer Betrachtung erweist sich die Abbildung 2.2 als etwas unpräzise, da in der unteren Zeile im
ersten Kasten unmittelbar nach der Anwendung des Operators T zum Zeitpunkt 1995 die Zeitstempel [95, ∞)
verwendet werden müßten. Die abgebildeten Zeitstempel sind also tatsächlich diejenigen, die jeweils durch die
nachfolgende Änderungsanweisung erzeugt werden. Dennoch erscheint die Darstellung geeignet, da der gesamte
Informationsgehalt der temporalen Relation besser sichtbar wird.
2.3. TEMPORALE ERWEITERUNG VON DATENMODELLEN
1996
aktueller
1999 Schnappschuß
1998
nicht-temporale
Relation Ang
1, Meier, 2000
2, Müller, 3000
3, Boss, 4000
}
1995
}
Zeit
15
u1
1, Meier, 2100
2, Müller, 3150
3, Boss, 4200
u2
1, Meier, 2205
2, Müller, 3308
3, Boss, 4410
u3
1, Meier, 2205
2, Müller, 3308
qS
identische Ergebnisse
T
qS
temporale
Relation AngT
(
1, Meier, 2000 [95,96)
2, Müller, 3000 [95,96)
3, Boss, 4000 [95,96)
u1
1, Meier, 2100 [96,98)
2, Müller, 3150 [96,98)
3, Boss, 4200 [96,98)
u2
1, Meier, 2205 [98,99)
2, Müller, 3308 [98,99)
3, Boss, 4410 [98,99)
u3
1, Meier, 2205 [99,∞)
2, Müller, 3308 [99,∞)
)
Abbildung 2.2: Beispiel zur Ausführung einer temporal aufwärtskompatiblen Anfrage
qS (τ (U (T (dbS )))) definiert werden. Im Beispiel in Abbildung 2.2 würde die abschließende Anfrage qS auf der temporalen Relation durch die Hintereinanderausführung von τ und der nichttemporalen Anfrage qS erreicht.
Der beschriebene Zeitscheiben- oder Schnappschuß-Operator τ bildet die Grundlage für die
Definition der Schnappschuß-Reduzierbarkeit temporaler Anfragen im folgenden Abschnitt.
2.3.2
Schnappschuß-Reduzierbarkeit
Im vorangegangenden Abschnitt wurden gewissermaßen zwei Klassen von Ausdrücken einer
temporalen Anfragesprache bestimmt: Erstens die aufwärtskompatiblen Ausdrücke mit nichttemporaler Funktionalität auf nicht-temporalen Datenbankzuständen und zweitens die temporal
aufwärtskompatiblen Ausdrücke mit nicht-temporaler Funktionalität auf temporalen Datenbankzuständen.
Was eine temporale Erweiterung eines Datenmodells jedoch offensichtlich ausmacht, sind
leistungsfähige temporale Operationen auf temporalen Datenbankzuständen. Diese sollen nachfolgend in einer weiteren Klasse festgelegt werden. Dazu wird der bereits erwähnte ZeitscheibenOperator τ benutzt, der für einen Zeitpunkt c und einen temporalen Datenbankzustand dbT den
Schnappschuß τc (dbT ) liefert. Dieser Schnappschuß enthält genau die Informationen aus dbT , die
zum Zeitpunkt c gültig sind.
Die folgende Definition der Schnappschuß-Reduzierbarkeit fordert nun von einem Sprachausdruck eines temporal erweiterten Datenmodells, daß sich ein nicht-temporaler Ausdruck im
ursprünglichen Modell finden läßt, so daß es keine Rolle spielt, ob man zuerst den Schnappschuß
bildet und dann den nicht-temporalen Ausdruck anwendet oder zuerst den temporalen Ausdruck
anwendet und dann den Schnappschuß bildet.
Dadurch wird eine leichte Erlernbarkeit der temporalen Erweiterungen in der Anfragesprache ermöglicht, wenn man davon ausgeht, daß der Benutzer mit der zugrundeliegenden nichttemporalen Sprache, etwa SQL-92, vertraut ist. Denn der dem Anwender bekannte, nicht-temporale Ausdruck sS liefert ja, bezüglich jeweils eines Zeitpunktes, stets dasselbe Ergebnis wie der
16
KAPITEL 2. GRUNDBEGRIFFE
Schnappschuß
c
}
Zeit
temporaler DB-Zustand dbT = (
qT = (
temporaler DB-Zustand qT(dbT) = (
...
τc(dbT)
qS
qS
qS(τc(dbT))
=
τc(qT(dbT))
qS
qS
)
qS
...
)
)
Abbildung 2.3: Ausführung einer sequentiellen Anfrage (nach [BJ96])
temporale Ausdruck sT .
Definition 6 (Schnappschuß-Reduzierbarkeit) [BJ96, Ste98a]
Es seien MT = (DST , QLT ) ein temporales Datenmodell und MS = (DSS , QLS ) ein nichttemporales Datenmodell. Eine temporaler Ausdruck sT ∈ QLT ist schnappschuß-reduzierbar
(snapshot reducible) auf sS ∈ QLS gdw. ∀dbT ∈ DST (∀c(τc (sT (dbT )) = sS (τc (dbT ))))
Ausdrücke der Anfragesprache des erweiterten temporalen Datenmodells, die der Anforderung der Schnappschuß-Reduzierbarkeit genügen, bezeichnet [BJ96] als sequentiell (sequenced).
Durch sie wird die eingangs erwähnte dritte Klasse von Ausdrücken einer temporalen Anfragesprache definiert, nämlich die der sequentiellen Erweiterungen (sequenced extensions).
Dieser Begriff soll anhand von Abbildung 2.3 am Beispiel der Ausführung einer sequentiellen
Anfrage verdeutlicht werden: Jedes Rechteck stellt wiederum einen Schnappschuß-Datenbankzustand dar; jeder temporale Datenbankzustand besteht aus einer Menge solcher Zeitscheiben.
In der Abbildung wird auf den temporalen Datenbankzustand dbT (obere Zeile) die Anfrage
qT angewandt, was qT (dbT ) als Ergebnis liefert (untere Zeile). Die temporale Anfrage qT wird
gewissermaßen durch die sequentielle Anwendung von qS auf jeden Schnappschuß ausgeführt,
daher liegt der Begriff der sequentiellen Anfrage nahe.
Über die Schnappschuß-Reduzierbarkeit hinaus wäre es hilfreich, einen Zusammenhang bezüglich der Syntax zwischen zwei Ausdrücken sS und sT herzustellen, wenn sT schnappschußreduzierbar auf sS ist. Zwei Anfragen, die bezüglich ihrer Semantik für den Anwender ähnlich
sind, da sie betrachtet auf einzelnen Zeitscheiben dasselbe Ergebnis liefern, sollen auch syntaktisch nur eine möglichst geringe Abweichung zeigen, damit der Benutzer die temporalen Anfragen
leichter formulieren kann. Dieser Idee soll die folgende Definition Rechnung tragen.
Definition 7 (syntaktisch ähnliche schnappschuß-reduzierbare Erweiterung) [BJ96]
Es seien MT = (DST , QLT ) ein temporales Datenmodell und MS = (DSS , QLS ) ein nichttemporales Datenmodell. Das Datenmodell MT ist eine syntaktisch ähnliche schnappschuß-reduzierbare Erweiterung (syntactically similar snapshot-reducible extension) von MS gdw.
1. ∀sS ∈ QLS ∃sT ∈ QLT , so daß qT schnappschuß-reduzierbar auf sS ist
2.3. TEMPORALE ERWEITERUNG VON DATENMODELLEN
17
2. es gibt zwei Zeichenketten S1 und S2 derart, daß für jeden Ausdruck sT ∈ QLT , der
schnappschuß-reduzierbar auf sS ist, gilt: sT ist syntaktisch identisch ist zu S1 sS S2
In der vorangehenden Definition können die beiden Zeichenketten S1 und S2 auch leer sein,
jedoch wird man im allgemeinen einen Widerspruch zur temporalen Aufwärtskompatibilität
erhalten, wenn beide Zeichenketten zugleich leer sind: Unter der sinnvollen Annahme, daß der
temporale Ausdruck sT ein temporales Ergebnis liefert, kann nicht die syntaktisch identische
Anfrage sS ein nicht-temporales Ergebnis liefern.
Insgesamt wurden bisher drei wesentliche Konzepte zur temporalen Erweiterung von Datenmodellen vorgestellt, nämlich die Aufwärtskompatibilität, die temporale Aufwärtskompatibilität
und die syntaktisch ähnliche schnappschuß-reduzierbare Erweiterung. Im folgenden Abschnitt
sollen weitere Anforderungen unter dem Begriff der temporalen Vollständigkeit gesammelt werden.
2.3.3
Temporale Vollständigkeit
Der Begriff der temporalen Vollständigkeit (temporal completeness) wird in [BJS95] benutzt, um
Anforderungen an temporale Datenmodelle bezüglich eines zugrundeliegenden nicht-temporalen
Modells zu spezifizieren. Wie dort selbst betont wird, hat die temporale Vollständigkeit nichts
gemein mit der üblichen Bezeichnung der Vollständigkeit, mit der die Ausdrucksfähigkeit einer
Sprache im Vergleich zur Relationenalgebra (siehe z. B. [Vos99, Kap. 10]) gemessen wird.
Zunächst benötigt man den Begriff der temporalen Semi-Vollständigkeit (temporal semicompleteness), den man mit Hilfe der Definitionen aus dem vorangehenden Abschnitt leicht
formulieren kann. Als erstes wird dabei gefordert, daß man im temporalen Datenmodell jeden
Datenbankzustand des zugehörigen nicht-temporalen Datenmodells durch Bildung einer Zeitscheibe zu einem bestimmten Zeitpunkt erzeugen kann. Damit wird sichergestellt, daß bei der
temporalen Erweiterung keine Informationen verloren gehen. Durch die temporale Aufwärtkompatibilität und den Operator T aus dem vorangehenden Abschnitt wäre diese Forderung bereits
garantiert. Zweitens benötigt man die syntaktisch ähnliche Schnappschuß-Reduzierbarkeit der
Modellerweiterung.
Definition 8 (semi-temporale Vollständigkeit) [BJS95]
Es seien MT = (DST , QLT ) ein temporales Datenmodell und MS = (DSS , QLS ) ein nichttemporales Datenmodell. Das Datenmodell MT ist semi-temporal vollständig (temporally semicomplete) bezüglich MS gdw.
1. ∀dbS ∈ DSS ∃dbT ∈ DST ∧ ∃c (dbS = τc (dbT ))
2. MT ist eine syntaktisch ähnliche schnappschuß-reduzierbare Erweiterung von MS .
Bei der sequentiellen Erweiterung bringt es das Prinzip der Schnappschuß-Reduzierbarkeit
mit sich, daß z. B. das Ergebnis der Anfrage zu einem bestimmten Zeitpunkt ausschließlich aus
Informationen des Datenbankzustandes zu eben diesem Zeitpunkt abgeleitet wird (s.a. Abbildung 2.3). Weitergehende Anfragen, die zu jedem Zeitpunkt Informationen aus möglicherweise allen Zeitpunkten des Datenbankzustandes benutzen, sind nach dem beschriebenen Prinzip
der sequentiellen Anfrageverarbeitung nicht realisierbar. Für diese Anforderungen bedarf es der
18
KAPITEL 2. GRUNDBEGRIFFE
Zeit
temporaler DB-Zustand dbT = (
...
qT = (
temporaler DB-Zustand qT(dbT) = (
)
)
...
)
Abbildung 2.4: Ausführung einer nicht-sequentiellen Anfrage (nach [BJ96])
Unterstützung von nicht-sequentiellen Anfragen (non-sequenced queries) durch die temporale Erweiterung des Datenmodells. Übertragen auf allgemeine Ausdrücke der Anfragesprache
benötigt man die Klasse der nicht-sequentiellen Erweiterungen (nonsequenced extensions).
Die Ausführung einer nicht-sequentiellen Anfrage soll anhand von Abbildung 2.4 erläutert
werden. Jedes Rechteck stellt wie gehabt einen Schnappschuß-Datenbankzustand dar, jeder temporale Datenbankzustand besteht aus einer Menge solcher Zeitscheiben. In der Abbildung wird
auf den temporalen Datenbankzustand dbT (obere Zeile) die Anfrage qT angewandt, was qT (dbT )
als Ergebnis liefert (untere Zeile). Zur Erzeugung des Ergebnisses zu einem festen Zeitpunkt
werden Informationen aus möglicherweise allen Zeitpunkten des Zustandes dbT benötigt. Eine
sequentielle Anwendung von qS für jeden Schnappschuß von dbT ist nicht mehr möglich.
Mit der Erläuterung der nicht-sequentiellen Erweiterungen wurden alle benötigten Begriffe
zur Definition der temporalen Vollständigkeit eines Datenmodells beschrieben. Dennoch soll hier
auf eine formale Definition verzichtet werden, da diese in [BJS95] durch den direkten Bezug auf
das relationale Datenmodell und die Anfragesprache SQL hier zu speziell erscheint.
Stattdessen wird dieses Kapitel mit der Aufzählung von drei nützlichen Anforderungen der
temporalen Vollständigkeit an eine temporale Erweiterung MT eines Datenmodells MS geschlossen.
1. MT ist semi-temporal vollständig bezüglich MS .
2. MT bietet nicht-sequentielle Erweiterungen der Anfragesprache, die über die Schnappschuß-Reduzierbarkeit hinaus gehen. Dabei gibt es wie in Definition 8 zwei Zeichenketten,
die jeden nicht-temporalen Ausdruck der Sprache zu einem nicht-sequentiellen ergänzen.
3. In MT werden zum Vergleich temporaler Informationen die Beziehungen aus Tabelle 2.4
zur Verfügung gestellt.
Kapitel 3
Temporale Erweiterungen von SQL
Es existiert inzwischen eine Vielzahl von Vorschlägen für temporale Anfragesprachen, davon
sind wegen der weiten Verbreitung von SQL natürlich ein Großteil temporale Erweiterungen von
SQL.
Wie bereits in der Einleitung erwähnt, soll hier die Sprache ATSQL2 [BJS95] vorgestellt werden, da für sie sowohl umfangreiche theoretische Grundlagen als auch praktische Erfahrungen
durch die Entwicklung von Prototypen bestehen (s. a. Abschnitte 1.1 und 3.1). Im Rahmen dieser
Vorstellung werden sich für die Entwicklung einer eigenen temporalen Erweiterung zusammen
mit den Grundlagen aus dem vorangegangenen Kapitel einige Punkte ergeben, die veränderungswürdig erscheinen. Es soll daher abschließend eine neue temporale Erweiterung von SQL
mit dem Namen SQLTE (structured query language temporal extended) vorgestellt werden, die
sich zwar in weiten Teilen an ATSQL2 orientiert, jedoch auch einige wesentliche Unterschiede
aufweist.
3.1
Der Sprachvorschlag ATSQL2
Bei ATSQL2 (applied temporal structured query language) [BJS95, SBJ96a, SBJ96b] handelt es
sich um eine bitemporale Datenbanksprache, die neben der temporalen Anfragesprache auch
Datenmodifikation (DML), Datendefinition (DDL) sowie die Formulierung von temporalen Integritätsbedingungen unterstützt.
Nach [Ste98a, ch. 4] ist ATSQL2 das Ergebnis der Integration von drei unterschiedlichen
Ansätzen zu temporalen Anfragesprachen, nämlich TSQL2 [SAA94a, SAA94b, ZCF97], ChronoLog [Böh94] und Bitemporal ChronoSQL (entwickelt von A. Steiner, siehe z. B. [Pul95]).
Die Bezeichnung applied im Namen von ATSQL2 weist schon auf die wesentliche Veränderung gegenüber TSQL2 hin: Es handelt sich um eine anwendungsbezogene Sprache, die das
Datenmodell der mengenwertigen Relationen aufgibt, um stattdessen der Praxis von Duplikaten zu entsprechen. Weiterhin wird neben der reinen Aufwärtskompatibilität gegenüber SQL-92
auch die temporale Aufwärtskompatibilität (s. Abschnitt 2.3.1) gefordert, um einen einfachen
Wechsel von der konventionellen zur temporalen Datenbank und eine Koexistenz von vorhandenen (nicht-temporalen) und neuen (temporalen) Programmteilen zu ermöglichen. Schließlich soll
ATSQL2 den unter dem Begriff der temporalen Vollständigkeit (s. Abschnitt 2.3.3) gesammelten
Ansprüchen gerecht werden.
19
20
KAPITEL 3. TEMPORALE ERWEITERUNGEN VON SQL
Diesen Forderungen folgend ergibt sich in [BJS95, SBJ96a, SBJ96b] der Sprachvorschlag
ATSQL2 in den folgenden vier Stufen der Erweiterung gegenüber SQL-92:
• Ebene 1: upward compatibility — Es werden nicht-temporale Sprachkonstrukte auf nichttemporale Tabellen angewendet. Die Funktionalität ist mit der von SQL-92 identisch.
• Ebene 2: temporal upward compatibility — Es werden nicht-temporale Sprachkonstrukte
auf temporale Tabellen angewendet. Die Funktionalität ist mit der von SQL-92 identisch,
jedoch unter Benutzung von temporalen Tabellen, die nur für den aktuellen Zeitpunkt
ausgewertet werden.
• Ebene 3: sequenced extensions — Es werden temporale Sprachkonstrukte auf temporale
Tabellen angewendet. Die Auswertung erfolgt sukzessiv für alle vorhandenen Zeitpunkte,
d. h. Informationen werden für einen bestimmten Zeitpunkt der Ergebnistabelle jeweils ausschließlich aus genau diesem Zeitpunkt der Quelltabelle(n) abgeleitet. Die Interpretation
der Zeitstempel erfolgt implizit durch das DBMS.
• Ebene 4: nonsequenced extensions — Es werden ebenfalls temporale Sprachkonstrukte auf
temporale Tabellen angewendet. Die Auswertung erfolgt über alle möglichen Zeitpunkte
hinweg, d. h. Informationen werden für einen bestimmten Zeitpunkt der Ergebnistabelle
jeweils u. U. aus allen vorhandenen Zeitpunkten der Quelltabelle(n) abgeleitet. Dem Benutzer stehen die Zeitstempel explizit zur Verfügung; das DBMS übernimmt keine implizite
Interpretation der Zeitinformationen.
Diese vier Ebenen folgen unmittelbar den Modellerweiterungen aus dem Abschnitt 2.3. Für
jede dieser vier Ebenen findet man in [SBJ96a, ch. 6] und [BJ96, ch. 3] einige Beispiele in Form
einer Quick Tour.
Nachfolgend soll nun, ebenfalls entlang der oben beschriebenen vier Ebenen, ein Überblick
über die Syntax und die Semantik von ATSQL2 gegeben werden. Wie bereits in Abschnitt 2.1.1
beschrieben, beschränkt sich diese Darstellung auf die Erweiterungen bzgl. der Gültigkeitszeit.
3.1.1
Syntax
Die vorliegende Einführung in die Syntax von ATSQL2 orientiert sich an [Ste98a, ch. 4.3], basierend auf dem in [SBJ96a] vorgestellten Standard für SQL/Temporal. Es existieren dabei
geringfügige Abweichungen zur Syntax der Spracherweiterung aus [BJ96]. Die Beschreibung erfolgt anhand von Beispielen mit entsprechenden Erläuterungen. Für eine formale Definition der
gültigen Syntax sei auf [Ste98b] verwiesen.
Für die Ebene der aufwärtskompatiblen Sprachausdrücke bedarf es keiner umfangreichen Beschreibung: Der Definition 4 folgend, werden alle SQL-Befehle weiterhin unterstützt, und
diese werden dem Leser vertraut sein. Die nachfolgenden nicht-temporalen Anweisungen dienen
somit nur zur Erzeugung und Initialisierung der in diesem Abschnitt verwendeten Beispieltabelle.
create table Ang (AngNr number, Name varchar(30), Gehalt number, ChefNr number);
insert into Ang values (12, ’Müller’, 5000, 27);
insert into Ang values (13, ’Meier’, 6000, 27);
insert into Ang values (27, ’Boss’, 5900, 27);
3.1. DER SPRACHVORSCHLAG ATSQL2
21
select a1.AngNr, a1.Name, a1.Gehalt, a2.Name as Chef
from Ang a1, Ang a2 where a1.ChefNr = a2.AngNr and a1.Gehalt > 5900;
=⇒
a1.AngNr
a1.Name
a1.Gehalt
Chef
13
Meier
6000
Boss
Bei der so angelegten Tabelle mit Informationen über die Angestellen eines Unternehmens
sind nur aufwärtskompatible Sprachausdrücke zugelassen wie z. B. die obige Anfrage. Jede Verwendung der Tabelle in Kombination mit temporalen ATSQL2-Befehlen ist unzulässig.
Um temporale Funktionalität zu demonstrieren, bedarf es daher einer temporalen Erweiterung der Tabelle mittels alter table. Man erhält so eine temporale Tabelle, die für jedes Tupel
die zugehörige Gültigkeitszeit aufnimmt. Diese Zeitabschnitte enden voreingestellt an einem ungewissen Zeitpunkt in der Zukunft und beginnen zum Zeitpunkt der temporalen Aufwertung der
Tabelle. Im nachfolgenden Beispiel sei dies der 5. Oktober 1999.
alter table Ang add validtime;
validtime select * from Ang;
=⇒
AngNr
Name
Gehalt
ChefNr
VT
12
13
27
Müller
Meier
Boss
5000
6000
5900
27
27
27
[05.10.1999, ∞)
[05.10.1999, ∞)
[05.10.1999, ∞)
Um die neu hinzugefügten Gültigkeitszeiten darstellen zu können, wird im obigen Beispiel als
kleiner Vorgriff bereits eine sequentielle temporale Anfrage verwendet. Durch das Voranstellen
des Schlüsselwortes validtime wird die Interpretation als sequentielle Anfrage erreicht.
Im Rahmen der temporalen Aufwärtskompatibilität erlaubt die nun temporale Tabelle
Ang weiterhin die Anwendung von nicht-temporalen Befehlen. Diese werden dann stets unter
Berücksichtigung des Zeitpunktes der Anwendung ausgeführt. Im nachfolgenden Beispiel sei
dieses der 12. Oktober 1999. Die erste Anweisung erhöht das Gehalt des Angestellten Boss
zum aktuellen Zeitpunkt. Die zweite Anweisung wurde in ihrer nicht-temporalen Version bereits
zu Beginn dieses Abschnitts verwendet. Sie liefert hier wegen der inzwischen durchgeführten
Gehaltserhöhung ein anderes Ergebnis.
update Ang set Gehalt = 6400 where Name = ’Boss’;
validtime select * from Ang;
=⇒
AngNr
Name
Gehalt
ChefNr
VT
12
13
27
27
Müller
Meier
Boss
Boss
5000
6000
5900
6400
27
27
27
27
[05.10.1999, ∞)
[05.10.1999, ∞)
[05.10.1999, 12.10.1999)
[12.10.1999, ∞)
select a1.AngNr, a1.Name, a1.Gehalt, a2.Name as Chef
from Ang a1, Ang a2 where a1.ChefNr = a2.AngNr and a1.Gehalt > 5900;
22
=⇒
KAPITEL 3. TEMPORALE ERWEITERUNGEN VON SQL
a1.AngNr
a1.Name
a1.Gehalt
Chef
13
27
Meier
Boss
6000
6400
Boss
Boss
Man kann sich an dieser Stelle von der Einhaltung der temporalen Aufwärtskompatibilität
leicht überzeugen: Die nicht-temporale Tabelle zu Beginn des Abschnitts würde nach der Anwendung der obigen Befehle update und select dasselbe Ergebnis liefern wie die temporal erweiterte
Tabelle im Beispiel — lediglich die Gehaltshistorie des Angestellen Boss würde verlorengehen.
In den vorangehenden Beispielen wurde die Tabelle Ang zunächst durch nicht-temporale Befehle erzeugt und initialisiert, um dann mit Hilfe von alter table temporal erweitert zu werden.
Weiterhin wurden mit der dann temporalen Tabelle einige temporal aufwärtskompatible Befehle ausgeführt. Betrachtet man die Ebene der sequentiellen Erweiterungen, so ist es ebenso
möglich, eine temporale Tabelle direkt zu erzeugen und zu initialisieren. Im folgenden Beispiel
erhält man die gleiche Tabelle wie nach der letzten temporal aufwärtskompatiblen Anweisung.
Wie bereits gesehen, werden Befehle der sequentiellen Erweiterungsebene durch das Schlüsselwort validtime gekennzeichnet.
create table Ang (AngNr number, Name varchar(30), Gehalt number, ChefNr number)
as validtime;
validtime period [date ’05.10.1999’- forever)
insert into Ang values (12, ’Müller’, 5000, 27);
validtime period [date ’05.10.1999’- forever)
insert into Ang values (13, ’Meier’, 6000, 27);
validtime period [date ’05.10.1999’- date ’12.10.1999’)
insert into Ang values (27, ’Boss’, 5900, 27);
validtime period [date ’12.10.1999’- forever)
insert into Ang values (27, ’Boss’, 6400, 27);
validtime select a1.AngNr, a1.Name, a1.Gehalt, a2.Name as Chef
from Ang a1, Ang a2 where a1.ChefNr = a2.AngNr and a1.Gehalt > 5900;
=⇒
AngNr
Name
Gehalt
Chef
VT
13
13
27
Meier
Meier
Boss
6000
6000
6400
Boss
Boss
Boss
[05.10.1999, 12.10.1999)
[12.10.1999, ∞)
[12.10.1999, ∞)
Jede gültige nicht-temporale SQL-Anfrage kann durch Voranstellen von validtime als sequentielle Anfrage auf temporalen Tabellen ausgeführt werden, so daß diese der SchnappschußReduzierbarkeit genügt. Ebenso können die Befehle der DML (data manipulation language)
insert, update und delete temporal erweitert werden. In allen Fällen ist eine Einschränkung
auf bestimmte Intervalle durch Angabe von period möglich, wie im obigen Beispiel zu sehen ist.
Weiterhin wird dort an die temporale Tabelle Ang die bereits bekannte Anfrage nach Angestellten mit einem Gehalt über 5900 in Kombination mit ihren Vorgesetzten gestellt, hier jedoch
durch das Schlüsselwort validtime in der Version der sequentiellen Anfrage. Es erscheinen dabei für den Angestellten Meier zwei Tupel mit einander berührenden Gültigkeitszeitintervallen.
Dieses ist eine Eigenart von ATSQL2, deren Ursprung in der temporalen Variante des kartesischen Produktes liegt. In Abschnitt 3.1.2 wird auf diesen inhaltlichen Aspekt der Sprache näher
eingegangen.
3.1. DER SPRACHVORSCHLAG ATSQL2
23
In der Ebene der nicht-sequentiellen Spracherweiterungen stehen dem Anwender die
Gültigkeitszeitintervalle der Tupel explizit zur Verfügung, und es werden keine sprachinhärenten Interpretationen dieser Intervalle vorgenommen. So ist es im Gegensatz zu den sequentiellen
Anfragen möglich, Ergebnistupel durch Auswertung von Tupeln mit nicht überlappenden Zeitintervallen zu bilden. Zu diesem Zweck können die temporalen Beziehungen aus Tabelle 2.5 benutzt
werden sowie die dort verwendeten Funktionen begin und end. Die Funktion validtime liefert
zu einem temporalen Tupel das zugehörige Gültigkeitszeitintervall.
Die nicht-sequentiellen Befehle werden durch das Voranstellen der beiden Schlüsselwörter
nonsequenced validtime gekennzeichnet. Im folgenden Beispiel soll nach allen Angestellten
gefragt werden, die in der Vergangenheit eine Gehaltserhöhung erhalten haben.
nonsequenced validtime
select begin(validtime(a2)) as Datum, a2.Name as Name from Ang a1, Ang a2
where a1.AngNr = a2.AngNr and validtime(a1) meets validtime(a2)
and a1.Gehalt < a2.Gehalt;
=⇒
Datum
Name
12.10.1999
Boss
Bisher wurden Beispiele mit Anfragen vorgestellt, die nur Sprachelemente einer temporalen
Erweiterungsebene enthalten. Es ist jedoch auch eine Kombination der verschiedenen Spracherweiterungen innerhalb einer Anfrage möglich, nämlich bei der Verwendung von abgeleiteten
Tabellen (derived tables oder inline-views) und Subqueries. Bei abgeleiteten Tabellen muß jedoch beachtet werden, welchen Typ die jeweils verwendete Anfrage zurückliefert: Nur sequentielle
Anfragen haben ein temporales Ergebnis und können so anstatt einer temporalen Tabelle verwendet werden. Aufwärtskompatible, temporal aufwärtskompatible und nicht-sequentielle Anfragen
können dagegen nur anstelle gewöhnlicher, nicht-temporaler Tabellen eingesetzt werden.
Das folgende Beispiel macht deutlich, wann eine solche Kombination notwendig sein kann.
Es soll dort die Frage beantwortet werden, welche Mitarbeiter seit der Firmengründung am
05.10.1999 ununterbrochen Mitglieder des Unternehmens waren und es noch immer sind. Daß
eine sequentielle Anfrage dafür nicht ausreicht, ist offensichtlich: Durch die sequentielle Auswertung einzelner Schnappschüsse kann nicht entschieden werden, ob das jeweilige Beschäftigungsverhältnis die ganze Zeit über bestand.
Die folgende — zunächst naheliegende — nicht-sequentielle Anfrage liefert leider nicht das
entsprechende Ergebnis, da der Mitarbeiter Boss nicht erscheint. Das liegt an der Festlegung
der Anfragesprache, die vom Benutzer angegebenen Intervalle in ihrer ursprünglichen Form zu
belassen und keine automatische zeitliche Vereinigung wertgleicher Einträge (coalescing) vorzunehmen. Im nachfolgenden Abschnitt über die Semantik von ATSQL2 wird diese Operation
näher erläutert.
nonsequenced validtime
select * from Ang a where validtime(a) contains period [date ’05.10.1999’-now);
=⇒
AngNr
Name
Gehalt
ChefNr
12
13
Müller
Meier
5000
6000
27
27
Das gewünschte Ergebnis aller firmentreuen Mitarbeiter erhält man mit dem folgenden abschließenden Beispiel. In der verwendeten abgeleiteten Tabelle werden zunächst alle Mitarbeiter
24
KAPITEL 3. TEMPORALE ERWEITERUNGEN VON SQL
über die als zeitinvariant angenommene Angestelltennummer bestimmt. Durch die Angabe von
(period) wird die zeitliche Verschmelzung wertgleicher Tupel erreicht, so daß die abgeleitete
Tabelle alle Angestelltennummern mit maximalen temporalen Intervallen liefert. Durch den temporalen Join mit der Angestelltentabelle werden die bei der Verschmelzung wegprojizierten Attribute wieder hinzugefügt. Dabei wird mit Hilfe der Bedingung validtime(a1) contains now
die aktuelle Version der Angestellteninformation ausgewählt. Die Formulierung validtime(a2)
contains period [date ’05.10.1999’-now) stellt schließlich die eigentliche Anforderung sicher, daß die Mitarbeiter ununterbrochen für die Firma tätig waren.
nonsequenced validtime
select a1.AngNr, a1.Name, a1.Gehalt, a1.ChefNr
from Ang a1, (validtime (select AngNr from Ang) (period)) a2
where validtime(a1) contains now and a1.AngNr = a2.AngNr and
validtime(a2) contains period [date ’05.10.1999’-now);
AngNr
Name
Gehalt
ChefNr
12
13
27
Müller
Meier
Boss
5000
6000
6400
27
27
27
=⇒
3.1.2
Semantik
Nachdem im vorangehenden Abschnitt versucht wurde, einen Eindruck von der Verwendung der
neuen Sprachkonstrukte zu vermitteln, soll nun mit der Erläuterung der inhaltlichen Basis dieser
Konstrukte fortgefahren werden. Echte temporale Funktionalität existiert nur in der Ebene der
sequentiellen Erweiterungen. Aufwärtskompatible Sprachelemente basieren naturgemäß auf dem
relationalen Datenmodell. Gleiches gilt für temporal aufwärtskompatible Anweisungen, nach der
Schnappschußbildung auf einer temporalen Tabelle. Die Ebene der nicht-sequentiellen Erweiterungen basiert ebenso auf dem relationalen Datenmodell, wobei die temporalen Intervalle in
explizite nicht-temporale Attribute umgewandelt werden.
Allein die sequentiellen Anfragen besitzen ein neues Datenmodell — das temporale relationale Datenmodell von ATSQL2. Die vorliegende Beschreibung der temporalen Modellerweiterung orientiert sich weitgehend an [BJ96], verwendet jedoch einige Änderungen gemäß
[BJS95, SBJ96a]. Während in den genannten Arbeiten bitemporale Versionen der Modellerweiterung vorgestellt werden, wird hier, wie gehabt, nur auf die Unterstützung der Gültigkeitszeit
eingegangen.
Ausgehend von den Datenstrukturen des relationalen Modells (eine vollständige formale Definition findet man z. B. in [Vos99, ch. 5]) wird eine nicht-temporale Relation durch Hinzufügen
eines Zeitstempels für jedes Tupel zu einer temporalen Relation erweitert. Gemäß Definition 1
werden halboffene Intervalle verwendet, repräsentiert durch die Beginn- und Endzeitpunkte.
Abbildung 3.1 zeigt eine Übersicht der bekannten Relationenalgebra mit den Operationen
Selektion, Projektion, Vereinigung, Kreuzprodukt und Differenz — eine vollständige formale
Definition gibt z. B. [Vos99, ch. 10]. Dabei beschreibt c eine Selektionsbedingung und f eine Einschränkung des Tupels auf bestimmte Attribute. Entsprechend den zugehörigen nicht-temporalen
Operationen werden in Definition 9 die Operationen auf temporalen Relationen definiert. Die
3.1. DER SPRACHVORSCHLAG ATSQL2
25
σc (r) = {t | t ∈ r ∧ c(t)}
πf (r) = {t1 | ∃t2 (t2 ∈ r ∧ t1 = f (t2 ))}
r1 ∪ r2 = {t | t ∈ r1 ∨ t ∈ r2 }
r1 × r2 = {t1 ◦ t2 | t1 ∈ r1 ∧ t2 ∈ r2 }
r1 \ r2 = {t | t ∈ r1 ∧ t ∈
/ r2 }
Abbildung 3.1: Nicht-temporale Relationenalgebra
temporalen Operationen werden durch das Anhängen von
vt
gekennzeichnet.
Definition 9 (temporale Relationenalgebra) [BJS95, BJ96, SBJ96a]
σcvt (r) = {ht k V T i | ht k V T i ∈ r ∧ c(t)}
πfvt (r) = {ht1 k V T i | ∃t2 (ht2 k V T i ∈ r ∧ t1 = f (t2 ))}
r1 ∪vt r2 = {ht k V T i | ht k V T i ∈ r1 ∨ ht k V T i ∈ r2 }
r1 ×vt r2 = {ht1 ◦ t2 k V T i | ht1 k V T1 i ∈ r1 ∧ ht2 k V T2 i ∈ r2 ∧
V T = intersect(V T1 , V T2 ) ∧ V T1 overlaps V T2 }
vt
r1 \ r2 = {ht k V T i | ∃V T1 (ht k V T1 i ∈ r1 ∧
(∃V T2 (ht k V T2 i ∈ r2 ∧ V T1− ≤ V T2+ ∧ V T − = V T2+ ) ∨ V T − = V T1− ) ∧
(∃V T3 (ht k V T3 i ∈ r2 ∧ V T1+ ≥ V T3− ∧ V T + = V T3− ) ∧ V T + = V T1+ ) ∧
V T − < V T + ∧ 6 ∃V T4 (ht k V T4 i ∈ r2 ∧ V T4 overlaps V T ))}
In der Definition 9 wird für zeitgestempelte Tupel die bereits in Abschnitt 2.2.2 verwendete Notation benutzt: ht k V T i bezeichnet ein Tupel mit den expliziten Attributen t und
dem Zeitstempel V T . Der Zeitstempel V T steht für ein Intervall der Gültigkeitszeit, bestehend
aus Anfangs- und Endpunkt, welche durch V T − und V T + dargestellt werden. Die Beziehung
overlaps genügt der Definition aus Tabelle 2.5, die Funktion intersect liefert genau das Schnittintervall von overlaps. Das Symbol ◦ bezeichnet die Konkatenation zweier Tupel, präzise die
Konkatenation der expliziten Attribute zweier Tupel.
Die ersten drei Operationen sind einfache Erweiterungen ihrer nicht-temporalen Gegenstücke.
Zu beachten ist, daß für die Selektionsbedingung c wie für die Projektionsliste f kein Zugriff
auf den Zeitstempel vorgesehen ist. Es können also z. B. keine temporalen Auswahlbedingungen
formuliert werden. Diese Restriktion ist notwendig, da sonst die resultierenden Anfragen nicht
mehr der Schnappschuß-Reduzierbarkeit genügen würden. Dieses liegt einfach daran, daß nach
der Schnappschußbildung keine temporalen Informationen mehr zur Verfügung stehen, und es
somit nicht möglich ist, ein nicht-temporales Äquivalent zu einer Anfrage mit temporaler Auswahlbedingung zu finden (vgl. Definition 6).
An dieser Stelle wird auch das Ergebnis der sequentiellen Beispielanfrage aus dem vorangehenden Abschnitt klar: Das Kreuzprodukt paart alle Tupel, die eine überlappende Gültigkeitszeit
haben und bildet aus den expliziten Attributen ein neues Tupel mit der Überlappung als Zeitstempel. Ist ein Objekt in der realen Welt über zwei oder mehrere Tupel verteilt (im Fall des
Beispiels der Mitarbeiter Boss wegen einer Gehaltserhöhung), so nehmen alle Versionen des Tupels einzelnd an der Paarbildung teil, sofern ein zeitlicher Überlapp vorhanden ist. Das Ergebnis
26
KAPITEL 3. TEMPORALE ERWEITERUNGEN VON SQL
r1
r2
r1\r2
VT1
VT2
VT3
VT
Abbildung 3.2: Differenz-Operator der temporalen Relationenalgebra (nach [BJ96])
ist (wie im Beispiel) eine Vielzahl von Ergebnistupeln, wodurch das Ablesen der gewünschten
Information nicht unbedingt erleichtert wird. Häufig werden die mehrfach auftretenden Ergebnistupel bezüglich ihrer expliziten Attribute wertgleich sein, so daß eine abschließende zeitliche
Verschmelzung (coalesce) Abhilfe schaffen würde. Eine implizite Verschmelzung nach jeder Produktbildung würde jedoch u. U. der Schnappschuß-Reduzierbarkeit widersprechen, da sich —
betrachtet für einzelne Schnappschüsse — die Anzahl der Ergebnistupel durch Zusammenfügen
von Duplikaten ändern könnte. Wie im letzten Beispiel des Abschnitts 3.1.1 zu sehen ist, wurde
daher für ATSQL2 die Möglichkeit vorgesehen, eine zeitliche Verschmelzung explizit vorzunehmen. Der zugehörige Verschmelzungsoperator der temporalen Relationenalgebra ist am Ende
dieses Abschnitts beschrieben.
Die Differenzbildung besitzt eine etwas umfangreichere Definition, sie wird daher in Abbildung 3.2 veranschaulicht. Es werden stets nur Tupel mit den expliziten Attributen t betrachtet,
d. h. ausschließlich wertgleiche Tupel werden an der Differenzbildung beteiligt. In der ersten Zeile
der Definition wird das Tupel ht k V T1 i der Relation r1 gewissermaßen als Ausgangsintervall
benutzt, zu dem in den folgenden vier Zeilen wertgleiche Tupel der Relation r2 mit maximal
überlappenden Zeitstempeln gesucht werden. Dabei werden in der zweiten Zeile der Anfangspunkt und in der dritten Zeile der Endpunkt solcher Zeitstempel festgelegt. Die vierte Zeile stellt
sicher, daß bei der punktweisen Betrachtung nur sinnvolle Intervalle berücksichtigt werden. In
der letzten Zeile wird auf Maximalität der Zeitstempel geprüft, d. h. Zeitstempel von Tupeln aus
r2 dürfen keinen Zeitstempel eines wertgleichen Ergebnistupels überlappen.
Alle in Definition 9 dargestellten Operationen genügen der Schnappschuß-Reduzierbarkeit.
Ein Beweis dafür findet sich in [BJ96]. Dort wird jedoch, wie auch in [SBJ96a], eine andere
Variante des Kreuzproduktes verwendet, die ursprünglich nicht schnappschuß-reduzierbar ist.
Es werden dabei jeweils die Zeitstempel der zwei konkatenierten Tupel als explizite Attribute
in das neue Tupel übergeben. Das heißt r1 ×vt r2 = {hht1 , V T1 i ◦ ht2 , V T2 i k V T i | (. . .)}, wobei (. . .) der Definition 9 entspricht und ht1 , V T1 i ein nicht-temporales Tupel darstellt, in dem
der alte Zeitstempel V T1 nun explizit vorhanden ist. Dadurch ist es möglich, auch nach der Bildung eines temporalen Kreuzproduktes oder temporalen Verbundes, noch auf die ursprünglichen
Zeitstempel aus den beteiligten Relationen zuzugreifen. Genau diese Existenz der Zeitstempel
widerspricht jedoch der Schnappschuß-Reduzierbarkeit auf das nicht-temporale Kreuzprodukt,
da das Produkt zweier Schnappschüsse keine Zeitstempel mehr enthält (vgl. Definition 6). Damit
wird das Prinzip der sequentiellen Anfrageauswertung, bei der kein expliziter Zugriff auf temporale Informationen durch den Benutzer erwünscht ist, verletzt. Wenn der Bedarf nach einem
solchen expliziten Zugriff besteht, ist dieses durch die Formulierung einer nicht-sequentiellen Anfrage vorgesehen. Daher soll hier der ursprünglichen Definition des Kreuzproduktes aus [BJS95]
gefolgt werden, die auch in [Ste98a] verwendet wird.
Eine weitere Operation der temporalen Relationenalgebra findet man in Definition 10. Die
3.2. DIE NEUE TEMPORALE ERWEITERUNG SQLTE
r
coal(r)
27
VT1
VT2
VT
Abbildung 3.3: Verschmelzungs-Operator der temporalen Relationenalgebra (nach [BJ96])
Verschmelzungsoperation (coalescing operation) besitzt kein nicht-temporales relationales Gegenstück. Sie zerstört die ursprünglichen Zeitstempel und faßt Tupel zusammen, um eine Darstellung mit maximalen Intervallen für jeweils wertgleiche Tupel zu erreichen. Ein Anwendungsbeispiel ist zum Abschluß des Abschnitts 3.1.1 beschrieben. Bei der Erläuterung des temporalen
Kreuzproduktes (s. o.) wurde auf die Gründe hingewiesen, weshalb die zeitliche Verschmelzung
bei ATSQL2 nicht als implizite Operation vorgesehen ist.
Definition 10 (Verschmelzungsoperator der temporalen Relationenalgebra) [BJ96]
coalvt (r) = {ht k V T i | ∃V T1 , V T2 (ht k V T1 i ∈ r ∧ ht k V T2 i ∈ r ∧
V T1− < V T2+ ∧ V T − = V T1− ∧ V T + = V T2+ ∧
∀V T3 (ht k V T3 i ∈ r ∧ V T − < V T3− < V T + ⇒
∃V T4 (ht k V T4 i ∈ r ∧ V T4− < V T3− ≤ V T4+ )) ∧
6 ∃V T5 (ht k V T5 i ∈ r ∧ (V T5− < V T − ≤ V T5+ ∨ V T5− ≤ V T + < V T5+ )))}
Nachfolgend soll nun abschließend mit Hilfe der Abbildung 3.3 die Funktionsweise der Verschmelzungsoperation erläutert werden. Wie bei der temporalen Differenz werden stets nur Tupel
mit den expliziten Attributen t betrachtet, d. h. nur jeweils wertgleiche Tupel werden an der Verschmelzung beteiligt. Die beiden Zeitstempel V T1 und V T2 aus der ersten Zeile der Definition
bestimmen den Anfangs- und Endpunkt des verschmolzenen Ergebnistupels. Die beiden Punkte werden in der zweiten Zeile spezifiziert. Die dritte und vierte Zeile sorgen dafür, daß keine
Lücken zwischen Anfangs- und Endpunkt existieren: Dieses geschieht, indem für jedes Intervall
V T3 mit Anfangspunkt innerhalb des neuen verschmolzenen Intervalls V T ein weiteres Intervall
V T4 gefordert wird, welches diesen Anfangspunkt überdeckt. Schließlich wird in der letzten Zeile
sichergestellt, daß die erzeugten Ergebnistupel maximale Zeitstempel besitzen. Es dürfen somit
keine Intervalle existieren, die Anfangs- oder Endpunkt des verschmolzenen Intervalls überlappen.
3.2
Die neue temporale Erweiterung SQLTE
Nach der Vorstellung der Anfragesprache ATSQL2 im vorangehenden Abschnitt soll an dieser
Stelle die Formulierung einer temporalen Erweiterung von SQL mit etwas anderen Schwerpunkten vorgenommen werden.
Sehr gelungen erscheint bei ATSQL2 die Aufteilung der Sprache in die vier beschriebenen
Ebenen von Spracherweiterungen und insbesondere die Prinzipien der temporalen Aufwärtskompatibilität und der Schnappschuß-Reduzierbarkeit. Dadurch wird eine leichte Formulierbarkeit
28
KAPITEL 3. TEMPORALE ERWEITERUNGEN VON SQL
und Erlernbarkeit der Sprache ermöglicht. Es wird für den Anwender mit relativ einfachen Mitteln ein beträchtliches Maß an temporaler Funktionalität zur Verfügung gestellt.
Es sind jedoch auch einige Punkte deutlich geworden, die im Rahmen der Neuentwicklung
überdacht werden sollen. Dazu gehört vor allem das Problem der vertikalen temporalen Anomalie (s. Abschnitt 2.2.2), bedingt durch die Verwendung von Intervall-Zeitstempeln auf Tupeln.
Dieses tritt im Beispiel zu den sequentiellen Erweiterungen (s. Abschnitt 3.1.1) zutage: Dort
erscheint der Angestellte Meier doppelt, weil sein Chef während der interessanten Zeit eine Gehaltserhöhung erhalten hat. Zwar kann man diesen Effekt durch eine explizit ausgeführte zeitliche
Verschmelzungsoperation vermeiden, jedoch erscheint ein Sprachkonstrukt sinnvoll, das die vertikale Anomalie an dieser Stelle vermeidet, ohne die Schnappschuß-Reduzierbarkeit durch eine
automatische Coalesce-Operation zu verletzen (s. a. Abschnitt 3.1.2).
Das abschließende Beispiel im Abschnitt 3.1.1 zeigt ein ähnliches Problem in der Ebene der
nicht-sequentiellen Erweiterungen. Ebenfalls durch die Verteilung der Informationen über den
Angestellten Boss auf mehrere Tupel ist der Anwender gezwungen, eine explizite Verschmelzung und einen Verbund in seine Anfrage einzubauen. Es findet keine Abstraktion zwischen
der physischen Speicherung in den zugrundeliegenden nicht-temporalen Tabellen und der nichtsequentiellen Semantik statt, der Benutzer muß die Speicherstruktur bei der Anfrageformulierung
unmittelbar berücksichtigen.
Im folgenden soll daher speziell auf diese veränderungswürdig erscheinenden Punkte eingegangen werden, wenn die Konzeption der neuen temporalen Erweiterung von SQL festgelegt
wird. Dabei werden zunächst grundlegende Konzepte beschrieben, um diese dann für die einzelnen Teile der Spracherweiterung zu konkretisieren. Die Syntax der Erweiterung orientiert sich
so weit wie möglich an ATSQL2, eine abschließende formale Definition der gültigen Sprachausdrücke findet der Leser in Anhang A. Zum Abschluß dieses Kapitels werden verschiedene
Möglichkeiten zur Präsentation der temporalen Informationen diskutiert.
3.2.1
Grundlegende Konzepte
Durch das konzeptionelle Modell wird der gedankliche Entwurf der Spracherweiterung festgelegt
in dem Sinne, daß für den Benutzer klar wird, wie er sich die Ausführung bestimmter Operationen auf den Daten vorzustellen hat. Für nicht-temporale relationale Datenbanken ist das
konzeptionelle Modell hinlänglich bekannt: Der Benutzer stellt sich die Daten in Form von Relationen, Attributen und Tupeln vor und hat z. B. bei der Bildung des kartesischen Produktes
zweier Relationen R1 und R2 die Kombination jedes einzelnen Tupels aus R1 mit allen Tupeln
aus R2 vor Augen.
Dieses Gedankenmodell von den Operationen der Sprache und den Objekten der Datenbank
ist Voraussetzung für die erfolgreiche Formulierung von Datenbankanfragen. So wird eine Formulierung von Anfragen in SQL ohne die Vorstellungen des Benutzers aus dem obigen Beispiel
kaum Erfolg haben. Sicher ist auch die Einfachheit des konzeptionellen Modells relationaler
Datenbanken für deren große Verbreitung verantwortlich: Der Benutzer wird nach Kenntnisnahme weniger Datenobjekte und Operationen Anwender einer mächtigen Datenbanksprache, ohne
sich zunächst um die Details von physischer Speicherung und Optimierung kümmern zu müssen.
Diese Einfachheit gilt es nach Möglichkeit zu erhalten, wenn SQL um temporale Funktionalität
erweitert wird.
Diesem Anspruch wird das konzeptionelle Modell von ATSQL2 auf den ersten beiden Ebenen der Spracherweiterung zweifellos gerecht: Die aufwärtskompatiblen wie auch die temporal
3.2. DIE NEUE TEMPORALE ERWEITERUNG SQLTE
29
AngNr
Name
Gehalt
VT
12
13
27
27
Müller
Meier
Boss
Boss
5000
6000
5900
6400
[05.10.99, ∞)
[05.10.99, ∞)
[05.10.99, 12.10.99)
[12.10.99, ∞)
AngNr
VT
Name
VT
Gehalt
VT
12
13
27
[05.10.99, ∞)
[05.10.99, ∞)
[05.10.99, ∞)
Müller
Meier
Boss
[05.10.99, ∞)
[05.10.99, ∞)
[05.10.99, ∞)
5000
6000
5900
6400
[05.10.99, ∞)
[05.10.99, ∞)
[05.10.99, 12.10.99)
[12.10.99, ∞)
Tabelle 3.1: Die Datenmodelle von ATSQL2 und SQLTE am Beispiel
aufwärtskompatiblen Sprachausdrücke stimmen mit den Operationen von SQL überein. Im zweiten Falle werden lediglich die Daten durch Schnappschußbildung aus der temporalen Datenbank
gewonnen, was jedoch für den Benutzer zunächst verborgen bleibt. In beiden Fällen ist für den
Benutzer das nicht-temporale Gedankenmodell ausreichend, welches ihm durch den Einsatz von
SQL vertraut ist.
Diese ersten beiden Ebenen der Spracherweiterung sollen daher auch bei SQLTE Verwendung
finden. Durch die Aufwärtskompatibilität und die temporale Aufwärtskompatibilität sowie das
zugrundeliegende relationale Datenmodell ist die Semantik in diesem Bereich festgelegt.
Weiterhin sollen auch die dritte und vierte Ebene von Spracherweiterungen sowie die dort
enthaltene Schnappschuß-Reduzierbarkeit und die darüber hinaus vorgesehene temporale Funktionalität durch explizite Verwendung der Zeitstempel für SQLTE genutzt werden.
Um dabei dem genannten Problem der vertikalen temporalen Anomalie zu begegnen, sollen
Zeitstempel auf Attributen in Form von temporalen Elementen verwendet werden (s. Abschnitt
2.2.1 und 2.2.2). Auf diese Weise wird es, ebenso wie im nicht-temporalen relationalen Modell,
möglich, alle Informationen eines Objektes der realen Welt (real world object) in einem Tupel
der zugehörigen Relation abzulegen. Durch die Zeitstempel enthält ein temporales Tupel so die
gesamte Historie eines Objektes der realen Welt.
In Tabelle 3.1 sind zum Vergleich die Beispielinformationen aus Abschnitt 3.1.1 im Datenmodell von ATSQL2 und von SQLTE dargestellt. In der letztgenannten Variante findet keine
Aufteilung der verschiedenen temporalen Versionen der Informationen über mehrere Tupel statt.
Eine inhärente temporale Verschmelzung findet nur innerhalb der komplexen Tupel statt, d. h. es
werden nur dann wertgleiche Informationen verschmolzen, wenn sich diese auf dasselbe Objekt
der realen Welt beziehen.
Auf diese Weise vermeidet man auf der einen Seite zunächst die Notwendigkeit einer expliziten
Verschmelzungsoperation, da bereits alle sinnvollen temporalen Verschmelzungen automatisch
vorgenommen werden: Ein Zusammenfügen von wertgleichen Informationen aus verschiedenen
komplexen Tupeln ist keine Frage von temporaler Verschmelzung mehr, sondern vielmehr eine
der Duplikatelimination nach Art des bekannten Operators distinct in SQL. Andererseits gerät
man im Bereich der sequentiellen Erweiterungen nicht mit der Schnappschuß-Reduzierbarkeit in
Konflikt (vgl. Abschnitt 3.1.2), wenn man innerhalb eines komplexen Tupels keine zeitlichen
Überlappungen der einzelnen Attributeinträge zuläßt. Legt man eine einzelne lineare Zeitlinie
30
KAPITEL 3. TEMPORALE ERWEITERUNGEN VON SQL
zugrunde, erscheint eine solche Festlegung überaus sinnvoll — in der Tabelle 3.1 könnte der
Angestellte Boss beispielsweise nicht gleichzeitig zwei unterschiedliche Gehälter beziehen. Ist die
Darstellung einer solchen Information — etwa ein Mitarbeiter, der zwei Abteilungen zugleich
angehört — gewünscht, so ist dieses eine Erweiterung des Datenbankschemas und hat nichts mit
temporaler Erweiterung zu tun.
Das skizzierte gedankliche Modell der komplexen Tupel wird sowohl für die Ebene der sequentiellen wie auch für die der nicht-sequentiellen Erweiterungen gelten. Für diese beiden Ebenen
soll die neue Sprache nachfolgend beschrieben werden, insbesondere wird dort auf die genannte
Problematik in den Beispielen aus Abschnitt 3.1.1 eingegangen.
3.2.2
Temporale Anfrageformulierung
Abbildung 3.4 zeigt die Syntaxübersicht für die Anfrageformulierung in SQLTE. Weitergehende
Details sind in Anhang A zu finden. Wie bereits von ATSQL2 aus Abschnitt 3.1 gewohnt, beginnen temporale Anfragen durch die Schlüsselwörter validtime und nonsequenced validtime,
wenn sequentielle bzw. nicht-sequentielle Funktionalität gewünscht wird. Für beide Varianten
kann der Anwender ein Zeitintervall für die Befehlsausführung festlegen. Unterbleibt diese Festlegung, so wird stets das Intervall [now, forever) verwendet, welches vom Zeitpunkt der Befehlsanwendung1 bis zum Ende der Zeitdarstellung im System reicht. Zusammen mit den genannten Schlüsselwörtern bildet dieses explizit angegebene Intervall ein timeflag, welches Anfragen
vorangestellt werden kann (vgl. Abbildung 3.4).
Für die Mengenoperationen union, except und intersect bedarf es für jede einzelne zugehörige Teilanfrage des Timeflags, da auch die Kombination von Anfragen unterschiedlicher
temporaler Erweiterungsebenen möglich sind. Es gilt jedoch ebenso wie im nicht-temporalen
SQL die Maßgabe, daß Mengenoperationen nur bei kompatiblen Projektionslisten zulässig sind.
So ist im folgenden Beispiel die erste Anfrage erlaubt, während die zweite unzulässig ist. Dieses
begründet sich damit, daß sowohl sequentielle wie nicht-sequentielle Anfragen bei einfachen Projektionslisten ein temporales Ergebnis mit komplexen Tupeln liefern. Die erste Anfrage vereinigt
also typgleiche temporale Tupel, während bei der zweiten Anfrage der Versuch unternommen
wird, temporale Attribute vom Typ number und varchar zu mischen.
(nonsequenced validtime select Name from Ang a
where validtime(a) overlaps ’07.07.1977’) union
(validtime select Name from Ang);
(nonsequenced validtime select Nr from Ang a) union
(validtime select Name from Ang);
Gleiches gilt für die Verwendung von Subqueries in from- oder where-Klauseln: Durch das
Timeflag können Teilanfragen unterschiedlicher temporaler Erweiterungsebenen kombiniert wer1
Im Gegensatz zu dem dynamischen Zeitpunkt now, wie in Abschnitt 2.2.1 beschrieben, ist hier der feste
Zeitpunkt der Befehlsanwendung gemeint. Zur Unterscheidung ist in der Literatur dafür teilweise auch der Begriff
current zu finden. Diese Unterscheidung erscheint hier jedoch nicht notwendig, da für SQLTE nur das statische
now zu Anwendung kommt. Weiterhin ist nicht mit Verwechslungen zu rechnen, da das dynamische now i. a.
nur als Endpunkt eines Intervalls als Alternative zu forever verwendet wird, nicht jedoch als Anfangspunkt (vgl.
Abschnitt 2.2.1).
3.2. DIE NEUE TEMPORALE ERWEITERUNG SQLTE
statement :=
query :=
query
timeflag
dml
31
timeflag
sfw
;
)
ddl
)
query
UNION
MINUS
INTERSECT
timeflag :=
PERIOD
NONSEQUENCED
[
timestamp
,
timestamp
)
VALIDTIME
timeflag
sfw :=
SELECT
*
)
FROM
sfw
)
alias-id
table-id
scalarexp
...
,
,
...
WHERE
condexp
Abbildung 3.4: Syntax der SQLTE-Befehle zur Anfrageformulierung
den, jedoch müssen die beteiligten Typen kompatibel sein. Dabei werden nicht-temporale Typen (z. B. select Name from Ang) und temporale Typen (z. B. validtime select Name from
Ang) unterschieden. Das Ergebnis der folgenden Anfrage ist ebenfalls nicht-temporal und zwar
vom Datentyp date.
nonsequenced validtime select begin(validtime(a)) from Ang a;
Für den Rückgabewert des validtime-Operators innerhalb der Projektionsliste wäre die
Schaffung eines neuen nicht-temporalen Datentyps timestamp denkbar, so daß Anfrageergebnisse
wie das der nachfolgenden Anweisung für Subqueries oder temporale Berechnungen verwendet
werden könnten.
nonsequenced validtime select validtime(a) from Ang a;
Diese Variante soll jedoch zunächst nicht als Bestandteil der Sprache formuliert werden.
Ebenso wie für Kombinationen von temporalen Attributen und Zeitstempeln wie in der Anfrage nonsequenced validtime select a.name, validtime(a) from ang a bleibt hier die
Möglichkeit, die Anfragesprache in Zukunft entsprechend zu erweitern.
Sequentielle Anfragen
Bei sequentiellen Anfragen findet eine inhärente temporale Interpretation der Daten gemäß der
temporalen Semantik aus Abschnitt 3.1.2 statt, jedoch auf Basis der im vorangehenden Abschnitt
32
KAPITEL 3. TEMPORALE ERWEITERUNGEN VON SQL
beschriebenen komplexen Tupel. Das problematische Beispiel zu den sequentiellen Erweiterungen
aus Abschnitt 3.1.1 stellt sich unter SQLTE wie folgt dar.
create table Ang (AngNr number, Name varchar(30), Gehalt number, ChefNr number)
as validtime;
validtime period [’05.10.1999’, forever)
insert into Ang values (12, ’Müller’, 5000, 27);
validtime period [’05.10.1999’, forever)
insert into Ang values (13, ’Meier’, 6000, 27);
validtime period [’05.10.1999’, forever)
insert into Ang values (27, ’Boss’, 5900, 27);
validtime period [’12.10.1999’, forever)
update Ang set Gehalt = 6400 where AngNr = 27);
validtime select a1.AngNr, a1.Name, a1.Gehalt, a2.Name
from Ang a1, Ang a2 where a1.ChefNr = a2.AngNr and a1.Gehalt > 5900;
=⇒
AngNr
Name
Gehalt
a2.Name
VT
13
Meier
6000
Boss
[05.10.1999, ∞)
27
Boss
6400
Boss
[12.10.1999, ∞)
Es erfolgt die gewünschte Ausgabe der Angestellen für die Zeit, während der ein Gehalt von
über 5900 ausgezahlt wurde. Da in der Ausgabe nur zwei verschiedene Zeitstempel vorkommen,
erfolgt die Präsentation mit Hilfe von Zeitstempeln auf Tupeln (vgl. dazu Abschnitt 3.2.5).
Die Anfrageverarbeitung ist hier im Gegensatz zu dem entsprechenden Beispiel aus Abschnitt
3.1.1 unproblematisch, da an der Bildung des kartesischen Produktes nur drei (komplexe) Tupel beteiligt sind. Die resultierenden neun Paare werden durch die erste Selektionsbedingung
a1.ChefNr = a2.AngNr eingeschränkt auf die Kombinationen Müller/Boss, Meier/Boss und
Boss/Boss. Die zweite Selektionsbedingung a1.Gehalt > 5900 läßt nur die Tupel Meier/Boss
und Boss/Boss zu, wobei die letzte Kombination auf die Gültigkeitszeit des Selektionsbedingung
eingeschränkt wird, nämlich [12.10.1999, ∞).
Nicht-sequentielle Anfragen
Bei nicht-sequentiellen Anfragen findet keine inhärente temporale Interpretation der Daten statt.
Stattdessen stehen dem Benutzer die expliziten Zeitstempel von Tupeln (z. B. validtime(a))
oder ganzer temporaler Subqueries (z. B. validtime(nonsequenced validtime select Name
from Ang)) zur Verfügung. Die letztgenannte Anwendung von validtime liefert die Vereinigung
der Zeitstempel aller beteiligten komplexen Tupel, falls das Ergebnis der Subquery mehrere
Tupel umfaßt. Weiterhin sind die Beziehungen aus Tabelle 2.5 (precedes, overlaps, meets,
contains, equals sowie begin und end) als Operatoren auf Zeitstempeln anwendbar.
Das problematische Beispiel zu den nicht-sequentiellen Erweiterungen aus Abschnitt 3.1.1
stellt sich damit unter SQLTE wie folgt dar. Gefragt wurde dort nach allen Mitarbeitern, die
seit dem 05.10.1999 ununterbrochen im Unternehmen tätig waren und es noch immer sind. Die
gesamte Historie der gesuchten Mitarbeiter erhält man mit der folgenden Anfrage, während die
zweite Variante als TUC-Anfrage nur die aktuellen Informationen liefert, wie dieses in Abschnitt
3.2. DIE NEUE TEMPORALE ERWEITERUNG SQLTE
33
3.1.1 gewünscht war. In beiden Fällen ist keine explizite Verschmelzungsoperation wie unter
ATSQL2 vonnöten. Die Ausgabe erfolgt wiederum mit Hilfe von Zeitstempeln auf Tupeln (vgl.
dazu Abschnitt 3.2.5). Da der Ausdruck validtime(a) im Tupel des Angestellten Nr. 27 zum
Zeitstempel [05.10.1999, ∞) ausgewertet wird, entspricht auch dieses Tupel der Auswahlbedingung.
nonsequenced validtime
select * from Ang a where validtime(a) contains period [’05.10.1999’, now);
=⇒
AngNr
Name
Gehalt
ChefNr
VT
12
Müller
5000
27
[05.10.1999, ∞)
13
Meier
6000
27
[05.10.1999, ∞)
27
27
Boss
Boss
5900
6400
27
27
[05.10.1999, 12.10.1999)
[12.10.1999, ∞)
select * from (
nonsequenced validtime select * from Ang a
where validtime(a) contains period [’05.10.1999’, now));
=⇒
AngNr
Name
Gehalt
ChefNr
12
13
27
Müller
Meier
Boss
5000
6000
6400
27
27
27
Abschließend sei ein weiteres Beispiel beschrieben, das auch nicht-temporale Auswahlbedingungen sowie Verbundbildung berücksichtigt. Die Ausgangslage sei die folgende AngestellenTabelle. Es soll die Frage beantwortet werden, welche Personen von Anfang 1988 bis Ende 1989
in der Einkaufsabteilung gearbeitet haben und während des Jahres 1999 in der Verkaufsabteilung
tätig waren.
Nr
Name
Gehalt
Abteilung
23 | [88, ∞)
Müller | [88, ∞)
4000 | [88, 89)
4500 | [89, ∞)
Einkauf | [88, 95)
Verkauf | [95, ∞)
nonsequenced validtime select a1.Name from Ang a1,
(validtime select * from Ang s1 where s1.Abt = ’Einkauf’ and s1.Nr = a1.Nr) a2,
(validtime select * from Ang s2 where s2.Abt = ’Verkauf’ and s2.Nr = a1.Nr) a3
where validtime(a2) contains period [’01.01.1988’, ’01.01.1990’) and
validtime(a3) contains period [’01.01.1999’, ’01.01.2000’);
=⇒
a1.Name
VT
Müller
[01.01.1988, ∞)
Die beiden Unteranfragen zu a2 und a3 schränken die Tupel auf die Verkaufs- bzw. Einkaufsabteilung ein. Da es sich um sequentielle Anfragen handelt, erfolgt diese Einschränkung jeweils
für das gesamte Tupel, d. h. validtime(a2) und validtime(a3) liefern genau die Gültigkeitszeiten, währenddessen die Bedingungen s1.Abt = ’Einkauf’ bzw. s2.Abt = ’Verkauf’ erfüllt
sind.
34
KAPITEL 3. TEMPORALE ERWEITERUNGEN VON SQL
Für diese Anfrage ist die Kombination von sequentiellen und nicht-sequentiellen Konstrukten
notwendig, wie nachfolgend erläutert werden soll. Mit einem einzigen nicht-sequentiellen Kommando ließe sich diese Anfrage nicht formulieren, da mit einer Auswahlbedingung wie s1.Abt
= ’Einkauf’ nur ein komplexes Tupel ausgewählt wird, welches diese Bedingung erfüllt, jedoch
keine Einschränkung stattfindet. Somit würden alle Mitarbeiter gefunden, die jemals in der Einkaufsabteilung gearbeitet haben. Andererseits liefert eine rein sequentielle Anfrage auch nicht
das gewünschte Ergebnis, selbst wenn man mit Hilfe des Timeflags die Teilanfragen auf period
[’01.01.1988’, ’01.01.1990’) bzw. auf period [’01.01.1999’, ’01.01.2000’) einschränken würde. Das Resultat wären in diesem Fall Mitarbeiter, die während der genannten Zeiten
irgendwann in den entsprechenden Abteilungen tätig waren, jedoch nicht notwendigerweise die
Angestellten, die während der gesamten Zeit der Abteilung angehörten.
Alternativ läßt sich dieselbe Anfrage wie auch folgt stellen, dabei wird statt der Verbundbildung die Fähigkeit des validtime-Operators genutzt, komplette Subqueries zu verarbeiten.
nonsequenced validtime select a1.Name from Ang a1 where validtime(
validtime select * from Ang s1 where s1.Abt = Einkauf’ and s1.Nr = a1.Nr)
contains period [’01.01.1988’, ’01.01.1990’) and validtime(
validtime select * from Ang s2 where s2.Abt = Verkauf’ and s2.Nr = a1.Nr)
contains period [’01.01.1999’, ’01.01.2000’);
Nachfolgend ist die gleichbedeutende ATSQL2-Anfrage dargestellt. Dort sind jeweils Verschmelzungsoperationen notwendig, die durch (period) explizit angegeben werden.
nonsequenced validtime select a1.Name from
(validtime (select Nr, Name from Ang where Abteilung=’Einkauf’) (period)) a1,
(validtime (select Nr, Name from Ang where Abteilung=’Verkauf’) (period)) a2
where a1.Nr = a2.Nr
and validtime(a1) contains period [date ’1988’ - date ’1990’)
and validtime(a2) contains period [date ’1999’ - date ’2000’);
3.2.3
Temporale Datenmanipulation
Die in Kapitel 2.3 vorgestellten Prinzipien zur temporalen Erweiterung von Datenmodellen beschränken sich nicht auf Anfrageoperationen, wenngleich dort der Schwerpunkt vieler Veröffentlichungen und auch das Hauptaugenmerk dieser Arbeit liegt. Dennoch sollen zum Sprachumfang
von SQLTE Funktionen zur Datenmanipulation (data manipulation language — DML) gehören,
wenn auch nur in eingeschränktem Umfang. Dadurch wird eine vollständigere Testumgebung
geschaffen, mit der es möglich ist, den tatsächlichen Bedürfnissen der nachträglichen Datenmanipulation ohne eine komplette Löschung und Neuanlage von Tabellen gerecht zu werden.
In diesem Abschnitt wird daher beschrieben, welche Funktionalität von den einzelnen Kommandos zur Verfügung gestellt wird, wo deren Grenzen liegen und welche weiteren Möglichkeiten
denkbar sind. In Abbildung 3.5 ist die grundsätzliche Syntax der Befehle zur Datenmanipulation
zu sehen, für eine komplette und detaillierte Übersicht sei auf Anhang A verwiesen.
Die Kommandos zur Datenmanipulation beginnen ebenso wie die temporalen Anfragen mit
einem Timeflag, das die Art der temporalen Unterstützung signalisiert. Dieses Timeflag soll
im Bereich der Datenmanipulation derzeit nur eingeschränkt unterstützt werden, präzise ist die
3.2. DIE NEUE TEMPORALE ERWEITERUNG SQLTE
statement :=
35
query
timeflag
;
dml
ddl
timeflag :=
NONSEQUENCED
[
PERIOD
,
timestamp
timestamp
)
VALIDTIME
,
dml :=
)
INSERT INTO
table-id
VALUES
WHERE
DELETE FROM
,
)
column-id
)
table-id
)
condexp
table-id
WHERE
UPDATE
constant
SET
column-id
=
condexp
scalarexp
,
Abbildung 3.5: Syntax der SQLTE-Befehle zur Datenmanipulation
Verwendung von nonsequenced-Konstrukten nicht vorgesehen. Auf eine mögliche Funktionalität
dieser Befehle wird zum Abschluß dieses Abschnitts hingewiesen.
Zur Verfügung stehen somit die Anwendung eines leeren Timeflags auf temporale Tabellen, was zu einer temporal aufwärtskompatiblen Funktionalität (TUC) führt, sowie die Verwendung des Schlüsselwortes validtime, wodurch eine sequentielle Befehlsausführung (SEQ)
erreicht wird. Wie im Falle der select-Anweisungen kann die jeweilige Funktion durch die Angabe von period explizit auf ein Zeitintervall eingeschränkt werden. Fehlt diese Spezifikation, so
wird ebenso wie bei der Anfrageformulierung stets mit dem Intervall [now, forever) gearbeitet.
Nachfolgend wird die Funktionsweise der einzelnen Datenmanipulationsbefehle in den beiden Bereichen der temporalen Aufwärtskompatibilität und der sequentiellen Anweisungen beschrieben. Auf Begründungen für die Einhaltung von temporaler Aufwärtskompatibilität bzw.
Schnappschuß-Reduzierbarkeit wird verzichtet, da sich diese leicht anhand der Definitionen 5
und 6 nachvollziehen lassen.
Die beiden insert-Kommandos erschaffen dabei stets neue komplexe Tupel. Eine temporale
Änderung oder Ergänzung bestehender Tupel etwa durch ein insert bei Verwendung identischer
Primärschlüssel ist nicht vorgesehen. Diese Vorgehensweise begründet sich auf die temporale
Aufwärtskompabilität bzw. die Schnappschuß-Reduzierbarkeit: Ein nicht-temporales insert liefert stets ein neues Tupel, so daß TUC- wie auch SEQ-insert ebenso immer ein neues Tupel
erzeugen müssen, damit die Anzahl der Tupel je Schnappschuß übereinstimmt. Für einen weitergehenden Sprachumfang, etwa um Zeitbereiche bestimmter Tupel zu ergänzen, sei auf den
letzten Absatz in diesem Abschnitt verwiesen, wo auf die Möglichkeit von nicht-sequentiellen
36
KAPITEL 3. TEMPORALE ERWEITERUNGEN VON SQL
angegebenes Intervall
Zeit
Zeitstempel Attribut A
Zeitstempel Attribut B
Zeitstempel Attribut C
bildet Zeitstempel des alten Attributwerts:
bildet Zeitstempel des neuen Attributwerts:
Abbildung 3.6: Zeitstempel bei der sequentiellen Update-Operation
Datenmanipulationsoperationen eingegangen wird.
insert (TUC): Es wird stets ein neues komplexes Tupel geschaffen, mit einem Eintrag je Attribut und dem Zeitstempel [now, forever). Die Dateneingabe erfolgt durch Auflistung
der einzufügenden Werte, die Eingabe über eine Subquery ist nicht vorgesehen.
delete (TUC): Alle Zeitstempel der komplexen Tupel, die zum aktuellen Zeitpunkt now den
Auswahlkriterien genügen oder den aktuellen Zeitpunkt nur überlappen, falls keine Auswahlkriterien in der where-Klausel angegeben wurden, werden sofort beendet. Es erfolgt
also ein Schnitt zum Zeitpunkt now. Wird auf diese Weise der Zeitstempel eines Attributs
zum leeren Intervall beschnitten, wird der Attributwert gelöscht.
update (TUC): Alle Attribut-Einträge der komplexen Tupel, die zum aktuellen Zeitpunkt
now den Auswahlkriterien genügen oder den aktuellen Zeitpunkt nur überlappen, falls
keine Auswahlkriterien in der where-Klausel angegeben wurden, werden dupliziert und
mit neuen Zeitstempeln versehen. Die Duplikate werden gemäß der set-Klausel verändert.
Die alten Zeitstempel werden — wie beim delete — zum aktuellen Zeitpunkt beendet. Die
neuen Zeitstempel ergeben sich aus genau dem Rest, der bei den alten entfernt wurde.
Attribute mit leeren Zeitstempeln werden wiederum entfernt.
insert (SEQ): Es wird stets ein neues komplexes Tupel geschaffen, mit einem Eintrag je Attribut und dem spezifizierten Zeitstempel oder [now, forever), falls keine explizite Angabe
vorhanden. Die Dateneingabe erfolgt durch Auflistung der einzufügenden Werte, die Eingabe über eine Subquery ist nicht vorgesehen.
delete (SEQ): Alle Zeitstempel der komplexen Tupel, die im Bereich des angegebenen Intervalls den Auswahlkriterien genügen oder das angegebene Intervall nur überlappen, falls
keine Auswahlkriterien in der where-Klausel angegeben wurden, werden für die angegebene Zeit ausgeblendet. Es erfolgt also ein Entfernen der gewünschten Gültigkeitszeit aus
den Zeitstempeln. Wird der Zeitstempel eines Attributs komplett überlappt, so daß ein
leerer Zeitstempel entstehen würde, ist die Löschung dieses Attributwertes vorgesehen.
update (SEQ): Alle Attribut-Einträge der komplexen Tupel, die im Bereich des angegebenen
Intervalls den Auswahlkriterien genügen oder das angegebene Intervall nur überlappen,
falls keine Auswahlkriterien in der where-Klausel angegeben wurden, werden dupliziert
3.2. DIE NEUE TEMPORALE ERWEITERUNG SQLTE
37
und mit neuen Zeitstempeln versehen (s. Abbildung 3.6). Die Duplikate werden gemäß
der set-Klausel verändert. Die alten Zeitstempel werden — wie beim delete — für die
angegebene Zeit ausgeblendet. Die neuen Zeitstempel ergeben sich aus genau dem Rest,
der bei den alten entfernt wurde. Alte Attribute mit leeren Zeitstempeln (in Abbildung
3.6 das Attribut B) werden entfernt.
Verzichtet man bei der SEQ-Variante von insert auf die explizite Angabe eines Intervalls
im Timeflag, so ist die Funktionalität mit dem TUC-insert identisch. Dennoch sind beide
Kommandos berechtigt, da sie durch eine unterschiedliche Syntax erreicht werden: Der Anwender
des TUC-insert ist sich möglicherweise gar nicht bewußt, daß er mit einer temporalen Tabelle
arbeitet.
Bei update und delete beschränkt sich der Unterschied zwischen der TUC- und der SEQVariante ohne explizite Intervallangabe auf die Auswahl der beteiligten Tupel. Im Falle der
TUC-Befehle sind nur Tupel beteiligt, die zum aktuellen Zeitpunkt den Auswahlbedingungen
entsprechen, bei den sequentiellen Befehlen auch solche Tupel, deren Gültigkeitszeit in der Zukunft liegt, d. h. mit [now, forever) überlappt. So hat im Beispiel zu den sequentiellen Anfragen in
Abschnitt 3.2.2 die sequentielle update-Operation denselben Effekt wie eine temporal aufwärtskompatible Änderungsoperation, die am 12.10.1999 ausgeführt wird.
Abschließend sei bemerkt, daß mit den vorgestellten Operationen zur temporalen Datenmanipulation keine expliziten Änderungen an den Zeitstempeln möglich sind. Wie schon bei
der Anfrageformulierung gesehen, bedarf es für den expliziten Zugriff auf die Zeitinformationen der nicht-sequentiellen Sprachkonstrukte. Man könnte sich also in diesem Rahmen eine
Anweisung nonsequenced insert vorstellen, die es erlaubt, komplexe temporale Tupel mit
expliziten Zeitangaben einzufügen. Auch wären sicher Befehle der Art nonsequenced update
set begin(validtime(t)) = ’10.11.1999 16:18:34’ wünschenswert, um durch spezifische
Änderungsoperationen z. B. Zeitbereiche zu ergänzen. Wie jedoch zu Beginn dieses Abschnitts
erwähnt, sollen die Änderungsoperationen nicht schwerpunktmäßig betrachtet werden, so daß
ein weiterführender Funktionsumfang hier nicht spezifiziert werden soll.
3.2.4
Temporale Datendefinition
Im Bereich der temporalen Datendefinition verhält es sich ähnlich wie im vorangehenden Abschnitt zur temporalen Datenmanipulation: Es soll zunächst nur soviel Funktionalität vorgesehen
werden, wie für eine sinnvolle Testumgebung benötigt wird.
Das bedeutet konkret, daß nur die beiden Befehle create table und drop table vorgesehen
werden sollen und zwar in der Form, wie im Syntaxdiagramm in Abbildung 3.7 dargestellt. Für
eine komplette und detaillierte Übersicht sei wiederum auf Anhang A verwiesen.
Für die Datendefinition wird nicht das von Anfrageformulierung und Datenmanipulation
bekannte Timeflag benötigt, da keine Differenzierung bezüglich der Sequentialisierbarkeit vorgenommen wird. Stattdessen wird bei create table nur zwischen nicht-temporalen und temporalen Tabellen unterschieden, die letztgenannten werden durch das Nachstellen der Schlüsselwörter
as validtime erzeugt. Für das Entfernen von Tabellen mittels drop table ist der Grad der
temporalen Unterstützung unerheblich, daher fehlt hier eine enstprechende Angabe.
Wie weiterhin in der Abbildung 3.7 zu sehen ist, bleibt der Bereich der (temporalen) Integritätsbedingungen leider komplett unberücksichtigt. Die Anlage von Sichten, Indexen, Sequenzen und andererer SQL-Features sowie ein jeweils zugehöriges drop- und alter-Kommando
38
KAPITEL 3. TEMPORALE ERWEITERUNGEN VON SQL
ddl :=
CREATE TABLE
table-id
table-id
(
column-id
datatype
(
DROP TABLE
AS VALIDTIME
,
Abbildung 3.7: Syntax der SQLTE-Befehle zur Datendefinition
Nr
Name
Gehalt
Abteilung
12 | [84, ∞)
Rastlos | [84, 90) ∪ [93, ∞)
Rastlos-Meier | [90, 93)
4000 | [84, 91)
4500 | [91, 96)
5000 | [96, ∞)
Einkauf | [84, 87)
Verkauf | [88, 89) ∪ [92, ∞)
Produktion | [87, 88) ∪ [89, 92)
Stetsgleich | [80, ∞)
4800 | [80, ∞)
Personalbüro | [80, ∞)
13 | [80, ∞)
Tabelle 3.2: Beispiel der Darstellung mit Attribut-Zeitstempeln und temporalen Elementen
fehlen ebenfalls. Diese Eingrenzungen waren notwendig, um den Umfang der Arbeit überschaubar zu halten.
3.2.5
Präsentation temporaler Anfrageergebnisse
Das Präsentationsmodell legt die Art und Weise der Darstellung von Informationen aus dem
temporalen DBMS fest. Eine Ausgabe von Informationen erfolgt im wesentlichen im Rahmen
der Anfragebearbeitung, so daß zur Festlegung des Präsentationsmodells nur Anfrageoperationen
betrachtet werden.
Die Darstellung der Ergebnisse von aufwärtskompatiblen und temporal aufwärtskompatiblen
Anfragen erfolgt sinnvoller Weise ebenso wie im unterliegenden DBMS. Das heißt, die nichttemporalen Relationen werden wie üblich tabellenförmig ausgegeben, mit einer Zeile je Tupel
und einer Spalte je Attribut.
Für die sequentiellen und nicht-sequentiellen Spracherweiterungen bedarf es nun einer zweckmäßigen Erweiterung dieser Darstellung, so daß zusätzlich temporale Informationen präsentiert
werden können. Hier ist eine möglichst übersichtliche Darstellung Zielsetzung, also eine Vermeidung der temporalen Anomalien durch die zusammenhängende Ausgabe der gesamten Geschichte eines Objektes der realen Welt. Bedingung ist dabei nur eine tabellarische Darstellung
in Anlehnung an die Präsentation nicht-temporaler Relationen.
Wie in Abschnitt 2.2.2 gesehen, ermöglicht die Benutzung von Zeitstempeln auf Attributen
durch temporale Elemente eine solche Darstellung. Diese Art der Ausgabe würde auch der Konzeption der komplexen Tupel aus Abschnitt 3.2.1 entsprechen. Problematisch ist dabei jedoch die
unbefriedigende Lesbarkeit durch den Benutzer, wenn einzelne Tupel in ihrer Geschichte viele
Änderungen erfahren haben. Besonders in Kombination mit zahlreichen Attributen wird eine
solche Darstellung unübersichtlich. Dieses wird im Beispiel in Tabelle 3.2 angedeutet.
Im Grunde benötigte man für die zusammenhängende Darstellung neben Zeilen und Spalten
eine dritte Dimension für die Zeit. Eine Präsentation in Anlehnung an die einfache und gewohn-
3.2. DIE NEUE TEMPORALE ERWEITERUNG SQLTE
Nr
39
Name
Gehalt
Abteilung
VT
12
12
12
12
12
12
12
12
Rastlos
Rastlos
Rastlos
Rastlos-Meier
Rastlos-Meier
Rastlos-Meier
Rastlos
Rastlos
4000
4000
4000
4000
4500
4500
4500
5000
Einkauf
Produktion
Verkauf
Produktion
Produktion
Verkauf
Verkauf
Verkauf
[84, 87)
[87, 88) ∪ [89, 90)
[88, 89)
[90, 91)
[91, 92)
[92, 93)
[93, 96)
[96, ∞)
13
Stetsgleich
4800
Personalbüro
[80, ∞)
Tabelle 3.3: Beispiel der Darstellung mit Tupel-Zeitstempeln und temporalen Elementen
te zweidimensionale Tabellenstruktur von SQL erscheint daher mit Zeitstempeln auf Attributen
nicht realisierbar. Daher könnte sich die Verwendung von temporalen Elementen mit Zeitstempeln auf Tupeln als ein sinnvoller Mittelweg zwischen den Anforderungen der zusammenhängenden Historie und einer guten Lesbarkeit erweisen. Die dabei vorhandenen Redundanzen könnten
angesichts der erhöhten Übersichtlichkeit in Kauf genommen werden, zumal diese nur in der
Darstellung erscheinen und — abgesehen von Platz auf dem Bildschirm — keine Ressourcen
belegten.
Weiterhin erscheint neben der Wiederholung der unveränderten Attribute bei Änderung eines
einzelnen Attributwertes eine zeitlich aufsteigende Sortierung der Tupel sinnvoll, zumal sonst in
der Ebene der sequentiellen Erweiterungen keine Sortierung nach den impliziten Zeitstempeln
möglich ist. Für diese zeitliche Sortierung wäre die Ordnung nach den Anfangspunkten der temporalen Intervalle denkbar. Da ein komplexes Tupel in der Regel mehrere Zeilen beanspruchen
wird, würde ein größerer Abstand zwischen Zeilen verschiedener Tupel die Lesbarkeit erhöhen.
Schließlich erhält man eine Darstellung wie im Beispiel in Tabelle 3.3.
Insgesamt kann man feststellen, daß sich keine der beiden beschriebenen Varianten als die
eindeutig bessere herausstellt: Die erste entspricht der Konzeption der Sprache und bietet wegen
der stets vorhandenen zeitlichen Verschmelzung einen Überblick der einzelnen Attributwerte,
während die zweite eine gute Übersicht für den zeitlichen Verlauf des gesamten Tupels bietet,
diesen jedoch durch Auftreten der vertikalen temporalen Anomalie und der damit verbundenen
Redundanz erkauft. Bei der Implementierung der Sprache könnte man daher evtl. beide Varianten vorsehen und die Art der aktuellen Präsentation z. B. mit Hilfe eines Schalters in der
Benutzeroberfläche realisieren.
Kapitel 4
Planung und Entwurf der
Implementation
In den vorangegangenen beiden Kapiteln wurden Grundlagen zu temporalen Spracherweiterungen erläutert, mit ATSQL2 eine prototypisch verfügbare temporale Anfragesprache vorgestellt
und schließlich auf dieser Basis die temporale Erweiterung SQLTE nach eigenen Schwerpunkten
entwickelt. In diesem Kapitel sollen nun diese Vorbereitungen genutzt werden, um die Vorgehensweise zur Implementation von SQLTE in ihren Grundzügen festzulegen.
Ein Ziel der vorliegenden Arbeit ist es, bei der Implementation objekt-relationale Konzepte zu
berücksichtigen. Es werden daher zunächst die neuen objekt-orientierten Features von Oracle8
vorgestellt. Weiterhin werden auf der Grundlage dieser Möglichkeiten zwei Varianten für das
Speichermodell von SQLTE entwickelt. Abschließend werden Überlegungen zur Architektur des
Programms angestellt und ein erster Ansatz zur Modularisierung formuliert.
4.1
Objekt-relationale Möglichkeiten
Ein Ziel der Implementation der in den vorangehenden Kapiteln entwickelten temporalen Erweiterung von SQL ist der Einsatz der unter Oracle8 erstmalig verfügbaren objekt-relationalen
Features. Diese werden durch eine Ergänzung zum Datenbanksystem zur Verfügung gestellt, der
sogenannten Objects Option.
Auf diese Weise wird das bisher zugrundeliegende relationale Modell zum objekt-relationalen
Modell erweitert, das als wesentliche Neuerung benutzerdefinierte Objekttypen unterstützt, welche die abstrahierte Darstellung komplexer Entities der realen Welt in der Datenbank ermöglichen (s. [Ora98a]). Ein solcher Objekttyp besteht dabei aus drei Komponenten, nämlich dem
Namen, um eine eindeutige Identifikation zu gewährleisten, den Attributen in Form von systemeigenen Datentypen oder weiteren benutzerdefinierten Objekttypen, welche die Struktur
des neuen Typs festlegen, sowie den Methoden, die als spezielle Funktionen oder Prozeduren
spezifische Operationen auf den Daten definieren (s. [Ora98a]).
In diesem Abschnitt sollen die Möglichkeiten und Grenzen der neuen objekt-relationalen
Features dargelegt werden. Dieses geschieht zwar noch unabhängig von der späteren Anwendung,
jedoch beziehen sich die folgenden Erläuterungen im wesentlichen auf Prinzipien, die im Rahmen
der Implementation anwendbar erscheinen und enthalten daher keine vollständige Beschreibung
40
4.1. OBJEKT-RELATIONALE MÖGLICHKEITEN
41
aller objekt-relationalen Neuerungen. Die Informationen wurden hauptsächlich der offiziellen
Oracle-Dokumentation [Ora98a] bis [Ora99g] entnommen, wenngleich auch die unabhängigen
Werke [HP98], [KL97] und [Urm97] vielfach hilfreich waren.
4.1.1
Kollektionen
Kollektionen stellen eine Sammlung von Objekten eines einheitlichen Typs dar, wobei sowohl systemeigene Datentypen wie auch benutzerdefinierte Objekttypen verwendet werden können. Die
wesentliche Einschränkung bei der Verwendung von Kollektionen ist, daß diese nicht geschachtelt werden können, d. h. Kollektionen von Objekten, die ihrerseits Kollektionen enthalten, sind
nicht erlaubt.
Kollektionen bestehen entweder aus varrays oder aus nested tables. Varrays entsprechen den
in Hochsprachen üblichen Vektoren. Sie haben eine bei der Definition zu bestimmende Höchstzahl
von Elementen, und die enthaltenen Objekte weisen eine feste Reihenfolge auf. Varrays werden
physikalisch wie skalare Datentypen direkt je Tupel abgelegt, sofern sie eine gewisse Größe nicht
überschreiten, ab der sie wie ein large object (lob) behandelt werden.
Nested tables haben ähnliche Eigenschaften wie die gewöhnlichen Tabellen in Oracle. Sie
haben keine voreingestellte Obergrenze bezüglich ihrer Elementanzahl, und die enthaltenen Objekte haben keine festgelegte Reihenfolge. Nested tables werden bei der Verwendung in Tabellen
nicht wie skalare Datentypen je Tupel abgelegt. Stattdessen muß für jeden nested table-Typ die
Definition einer stored table erfolgen. Die stored table (auch: innere Tabelle) nimmt dann alle
Kollektionen eines nested table-Typs der äußeren Tabelle auf. Die innere Tabelle enthält eine
implizite Spalte nested table id, die jeweils auf dasjenige Tupel der äußeren Tabelle verweist,
zu der die Kollektion gehört.
Die nachfolgenden Anweisungen erzeugen einen kollektionswertigen Typ telefon typ, der
mehrere Telefonnummern aufnimmt und eine Beispieltabelle ang, die diesen Typ verwendet. In
Abbildung 4.1 ist diese Konstruktion schematisch dargestellt1 .
create type telefon_typ as table of varchar(20);
create table ang (
nr
number,
name
varchar(50),
tel
telefon_typ
) nested table tel store as ang_tel_st;
Auf die inneren Tabellen kann unter SQL nicht direkt in der gewohnten Form mittels select
zugegriffen werden. Stattdessen erfolgt der Zugriff stets über die äußere Tabelle und zwar mit
Hilfe der Operatoren the und table. Dabei können diese Operatoren anstatt einer Tabelle in
SQL-Anweisungen verwendet werden. Der Operator the liefert zu einer select-Anweisung die
Tabelle eines kollektionswertigen Objektes. Die verwendete select-Anweisung muß dabei genau
ein Tupel der äußeren Tabelle ergeben. Der Operator table wandelt ein kollektionswertiges
1
Das Kommando desc ang tel st liefert unter SQL*Plus den nicht dokumentierten Fehler SP2-0642:
SQL*Plus internal error state 2131, context 0:0:0, unsafe to proceed. Die systemseitig generierte Spaltenbezeichnung column value kann man jedoch der Dokumentation [Ora99a, ch. 4] sowie den folgenden Beispielen
entnehmen.
42
KAPITEL 4. PLANUNG UND ENTWURF DER IMPLEMENTATION
Ang
Nr
Name
13
Meier
Tel
Verweis auf Spalte(n) in innerer Tabelle
ang_tel_st
nested_table_id
...
12
Müller
0511 1738
040 1717
020 1212
...
...
27
column_value
0511 7384
0511 1244
...
Boss
Verweise auf Spalte(n)
in äußerer Tabelle
089 3014
Abbildung 4.1: Kollektionswertiges Attribut als nested table-Typ
Attribut direkt in eine Tabelle um. Die folgenden Beispiele demonstrieren diese beiden indirekten
Zugriffsmöglichkeiten mit der oben definierten Tabelle.
select * from the(select tel from ang where nr = 12);
select value(nt) from ang a, table(a.tel) nt where a.nr = 12;
insert into the(select tel from ang where nr = 12) values (’089 42168’);
select * from ang a where exists
(select * from table(a.tel) nt where value(nt) like ’0511%’);
Die ersten beiden Anfragen liefern beide dasselbe Ergebnis, nämlich alle Telefonnummern des
Angestellen mit der Nummer 12. In der dritten Zeile wird der the-Operator verwendet, um für
den Angestellten 12 eine weitere Telefonnummer hinzuzufügen. Im letzten Beispiel werden alle
Angestellen gesucht, die unter einer hannoverschen Telefonnummer zu erreichen sind. Im Falle der
obigen Beispiele liefert die Verwendung von value(nt) dasselbe Ergebnis wie nt.column value,
da der Typ telefon typ nur aus einem einzigen skalarem Typ besteht. Weitere Zugriffsmöglichkeiten ergeben sich durch die Operatoren cast und multiset, die häufig kombiniert werden. Im
nachfolgenden Beispiel liefern beide Anfragen dasselbe Ergebnis; multiset wandelt ein Anfrageergebnis in eine Kollektion, und cast nimmt eine explizite Typumformung vor.
select tel from ang where nr = 12;
select cast(multiset(select * from table(a.tel)) as telefon_typ)
from ang a where a.nr = 12;
Nach [HP98] wird bei stored tables für die Spalte nested table id vermutlich intern ein
Index angelegt, um die Werte einzelner Kollektionen der äußeren Tabelle effizient aufzufinden
— dieses läßt sich jedoch anhand der Oracle-Dokumentation Version 8.0 ([Ora98a] bis [Ora98e])
4.1. OBJEKT-RELATIONALE MÖGLICHKEITEN
Ang
Nr
Name
13
Meier
Tel
ang_tel_st
...
12
Müller
...
27
43
Boss
nested_table_id
column_value
0511 1244
0511 7384
...
020 1212
040 1717
0511 1738
...
089 3014
Abbildung 4.2: Nested table-Typ als index organized table (IOT)
nicht belegen. Für die Version 8.1 wurden jedoch einige Änderungen bezüglich der physikalischen
Speicherung von benutzerdefinierten Typen vorgenommen (s. [Ora99f]). Wie schon unter Version
8.0 können explizite Indexe auf stored tables angelegt werden, wie im untenstehenden Beispiel
zu sehen ist.
Darüberhinaus können nun nested tables unter Beteiligung der Spalte nested table id am
Primärschlüssel als index organized tables (IOT) angelegt werden. Die stored table liegt dann
stets sortiert vor, und jede einzelne Kollektion (d. h. alle inneren Tupel mit identischem Eintrag
für nested table id) wird physikalisch in einem Cluster abgelegt. Weiterhin kann bei der Tabellenanlage durch das Schlüsselwort compress erreicht werden, daß je Kollektion nested table id
nur einmalig gespeichert wird. In Abbildung 4.2 sind diese Eigenschaften dargestellt: Die Einträge sind nach nested table id und column value aufsteigend sortiert, je Kollektion wird nur
ein Verweis gespeichert, und jede Kollektion findet in einem grau dargestellten Cluster Platz.
Diese IOT -Konstruktion wird jedoch nicht standardmäßig eingerichtet, obwohl deren Verwendung von Oracle empfohlen wird. Stattdessen erreicht man diese wie im folgenden Beispiel
gezeigt. Ein Index für die äußere Tabelle auf der Spalte mit dem kollektionswertigen Typ (in
Beispiel und Abbildung ist dieses tel) wird systemseitig angelegt, die Verwendung eines Index
auf nested table id in der inneren Tabelle muß explizit vereinbart werden und ist besonders
im Zusammenhang mit der o. g. IOT -Konstruktion zu empfehlen (s. [Ora99c, ch. 18]).
create unique index ang_tel_idx on
ang_tel_st (nested_table_id, column_value);
create table ang (
nr
number,
name
varchar(50),
tel
telefon_typ
) nested table tel store as ang_tel_st (
(primary key (nested_table_id, column_value))
organization index compress);
44
4.1.2
KAPITEL 4. PLANUNG UND ENTWURF DER IMPLEMENTATION
Referenzen
Insbesondere wenn die Objektstrukturen stärker verschachtelt sind, z. B. wenn in einer Kollektion Adressen abgelegt werden sollen und jede Adresse aus mehreren Attributen wie Straße,
Stadt und PLZ besteht, die ihrerseits wieder benutzerdefinierten Typs sind (etwa Typ Straße
aus Straßenname und Hausnummer), lassen sich die Zusammenhänge mit Hilfe von Objekten
einfacher modellieren als traditionell im relationalen Modell über Fremdschlüsselbedingungen.
Informationen darüber, welche der beiden Methoden vom Datenbanksystem effizienter umgesetzt wird, waren leider Dokumentation wie Literatur nicht zu entnehmen. Diesbezüglich wären
spezielle Performance-Tests vonnöten.
Wie bereits im vorangehenden Abschnitt erwähnt, ist es nicht möglich, kollektionswertige Attribute zu schachteln. Diese Einschränkung ist recht hinderlich, zumal — wie gerade beschrieben
— ein großer Vorteil der objekt-relationalen Technik genau darin besteht, auch bei komplexen
und verschachtelten Modellierungen einen guten Überblick zu bieten.
Es gibt (u. a.) daher die Möglichkeit, mit Referenzen von Objekten zu arbeiten. Jedes Objekt,
das in der Datenbank abgelegt wird, erhält einen sogenannten Objektidentifikator (OID), eine
eindeutige interne Kennung. Mit Hilfe dieser OIDs kann auf Objekte verwiesen werden und somit die Einschränkung bei geschachtelten Kollektionen umgangen werden. Referenzen existieren
physikalisch in verschiedenen Ausführungen: Durch Einbeziehen der ROWID ist ein schnellerer
Zugriff auf das referenzierte Objekte möglich, durch Einschränken der Referenz auf die Objekte
einer bestimmten Tabelle benötigen die Verweise weniger Speicherplatz.
Erweitert man das Beispiel des vorangehenden Abschnitts, kann man wie folgt Referenzen
einsetzen, um eine mehrfache Schachtelung von kollektionswertigen Typen zu erreichen.
create type telefon_objtyp as object (tel_liste telefon_typ);
create type ang_objtyp as object (
nr
number,
name
varchar(50),
tel
ref telefon_objtyp);
create type ang_type as table of ang_objtyp;
create table firma (
name
varchar(50),
ang
ang_typ
) nested table ang store as firma_ang_st;
Leider ist die Handhabung der Referenzen von Kollektionen im Vergleich zu einfachen Kollektionen relativ aufwendig, da stets mittels ref und deref zwischen Objekt und Objektreferenz
gewechselt werden muß. Weiterhin ist es bei der Entwicklung von Methoden auf den verwendeten
Objekten z. B. unter PL/SQL hinderlich, daß nur Objekte referenziert werden können, die bereits in einer Tabelle der Datenbank abgelegt sind, denn dynamisch mittels Standardkonstruktor
generierte Objekte haben keinen Objektidentifikator.
4.1. OBJEKT-RELATIONALE MÖGLICHKEITEN
45
varray
321
17
99
407
83
622
105
19
67
278
x(1)
x(2)
x(3)
x(4)
x(5)
x(6)
x(7)
x(8)
x(9)
x(10)
622
105
19
x(6)
x(7)
x(8)
feste
Obergrenze
nested table nach Löschoperationen
321
x(1)
x(2)
99
407
x(3)
x(4)
x(5)
278
x(9)
unbegrenzt
x(10)
Abbildung 4.3: Zugriff auf Kollektionen unter PL/SQL (nach [Ora98d])
4.1.3
Methoden
Neben der Möglichkeit, komplexe Strukturen in Form von Objekten abzubilden, liegt in der
Formulierung von Methoden auf diesen Objekten eine weitere wesentliche Stärke des objektrelationalen Modells. Durch Methoden können sehr spezifische Operationen für einzelne Objekttypen definiert werden. Tritt man noch einmal den Vergleich zwischen Kollektionen und
Fremschlüsselkonstruktionen an, erfolgt mit Hilfe von Methoden eine automatische Kapselung
der Algorithmen, so daß eine bessere Übersichtlichkeit und Wartbarkeit der Modellation gewährleistet wird.
Die Formulierung von Methoden erfolgt z. B. mit der systemeigenen Programmiersprache
PL/SQL. Zusammen mit den dort vorhandenen Kontrollstrukturen existieren erweiterte Zugriffsmöglichkeiten auf Objekte und insbesondere auf Kollektionen. Die zwei möglichen Arten
der Kollektionen — varrays und nested tables — werden dabei prinzipiell gleichbehandelt, und
deren Elemente können wie Vektoren in üblichen Hochsprachen adressiert werden. Die Reihenfolge der Elemente bei nested tables ist zwar zufällig, aber dennoch fest. Bleibender Unterschied
ist, daß varrays lückenlos und begrenzt sind, die einzelnen Elemente also durch 1, . . . , n adressiert
werden können, wenn n die festgelegte Höchstzahl ist. Dagegen können nested tables beliebig
erweitert werden und Lücken aufweisen, wenn einzelne Elemente entfernt wurden, d. h. existieren
n Elemente in einer solchen Kollektion, können die einzelnen Elemente mit 1, . . . , m adressiert
werden, wobei u. U. m > n (s. a. Abbildung 4.3).
Weiterhin stehen systemeigene Methoden zur Manipulation von Kollektionen unter PL/SQL
zur Verfügung (collection methods). Dieses sind namentlich exists, count, limit, first,
last, prior, next, extend, trim und delete. Mit deren Hilfe lassen sich z. B. einzelne Elemente auf Existenz überprüfen oder löschen, ebenso können in einer (nested table-) Kollektion
zusätzliche freie Plätze angelegt werden.
Eine wichtige Rolle spielen Methoden bei dem Vergleich von Objekten. Grundsätzlich lassen
sich Objekte nur vergleichen, wenn sie gleichen Typs sind. Weiterhin ist standardmäßig nur
der Vergleich auf Gleichheit und Ungleichheit möglich und dieser auch nur unter SQL, nicht
jedoch unter PL/SQL. Dieser Vergleich funktioniert jedoch nicht immer wunschgemäß, wenn
die Objekte Referenzen oder Kollektionen beinhalten, d. h. nur skalare Typen werden bezüglich
identischer Inhalte verglichen.
46
KAPITEL 4. PLANUNG UND ENTWURF DER IMPLEMENTATION
Da ein Vergleich bei vielen Operationen implizit enthalten ist (z. B. bei order by, distinct
und Mengenoperationen) und diese ohne korrekte Vergleichsoperation somit ebenfalls nicht zur
Verfügung stehen, hat man die Möglichkeit, zusätzliche objektspezifische Methoden zu formulieren, die den Vergleich typgleicher Objekte erlauben. Dafür sind zwei Varianten vorgesehen,
nämlich sogenannte map- und order-Funktionen. Bei einer Objektdeklaration ist höchstens einer der Methodentypen erlaubt, und die zugehörige Funktion wird durch das Schlüsselwort map
bzw. order gekennzeichnet.
Eine map-Funktion bildet einen Objekttypen auf einen skalaren Typen (number, date oder
varchar) ab. Beim konkreten Vergleich werden dann nach der Abbildung der beteiligten Objekttypen nur die zugehörigen skalaren Werte verglichen. Auf diese Weise können Vergleiche,
bei denen einzelne Objekte mehrfach beteiligt sind (z. B. bei Sortierungen), relativ effizient ausgeführt werden, da die Abbildung auf ein Skalar nur einmal je Objekt erfolgt. In [Ora98d] findet
man als einfaches Beispiel einen Objekttyp, der die rationalen Zahlen mit Hilfe der beiden
numerischen Variablen Zähler und Nenner abbildet. Die zugehörige map-Funktion liefert den
Quotienten Zähler/Nenner, so daß ein korrekter Vergleich rationaler Zahlen erfolgt.
Eine order-Funktion vergleicht zwei typgleiche Objekte und liefert als Ergebnis kleiner, größer
oder gleich. Es können so beliebige Vergleichsoperationen geschaffen werden. Deren Anwendung
ist jedoch bei Mehrfachvergleichen erheblich aufwendiger als bei map-Funktionen, da für jeden
Vergleich die order-Funktion explizit aufgerufen wird. Bei nested table-Typen fällt wegen der
unbegrenzten Elementanzahl eine Abbildung auf skalare Werte häufig schwer, so daß nur der
zeitintensivere Vergleich mittels order-Funktion bleibt.
Map- und order-Funktionen müssen mit restrict references bezüglich ihrer Zugriffe eingeschränkt werden, um Seiteneffekte zu vermeiden. Es sind somit keine lesenden und schreibenden
Zugriffe auf die Datenbank oder auf globale PL/SQL-Variablen erlaubt.
4.1.4
Oracle 8.1 vs. Oracle 8.0
In der Version 8.0 wurden in Oracle erstmalig objekt-relationale Features zur Verfügung gestellt.
Weiterhin gehören diese Features nicht standardmäßig zum Datenbanksystem, sondern werden
im Rahmen eines Zusatzpakets (object option) angeboten. Vermutlich sind diese beiden Punkte
dafür verantwortlich, daß erstens der Umfang der umgesetzten objekt-orientierten Konzepte
zunächst recht gering ausgefallen ist und zweitens die Zuverlässigkeit der neuen Features teilweise
zu wünschen übrig läßt.
Zur ersten Anmerkung sind vor allem fehlende Mechanismen zur Vererbung von Objekttypen, zur Objektidentifikation dynamisch konstruierter Objekte und zur Schachtelung von kollektionswertigen Objekten zu nennen. Sinnvoll wäre auch die Möglichkeit der wechselseitigen
Verwendung von Objekttypen, d. h. Konstruktionen wie Typ A verwendet in seiner Methode
AM den Typ B, und Typ B hat Typ A als Eingabeobjekt in der Methode BM. In [HP98] — ein
recht frühes Werk zu Oracle 8.0 — wird näher auf wünschenswerte objekt-orientierte Funktionen
eingegangen.
Während der Tests zur Abbildung temporaler Strukturen wie Intervalle und temporale Elemente sind einige Fehler aufgetreten, die den Anlaß zu der zweiten obigen Anmerkung geben. Vor
allem die Kombination von Sichten, mehrfach geschachtelten Objekten mit Referenzen und Methodenaufrufen erweisen sich als problematisch. So ist ein einfacher Zugriff auf eine solche Sicht
möglich (select * from objview), während die Verwendung einer Auswahlbedingung zu einem
Fehler führt (select * from objview where nr=1) (oracle bug-no. 988230). Weiterhin ist ein
4.2. VARIANTEN DES SPEICHERMODELLS
47
Zugriff über eine angelegte Sicht möglich (create view objview (...) und dann select *
from objview), während der direkte Zugriff nicht funktioniert (select * from (...)) (Oracle
bug-no. 988306). Als hinderlich haben sich auch die Probleme beim Sortieren von nested tableObjekten unter PL/SQL erwiesen (Oracle bug-no. 841108, 977211).
Glücklicherweise bringt die neu verfügbare Version 8.1.5 gegenüber dem Vorläufer einige wesentliche Verbesserungen mit sich, so funktioniert hier die Sortierung von nested tableObjekten wenigstens eingeschränkt, d. h. eine aufsteigende Sortierung nach allen vorhandenen
Attributen wird ausgeführt. Laut Oracle-Support ist das Problem der Sortierung ab der Version 8.1.6 behoben. Weiterhin ist unter SQL das sogenannte unnesting of collections möglich,
d. h. für das Beispiel aus Abschnitt 4.1.1 ist nun eine Anfrage in der folgenden Art möglich,
um alle Telefonnummern eines Angestellten aufzulisten: select a.nr, value(nt) from ang
a, table(a.tel) nt;. Darüberhinaus existieren neue Möglichkeiten, die physikalische Speicherung von Objekten zu beeinflussen, siehe hierzu Abschnitt 4.1.1 sowie [Ora99c, ch. 18] und
[Ora99f, ch. 2].
Eine weitere neue Funktion in der Version 8.1.5, die insbesondere für die Umsetzung der
temporalen Erweiterung von SQL hilfreich sein könnte, sind temporäre Tabellen. Es können so
Tabellen geschaffen werden, deren Inhalt nur für einen Benutzer sichtbar ist und — abhängig
von der Definition der Tabelle — nur für die Dauer einer Sitzung oder einer Transaktion erhalten
bleiben. Leider unterstützen temporäre Tabellen keine benutzerdefinierten Typen.
Als sehr wichtige neue Funktion von Oracle 8.1.5 im Zusammenhang mit der Implementation des Übersetzungsprogramms erscheint die Verwendung dynamischer SQL-Kommandos unter
Pro*C in Kombination mit benutzer-definierten Typen. Auf diese Technik wird in Kapitel 5 näher
eingegangen; die offizielle Dokumentation findet man in [Ora99g]. Festzuhalten bleibt, daß ohne dieses Feature kaum temporale Daten außerhalb der Datenbank präsentiert werden können,
da die übersetzten Anfragen notwendig dynamisch sind, d. h. erst zur Laufzeit des Programms
feststehen. Außerdem sind an den auszugebenen Daten auf jeden Fall Objekttypen beteiligt, da
deren Verwendung ein Ziel der vorliegenden Arbeit ist. In der Version 8.0.5 ist zwar die Nutzung
dynamischer SQL-Kommandos unter Pro*C und auch die Übergabe von benutzer-definierten
Typen vorgesehen, jedoch nicht die Kombination der beiden Techniken.
Eine komplette Übersicht der Erweiterungen der Version 8.1 gegenüber 8.0 findet sich in
[Ora99f].
4.2
Varianten des Speichermodells
In diesem Abschnitt wird die Art und Weise der Speicherung temporaler Tabellen für die Implementation in Kapitel 5 festgelegt. Dazu werden zunächst Anforderungen an das zukünftige
Speichermodell formuliert, um dann zwei verschiedene Varianten vorzustellen. In beiden Varianten wird anhand von Beispielen zur Übersetzung temporaler Anfragen deren Funktionalität
verdeutlicht. Abschließend werden die unterschiedlichen Ansätze bewertet und die Festlegung
auf ein Speichermodell vorgenommen.
Eine erste grundsätzliche Anforderung, die sich zwar nicht speziell aus der Festlegung des
Speichermodells ergibt, diese jedoch maßgeblich beeinflußt, ist der Anspruch der gesamten Arbeit, die neuen objekt-relationalen Möglichkeiten von Oracle 8 zum Einsatz zu bringen. Der
Zweck dieser Arbeit wäre eindeutig verfehlt, wenn ein rein relationales Speichermodell zum
48
KAPITEL 4. PLANUNG UND ENTWURF DER IMPLEMENTATION
Einsatz käme, selbst wenn sich dieses für einzelne Anforderungen als einer möglichen objektrelationalen Variante überlegen herausstellen sollte.
Als Anforderungen, die sich unmittelbar an das Speichermodell ergeben, sind vor allem eine
einfache Übersetzbarkeit der temporalen Anweisungen und deren effiziente Ausführbarkeit zu
nennen. Beide Punkte beschränken sich im wesentlichen auf die Anfrageausführung, da Datenmanipulationsanweisungen schon bei der Formulierung der Sprache (s. a. Abschnitt 3.2.3) keinen
Schwerpunkt bilden und ihre Ausführung i. A. als weniger zeitkritisch erachtet wird.
Die einfache Übersetzbarkeit bezieht sich sowohl auf die Übersetzung der temporalen Anfragen in ausführbare SQL-Kommandos, wie auch die Darstellung der Anfrageergebnisse, was
man gewissermaßen als Rückübersetzung bezeichnen könnte. Für die Einfachheit beider Übersetzungsrichtungen erscheint offensichtlich ein Speichermodell angebracht, welches sich stark am
semantischen Modell der Sprache orientiert. Für den Bereich der temporal aufwärtskompatiblen
und sequentiellen Anweisungen kommt wegen der Ausführung bezüglich einzelner Zeitpunkte
ein eher flaches Speichermodell mit mehr Redundanz bei den nicht-temporalen Informationen in
Betracht, während für den Bereich der nicht-sequentiellen Anfragen durch die Konzeption der
komplexen Tupel ein eher geschachteltes Modell sinnvoll erscheint.
Eine effiziente Ausführbarkeit der übersetzten Anfragen wird vor allem durch eine effektive Vermeidung der sequentiellen Durchsicht (Relationenscan) aller vorhandenen Daten erreicht.
Dieses begründet sich damit, daß im Vergleich zu nicht-temporalen Datenbanken in der Regel
mehrere Versionen der Informationen mit denselbem zeitinvarianten Schlüssel existieren (s. a.
[ZCF97, ch. 7]). Um die Menge der sequentiell durchzusehenden Daten wirksam einzuschränken,
sollten also in einer Vorauswahl für die jeweilige Anfrage irrelevante Versionen aussortiert werden. Dieses könnte man etwa durch eine Indizierung der kleinsten und größten vorhandenen
Zeitpunkte der Gültigkeitszeit je Informationseinheit (z. B. Tupel) erreichen. Eine solche Indizierung könnte entweder durch eine explizite Vorauswahl oder durch Hinweise an den Optimierer
des DBMS bei der Übersetzung berücksichtigt werden.
4.2.1
Einfache Schachtelung
In der Variante zum Speichermodell mit einfacher Schachtelung wird der Weg der flachen Speicherstruktur mit Redundanzen bei nicht-temporalen Informationen gewählt. Es werden dabei
temporale Elemente als Zeitstempel auf Tupeln verwendet. Konkret wird eine Tabelle dabei um
zwei Spalten ergänzt, damit diese temporale Informationen aufnehmen kann. Dieses sind namentlich rwo nr und ts , wobei die erste einen zusätzlichen internen Schlüssel darstellt, der
die Zuordnung mehrerer physikalischer Tupel zu einem komplexen Tupel der Sprache ermöglicht
(rwo = real world object). Die zweite zusätzliche Spalte ist der eigentliche Zeitstempel, der die
Gültigkeitszeit des verwendeten Tupels beinhaltet und über einen nested table-Typen implementiert wird. In Abbildung 4.4 ist eine solche Konstruktion schematisch dargestellt (vgl. a.
Abbildung 4.1).
Die aus Kapitel 3 bekannte Tabelle Ang enthält dort eine kleine inhaltliche Erweiterung, um
die Kollektionswertigkeit der Zeitstempel darstellen zu können: Der Angestellte Boss wird am
17.11.99 auf sein altes Gehalt zurückgesetzt. Der Inhalt von rwo nr ist rein zufällig und enthält
nur die Information, daß die beiden Tupel zum Angestellten Boss einem einzigen komplexen
Tupel und somit nur einem Objekt der realen Welt zuzuordnen sind. Weiterhin kann man der
Abbildung entnehmen, daß eine bis auf weiteres gültige Information (forever oder ∞) durch
4.2. VARIANTEN DES SPEICHERMODELLS
Ang Nr
Name
Gehalt
rwo_nr_
12
Müller
5000
379
13
Meier
6000
322
49
ts_
ang_ntabl_ nested_table_id
vt_begin
vt_end
05.10.1999 31.12.9999
05.10.1999 31.12.9999
27
Boss
5900
421
27
Boss
6400
421
05.10.1999 12.10.1999
17.11.1999 31.12.9999
12.10.1999 17.11.1999
Abbildung 4.4: Temporale Tabelle Ang im einfach geschachtelten Speichermodell
das größte im System darstellbare Datum, nämlich den 31.12.99992 , repräsentiert wird.
Für die Implementation der obigen Beispieltabelle sind die folgenden Definitionen notwendig.
Man benötigt dabei das Objekt der Zeitstempel, das sich aus einer Menge von Intervallen zusammensetzt, sowie die eigentliche Tabellendefinition mit den genannten zusätzlichen Spalten und
der nested table. Über diese Definitionen hinaus sind verschiedene Methoden auf den Objekten
t interval type und t stamp type denkbar, wie z. B. temporale Schnitt- oder Differenzbildung
und temporale Verschmelzung.
create type t_interval_type as object (
vt_begin date,
vt_end
date);
create type t_element_tabl as table of t_interval_type;
create type t_stamp_type as object (
ts
t_element_tabl);
create table ang (
nr
number,
name
varchar(20),
gehalt
number,
rwo_nr_
number,
ts_
t_stamp_type
) nested table ts_.ts store as ang_ntabl_;
Nachdem das einfach geschachtelte Speichermodell am Beispiel vorgestellt wurde, sollen nachfolgend Möglichkeiten zur Indizierung der verwendeten Zeitstempel genannt werden. Abschließend wird das Modell durch exemplarische Übersetzungen für die einzelnen Erweiterungsebenen
2
Aus Platzgründen und im Sinne einer besseren Lesbarkeit wird auf die Angabe der Uhrzeit verzichtet. Präzise
handelt es sich um den Zeitpunkt 31.12.9999 23:59:59, um einer sekundengenauen Granularität gerecht zu werden.
50
KAPITEL 4. PLANUNG UND ENTWURF DER IMPLEMENTATION
der Sprache präzisiert. Die dabei eingesetzten Funktionen werden zunächst nicht explizit definiert, mit der jeweils vorhandenen kurzen Erläuterung sollte deren Anwendung jedoch verständlich sein.
Indizierung
Neben den in Abschnitt 4.1.1 erwähnten allgemeinen Möglichkeiten zur Indizierung und IndexOrganisierung von nested table-Typen ist vor allem durch die folgende Erweiterung eine effizientere Anfrageausführung zu erwarten. Der Typ t stamp type wird dabei um den kleinsten und
den größten Zeitpunkt im Zeitstempel ergänzt, und es werden jeweils Indexe auf diesen neuen
Spalten vereinbart. Nachfolgend finden sich die dazu benötigten erweiterten Definitionen für die
Beispieltabelle Ang. Durch diese Konstruktion könnte bei der Anfrageausführung eine temporale
Vorauswahl stattfinden, mit deren Hilfe die Menge der sequentiell zu lesenden Tupel deutlich
reduziert werden kann. Die Einträge in tp begin und tp end könnten z. B. durch einen Trigger
auf ang ntabl stets auf aktuellem Stand gehalten werden.
create type
ts
tp_begin
tp_end
t_stamp_type as object (
t_element_tabl,
date,
date);
create index t_begin_index on ang (ts_.tp_begin);
create index t_end_index on ang (ts_.tp_end);
Temporal aufwärtskompatible Anfragen
Es soll hier exemplarisch die folgende, bereits aus Abschnitt 3.1.1 bekannte, Anfrage übersetzt
werden3 . Der Ausführungszeitpunkt spielt für das Anfrageergebnis natürlich eine maßgebende Rolle, wird jedoch über die systemeigene Funktion sysdate automatisch generiert, so daß
die Übersetzung davon unabhängig erfolgt. Die verwendete Methode intersection liefere den
Schnitt zwischen Zeitstempel und Zeitpunkt oder NULL, falls dieser leer ist.
select a1.Nr, a1.Name, a1.Gehalt, a2.Name from Ang a1, Ang a2
where a1.ChefNr = a2.Nr and a1.Gehalt > 5900;
=⇒
select a1.Nr, a1.Name, a1.Gehalt, a2.Name from Ang a1, Ang a2
where a1.ChefNr = a2.Nr and a1.Gehalt > 5900 and
a1.ts_.intersection(sysdate) is not null and
a2.ts_.intersection(sysdate) is not null;
Sieht man bei der Anlage jeder temporalen Tabelle bereits eine Sicht nach Art des folgenden
Beispiels vor, so kann die Übersetzung weiter vereinfacht werden. Präzise beschränkt sich diese
dann auf den Austausch der verwendeten temporalen Tabellen durch die zugehörigen Sichten,
3
Die Tabelle Ang benötigt dazu die ungekürzte Fassung mit der Spalte ChefNr wie in Abschnitt 3.1.1.
4.2. VARIANTEN DES SPEICHERMODELLS
51
so daß auf eine explizite Anpassung der where-Klausel verzichtet werden kann. Auch die Verwendung einer Vorauswahl mit Hilfe eines Indexes nach o. g. Muster könnte man auf diese Weise
innerhalb der Sicht verbergen.
create view ang_tuc_ (
select Nr, Name, Gehalt, ChefNr from Ang a
where a.ts_.intersection(sysdate) is not null);
Sequentielle Anfragen
Auch hier wird auf die bekannte Anfrage als Beispiel zurückgegriffen. Die Übersetzung liefert
mehrere Teilschritte, die unter Einsatz temporaler Tabellen nacheinander ausgeführt werden.
Die zeitliche Verschmelzung, d. h. hier die Vereinigung aller wertgleichen Tupel mit identischer
rwo nr , bleibt vorerst unberücksichtigt, sie wird stattdessen erst im Rahmen der Präsentation
der Daten ausgeführt. Auf diese Weise werden zwar u. U. mehr Tupel belegt als nach zeitlicher
Verschmelzung benötigt würden, dafür bedarf es je Anfrage aber nur eines einzigen Verschmelzungsvorgangs und nicht eines Verschmelzungsvorgangs je Subquery. Für die Anfrageausführung
ist diese späte Verschmelzung unerheblich, da der Informationsgehalt der Tupel — verschmolzen
oder nicht — derselbe ist.
Werden an einer Anfrage mehrere Tabellen beteiligt, etwa wie im Beispiel durch einen Verbund, bedarf es bei der Zuordnung der physikalischen Tupel zu den komplexen Tupeln der
Sprache besonderer Aufmerksamkeit. Durch den Verbund werden komplexe Tupel miteinander
gepaart, und jedes Paar bildet so ein neues komplexes Tupel. Da jedes komplexe Tupel i. A. aus
mehreren physikalischen Tupeln mit identischen Einträgen bei rwo nr besteht, enthält ein Verbund der physikalischen Tupel zunächst Paare von rwo nr . Um jedoch ein gültiges komplexes
Tupel darzustellen, wird eine einzige rwo nr je physikalischem Tupel benötigt. Dieses erreicht
man durch Schaffung einer neuen rwo nr für jedes Paar von alten rwo nr . Läßt man im Beispiel aus der Abbildung 4.4 die Zeitstempel unberücksichtigt und bildet den Eigenverbund der
Tabelle Ang, so ergeben sich physikalisch 16 Paare, jedoch nur neun komplexe Paare, für die es
neun neue rwo nr zu generieren gilt, damit diese eindeutig sind.
In der folgenden Übersetzung der Beispielanfrage werden daher im ersten Schritt die verschiedenen möglichen Paarungen komplexer Tupel ermittelt und in der temporalen Tabelle rwotemp
abgelegt. Der zweite Schritt stattet diese Paarungen mit neuen eindeutigen Einträgen für rwo nr
aus. Diese neuen Einträge werden mit Hilfe der Sequenz rwo nr sqlte generiert. Im dritten
Schritt wird schließlich der Verbund unter der Beteiligung der temporalen Tabelle ausgeführt.
Die Methode intersection liefert in dieser Variante den Schnitt zweier temporaler Elemente
oder NULL, falls dieser leer ist.
validtime select a1.Nr, a1.Name, a1.Gehalt, a2.Name
from Ang a1, Ang a2 where a1.ChefNr = a2.Nr and a1.Gehalt > 5900;
=⇒
insert into rwotemp
(select distinct a1.rwo_nr_, a2.rwo_nr_, NULL from Ang a1, Ang a2
where a1.ChefNr = a2.Nr and a1.Gehalt > 5900);
52
KAPITEL 4. PLANUNG UND ENTWURF DER IMPLEMENTATION
update rwotemp set new_rwo_nr_ = rwo_nr_te_.nextval;
select a1.Nr, a1.Name, a1.Gehalt, a2.Name as Chef, rt_.new_rwo_nr_ as rwo_nr_,
a1.ts_.intersection(a2.ts_) as ts_
from Ang a1, Ang a2, rwotemp rt_
where a1.ChefNr = a2.Nr and a1.Gehalt > 5900 and
a1.ts_.intersection(a2.ts_) is not null and
a1.rwo_nr_ = rt_.rwo1 and a2.rwo_nr_ = rt_.rwo2;
Nicht-Sequentielle Anfragen
Bei nicht-sequentiellen Anfragen erscheint vor allem der explizite Umgang mit den Zeitstempeln
als problematisch. Einfache Anfragen können wie im folgenden Beispiel übersetzt werden4 . Dort
wird die Gültigkeitszeit validtime(a) für jedes komplexe Tupel dynamisch berechnet. Als sinnvoll könnte sich alternativ eine Abspeicherung aller Zeitstempel je Tabelle erweisen, so daß auf
diese mittels rwo nr zugegriffen werden kann. Für die dynamische Berechnung wird die Funktion
coal eingesetzt, die eine Menge von Zeigern auf Zeitstempel entgegennimmt (mehrere ref(ts )
im benutzerdefinierten Typ ts setref) und diese zu einem Zeitstempel zusammenfaßt. Dieser
verschmolzene Zeitstempel mit dem Inhalt validtime(a) wird mit dem Intervall [88, 96) zum
Schnitt gebracht. Genau wenn dieser Schnitt [88, 96) entspricht, ist die ursprünglich geforderte
Bedingung validtime(a) contains period [88, 96) erfüllt.
nonsequenced validtime
select * from Ang a where validtime(a) contains period [88, 96);
=⇒
select rwo_nr_, ts_, Nr, Name, Gehalt, ChefNr from Ang a where
(intersection(coal(
cast(multiset(select ref(ts_) from Ang a2 where a2.rwo_nr_ = a.rwo_nr_)
as ts_setref)), [88, 96)) = [88, 96));
Als umfangreicheres Beispiel soll nachfolgend die abschließende Anfrage aus Abschnitt 3.2.2
übersetzt werden. Dort wurden zwei verschiedene Varianten vorgestellt, hier soll diejenige ohne
Verbundbildung betrachtet werden, die den validtime-Opertor auf Unteranfragen anwendet.
Die Gültigkeitszeiten werden also notwendiger Weise dynamisch berechnet.
nonsequenced validtime select a1.Name from Ang a1 where validtime(
validtime select * from Ang s1 where s1.Abt = Einkauf’ and s1.Nr = a1.Nr)
contains period [1988, 1990) and validtime(
validtime select * from Ang s2 where s2.Abt = Verkauf’ and s2.Nr = a1.Nr)
contains period [1999, 2000);
4
Da die Übersetzungsbeispiele hier noch Entwurfscharakter haben, wird der besseren Lesbarkeit wegen auf eine
korrekte Darstellung der Intervalle verzichtet. Tatsächlich wird das Intervall [88, 96) durch die Anwendung des
Standardkonstruktors mittels t interval type(’01.01. 1988 00:00:00’, ’01.01. 1996 00:00:00’) erzeugt.
4.2. VARIANTEN DES SPEICHERMODELLS
53
=⇒
select rwo_nr_, ts_, a1.Name from Ang
(intersection(coal(cast(multiset(
select ref(ts_) from Ang a2 where
as ts_setref)), [1988, 1990)) =
(intersection(coal(cast(multiset(
select ref(ts_) from Ang a3 where
as ts_setref)), [1999, 2000)) =
a1 where
a2.Nr = a1.Nr and a2.Abt = ’Einkauf’)
[1988, 1990)) and
a3.Nr = a1.Nr and a3.Abt = ’Verkauf’)
[1999, 2000));
Für die Verbundbildung ist es fraglich, wie eine Übersetzung im einfach geschachtelten Speichermodell vorzunehmen ist. Im Unterschied zum sequentiellen Fall findet hier keine zeitliche
Schnittbildung der am Verbund beteiligten flachen Tupel statt, und es sollen auch solche Tupel
miteinander kombiniert werden, die keinen zeitlichen Überlapp besitzen. Man benötigte gewissermaßen ein Äquivalent zum nicht-temporalen outer join, bei dem auch NULL-Werte bei der
Verbundbildung beteiligt werden. Hier würden stattdessen bei der Verbundbildung flache Tupel
kombiniert, die gar keine gemeinsame Gültigkeitszeit besitzen.
4.2.2
Doppelte Schachtelung
In der Variante zum Speichermodell mit doppelter Schachtelung wird das Konzept der komplexen
Tupel aus dem semantischen Modell der Sprache übernommen: Es werden dabei temporale Elemente als Zeitstempel auf Attributen verwendet. Wegen der zweifachen Schachtelung und der
in Abschnitt 4.1.2 in diesem Zusammenhang geschilderten Notwendigkeit von Referenzen fallen die zugehörigen Definitionen deutlich komplizierter aus als im Speichermodell mit einfacher
Schachtelung aus dem vorangehenden Abschnitt. Unverändert übernommen werden die Typen
zur Modellierung der Zeitstempel, t interval type und t stamp type. Zusätzlich werden für
die systemeigenen Typen date, number und varchar drei kollektionswertige Typen benötigt, die
Paare von Werten und Zeitstempeln aufnehmen können. Diese werden nachfolgend als temporale Datentypen bezeichnet. Da diese Kollektionen keine weiteren Kollektionen enthalten dürfen,
sind die Zeitstempel als Referenz auf Objekte vom Typ t stamp type realisiert. Die referenzierten Zeitstempel werden zentral in einer Tabelle stamps gesammelt. Insgesamt ergibt sich
eine Konstruktion wie in Abbildung 4.5 am bekannten Beispiel dargestellt (auf die Nummer der
Angestellten wurde nur einer besseren Übersicht wegen verzichtet).
Die geraden Pfeile stellen dort, wie aus den vorangehenden Abbildungen bekannt, Bezüge zu
inneren Tabellen dar. Zur Unterscheidung sind die Verknüpfungen, die über Referenzen realisiert
werden, durch Kurven dargestellt. Die Tabelle stamps dient gewissermaßen als Wertevorrat für
Zeitstempel und ist allen temporalen Tabellen der Datenbank zugänglich, so daß auch mehrfach verwendete Zeitstempel nur einfach abgelegt werden. Neben den Definitionen der Typen
t interval type und t stamp type aus Abschnitt 4.2.1 benötigt man die folgenden Anweisungen, um die im Beispiel dargestellte temporale Tabelle anzulegen.
create table stamps of t_stamp_type
nested table ts store as stamps_ntabl_;
create type t_tsnum_type as object (
val
number,
54
Ang
KAPITEL 4. PLANUNG UND ENTWURF DER IMPLEMENTATION
Name
Gehalt
ang_gehalt_ntabl_ nested_table_id
val
5000
ts
6000
5900
6400
ang_name_ntabl_
nested_table_id
val
Müller
ts
Meier
Boss
stamps_ntabl_
nested_table_id
vt_begin
vt_end
05.10.1999 31.12.9999
stamps
ts
05.10.1999 12.10.1999
17.11.1999 31.12.9999
12.10.1999 17.11.1999
Abbildung 4.5: Temporale Tabelle Ang im doppelt geschachtelten Speichermodell
ts
ref t_stamp_type);
create type t_num_tabl as table of t_tsnum_type;
create type t_tschr_type as object (
val
varchar2(4000),
ts
ref t_stamp_type);
create type t_chr_tabl as table of t_tschr_type;
create table ang (
name
t_chr_tabl,
gehalt
t_num_tabl
) nested table name store as ang_name_ntabl_,
nested table gehalt store as ang_gehalt_ntabl_;
Das am Beispiel vorgestellte Speichermodell soll nun nachfolgend für die einzelnen temporalen Spracherweiterungsebenen durch entsprechende Funktionalität ergänzt werden. Alle dazu
benötigten Funktionen werden zusammen in einem Paket (package) mit dem Namen vt formuliert. Auf diese Weise können sie gemeinsame paketinterne Variablen und Unterfunktionen
benutzen, auf die von außerhalb des Pakets nicht zugegriffen werden kann. Das Aufrufen von
Funktionen des Pakets erfolgt durch die Voranstellung von vt. vor die Funktionsnamen, so daß
4.2. VARIANTEN DES SPEICHERMODELLS
55
die Funktionen modulartig gekapselt sind.
Im Paket vt sind die Funktionen snapshot, output, intersection und coalescence enthalten, deren Aufgaben nachfolgend beschrieben werden. Alle vier Funktionen machen von der
Möglichkeit des overloading Gebrauch, d. h. sie existieren in verschiedenen Versionen mit unterschiedlichen Ein- und Ausgabeparametern und können dennoch jeweils über denselben Namen
aufgerufen werden. Auf diese Weise können inhaltlich zusammengehörige Funktionen unter einem Namen zusammengefaßt werden, so daß sich die Anwendung des Pakets vereinfacht. Für
die Beschreibung der Funktionen wird die oben definierte Beispieltabelle mit dem Inhalt wie in
Abbildung 4.5 verwendet.
In den ersten drei Beispielen werden die Ergebnistupel durch die etwas abstrakte Darstellung h . . . k . . . i angegeben, nur im letzten Beispiel entspricht das Ergebnis der tatsächlichen
Bildschirmausgabe.
snapshot liefert zu einem Objekt eines temporalen Datentyps und einem Zeitpunkt denjenigen
skalaren Wert, der zum gegebenen Zeitpunkt gültig war, ist oder sein wird. Es wird nicht
unterschieden, ob zu dem Zeitpunkt kein Wert vorhanden ist, oder ob der Wert zu dem
Zeitpunkt NULL ist. In beiden Fällen wird NULL zurückgegeben.
select vt.snapshot(Name, ’11.10.99’)
vt.snapshot(Gehalt, ’11.10.99’) from Ang;
=⇒ hMüller, 5000i, hMeier, 6000i, hBoss, 5900i
intersection erlaubt die temporale Schnittbildung. Dabei ist es möglich, ein Objekt eines temporalen Datentyps mit einem Intervall oder einem temporalen Element zu schneiden oder
zwei temporale Elemente zum Schnitt zu bringen. Ist ein Objekt mit temporalen Datentyp
am Schnitt beteiligt, so hat der Rückgabewert denselben Typ. Das Ergebnis des Durchschnitts zweier temporaler Elemente ist ein temporales Element. Besteht zwischen den
beiden Objekten keine zeitliche Überlappung, so wird NULL zurückgegeben.
select vt.intersection(Gehalt, t interval type(’15.10.99’, ’15.12.99’))
from Ang;
=⇒ h5000 k [15.10.99, 15.12.99)i, h6000 k [15.10.99, 15.12.99)i,
h(6400 k [15.10.99, 17.11.99)), (5900 k [17.11.99, 15.12.99))i
coalescence nimmt ein Objekt eines temporales Datentyps entgegen und vereinigt den Inhalt
sowohl wertmäßig als auch temporal. Das heißt, es werden jeweils alle wertgleichen Einträge des Objektes zusammengefügt, indem die zugehörigen temporalen Elemente vereinigt
werden. Diese temporale Vereinigung erfolgt unter Berücksichtigung der Minimalität der
temporalen Elemente, so daß mögliche Überlappungen und Berührungen entfernt werden.
Im folgenden Beispiel sei MyRound eine Hilfsfunktion, die alle skalaren numerischen Werte
in einem Objekt vom Typ t num tabl auf volle Tausender rundet.
select MyRound(Gehalt) from Ang;
=⇒ h5 k [05.10.1999, 31.12.9999)i, h6 k [05.10.1999, 31.12.9999)i
h(6 k [05.10.1999, 12.10.1999), [17.11.1999, 31.10.1999)), (6 k [05.10.1999, 31.12.9999))i
select vt.coalescence(MyRound(Gehalt)) from Ang;
=⇒ h5 k [05.10.1999, 31.12.9999)i, h6 k [05.10.1999, 31.12.9999)i
h6 k [05.10.1999, 31.12.1999)i
56
KAPITEL 4. PLANUNG UND ENTWURF DER IMPLEMENTATION
Das Beispiel zur Verschmelzungsoperation wirkt etwas konstruiert. Tatsächlich sind im
komplexen Speichermodell die temporalen Datentypen per Definition stets verschmolzen.
Die Verschmelzungsfunktion wird stattdessen auf Zwischenprodukte angewendet, um diese
in reguläre Objekte zu transformieren und in der temporalen Datenbank ablegen zu können.
output erzeugt zu einer temporalen Tabelle eine vorläufige Ausgabe, um die Daten direkt in
SQL*Plus übersichtlich darzustellen.
select vt.output(Name), vt.output(Gehalt) from Ang;
=⇒ Name
Gehalt
——————————————— ———————————————
Müller [05.10.1999, 31.12.9999)
5000 [05.10.1999, 31.12.9999)
Meier [05.10.1999, 31.12.9999)
6000 [05.10.1999, 31.12.9999)
Boss [05.10.1999, 31.12.9999)
5900 [05.10.1999, 12.10.1999)
[17.11.1999, 31.12.9999)
6400 [12.10.1999 17.11.1999)
Vorangehend wurden die datenbankseitig verwendeten Datentypen, Objekte und Funktionen
vorgestellt. Es soll nun ein Überblick darüber gegeben werden, wie diese für die Umsetzung
temporaler Funktionalität in den einzelnen temporalen Erweiterungsebenen verwendet werden.
Temporal aufwärtskompatible Anfragen
Die Anfrageverarbeitung in der Ebene der temporalen Aufwärtskompatibilität (TUC) erfolgt
mit Hilfe der oben beschriebenen Funktion snapshot, der systemeigenen Funktion sysdate und
einer Sicht je temporaler Tabelle. Für die bekannte Beispieltabelle lautet die Definition dieser
Sicht wie folgt.
create or replace view ang_tuc_ as
select vt.snapshot(Nr, sysdate) as Nr, vt.snapshot(Name, sysdate) as Name,
vt.snapshot(Gehalt, sysdate) as Gehalt from Ang
where not (vt.snapshot(Nr, sysdate) is null and
vt.snapshot(Name, sysdate) is null and
vt.snapshot(Gehalt, sysdate) is null)
Die Funktionsweise dieser Sicht dürfte unmittelbar verständlich sein: Für jedes temporale Attribut wird der gültige Wert zur aktuellen Systemzeit ermittelt und ausgegeben. Dem Anwender
stellt sich ang tuc wie eine gewöhnliche nicht-temporale Tabelle dar. Durch die Verwendung
der Funktion sysdate wird sichergestellt, daß je Anfrage durchgängig mit demselben Zeitpunkt
gearbeitet wird und nicht durch die zeitliche Verzögerung der Anfrageausführung leicht unterschiedliche Zeitpunkte ausgewertet werden.
Problematisch bleibt bei der obigen Definition die Verwendung des Wertes NULL. Wie deutlich zu sehen ist, wird zu einem Zeitpunkt, an dem snapshot für alle vorhandenen Attribute
NULL liefert, die Ausgabe unterdrückt, d. h. der Anwender erhält als Ergebnis keine ausgewählten
Zeilen. Es wird in diesem Fall davon ausgegangen, daß ein Zeitpunkt ausgewertet wurde, der in
dem zugrundeliegenden temporalen Tupel nicht vorhanden ist. Es könnte aber auch möglich sein,
4.2. VARIANTEN DES SPEICHERMODELLS
57
daß zu dem vorhandenen Zeitpunkt tatsächlich alle gewünschten Attribute den Wert NULL besitzen. Um dieses Problem zu beheben, wäre ein komplexer Typ als Rückgabewert der Funktion
snapshot denkbar, so daß unterschieden werden kann, ob es sich um einen leeren vorhandenen oder nicht vorhandenen Wert handelt. Am Prinzip der Funktion würde sich jedoch nichts
ändern, nur die Definition der obigen Sicht würde etwas komplizierter ausfallen, so daß auf diese
Maßnahme an dieser Stelle verzichtet werden soll.
Sequentielle Anfragen
Wie bereits erläutert, werden die Zeitstempel im komplexen Speichermodell über Referenzen
realisiert. Da nur in der Datenbank abgelegte, nicht jedoch dynamisch produzierte, Objekte
einen OID erhalten, müssen auch solche Zeitstempel in Tabellen abgelegt werden, die nur als
Zwischenergebnis benötigt werden (s. a. Abschnitt 4.1.2). Innerhalb von select-Anweisungen
ist jedoch die Verwendung von Funktionen und Prozeduren, die schreibend auf Tabellen der
Datenbank zugreifen, nicht gestattet, um Seiteneffekte zu verhindern. Um diese Problematik zu
umgehen, wird das folgende, bereits bekannte Beispiel übersetzt, indem die Ergebnisse zunächst
in eine temporäre Tabellen eingefügt werden5 . Die temporäre Tabelle temp tab sei dazu bereits in geeigneter Weise angelegt. Weiterhin liege die Funktion intersection zur temporalen
Schnittbildung in der Art vor, daß sechs Zeitstempel zugleich als Parameter entgegengenommen
werden.
Bei der Übersetzung werden je Paarung komplexer Tupel a und b alle sechs beteiligten Attribute (a.nr, a.name, a.gehalt, a.chefnr, b.nr, b.name) durch einen Verbund entschachtelt.
Auf diesem Verbund wird der temporale Schnitt gebildet und die eigentliche Selektionsbedingung geprüft. Aus der gesamten Selektion werden schließlich ein Attribut und der gebildete
Durchschnitt als Zeitstempel übernommen und nach temporaler Verschmelzung ausgegeben.
Diese etwas unübersichtliche Entschachtelung inklusive Selektionsbedingung wird für jedes
der vier Ausgabeattribute benötigt, da jeweils der Durchschnitt als neuer Zeitstempel benötigt
wird. Für die letzten beiden Ausgabeattribute gehalt und chef ist daher auf die Wiederholung
von Schnitt, Verbund und Auswahl zugunsten von ... verzichtet worden.
validtime select a.Nr, a.Name, a.Gehalt, b.Name
from Ang a, Ang b where a.ChefNr = b.Nr and a.Gehalt > 5900;
=⇒
insert into temp_tab (
select vt.coalescence(cast(multiset(
select nta1.val,
vt.intersection(nta1.ts, nta2.ts, nta3.ts, nta4.ts, ntb1.ts, ntb2.ts)
5
Theoretisch macht dieser Umweg über eine Einfügeoperation keinen großen Sinn, denn warum sollte eine
Selektionsanweisung mit schreibendem Datenbankzugriff in Kombination mit einer Einfügeanweisung zugelassen
sein, wenn dieselbe Selektionsanweisung alleine abgelehnt wird? Tatsächlich funktioniert diese Konstruktion jedoch
unter Oracle 8.1.5 und liefert in diesem Fall auch korrekte Ergebnisse. Eine wirklich gelungene Lösung wäre die
der eigentlichen Abfrage vorausgehende Konstruktion und Abspeicherung aller benötigten Zeitstempel. Wegen des
demonstrativen Charakters der hier dargestellten Übersetzungen soll darauf jedoch verzichtet werden. Fraglich
bleibt in diesem Zusammenhang, ob es sich bei der Freizügigkeit der Version 8.1.5 um ein Feature oder einen
Fehler handelt, denn Oracle 8.0.5 lehnt insert-Konstrukte der beschriebenen Art ab.
58
KAPITEL 4. PLANUNG UND ENTWURF DER IMPLEMENTATION
from table(a.nr) nta1, table(a.name) nta2, table(a.gehalt) nta3,
table(a.chefnr) nta4, table(b.nr) ntb1, table(b.name) ntb2
where vt.intersection(nta1.ts, nta2.ts, nta3.ts, nta4.ts, ntb1.ts, ntb2.ts)
is not null and nta3.val > 5900 and nta1.val = ntb1.val) as t_num_tabl))
as nr,
vt.coalescence(cast(multiset(
select nta2.val,
vt.intersection(nta1.ts, nta2.ts, nta3.ts, nta4.ts, ntb1.ts, ntb2.ts)
from table(a.nr) nta1, table(a.name) nta2, table(a.gehalt) nta3,
table(a.chefnr) nta4, table(b.nr) ntb1, table(b.name) ntb2
where vt.intersection(nta1.ts, nta2.ts, nta3.ts, nta4.ts, ntb1.ts, ntb2.ts)
is not null and nta3.val > 5900 and nta1.val = ntb1.val) as t_chr_tabl))
as name,
vt.coalescence(cast(multiset(
select nta3.val, ... ) as t_num_tabl)) as gehalt,
vt.coalescence(cast(multiset(
select ntb1.val, ... ) as t_chr_tabl)) as chef
from Ang a, Ang b);
select vt.output(nr) as nr, vt.output(name) as name,
vt.output(gehalt) as gehalt, vt.output(chef) as chef from temp_tab;
Augenscheinlich liefern Übersetzungen dieser Art schnell sehr umfangreiche und unübersichtliche Ergebnisse. Weiterhin ist der Aufwand für die Entschachtelung je Paarung komplexer
Objekte schlecht abzuschätzen. Um diesen zu verringern, kann man in dem obigen Beispiel für die
äußere Selektionsanweisung die folgende where-Klausel hinzufügen, um eine Vorauswahl unter
den komplexen Tupeln zu schaffen.
where exists (select * from table(a.gehalt) nta3 where nta3.val > 5900) and
exists (select * from table(a.chefnr) nta4 where nta4.val in
(select ntb1.val from table(b.nr) ntb1))
Nicht-Sequentielle Anfragen
Wie auch im Falle der nicht-sequentiellen Anfragen im einfach geschachtelten Speichermodell
soll zunächst die folgende Anfrage übersetzt werden. Auch hier ergibt sich als einzige Herausforderung die Berechnung der Operators valitime(a). Zu diesem Zweck wird der Funktion coalescence im folgenden Beispiel als Wert stets NULL übergeben, so daß aufgrund der
Wertgleichheit alle Zeitstempel verschmolzen werden. Die Berechnung wird wie in Abschnitt
4.2.1 dynamisch vorgenommen, effizienter wäre für physikalische komplexe Tupel die Ablage eines zusätzlichen Zeitstempels, der stets die verschmolzene Vereinigung aller Attributzeitstempel
enthält.
nonsequenced validtime
select * from Ang a where validtime(a) contains period [88, 96);
=⇒
4.2. VARIANTEN DES SPEICHERMODELLS
59
select * from Ang a where vt.intersection(vt.coalescence(cast(multiset(
select NULL, nt1.ts from table(a.nr) nt1 union
select NULL, nt2.ts from table(a.name) nt2 union
select NULL, nt3.ts from table(a.gehalt) nt3 union
select NULL, nt4.ts from table(a.chefnr) nt4) as t_num_tabl)), [88, 96))
= [88, 96);
Abschließend soll ein Ansatz für die Übersetzng der folgenden, bereits bekannten Anfrage
gegeben werden. Die sequentiellen Teilanfragen wurden dabei geringfügig von * auf s.Abt bzw.
t.Abt verändert, um die Übersetzung kürzer zu halten — der Sinn der Abfrage ändert sich dadurch nicht. Die Funktion intersection wird in der eingangs beschriebenen Variante benötigt,
die ein Objekt temporalen Datentyps mit einem Intervall zum Schnitt bringt. Abgesehen von der
aufwendigen Entschachtelung in den sequentiellen Teilanfragen läßt sich der nicht-sequentielle
Verbund einfach übersetzen. Es ergeben sich hier keine Probleme wie im Falle des einfachen
Speichermodells, da die komplexen Tupel ohne Rücksicht auf zeitlichen Überlapp kombiniert
werden können.
nonsequenced validtime select a.Name from Ang a,
(validtime select s.Abt from Ang s where s.Abt = ’Einkauf’ and s.Nr = a.Nr) b,
(validtime select t.Abt from Ang t where t.Abt = ’Verkauf’ and t.Nr = a.Nr) c
where validtime(b) contains period [1988, 1990) and
validtime(c) contains period [1999, 2000);
=⇒
select a.Name from Ang a,
(select vt.coalescence(cast(multiset(
select nts1.val, vt.intersection(nts1.ts, nts2.ts, nta1.ts)
from table(s.Abt) nts1, table(s.Nr) nts2, table(a.Nr) nta1
where vt.intersection(nts1.ts, nts2.ts, nta1.ts) is not null and
nts1.val = ’Einkauf’ and nts2.val = nta1.val) as t_chr_tabl)) as abt
from Ang s1) b,
(select vt.coalescence(cast(multiset(
select ntt1.val, vt.intersection(ntt1.ts, ntt2.ts, nta1.ts)
from table(t.Abt) ntt1, table(t.Nr) ntt2, table(a.Nr) nta1
where vt.intersection(ntt1.ts, ntt2.ts, nta1.ts) is not null and
ntt1.val = ’Verkauf’ and ntt2.val = nta1.val) as t_chr_tabl)) as abt
from Ang s1) c
where vt.intersection(b.Abt, [1988, 1990)) = [1988, 1990) and
vt.intersection(c.Abt, [1999, 2000)) = [1999, 2000);
4.2.3
Festlegung
Nach der prinzipiellen Vorstellung der beiden Speichermodelle, deren Präzisierung durch Übersetzungsbeispiele und Erläuterung von zusätzlichen Optionen wie Indizierung oder redundantem
Vorhalten von Zeitstempeln in den beiden vorangehenden Abschnitten, sollen nun deren Vorund Nachteile abgewogen und ein Modell für die Implementierung ausgewählt werden.
60
KAPITEL 4. PLANUNG UND ENTWURF DER IMPLEMENTATION
Zum Speichermodell mit einfacher Schachtelung lassen sich die Erkenntnisse auf die folgenden
wesentliche Punkte reduzieren:
• Darstellung durch einfache Datentypen, flache Darstellung; daher vermutlich geringere
Probleme mit Fehlerhaftigkeit des DBMS
• leicht temporal indizierbar
• übersetzte Anfragen haben übersichtlichen Umfang
• aufwendige Zuordung zu komplexen Tupeln mittels RWO-Nr.
• Redundanz der Daten
• zu erwartende Probleme bei nicht-sequentiellen Anfragen, z. B. Verbundbildung
Das Speichermodell mit zweifacher Schachtelung kann durch die nachfolgend aufgezählten
Erfahrungen charakterisiert werden:
• keine Redundanz der Daten
• einfache zeitliche Verschmelzung, da diese für einzelne Attribute vorgenommen wird
• modellinhärente Zuordnung zu komplexen Tupeln
• explizite Speicherung aller verwendeten Zeitstempel notwendig, wegen REFs
• Übersetzung liefert sehr umfangreiche Anfragen mit aufwendiger Entschachtelung
• komplizierte temporale Datentypen, daher verstärkt Probleme durch Fehlerhaftigkeit des
DBMS möglich
Insgesamt kann man sagen, daß keiner der beiden Kandidaten durch deutliche Vorteile hervorsticht. Jedoch erscheint das geschachtelte Speichermodell wegen der schnell sehr aufwendigen
Übersetzungen im sequentiellen Bereich und der Unwägbarkeiten der Leistungsfähigkeit der hier
in höherem Maße eingesetzten neuen objekt-relationalen Funktionen als die weniger erfolgversprechende Variante. Auch das Problem der stets explizit abzuspeichernden Zeitstempel, um
diese mit Referenzen belegen zu können, läßt sich kaum effizient vermeiden. Es soll daher für die
im Kapitel 5 beschriebene Implementation das Speichermodell mit einfacher Schachtelung zum
Einsatz kommen.
4.3
Schichtenarchitektur
In [TJB97] finden sich einige Konzepte zur Implementation einer temporalen Datenbanksprache
auf einem vorhandenen Datenbankmanagementsystem. Die dort als Schichtenarchitektur (layered architecture) bezeichnete Technik ist in Abbildung 4.6 dargestellt. Die Funktionalität dieser
Architektur ist in ihren Grundzügen schnell erläutert: Die Schicht oberhalb des DBMS nimmt
eine temporale Anfrage entgegen, analysiert und übersetzt diese mit Hilfe entsprechender Informationen aus der Datenbank in eine einzige SQL-92 Transaktion, um diese ohne weiteren
4.3. SCHICHTENARCHITEKTUR
61
temporale Anfrage Q
Fehlermeldung
Ergebnis
Schicht
Scanner
MetadatenManagement
Parser
AusgabeVerarbeitung
Code Generator
SQL-92 Anfrage, Q’
DBMS
Abbildung 4.6: Schichtenarchitektur temporaler Datenbanken (nach [TJB97])
Einfluß des Übersetzters auf dem DBMS auszuführen. Schließlich wird das Ergebnis zurückgeliefert, nachbearbeitet und ausgegeben.
Neben den hier sprachinhärenten Forderungen von Aufwärtskompabilität und temporaler
Aufwärtkompabilität und damit der stufenweisen Verfügbarkeit von temporaler Funktionalität
werden in [TJB97] vier weitere Ziele der dort verwendeten Schichtenarchitektur genannt. Dieses
ist erstens die möglichst weitgehende Verwendung der Funktionalität des unterliegenden DBMS,
so daß nur Features, die nicht im DBMS zu finden sind, neu implementiert werden müssen.
Zweitens wird die Beibehaltung aller wünschenswerten Eigenschaften des relationalen DBMS
genannt; es soll durch die temporale Erweiterung tatsächlich Funktionalität hinzugefügt werden.
Drittens soll Plattformunabhängigkeit erreicht werden, um die Erweiterung auf allen SQL-92fähigen DBMS verfügbar zu machen. Schließlich wird eine adäquate Performance angestrebt, genauer sollen nicht-temporale Anfragen ebenso schnell ablaufen wie im zugrundeliegenden DBMS
und temporale Anfragen in etwa der gleichen Zeit ausgeführt werden, wie entsprechende nichttemporale Anfragen auf einer nicht-temporalen Datenbank mit demselben Informationsgehalt.
Das Ziel der Plattformunabhängigkeit kann in der vorliegenden Arbeit nur eingeschränkt
übernommen werden, da die Implementation von vorherein auf die objekt-relationale Funktionalität von Oracle ausgerichtet ist. Insofern sollte eine Verwendung auf allen Plattformen mit
Oracle und der object option möglich sein. Das Ziel der Portierbarkeit kann aber hier dennoch
kaum als vordringlich bezeichnet werden, da es zunächst um die generelle Verfügbarkeit eines
Prototypen geht.
62
KAPITEL 4. PLANUNG UND ENTWURF DER IMPLEMENTATION
Die weitestgehende Verwendung der Funktionalität des unterliegenden DBMS ist sicherlich
auch hier anzustreben, wenngleich das Nachkommen dieser Forderung — wie schon in [TJB97]
festgestellt — vermutlich den Performancezielen entgegenwirken wird. Dennoch erscheint es gerade im Sinne einer Beschränkung des Entwicklungsumfangs angebracht, nach Möglichkeit der
Verwendung von vorhandener Datenbankfunktionalität gegenüber Neuentwicklungen den Vorrang zu geben.
Das zweite Ziel der Beibehaltung aller wünschenswerten Eigenschaften des eingesetzten relationalen DBMS, so daß durch die temporale Erweiterung tatsächlich Funktionalität hinzugefügt
wird, ist prinzipiell sinnvoll. Für eine prototypische Implementation erscheint jedoch die Erhaltung vorhandener nicht-temporaler Features nicht vordringlich, da es um die Möglichkeit von
Tests temporaler Funktionalität geht. Eher im Rahmen einer Kommerzialisierung wird man vom
neuen System die Erhaltung aller nicht-temporalen Features erwarten.
In [TJB97] findet man eine interessante Technik, um den Entwicklungsaufwand im Rahmen zu halten, gleichzeitig aber für aufwärtskompatible Sprachkonstrukte die komplette Funktionalität des unterliegenden DBMS zur Verfügung zu stellen. Mit Hilfe eines unvollständigen
Parsers (partial parser) werden nur temporale Anweisungen erkannt. Sobald beim Parsen einer
Anweisung ein Fehler auftritt, wird diese an das DBMS zur Ausführung weitergegeben in der
Annahme, es handele sich um eine aufwärtskompatible Anweisung. Auf diese Weise bedarf es
keiner eigenen Entwicklung eines SQL-92 Parsers, und gleichzeitig werden bei der Ausführung
von aufwärtskompatiblen Kommandos keine zwei Parser-Durchläufe mehr benötigt. Leider gehen
bei dieser Konstruktion fehlerhafte temporale und aufwärtskompatible Anweisungen denselben
Weg, so daß man bei fehlerhaften temporalen Kommandos wenig aussagekräftige Fehlermeldungen erhalten wird. Um dieses Problem zu umgehen, werden in [TJB97] die Anweisungen um die
Hinweise plain oder temporal ergänzt. So kann vorab entschieden werden, ob der eigene oder
der Parser des DBMS die Anweisung erhält. Durch diese Hinweise geht jedoch die (temporale)
Aufwärtskompatibilität verloren. Insgesamt erscheint es hier zur Reduzierung des Entwicklungsaufwandes sinnvoller, einen vollständigen Parser mit nur eingeschränkter SQL-92-Unterstützung
zu entwickeln, als einen unvollständigen Parser mit den o. g. Nachteilen in Kauf zu nehmen.
Insgesamt ist die in Abbildung 4.6 dargestellte Architektur für einen ersten Ansatz zur Modularisierung der Implementation dieser Arbeit nutzbar. Dem Code Generator sollten dabei
auch die Informationen des Metadaten-Management zur Verfügung stehen, da z. B. zur Unterscheidung einer Anweisung zwischen aufwärtskompatibel und temporal aufwärtskompatibel die
beteiligten Tabellen ausschlaggebend sind. Weiterhin sollten auch Scanner und Code Generator im Rahmen der lexikalen bzw. semantischen Analyse in der Lage sein, Fehlermeldungen zu
generieren.
Wie bereits eingangs argumentiert, ist die Plattformunabhängigkeit der Implementation kein
vorrangiges Ziel der vorliegenden Arbeit. Es wird daher kein besonderer Wert darauf gelegt, daß
der Code Generator reine SQL-92 Anweisungen liefert. Stattdessen ist die Verwendung Oraclespezifischer Features und insbesondere der Nutzung eigener Datenbankfunktionen vorgesehen.
Auf diese Weise können Funktionen — etwa in PL/SQL formuliert — innerhalb des DBMS
ausgeführt werden. Dadurch ist ein vereinfachter Umgang mit den beteiligten Objekten gewährleistet. Darüberhinaus erscheinen speicherplatzintensive Funktionen — z. B. Sortiervorgänge im
Rahmen einer temporalen Verschmelzung — besonders für größere Tabellen innerhalb des DBMS
besser aufgehoben, da außerhalb ein beträchtliches Maß an eigener Speicherverwaltung zu implementieren wäre. Es wird daher ein Teil des Codes innerhalb des DBMS in Form von Methoden
auf den verwendeten Objekten und weitergehenden Funktionen formuliert werden.
Kapitel 5
Implementation
In diesem Kapitel wird die prototypische Implementation der in Kapitel 3 formulierten temporalen Erweiterung SQLTE auf der Grundlage des im vorangehenden Kapitel entwickelten
Entwurfs beschrieben. Dabei wird zunächst ein Überblick des gesamten Programmablaufs gegeben. Anschließend erfolgt die Erläuterung der verwendeten Strukturen innerhalb der Datenbank
und des Übersetzungsprogramms außerhalb der Datenbank. Zum Abschluß des Kapitels werden erste praktische Erfahrungen mit der neuen temporalen Datenbank anhand umfangreicherer
Testdaten vorgestellt.
5.1
Programmübersicht
In Abschnitt 4.3 wurde die in [TJB97] beschriebene Schichtenarchitektur für temporale Datenbanken vorgestellt, an der sich die vorliegende Implementierung orientiert. Es wurde dort bereits
auf die Notwendigkeit einiger Veränderungen hingewiesen. Auf dieser Basis ergibt sich die Konstruktion wie in Abbildung 5.1 dargestellt: Zu sehen sind die sieben verwendeten Module, die
Datenbank mit den dort definierten Strukturen sowie der Datenfluß zwischen den genannten
Komponenten.
Der Programmablauf läßt sich wie folgt zusammenfassen. Nach dem Programmstart erfolgt
die Anmeldung an die Datenbank. Ist diese abgeschlossen, wird die Existenz der notwendigen
SQLTE-Datenbankstrukturen im Schema des angemeldeten Benutzers überprüft. Nach Abschluß
dieser einmaligen Initialisierung werden SQLTE-Kommandos entgegengenommen. Diese werden
zeichenweise zerlegt an den Scanner übermittelt, der einzelne Zeichen zu gültigen Symbolen
zusammenfaßt oder eine Fehlermeldung generiert, falls dieses nicht möglich ist. Die Symbole
überprüft der Parser nach dem Prinzip des rekursiven Abstiegs auf Syntaxübereinstimmung
zu SQLTE und erzeugt dabei eine Baumstruktur aus den gelesenen Symbolen. Mit Hilfe von
Metadaten aus der Datenbank wird dieser Parserbaum im Codegenerator semantisch erweitert,
um daraufhin rekursiv entlang der Knoten des Baumes die SQLTE-Befehle in SQL-Kommandos
zu übersetzen. Die so erzeugten Kommandos werden schließlich durch das Modul Oracle auf der
Datenbank ausgeführt. Bei Anfragen erfolgt die formatierte Ausgabe der Ergebnisse.
Die Funktionalität der Implementierung ist damit zweigeteilt: Der oben beschriebene Programmablauf vollzieht sich bis zur vollendeten Übersetzung im wesentlichen außerhalb der Datenbank. Danach werden die übersetzten Anweisungen mit Hilfe der innerhalb der Datenbank
63
64
KAPITEL 5. IMPLEMENTATION
Eingabe
SQLTE-Kommandos
als Eingabezeichen
Anmeldung
lexikalische
Fehler
Symbole
SQLTE
Initialisierungfehler
Parser
Parserbaum
Anmeldung
Oracle
DB-Zugriffe
Scanner
Ergebnisse
Metadaten
SQL-Kommandos
CodeGen
Anfrageergebnisse
SQLTE-Package
semantische
Fehler
syntaktische
Fehler
SQLTE-Objekte
Datenbank
Ausgabe
Abbildung 5.1: Datenflußdiagramm der SQLTE-Implementation
mit PL/SQL definierten Methoden und Funktionen ausgeführt. An dieser Zweiteilung der Funktionalität orientieren sich die folgenden Erläuterungen, d. h. es folgt zunächst der Abschnitt über
die Datenbankstrukturen, um anschließend die einzelnen Module zu erläutern.
5.2
Datenbankstrukturen
In diesem Abschnitt werden die Strukturen vorgestellt, die auf der Seite der Datenbank benötigt
werden, um die temporale Erweiterung SQLTE umzusetzen. Diese Strukturen bilden die Grundlage der temporalen Funktionalität und dienen dem in Abschnitt 5.3 beschriebenen Übersetzungsprogramm dazu, dem Anwender die komplette temporale Datenbanksprache zur Verfügung
zu stellen.
Die datenbankseitigen Erweiterungen werden sämtlich unter SQL und PL/SQL erstellt. Vor
der ersten Verwendung des SQLTE-Übersetzers werden diese durch die Ausführung der Skriptdatei sqlte.sql unter SQL*Plus im zugrundeliegenden Datenbankschema erzeugt. Es bedarf
dafür entsprechender Rechte des Datenbanknutzers im eigenen Schema, deren Existenz z. B.
durch die Anwendung der vordefinierte Rolle connect sichergestellt werden.
Nachfolgend werden zunächst grundsätzliche Festlegungen für die temporalen Strukturen
5.2. DATENBANKSTRUKTUREN
65
getroffen. Anschließend erfolgt eine Vorstellung der beteiligten Objekte und Funktionen. Die
Unterscheidung zwischen Methoden in Abschnitt 5.2.2 und Funktionen in Abschnitt 5.2.3 erscheint vielleicht verwunderlich. Sie ist jedoch erforderlich, da der Ansatz, möglichst die gesamte
Funktionalität objekt-orientiert, d. h. mit Hilfe von Objekten samt ihrer zugehörigen Methoden umzusetzen, leider keinen Erfolg gezeigt hat. Einige Programmteile lassen sich nicht durch
Methoden auf den entsprechenden Objekten, wohl aber als eigenständige Funktionen definieren
(s. a. Abschnitt 4.1).
5.2.1
Grundlegende Vereinbarungen
Es werden nun grundsätzliche Festlegungen vorgestellt, die Voraussetzung für das Verständnis
der Datentypen, Objekte und Funktionen sind. Sofern sich die Motivation der einzelnen Punkte
nicht unmittelbar aus den Überlegungen der vorangehenden Kapitel ergibt, erfolgen entsprechende Erläuterungen in den späteren Abschnitten, wenn die einzelnen Vereinbarungen benötigt
werden.
• Es werden physikalisch temporale Elemente als Zeitstempel auf Tupeln verwendet (s. a.
Speichermodell in Abschnitt 4.2.1). Temporale Elemente bestehen aus einer endlichen
Menge disjunkter temporaler Intervalle, die einander nicht berühren, d. h. die temporalen Elemente sind stets verschmolzen (s. Definition 2 in Abschnitt 2.2.1).
• Die Granularität wird durch den kürzesten darstellbaren Zeitpunkt von einer Sekunde
festgelegt.
• Der früheste darstellbare Zeitpunkt ’01.01.-4712 00:00:00’ wird als −∞ (beginning)
definiert und bezeichnet eine Gültigkeitszeit mit unbekanntem Anfangspunkt in der Vergangenheit. Dieser Zeitpunkt ist nur als Anfangspunkt temporaler Intervalle zugelassen.
• Der späteste darstellbare Zeitpunkt ’23.12.9999 23:59:59’ wird als ∞ (forever) definiert und bezeichnet eine Gültigkeitszeit mit unbekanntem Endpunkt in der Zukunft.
Dieser Zeitpunkt ist nur als Endpunkt temporaler Intervalle zugelassen (s. a. Definition 1
in Abschnitt 2.2.1).
• Ein komplexes Tupel der Sprache enthält stets die gesamte Historie eines Objektes der realen Welt. Daher müssen alle Attributausprägungen eines Tupels stets denselben Zeitraum
abdecken. Somit ist der kleinste und der größte Zeitpunkt für alle Attributausprägungen
eines Tupels identisch, und es sind zwischen diesen Zeitpunkten keine Lücken zugelassen.
5.2.2
Objekte
Es werden die Typen t interval type, t element tabl und t stamp type verwendet, wie sie
in Abschnitt 4.2.1 definiert und beschrieben sind. Zusätzlich vereinbart werden für Intervalle
die Methode ti intersection sowie für temporale Elemente die Methoden tp intersection,
ti intersection, te intersection und ti difference.
Dabei sind die intersection-Funktionen für die temporale Schnittbildung bei den verschiedenen Typkombinationen verantwortlich, z. B. bringt die Methode ti intersection des Typs
t stamp type ein Intervall mit einem temporalen Element zum Schnitt. Es steht somit ti für
ein Intervall, tp bedeutet Zeitpunkt und te meint ein temporales Element.
66
KAPITEL 5. IMPLEMENTATION
Die Differenzbildung zwischen zwei Intervallen ist nicht als Methode von t interval type zu
finden (nach der obigen Namensgebung hätte diese dann ti difference geheißen), da das Ergebnis ein temporales Element also vom Typ t stamp type gewesen wäre. Die Methodendefinition
benötigte daher eine wechselseitige Verwendung der Typen t interval type und t stamp type,
was in Oracle leider nicht vorgesehen ist (s. a. Abschnitt 4.1). Statt der Methode wurde die Funktion ti subtraction formuliert (s. u.), deren Bezeichnung nicht ganz mit der Namensgebung der
Methoden harmoniert.
Eine Übersicht der Methoden mit detaillierter Beschreibung der vereinbarten Parameter
findet sich in Anhang B.
5.2.3
Funktionen
Alle benötigten Funktionen außerhalb der Methoden aus Abschnitt 5.2.2 werden in dem Package
sqlte formuliert. Nachfolgend sollen diese kurz vorgestellt werden, präzise Definitionen finden
sich wiederum in Anhang B.
ti subtraction liefert die Differenz zweier temporaler Intervalle in dem Rückgabewert von Typ
t stamp type. Diese Funktion kommt z. B. beim temporalen delete oder update zum
Einsatz, wenn Teile von Zeitstempeln ausgeblendet werden sollen.
ts2string wandelt ein temporales Element durch Aneinanderreihen der vorhandenen Intervalle
in eine Zeichenkette um. Dabei werden entsprechende Trennzeichen eingefügt.
Die Funktion dient als Provisorium zur objektfreien Übergabe von temporalen Elementen
an das Übersetzungsprogramm in Pro*C (s. a. Abschnitt 5.3.7).
te intersection macht von der Funktion des overloading Gebrauch (s. a. Abschnitt 4.2.2) und
existiert in vier verschiedenen Versionen: Auf diese Weise können bis zu fuenf temporale
Elemente zum Schnitt gebracht werden.
Die Funktion basiert auf der Methode te intersection und stellt nicht wirklich neue
Funktionalität zur Verfügung, sondern dient nur einer bequemeren Übersetzung. Neben
der hier vorgesehenen variablen Anzahl der Eingabeparameter läßt sich eine Funktion
im Gegensatz zu einer Methode ohne explizite Angabe eines Tabellenalias aufrufen, d.h.
die Formulierung ts1.te intersection(ts2) ist nicht ausreichend, sondern es bedarf
stattdessen alias.ts1.te intersection(ts2).
Die temporale Schnittbildung kommt bei der Übersetzung von SQLTE vielfältig zum Einsatz, so z. B. beim sequentiellen Verbund oder bei sequentiellen Subqueries.
5.2.4
Temporäre Tabellen
Die Übersetzung der SQLTE-Kommandos liefert in vielen Fällen Resultate, die sich über mehrere
SQL-Befehle unter Verwendung entsprechender Zwischenergebnisse erstrecken. Für die Ablage
dieser Zwischenergebnisse bietet sich die Verwendung von temporären Tabellen als neues Feature
von Oracle 8.1.5 an (s. a. Abschnitt 4.1).
Von vornherein fest vereinbart wird daher eine temporäre Tabelle mit Hilfe der folgenden
Anweisung. Sie nimmt nur Werte vom Typ rowid auf und kann somit zur Auswahl bestimmter
Tupel etwa im Rahmen von temporalen DML-Anweisungen dienen. Weiterhin werden während
5.3. PROGRAMMBESCHREIBUNG
Oracle
67
CodeGen
SQLTE
Info
Ausgabe
Prompt
Fehler
Ergebnisse
Eingabe
Kommando Anmeldung
Parser
Scanner
Abbildung 5.2: Abhängigkeitsgraph der Module
der Übersetzung dynamisch temporäre Tabellen erzeugt, die sich mit Hilfe der Anweisung create
table as bezüglich Attributname, -anzahl und -typ nach der jeweiligen Anfrage richten (s. a.
Abschnitt 5.3.6).
create global temporary table sqlte_rowidtemp_ (tupel rowid)
on commit delete rows;
5.3
Programmbeschreibung
In Abschnitt 5.1 wurde anhand der Abbildung 5.1 bereits ein Überblick über den Ablauf des
SQLTE-Übersetzers gegeben. Nachdem im vorangegangenen Abschnitt die innerhalb der Datenbank benötigten Strukturen vorgestellt wurden, erfolgt nun die Beschreibung des eigentlichen
Übersetzungsprogramms außerhalb der Datenbank. Dieses ist in der Sprache C entwickelt und
mit Hilfe des Oracle Präprozessors Pro*C/C++ Release 8.1.5 sowie des GNU C Compilers GCC
Version 2 kompiliert worden.
Abbildung 5.2 zeigt den Abhängigkeitsgraph der Module. Wie üblich bezeichnet dort ein Pfeil
von Modul A nach Modul B die Beziehung Modul A verwendet Funktion von Modul B. Die Module Eingabe und Ausgabe sind in der Darstellung ihrerseits unterteilt, um die Zusammenhänge
etwas zu präzisieren. Auf diese Unterteilung wird im Abschnitt 5.3.2 näher eingegangen.
Nachfolgend werden nun zunächst global verwendete Definitionen und Variablen vorgestellt,
um dann die Funktionsweise der einzelnen Module zu erläutern. Diese Erläuterungen beschränken
68
KAPITEL 5. IMPLEMENTATION
sich auf die wichtigsten Aufgaben der Module, eine umfassende Auflistung aller verwendeten
Funktionen und Variablen ist in Anhang B zu finden.
5.3.1
Globale Strukturen
Die Datei sqlte.h nimmt alle globalen Definitionen auf, d. h. sie enthält modulübergreifende
Konstanten, Typen, Variablen und Funktionen. Die wichtigste globale Datenstruktur ist ohne
Zweifel der Parserbaum. Dieser wird durch den Parser während der lexikalischen Analyse aufgebaut und enthält die vom Benutzer eingegebenen Symbole. In den Knoten des Baumes werden
diese Symbole gemäß der Syntaxgraphen aus Anhang A zu größeren syntaktischen Einheiten
zusammengefaßt. Die Übersetzung kann dann rekursiv mit einer Übersetzungsfunktion je Knotenart erfolgen. Der Baum wird mit Hilfe der Typen tdKnoten, tdSymbol und der globalen
Variable pWurzel wie folgt implementiert.
typedef struct sKnoten {
struct sSymbol {
WORD
wSymbol;
char
szText[STRINGLAENGE];
WORD
wTyp;
WORD
wZeile;
WORD
wSpalte;
struct sKnoten* pWeiter;
struct sKnoten* pTimeflag;
} s[MAXSYM];
WORD nSymbole;
WORD wKnoten;
WORD wTyp;
} tdKnoten;
typedef struct sSymbol tdSymbol;
tdKnoten* pWurzel;
Wie man der Definition entnehmen kann, enthält jeder Knoten höchstens MAXSYM Symbole,
die aktuelle Anzahl der Symbole nimmt die Variable nSymbole auf. In wKnoten wird die Art des
Knotens gemäß der Syntaxgraphen aus Anhang A gespeichert, dazu existieren die globalen Konstanten beginnend mit ntk (z. B. ntkSTATEMENT, ntkDML, ntkQUERY). Zusätzliche Informationen
zur Übersetzung der Knoten können in wTyp abgelegt werden, so z. B. welche Art von temporaler
Unterstützung gewünscht ist. Dazu dienen die globalen Konstanten, die mit typ beginnen (z. B.
typTEMPORAL, typKOMMANDO, typSEQUENCED). Die Baumstruktur wird in Abschnitt 5.3.5 anhand
eines Beispiels näher erläutert.
Jedes Symbol innerhalb eines Knotens verweist durch den Zeiger pWeiter entweder zu einem
weiterem Knoten, oder es enthält Informationen zu dem entsprechenden Symbol. Die Verwendung der weiteren Variablen des Typs tdSymbol wird in den jeweiligen Modulen näher erläutert.
Hier sei nur ein kurzer Überblick gegeben: wSymbol nimmt für jedes Terminalsymbol eine über
Konstanten festgelegte Ganzzahl auf (z. B. tkSELECT, tkFROM, tkWHERE — alle hier verwendeten Konstanten beginnen mit tk). In den Variablen wZeile und wSpalte wird die Position des
5.3. PROGRAMMBESCHREIBUNG
69
Symbols in der Benutzereingabe abgelegt, um im Modul Parser aussagekräftige Fehlermeldungen
generieren zu können. Die Zeichenkette szText speichert die Benutzereingabe des betreffenden
Symbols, benötigt wird diese weniger für terminale Symbole als für Identifier und Konstanten.
Der Zeiger pTimeflag verweist bei Bedarf auf einen anderen Knoten des Baumes, nämlich auf
einen für die Übersetzung relevanten Knoten der Art ntkTIMEFLAG. In wTyp werden schließlich, ebenso wie in wTyp bei Knoten, mit Hilfe von bitweisen oder-Verknüpfungen von Konstanten verschiedene Zusatzinformationen zum Symbol gesammelt (z. B. typNONTEMP, typTABLEID,
typINLINEVIEW — alle hier verwendeten Konstanten beginnen mit typ).
Im Zusammenhang mit dem Aufbau des Parserbaumes existiert eine weitere globale Variable,
namentlich der Zeiger pSymbol, der stets auf das zuletzt gescannte Symbol verweist.
Schließlich ist pSQL92 als dritte und letzte globale Variable für die Aufnahme der übersetzten Anweisungen zuständig. Sie stellt als Zeiger vom Typ tdKommando eine verkettete Liste
dar und ermöglicht so die Aufnahme einer dynamischen Anzahl von Zeichenketten der Länge
KOMMANDOLAENGE. Diese werden i. d. R. nach erfolgreicher Übersetzung in der vorgegebenen Reihenfolge ausgeführt. Eine Ausnahme kann dabei die Anlage von temporären Tabellen bilden,
bei denen die Ausführung schon während der Übersetzung stattfindet und die Variable bSofort
zum Einsatz kommt. Die Definition geschieht wie folgt.
typedef struct
char
BOOL
struct sKom*
} tdKommando;
sKom {
szText[KOMMANDOLAENGE];
bSofort;
pWeiter;
tdKommando* pSQL92;
Wie eingangs erwähnt, werden die Prototypen der globalen Funktionen ebenfalls in der Datei
sqlte.h deklariert. Diese Deklarationen sind sortiert nach den Modulen, in denen die zugehörigen Definitionen erfolgen. Die Beschreibung der globalen Funktionen befindet sich jeweils in den
nachfolgenden Abschnitten, eine Übersicht liefert Tabelle 5.1.
5.3.2
Ein- und Ausgabeverarbeitung
Die Ein- und Ausgabeverarbeitung des SQLTE-Übersetzers erfolgt durch die beiden Module
Eingabe und Ausgabe. Wie bereits in Abbildung 5.2 zu sehen ist, lassen sich beide Module
etwas feiner unterteilen, nämlich Eingabe in Kommando und Anmeldung sowie Ausgabe in Info,
Prompt, Fehler und Ergebnisse. Diese Unterteilung wird innerhalb der Module durch die Namensgebung der globalen Funktionen berücksichtigt, d. h. für die Ausgabe existieren die Funktionen ZeigeInfo, ZeigePrompt, ZeigeErgebnis, ZeigeFehler, und für die Eingabe stehen
KommandoZeichen, KommandoLeer sowie AnmeldungZeichen zur Verfügung. Weiterhin existieren die globalen Funktionen InitEingabe, InitAusgabe und KommandoReInit.
Diese inhaltliche Trennung der Eingabequellen und Ausgabeziele ermöglicht eine einfache
Umsetzung einer anspruchsvollen Benutzeroberfläche. In der vorliegenden prototypischen Implementation wird von dieser Möglichkeit zunächst kein Gebrauch gemacht. Stattdessen erfolgt
die Ein- und Ausgabe über die Standardgeräte stdin und stdout, wie von SQL*Plus gewohnt.
Durch die Kapselung der Ein- und Ausgabefunktionen in eigenen Modulen und die beschriebene
70
KAPITEL 5. IMPLEMENTATION
Modul
Funktionsprototyp
Ausgabe
void
void
void
void
void
void
char
BOOL
void
char
BOOL
WORD
WORD
WORD
WORD
BOOL
void
void
BOOL
BOOL
BOOL
BOOL
BOOL
BOOL
void
BOOL
Eingabe
Scanner
Parser
CodeGen
Oracle
InitAusgabe(FILE *);
ZeigeInfo(char *);
ZeigeFehler(char *);
ZeigeErgebnis(char *);
ZeigePrompt(void);
InitEingabe(FILE *);
KommandoZeichen(void);
KommandoLeer(void);
KommandoReInit(void);
AnmeldungZeichen(void);
ScannerInit(void);
Scanner(void);
Text2Symbol(char *);
Symbol2Text(char *);
Parser(void);
CodeGen(void);
sqlConnect(char *, char *);
sqlDisconnect(void);
sqlExecute(char *);
sqlCommit(void);
sqlTEexists(void);
sqlTableTemporal(char *);
sqlTableExists(char *);
sqlStrcatColumns(char *, char *);
sqlDropTempTab(void);
sqlGetSysdate(char *);
Tabelle 5.1: Übersicht der globalen Funktionen
5.3. PROGRAMMBESCHREIBUNG
71
Unterteilung dieser Module sollte es jedoch leicht möglich sein, später etwa einen unabhängigen
Anmeldedialog und ein mehrfach unterteiltes Ausgabefenster hinzuzufügen.
5.3.3
Hauptprogramm
Das Modul SQLTE enthält das Hauptprogramm und initiiert damit alle weiteren Funktionen
(s. a. Abbildung 5.1 und 5.2). Zunächst erfolgt die Initialisierung von Eingabe und Ausgabe, die
Anmeldung an die Datenbank und die Überprüfung auf vorhandene SQLTE-Datenbankstrukturen. Nach Ausgabe des Kommandoeingabeprompts wird der Parser aufgerufen. Ist dieser erfolgreich, werden die eingegebenen Kommandos übersetzt und nach fehlerfreier Übersetzung
ausgeführt. Wird kein Programmende gewünscht, wiederholt sich der beschriebene Ablauf durch
Anzeigen des Kommandoeingabeprompts.
Die dabei zur Verfügung gestellte Benutzeroberfläche ist wegen des prototypischen Charakters der Implementation relativ einfach und an SQL*Plus angelehnt (s. a. Abschnitt 5.3.2). Der
Benutzer gibt nach der Anmeldung an die Oracle-Datenbank beim Eingabeprompt SQLTE> das
temporale Kommando gemäß der Syntax in Anhang A ein und übergibt dieses durch Eingabe von EOF (end of file — die Tastatureingabe erfolgt häufig mit Ctrl-D) dem Programm. Die
ausschließliche Eingabe von EOF verursacht die Beendigung des Programms.
5.3.4
Lexikalische Analyse
Das Modul Scanner stellt die globalen Funktionen ScannerInit, Scanner, Text2Symbol und
Symbol2Text zur Verfügung. Die beiden letztgenannten Funktionen ermöglichen mit Hilfe der
nachfolgend definierten lokalen Datenstruktur die Zuordnung der Benutzereingaben in Textform zu den Konstanten der terminalen Symbole der Sprache und umgekehrt. Dabei ist vor
allem die Übersetzungsrichtung der Funktion Text2Symbol hilfreich, da durch sie jede gescannte
Benutzereingabe einmalig in eine Symbolkonstante umgewandelt wird, so daß im weiteren Programmverlauf beispielsweise Vergleiche bequem durch Konstanten ausgeführt werden können.
Die Funktion der Rückübersetzung von Symbol2Text wird für die Generierung von Fehlermeldungen verwendet, um darauf hinzuweisen, daß einzelne Symbole an bestimmten Eingabepositionen erwartet werden.
static struct {
char szText[STRINGLAENGE];
WORD wKonstante;
} sTerminal[] = {
"NONSEQUENCED",
"VALIDTIME",
"SELECT",
"INSERT",
"DELETE",
"UPDATE",
";",
"(",
")",
(...)
tkNONSEQUENCED,
tkVALIDTIME,
tkSELECT,
tkINSERT,
tkDELETE,
tkUPDATE,
tkSEMICOLON,
tkLRBRACKET,
tkRRBRACKET,
72
KAPITEL 5. IMPLEMENTATION
"IS",
STOPMRK,
tkIS,
-1
};
Die Funktion ScannerInit besorgt durch Belegen lokaler Variablen und Einlesen des ersten
Eingabezeichens die Initialisierung des Scanners und wird je Benutzereingabe einmal durch das
Modul Parser aufgerufen. Das eigentliche Einlesen der Benutzereingaben übernimmt die Funktion Scanner. Diese wird aus dem Modul Parser aufgerufen und verlangt als Parameter einen
Zeiger auf den aktuellen Knoten des Parserbaums. Die Funktion nimmt die nächsten Eingabezeichen entgegen und faßt diese zu gültigen Symbolen der Sprache zusammen. Dabei werden die
gesammelten Informationen — namentlich Symbolkonstante, Texteingabe sowie Zeile und Spalte
des Symbols in der Benutzereingabe — lokal in einer Variable vom Typ tdSymbol zwischengespeichert. Beim nächsten Aufruf von Scanner werden diese Symbolinformationen schließlich im
aktuellen Knoten des Parserbaumes abgelegt. Der Symboleintrag im Parserbaum erfolgt also
stets um ein Symbol zeitverzögert, so daß der Parser das aktuelle Symbol zunächst analysieren
kann und ggf. eine Verzweigung im Parserbaum vornehmen kann, ehe das Symbol abgespeichert
wird. Der Eintrag des letzten gültigen Symbols erfolgt durch den letzten Aufruf des Scanners,
bei dem das Pseudosymbol tkEMPTY gelesen wird.
Der Scanner verwendet die drei lokalen Hilfsfunktionen leseLeer, IdentifierTest und
ScannerFehler. Die erste wird zum Überlesen syntaktisch irrelevanter Eingabezeichen (whitespaces — Tabulatoren, Leerzeichen und Zeilenvorschübe) verwendet.
Die Funktion IdentifierTest entscheidet bei einer Benutzereingabe, die keiner Symbolkonstante zugeordnet werden kann, um welche Art von Eingabe es sich handelt, d. h. sie liefert eine
der Konstanten tkIDENTIFIER, tkINTEGERVAL, tkFLOATVAL oder tkSTRINGVAL.
Die Funktion ScannerFehler gibt schließlich Fehlermeldungen aus. Es handelt sich dabei
um interne Fehler, etwa zuviele Symbole je Knoten oder zu lange Identifier, so daß diese nicht
im Rahmen der syntaktischen Fehlermeldungen des Parsers generiert werden.
5.3.5
Syntaktische Analyse
Die einzige globale Funktion des Moduls Parser ist die namensgleiche Funktion Parser. Diese
wird vom Hauptprogramm aus einmal je Benutzereingabe aufgerufen. Sie erhält keine Parameter
und baut ausgehend von der globalen Variable pWurzel den Parserbaum zur Benutzereingabe
auf. Dabei wird nach dem Prinzip des rekursiven Abstiegs (recursive descent) die syntaktische
Korrektheit der Eingabe gemäß der Syntaxgraphen aus Anhang A überprüft.
In Abbildung 5.3 ist die schematische Darstellung eines Parserbaums am Beispiel der untenstehenden Anfrage aus Abschnitt 3.2.2 zu sehen. Dargestellt sind dort die Anzahl der Symbole
je Knoten, die Knotenart und die Klartexte der Symbole — man vergleiche hierzu die Definition
der zugehörigen Datenstrukturen in Abschnitt 5.3.1.
select * from (
nonsequenced validtime select * from Ang a
where validtime(a) contains period [’05.10.1999’, now));
5.3. PROGRAMMBESCHREIBUNG
2 ntkSTATEMENT
73
;
1 ntkQUERY
7 ntkSFW select * from (
2 ntkTIMEFLAG nonsequenced validtime
)
7 ntkSFW select * from ang a where
3 ntkCONDEXP
4 ntkSCALAREXP validtime ( a )
1 ntkCONDOP contains
6 ntkSCALAREXP period [
,
...
)
...
Abbildung 5.3: Schematische Darstellung eines Parserbaums
Für den eigentlichen rekursiven Abstieg werden die folgenden lokalen Funktionen verwendet,
die alle mit einem Zeiger auf den aktuellen Knoten des Parserbaums als Parameter aufgerufen werden. Der Aufbau dieser Funktionen ist dabei prinzipiell identisch: Zu Beginn wird eine
Verzweigung des Knotens vorgenommen, so daß jede Funktion genau einem Syntaxgraphen und
einer ntk-Konstante entspricht. Mit Hilfe dieser Konstante wird nach der Verzweigung des Knotens die Knotenart abgelegt. Dann erfolgt das eigentliche Parsen, d. h. das aktuelle Token wird
interpretiert und entweder akzeptiert oder durch eine Fehlermeldung abgelehnt. Weiterhin wird
mit Hilfe der globalen Funktion Scanner das nächste Token entnommen und so fort. Dabei liest
jede der Funktionen zum Abschluß ein Token und analysiert dieses nicht, so daß zu Beginn jeder
Parserfunktion stets von der Existenz eines neuen Token ausgegangen wird. Das aktuelle Token
wird in der lokalen Variable wToken abgelegt.
void
void
void
void
void
void
void
void
void
void
void
pStatement(void);
pTimeflag(tdKnoten*);
pQuery(tdKnoten*);
pSFW(tdKnoten*);
pDML(tdKnoten*);
pDDL(tdKnoten*);
pDatatype(tdKnoten*);
pCondExp(tdKnoten*);
pCondOp(tdKnoten*);
pScalarExp(tdKnoten*);
pTimestamp(tdKnoten*);
74
KAPITEL 5. IMPLEMENTATION
void pConstant(tdKnoten*);
Wie der obigen Definition zu entnehmen ist, weicht die Funktion pStatement etwas von dem
beschriebenen Schema der Parserfunktionen ab, da diese den rekursiven Abstieg initiiert. Sie besitzt deshalb keinen Parameter und nimmt statt der Verzweigung des Knotens die Initialisierung
der globalen Variable pWurzel vor. Außerdem ist es hier notwendig, zu Beginn der Funktion ein
Token einzulesen.
Einige Parserfunktionen besitzen eine etwas erweiterte Funktionalität, um Zusatzinformationen für die spätere Übersetzung abzulegen. So werden z. B. in der Funktion pTimeflag ein Zeiger
auf eben diesen Timeflag-Knoten für das erste Symbol im nachfolgenden Knoten erzeugt und der
Typ des nachfolgenden Kommandos abgelegt (typSEQ oder typNONSEQ). Auch die Konstanten
typINLINEVIEW, typTABLEID, typJOIN, typTEMP und typNONTEMP werden zur Kennzeichung von
Symbolen oder Knoten verwendet. Auf deren Einsatz wird in Abschnitt 5.3.6 näher eingegangen,
er dürfte sich aber auch durch die Namensgebung der Konstanten erklären: typTABLEID bezeichnet ein Symbol, das eine Tabelle identizifiert, typINLINEVIEW wird für Symbole mit Verweis auf
eine Unteranfrage verwendet. Die Konstante typJOIN zeichnet eine Anfrage als verbundbildend
aus, während typTEMP und typNONTEMP sowohl für Symbole als auch für Knoten eingesetzt
werden, um auf temporale Funktionalität hinzuweisen.
Über die Parserfunktionen hinaus finden die vier lokalen Hilfsfunktionen ParserFehler,
neuerKnoten, verzweigeKnoten und loescheKnoten Verwendung. Die Funktion ParserFehler
generiert eine Fehlermeldung entweder mit Ausgabe einer Zeichenkette oder dem Hinweis auf
ein fehlendes Symbol, je nachdem, ob sie mit einer Fehlernummer oder einer Symbolkonstante
als Parameter aufgerufen wird. Durch die Verwendung der lokalen booleschen Variable bFehler
wird nur der erste syntaktische Fehler angezeigt.
Die Funktionalität der drei o. g. Funktionen zur Knotenverwaltung ist nicht sehr umfangreich
und dürfte sich aus deren Bezeichnungen schließen lassen.
5.3.6
Semantische Analyse und Übersetzung
Das Modul CodeGen übernimmt die Übersetzung der Benutzereingabe in eine oder mehrere
SQL-Anweisungen, die vom zugrundeliegenden DBMS in einer Transaktion ausfgeführt werden.
Zur Erläuterung des Moduls sollen nachfolgend zunächst allgemein die Aufgaben der einzelnen
Funktionen vorgestellt werden, um anschließend mit Hilfe der Übersetzungsschemata der verschieden Knotenarten in Tabelle 5.2 bis 5.7 näher auf die Details der Übersetzung eingehen zu
können.
Modulbeschreibung
Die einzige globale Funktion des Moduls ist CodeGen, welche die zur Übersetzung des Parserbaums notwendigen Initialisierungen vornimmt, durch ErgaenzeKnoten dem Parserbaum semantische Informationen hinzufügt und den rekursiv ausgeführten Übersetzungsvorgang durch
die lokale Funktion tStatement initiiert.
Die lokale Funktion ErgaenzeKnoten beinhaltet dabei einen Teil der semantischen Analyse,
die sich im wesentlichen auf die temporalen Erweiterungen bezieht, da viele Fehlermeldungen
— z. B. fehlerhafte Spaltenbezeichnungen — nach der Übersetzung von Oracle generiert werden. Diese Ergänzung wird ebenfalls rekursiv je Knoten vorgenommen. Es werden Tabellen und
5.3. PROGRAMMBESCHREIBUNG
75
Anfragen auf den Grad ihrer temporalen Funktionalität hin überprüft und die zugehörigen Knoten entsprechend klassifiziert. Dazu werden die Konstanten typTEMP, typNONTEMP, typUC und
typTUC vergeben, die zusammen mit den Typinformationen aus dem Parservorgang typSEQ und
typNONSEQ den späteren Übersetzungsvorgang vereinfachen. Bei Typunverträglichkeiten oder
bei Anwendung noch nicht unterstützter Funktionalität (z. B. nonsequenced-Konstrukte) werden entsprechende Fehlermeldungen generiert und die Übersetzung abgelehnt.
Weiterhin kommen die folgenden lokalen Funktionen zum Einsatz. Bei den letzten beiden
handelt es sich um Hilfsfunktionen zur Verwaltung der verketteten Listenstruktur vom Typ
tdKommando, die über die globale Variable pSQL92 die Übersetzungsergebnisse aufnimmt. Die
sieben Funktionen tQuery bis tCondExp übernehmen die eigentliche Übersetzung und haben jeweils die Aufgabe, ihrem Namen zugehörige Knotenarten zu übersetzen. Für Knoten wie z. B. der
Art ntkDATATYPE oder ntkCONSTANT, die keine Übersetzungsfunkton besitzen, findet derzeit noch
keine Übersetzung statt, d. h. der Inhalt der Knoten wird unverändert in die SQL-Anweisungen
übernommen. Die Funktionsweise der einzelnen Übersetzungsfunktionen wird später in diesem
Abschnitt anhand der Übersetzungsschemata erläutert.
BOOL
BOOL
BOOL
BOOL
BOOL
BOOL
BOOL
BOOL
void
void
Klartext(tdSymbol**, char *, tdSymbol*, WORD, WORD);
tQuery(tdKnoten*, char*);
tDDL(tdKnoten*, char*);
tDML(tdKnoten*, char*);
tSFW(tdKnoten*, char*);
tTimestamp(tdKnoten*, char*);
tTimeflag(tdKnoten*, char*);
tCondExp(tdKnoten*, char*);
loescheKommando(tdKommando* p);
neuesKommando(char*, BOOL);
Die lokale Funktion Klartext übernimmt die Koordination der Übersetzungen. Dazu werden
sequentiell, beginnend bei einem gegebenen Symbol eines festen Knotens, alle Symbole übersetzt,
bis die Abbruchbedingung erfüllt ist, d. h. eine bestimmte Anzahl von Symbolen durchlaufen
wurde oder ein gesuchtes Symbol erreicht wurde. Dabei werden Symbole ohne Verzweigung im
Klartext kopiert und an die gegebene Zeichenkette angehängt. Bei Symbolen mit Verzweigung
wird der zugehörige Nachfolgerknoten übersetzt und zwar wiederum mit der Funktion Klartext
bzw. mit einer spezifischen Übersetzungsfunktion, falls zu der Knotenart eine solche existiert.
Als Rückgabewert wird stets der Erfolg der vorgenommen Übersetzung in Form einer booleschen
Variable geliefert.
Verwendet man also die Funktion Klartext für den Wurzelknoten, so wird der gesamte
Inhalt des Parserbaums unter Berücksichtigung spezifischer Übersetzungsfunktionen rekursiv
übersetzt. Man könnte daher als Beispiel die o. g. Funktion tStatement zur Initiierung des
Übersetzungsvorgangs nur durch die eine folgende Anweisung formulieren. Tatsächlich bedarf es
hier spezifischer Übersetzungen, weshalb die alleinige Anwendung der Funktion Klartext nicht
ausreicht (s. a. Abschnitt 5.3.6).
return(Klartext(NULL, szKommando, (*pWurzel).s, (*pWurzel).nSymbole-1, 0));
Der Wurzelknoten würde von seinem ersten Symbol an ((*pWurzel).s) bis zum Erreichen
des vorletzten Symbols ((*pWurzel).nSymbole-1 — ohne das obligatorische abschließende Se-
76
KAPITEL 5. IMPLEMENTATION
Bedingung
SQLTE =⇒ SQL92
tkDROP und
typUC
tkDROP und
typTUC
drop table <tab>
=⇒ drop table <tab>
drop table <tab>
=⇒ drop table <tab>
drop view <tab> tuc
create <tab> ({<col> <ntkDATATYPE> [,]})
=⇒ create <tab> ({<col> <datatype> [,]})
create <tab> ({<col> <ntkDATATYPE> [,]}) as validtime
=⇒ create <tab> (rwo nr number, ts t stamp type,
{<col> <ntkDATATYPE> [,]})
create view <tab> tuc as select {<col> [,]} from <tab> t
where t.ts .tp intersection(sysdate) is not null
tkCREATE und
typNONTEMP
tkCREATE und
typTEMP
Tabelle 5.2: Übersetzungsschema für ntkDDL-Knoten
mikolon) übersetzt. Die Übersetzung würde dabei, neben mittels neuesKommando dynamisch
generierter zusätzlicher Befehle, an die Zeichenkette szKommando angehängt.
Übersetzung
Wie soeben erläutert, befindet sich die eigentliche Funktionalität zur Übersetzung in den Funktionen tQuery bis tCondExp. Die Vorgehensweise in diesen Funktionen basiert auf den Übersetzungsschemata, die in den Tabellen 5.2 bis 5.7 dargestellt sind. Diese Darstellung erfolgt in
leicht formalisierter Weise. Wenngleich diese Formalisierung — etwa im Vergleich zur erweiterten
Backus-Naur-Form (EBNF) — etwas abkürzend und damit unpräzise ist, dürfte sie zusammen
mit den Syntaxgraphen aus Anhang A gut verständlich sein. Es kommen dabei, neben den terminalen Symbolen der Sprache, spitze, eckige und geschweifte Klammern vor, um Platzhalter,
Optionen und Mehrfachvorkommen darzustellen. Der senkrechte Strich wird wie üblich für die
exklusive Auswahl zwischen verschiedenen Varianten verwendet. Terminale Symbole werden ohne kennzeichnende Anführungszeichen dargestellt, so daß runde Klammern nur als terminale
Symbole vorkommen.
Die Darstellung erfolgt stets der Art sqlte =⇒ sql92. Erscheinen auf der SQL92-Seite nicht
übersetzte Knoten in spitzen Klammern (z. B. <ntkTIMEFLAG>), so ist die zugehörige Übersetzung des Knotens gemeint. Das Ergebnis der Übersetzung ist häufig mehrzeilig, es stellt dann
eine mehrere Kommandos umfassende Transaktion dar. Diese sind durch entsprechende Zeileneinrückungen gekennzeichnet, die Ausführung erfolgt in der dargestellten Reihenfolge. Das
letzte Kommando wird von den Übersetzungsfunktionen direkt an die angegebene Zeichenkette
angehängt, alle vorherigen Befehle erhalten dynamisch Plätze in der Kommandoliste (s. a. Modulbeschreibung zu Beginn dieses Abschnitts). Kommen in der Übersetzung temporäre Tabellen
zum Einsatz1 , so werden diese mit einem Index versehen, d. h. sqlte tempj meint die nächste
noch nicht belegte Tabelle dieser Art.
1
Temporäre Tabellen müssen hier nicht notwendiger Weise temporäre Tabellen im Sinne von Oracle, d. h.
durch create global temporary table angelegt, sein. Auch gewöhnliche Tabellen werden in dem Sinne temporär
verwendet, daß sie nach der Transaktion wieder entfernt werden. Das liegt daran, daß Oracle-temporäre Tabellen
wegen ihres Unvermögens, benutzerdefinierte Typen aufzunehmen, nicht die benötigte Flexibilität mitbringen.
tkDELETE und typSEQ
tkDELETE und typTUC
tkDELETE und typUC
tkUPDATE und typSEQ
tkUPDATE und typUC
Tabelle 5.3: Übersetzungsschema für ntkDML-Knoten
insert into <tab> [({<col> [,]})] values ({<constant> [,]})
=⇒ insert into <tab> [({<col> [,]})] values ({<ntkCONSTANT> [,]})
[<timeflag>] insert into <tab> [({<col> [,]})] values ({<constant> [,]})
=⇒ insert into <tab> [(rwo nr , ts , {<col> [,]})] values (rwo nr te .nextval,
t stamp type(t element tabl(<ntkTIMEFLAG>)), {<ntkCONSTANT> [,]})
update <tab> set {<col> = <scalarexp> [,]} [where <condexp>]
=⇒ update <tab> set {<col> = <ntkSCALAREXP> [,]} [where <ntkCONDEXP>]
<timeflag> update <tab> set {<col> = <scalarexp> [,]} [where <condexp>]
=⇒ insert into sqlte rowidtempi (select rowid from <tab> t
where t.ts .ti intersection(<ntkTIMEFLAG>) is not null [and (<ntkCONDEXP>)])
create table sqlte tempj nested table ts .ts store as sqlte tempj ntabl as
(select * from <tab> t where rowid in (select tupel from sqlte rowidtempi ))
update <tab> t set ts = t.ts .ti difference(t interval type(<ntkTIMEFLAG>))
where rowid in (select tupel from sqlte rowidtempi )
delete from <tab> where ts is null
update sqlte tempj t set t.ts = t.ts .ti intersection(t interval type(<ntkTIMEFLAG>)),
{<col> = <scalarexp> [,]}
insert into <tab> (select * from sqlte tempj )
delete from table <tab> [where <condexp>]
=⇒ delete from table <tab> [where <ntkCONDEXP>]
delete from table <tab> [where <condexp>]
=⇒ update <tab> t set t.ts = t.ts .ti difference(<ntkTIMEFLAG>)
where t.ts .tp intersection(sysdate) is not null [and (<ntkCONDEXP>)]
delete from <tab> where ts is null
<timeflag> delete from table <tab> [where <condexp>]
=⇒ update <tab> t set t.ts = t.ts .ti difference(<ntkTIMEFLAG>)
where t.ts .ti intersection(<ntkTIMEFLAG>) is not null [and (<ntkCONDEXP>)]
delete from <tab> where ts is null
tkINSERT und typUC
tkINSERT und
(typTUC oder typSEQ)
SQLTE =⇒ SQL92
Bedingung
5.3. PROGRAMMBESCHREIBUNG
77
typSEQ und
typJOIN
typSEQ und
nicht typJOIN
Tabelle 5.4: Übersetzungsschema für ntkSFW-Knoten
select * | {<scalarexp> [,]} from {(<sfw>) | <tab> [<alias>] [,]} [where <condexp>]
=⇒ select * | {<ntkSCALAREXP> [,]} from {(<ntkSFW>) | <tab> [<alias>] [,]}
[where <ntkCONDEXP>]
select * | {<scalarexp> [,]} from {(<timeflag> <sfw>) | <tab> [<alias>] [,]} [where <condexp>]
=⇒ select * | {<ntkSCALAREXP> [,]} from {(select {<col> [,]} from <ntkSFW>temp t
where t.ts .tp intersection(sysdate) is not null) | <tab> tuc [<alias>] [,]}
[where <ntkCONDEXP>]
<timeflag> select * | {<scalarexp> [,]} from (<timeflag> <sfw>) | <tab> [<alias>]
[where <condexp>]
=⇒ create table sqlte tempi nested table ts .ts store as sqlte tempi ntabl as (select * |
rwo nr , ts , {<ntkSCALAREXP> [,]} from <ntkSFW>temp | <tab> <alias>
where [<ntkCONDEXP> and] (<alias>.ts .ti intersection(<ntkTIMEFLAG>) is not null))
sqlte tempi
<timeflag> select * | {<scalarexp> [,]} from {(<timeflag> <sfw>) | <tab> [<alias>] [,]}
[where <condexp>]
=⇒ create table sqlte tempi nested table ts .ts store as sqlte tempi ntabl as
(select {<alias>l .rwo nr as rwo nrl }, sqlte .te intersection({<alias>l .ts [,]}) as ts ,
{<alias>l .<col> as <alias>l <col> [,]} | {<ntkSCALAREXP> [,]}
from {<ntkSFW>temp | <tab> <alias>l [,]} where [<ntkCONDEXP> and]
({<alias>l .ts .ti intersection(<ntkTIMEFLAG>) is not null and}
sqlte .te intersection({<alias>l .ts [,]}) is not null))
create global temporary table sqlte tempj on commit delete rows as
(select distinct {rwo nrl ,} NULL as newrwo nr from sqlte tempi )
update sqlte tempj set newrwo nr = rwo nr te .nextval
create table sqlte tempk nested table ts .ts store as sqlte tempk ntabl as
(select newrwo nr as rwo nr , ts , {<col> [,]}
from sqlte tempi dt , sqlte tempj rt where {rt .rwo nrl = dt .rwo nrl [and]}
sqlte tempk
typUC
typTUC
SQLTE =⇒ SQL92
Bedingung
78
KAPITEL 5. IMPLEMENTATION
5.3. PROGRAMMBESCHREIBUNG
79
Wie schon zu Beginn dieses Abschnitts in der Modulbeschreibung erwähnt, werden Knoten
ohne Übersetzungsfunktion im Klartext übernommen. Für diese Knoten existiert daher auch
keine Übersetzungsvorschrift.
Am Beispiel der Tabelle 5.2 liest sich eine Übersetzungsvorschrift wie folgt. Die createAnweisung für temporale Tabellen befindet sich in der letzten Zeile. In der ersten Anweisung
der Übersetzung werden Tabellenname und Spaltenbezeichner wie auch die Datentypen übernommen, nur die Attribute rwo nr und ts zur Aufnahme des Schlüssels zur Tupelzuordnung
und des Zeitstempels werden ergänzt. In der zweiten Anweisung wird eine Sicht mit dem Namen
<tab> tuc angelegt, die für temporal aufwärtskompatible Anfragen stets die aktuellen Einträge
einer temporalen Tabelle bereithält.
In Tabelle 5.3 sind die Übersetzungsregeln für Knoten mit Datenmanipulationsanweisungen
zu sehen. Für den insert-Befehl unterscheiden sich die TUC- und die SEQ-Variante nur durch
die u. U. andere Interpretation von <nktTIMEFLAG> (s. a. Tabelle 5.7) und stehen daher zusammen
in einer Zeile. Auf die Darstellung des update-Kommandos bei typTUC wurde aus Platzgründen
verzichtet, da im Vergleich zur SEQ-Version nur eine andere Variante der temporalen Schnittbildung (tp intersection statt ti intersection) verwendet wird — beim delete-Befehlt ist
der gleiche Unterschied zwischen TUC und SEQ gut ablesbar.
Selektionsanweisungen werden gemäß Tabelle 5.4 übersetzt. Für temporal aufwärtskompatible Anfragen (typTUC) wurde dabei die im folgenden erläuterte, leicht vereinfachte Darstellung
gewählt. Eine TUC-Anfrage zeichnet sich dadurch aus, daß wenigstens eine der verwendeten
Tabellen oder Inline-Views temporal ist, nicht notwendiger Weise alle müssen temporal sein.
In der Übersetzungsvorschrift wird nur von temporalen Tabellen und Inline-Views ausgegangen,
d. h. es wird immer für den aktuellen Zeitpunkt ausgewertet. Tatsächlich werden nicht-temporale
Tabellen und Inline-Views von dieser Behandlung ausgeschlossen und unverändert eingesetzt.
Sequentielle Selektionsanweisungen produzieren als Übersetzung generell eine temporäre Tabelle, die das Anfrageergebnis enthält. Das gilt sowohl für die Übersetzungen von tQuery wie
auch für die der Funktion tSFW. Wie man Tabelle 5.4 entnehmen kann, werden diese temporären
Tabellen durch create table as erzeugt und jeweils als letztes Kommando an die aufrufende
Funktion übergeben. Der Zugriff auf diese so übergebenen Tabellen ist in den Übersetzungsschemata dann z. B. durch <ntkSFW>temp dargestellt. Die Tabellen enthalten ebenso wie die explizit
vom Benutzer angelegten temporalen Tabellen zwei zusätzliche Spalten zur Aufnahme der temporalen Informationen.
Diese verbindliche Verwendung temporärer Tabellen hat unterschiedliche Gründe: Zum einen
gibt es verschiedene unkalkulierbare Resultate von Oracle Version 8.1.5 bei der Verwendung von
geschachtelten Anfragen in Zusammenhang mit benutzerdefinierten Typen (s. a. Abschnitt 4.1.4).
Zum anderen vereinfacht sich das Auslesen von Attributnamen bei der Verwendung von expliziten Tabellen im Gegensatz zu geschachtelten Anfragen. Insbesondere der *-Operator läßt sich
so mit deutlich weniger Aufwand unterstützen. Schließlich wird die Übersetzung einfacher nachvollziehbar, und auch die Fehlersuche wird durch die Materialisierung von Zwischenergebnissen
vereinfacht. Dennoch scheint dieses nicht unbedingt die effektivste Lösung zu sein, so daß für
nicht-temporale Ergebnisse im Bereich von UC- und TUC-Anfragen — da dort die o. g. Gründe
nicht gelten — auf die obligatorische Verwendung von temporären Tabellen verzichtet wird.
Tabelle 5.5 zeigt die Übersetzungsvorschrift für ntkSTATEMENT-Knoten. Dort werden nur
Knoten der Art ntkQUERY explizit übersetzt, bei den anderen werden die Ergebnisse der entsprechenden Übersetzungsfunktionen unverändert übernommen. Diese Ausnahme für sequentielle
Queries begründet sich durch die notwendige Umwandlung der objektwertigen Zeitstempel in
80
KAPITEL 5. IMPLEMENTATION
Bedingung
SQLTE =⇒ SQL92
ntkQUERY und
nicht typSEQ
ntkQUERY und
typSEQ
<query>
=⇒ <ntkQUERY>
<query>
=⇒ select rwo nr , sqlte .ts2string(ts ) as ts , {<col> [,]}
from <ntkQUERY>temp order by rwo nr , ts
Tabelle 5.5: Übersetzungsschema für ntkSTATEMENT-Knoten
Bedingung
SQLTE =⇒ SQL92
tkUNION und
nicht typSEQ
tkUNION und
typSEQ
<sfw1> | <query1> union <sfw2> | <query2>
=⇒ <ntkSFW1> | <ntkQUERY1> union <ntkSFW2> | <ntkQUERY2>
<timeflag1> <sfw1> | <query1> union
<timeflag2> <sfw2> | <query2>
=⇒ create global temporary table sqlte tempi
on commit delete rows as
(select sqlte .nextval as rwo nr , {<col> [,]} from
(select {<col> [,]} from <ntkSFW1>temp | <ntkQUERY1>temp
union
select {<col> [,]} from <ntkSFW2>temp | <ntkQUERY2>temp ))
create table sqlte tempj nested table ts .ts store as
sqlte tempj ntabl as (select u.rwo nr , v.ts ,
{u.<col> [,]} from sqlte tempi u,
(select * from <ntkSFW1>temp | <ntkQUERY1>temp union all
select * from <ntkSFW2>temp | <ntkQUERY2>temp ) v
where {u.<col> = v.<col> [,]})
Tabelle 5.6: Übersetzungsschema für ntkQUERY-Knoten
Zeichenketten für Anfragen in der obersten Stufe der Schachtelung, d. h. genau für solche, die in
ntkSTATEMENT zu finden sind. Die Umwandlung wird mit Hilfe der Funktion ts2string vorgenommen (s. a. Abschnitt 5.2.3 und 5.3.7).
Für die Übersetzung temporaler Mengenoperationen ist in Tabelle 5.6 das noch unvollständige Schema dargestellt. Differenz und Schnittbildung bleiben dort unberücksichtigt, es bedarf
hier weiterer Entwicklungsarbeit. Man findet in [Ste98a] Algorithmen für temporale Mengenoperationen, jedoch lassen diese Duplikate unberücksichtigt. Damit ergibt sich ein Widerspruch
zur Schnappschuß-Reduzierbarkeit auf nicht-temporale Mengenoperationen, bei denen Duplikate
stets entfernt werden.
Die Übersetzung in der Tabelle 5.6 sieht die temporale Vereinigung mit Duplikatelimination
vor. Es werden dort zunächst alle nicht-temporalen Attribute ({<col>}) vereinigt und mit neuen
rwo nr versehen, um diese dann den temporalen Tupeln durch Verbundbildung wieder hinzuzufügen. Auf diese Weise erhalten wertgleiche Tupel identische rwo nr , so daß bei der Ausgabe
eine temporale Verschmelzung vorgenommen wird.
Für die Übersetzung von Knoten der Art ntkCONDEXP, die WHERE-Klauseln aufnehmen, bleibt
in der vorliegenden Arbeit leider keine Zeit. Diese Knoten werden derzeit identisch in die Über-
5.3. PROGRAMMBESCHREIBUNG
Knotenart
SQLTE =⇒ SQL92
ntkTIMESTAMP
NOW =⇒ SYSDATE
BEGINNING =⇒ ’01.01.-4712 00:00:00’
FOREVER =⇒ ’31.12.9999 23:59:59’
’dateval’ =⇒ ’dateval’
[[nonsequenced] validtime]
=⇒ t interval type(sysdate, ’31.12. 9999 23:59:59’)
[nonsequenced] validtime period [<timestamp>, <timestamp>)
=⇒ t interval type(<ntkTIMESTAMP>, <ntkTIMESTAMP>)
ntkTIMEFLAG
81
Tabelle 5.7: Übersetzungsschema für ntkTIMESTAMP- und ntkTIMEFLAG-Knoten
setzung übernommen, so daß bei temporalen Anfragen nur einfache WHERE-Klauseln korrekte
Ergebnisse liefern. Diese Vereinfachung ist damit zu begründen, daß sich bei komplizierteren
Konstrukten mit EXISTS oder IN die Übersetzung als etwas problematisch darstellt: Es muß
stets die temporale Überlappung zwischen Haupt- und Unteranfrage sichergestellt, werden und
dieser temporale Durchschnitt wird dann außerhalb des Gültigkeitsbereichs der Unteranfrage
in der Hauptanfrage benötigt. In [TJB97] ist dieses Problem anhand eines Beispiels illustriert,
leider wird dort kein konkreter Lösungsvorschlag geboten.
Schließlich sind in Tabelle 5.7 die Übersetzungsregeln für ntkTIMESTAMP- und ntkTIMEFLAGKnoten dargestellt. Wie man dort sehen kann, werden explizite Datumsangaben unverändert
übernommen, so daß auch Fragmente des fixen Formats ’TT.MM.±JJJJ ST:MI:SE’ anwendbar
sind, sofern diese von Oracle richtig interpretiert werden können. Weiterhin ist für ntkTIMEFLAG
zu bemerken, daß auch wenn dieses bei TUC-Anweisungen nicht vorhanden ist, eine Übersetzung erfolgt, nämlich in t interval type(sysdate, ’31.12. 9999 23:59:59’). Diese Vorgehensweise ist für die Übersetzung von TUC-Kommandos hilfreich und führt auch bei UCAnweisungen nicht zu fehlerhaften Ergebnissen, da die Funktion tTimeflag nur explizit bei
Bedarf aufgerufen wird und nicht automatisch bei jedem Auftreten von ntkTIMEFLAG-Knoten.
5.3.7
Datenbankschnittstelle
Das Modul Oracle stellt dem Übersetzungsprogramm Funktionen zur Kommunikation mit der
Datenbank zur Verfügung. Für diesen Zugriff auf die Datenbank sind für C-Programme verschiedene Möglichkeiten vorgesehen (s. a. [Ora99g]). Diese sollen zunächst kurz vorgestellt werden,
ehe sich die eigentliche Beschreibung des Moduls anschließt.
Pro*C/C++ — Embedded SQL
Wie der Name es bereits vermuten läßt, werden in dieser Variante direkt in den Quellcode spezielle Datenbankkommandos eingebettet. Es bedarf nicht der expliziten Verwendung von Bibliotheksfunktionen des Datenbankpakets, daher sind diese Kommandos relativ einfach zu handhaben. Der erweiterte C-Quellcode wird i. d. R. durch die Dateiextension .pc gekennzeichnet.
Der Oracle Pro*C/C++ Precompiler übersetzt diese Dateien dann in C-Quelldateien mit der
Dateiextension .c, indem gewöhnliche C-Konstrukte unberührt bleiben und die Datenbankkom-
82
KAPITEL 5. IMPLEMENTATION
mandos in entsprechende Funktionsaufrufe der mitgelieferten Laufzeitbibliothek2 umgewandelt
werden. Ein wesentlicher Teil der Kommunikation findet dabei über sogenannte Host-Variablen
statt, die über Precompiler-Anweisungen definiert werden und sowohl vom C-Programm wie
auch vom DBMS gelesen und geschrieben werden können.
Die Sprache embedded SQL läßt sich zunächst in statisches und dynamisches SQL unterteilen,
wobei die Anweisungenen von statischem SQL zur Zeit der Übersetzung bis auf den Inhalt
evtl. beteiligter Host-Variablen festliegen. Diese Methode findet z. B. Verwendung, wenn das
Datumsformat für die aktuelle Sitzung geändert werden soll, oder eine Anfrage an eine von
vornherein bekannte Tabelle mit Hilfe einer Host-Variablen (hier TableName) gestellt wird:
EXEC SQL ALTER SESSION SET NLS_DATE_FORMAT = ’DD.MM.SYYYY HH24:MI:SS’;
EXEC SQL SELECT * FROM USER_NESTED_TABLES WHERE TABLE_NAME = :TableName;
Bei dynamischem SQL handelt es sich um Datenbankkommandos, die zur Zeit der Übersetzung noch nicht feststehen und stattdessen erst zur Laufzeit durch Zeichenketten generiert
werden. Dazu zählen im Falle des SQLTE-Übersetzers z. B. SQL-Anfragen, die wegen der beliebigen Anzahl der verwendeten Tabellen und Spalten nicht in statische Anfragen übersetzt
werden können. Dynamisches SQL wird wiederum in vier verschiedene Methoden unterteilt,
je nachdem, ob bei dem Datenbankzugriff Anfragen verwendet werden und ob die Anzahl der
benötigten Host-Variablen von vornherein bekannt ist (s. [Ora99g]). Schon um nur beliebige Benutzeranfragen ohne temporale Funktionalität im Bereich der Aufwärtskompabilität ausführen
zu können, bedarf es somit der vierten Methode, die sowohl Anfragen als auch eine freie Anzahl
von Host-Variablen zuläßt. Von den o. g. einfachen statischen Zugriffen abgesehen, kommt daher
im wesentlichen diese Methode zum Einsatz.
Diese sogenannte vierte Methode wird ab der Version Oracle 8i wiederum in zwei Varianten
angeboten, nämlich Oracle dynamic SQL und ANSI dynamic SQL. Dabei unterstützt nur die
letztgenannte Variante die Verwendung von Objekttypen, so daß in der vorliegenden Arbeit nur
diese zum Einsatz kommt. Die dafür benötigten Deklarationen sind relativ umfangreich, so daß
an dieser Stelle auf ein Beispiel verzichtet werden soll. Einzelheiten zur Verwendung von ANSI
dynamic SQL findet man in [Ora99g].
Oracle Call Interface — OCI
Das Oracle Call Interface erlaubt die direkte Verwendung von Oracle-Bibliotheksfunktionen. Es
stellt damit die low level -Schnittstelle für die Entwicklung von Datenbankanwendungen dar und
kann nur zusammen mit der Programmiersprache C verwendet werden [Ora99h]. In der Version
Oracle 8.0 ist das OCI die einzige Möglichkeit, um benutzerdefinierte Typen in dynamischen
SQL-Anfragen mit variablen Tabellennamen zu verwenden. Durch ANSI dynamic SQL (s. o.)
wird ab der Version 8i diese Funktion auch mit embedded SQL möglich. Das OCI ist im Gegensatz
zu Pro*C/C++ nicht ANSI/ISO-verträglich, die zukünftige Unterstützung wird aber von Oracle
garantiert.
Die OCI-Funktionen sind auch in embedded SQL-Programmen verwendbar, d. h. beide Methoden können kombiniert werden. Diese Kombination ist z. B. bei der Verwendung von benutzerdefinierten Datentypen in embedded SQL-Programmen notwendig. Es werden dann in Form
2
namentlich SQLLIB bzw. libsql.a unter UNIX
5.3. PROGRAMMBESCHREIBUNG
83
von C-Strukturen vordefinierte OCI-Datentypen verwendet. Für die Darstellung von selbstdefinierten Objekttypen durch C-Strukturen liefert Oracle ein Werkzeug namens Object Type Translator (OTT) mit. So wird im Falle der für SQLTE verwendeten Typen (vgl. Abschnitt 4.2.1 und
5.2.2) durch die folgende Anweisung die dargestellte Datei oracle.h erzeugt, in der C-Äquivalente zu den Datenbanktypen auf OCI-Basis definiert werden. Der Object Type Translator
verwendet hier die Textdatei in.typ, in der die zu übersetzenden Typen spezifiziert sind (in
diesem Fall t interval type, t element tabl und t stamp type). Die Definitionen der Typen
werden direkt aus dem angegebenen Datenbankschema gelesen.
ott intype=in.typ outtype=out.typ hfile=oracle.h
user=reinhard/sqlte@spatial code=c
typedef OCIRef t_interval_type_ref;
typedef OCITable t_element_tabl;
typedef OCIRef t_stamp_type_ref;
struct t_interval_type
{
OCIDate vt_begin;
OCIDate vt_end;
};
typedef struct t_interval_type t_interval_type;
struct t_interval_type_ind
{
OCIInd _atomic;
OCIInd vt_begin;
OCIInd vt_end;
};
typedef struct t_interval_type_ind t_interval_type_ind;
struct t_stamp_type
{
t_element_tabl * ts;
};
typedef struct t_stamp_type t_stamp_type;
struct t_stamp_type_ind
{
OCIInd _atomic;
OCIInd ts;
};
typedef struct t_stamp_type_ind t_stamp_type_ind;
Modulbeschreibung
In dem Modul Oracle werden die folgenden globalen Funktionen definiert, die — mit Ausnahme von sqlExecute und sqlDropTempTab — mittels statischem embedded SQL mit dem
84
KAPITEL 5. IMPLEMENTATION
DBMS kommunizieren. Für die An- und Abmeldung an die Datenbank sorgen die Funktionen
sqlConnect und sqlDisconnect, dabei wird im Falle von Mißerfolg durch die lokale Fehlerprozedur eine Fehlermeldung generiert und das Programm beendet, während die anderen Funktionen
mögliche Fehlerzustände per Rückgabewert übergeben.
void
void
BOOL
BOOL
BOOL
BOOL
BOOL
BOOL
void
BOOL
sqlConnect(char *, char *);
sqlDisconnect(void);
sqlExecute(char *);
sqlCommit(void);
sqlTEexists(void);
sqlTableTemporal(char *);
sqlTableExists(char *);
sqlStrcatColumns(char *, char *);
sqlDropTempTab(void);
sqlGetSysdate(char *);
Durch sqlCommit werden die Änderungen der letzten Transaktion endgültig an die Datenbank übergeben. Bis auf sqlDropTempTab lesen die übrigen Funktionen zur Übersetzung
benötigte Informationen aus dem Data Dictionary. Eine Beschreibung der einzelnen Funktionen
findet sich in Anhang B.
Weiterhin existiert die Funktion sqlExecute, die dynamisch SQL-Befehle mit Hilfe des
oben beschriebenen ANSI dynamic SQL ausführt und zur Ausführung der übersetzten SQLTEKommandos verwendet wird. Zur Darstellung von Oracle-Fehlermeldungen und der Ausgabe
von Anfrageergebnissen werden dabei die folgenden lokalen Funktionen verwendet.
void
void
void
void
sqlError(void);
ZeigeAnfrageErgebnis(void);
ZeigeZeitstempel(char*, WORD);
VerschmelzeZeitstempel(char*, char*, char*);
Die Funktion sqlError wird bei der Anmeldung an die Datenbank mit Hilfe der nachfolgenden embedded SQL-Anweisung als lokale Fehlerprozedur installiert, so daß diese bei auftretenden Fehlern bzgl. der Datenbankkommunikation direkt aufgerufen wird und die entsprechende
Oracle-Fehlermeldung anzeigt.
EXEC SQL WHENEVER SQLERROR DO sqlError();
Die Funktion ZeigeAnfrageErgebnis übernimmt im Falle von dynamisch ausgeführten Anfragen die formatierte Darstellung der Ergebnisse. Diese Formatierung hat nur prototypischen
Charakter, d. h. es werden feste Feldlängen verwendet, und es besteht für den Benutzer nicht
die Möglichkeit, zusätzliche Formatierungen, wie etwa von SQL*Plus gewohnt, anzubringen. Die
Funktion überprüft die vom DBMS rückgelieferten Daten auf die Spalten RWO NR sowie TS und
stuft danach das Ergebnis ggf. als temporal ein. Temporale Ergebnisse werden mit Hilfe der
Funktion ZeigeZeitstempel zusammen mit ihren Zeitstempeln dargestellt. Weiterhin erfolgt
unter Verwendung der Funktion VerschmelzeZeitstempel bei identischer RWO NR und übriger
Wertgleichheit in der Ausgabe eine Verschmelzung der Zeitstempel, so daß die nicht-temporalen
5.4. TESTDATENBANK
85
Daten dann nur einmalig dargestellt werden. Voraussetzung für diese temporale Vereinigung
ist die nach RWO NR sortierte Ausgabe der Anfrageergebnisse, die im Rahmen der Übersetzung
berücksichtigt wird (s. Abschnitt 5.3.6).
Leider hat sich bei der Übergabe der objektwertigen Zeitstempel im Rahmen der Ergebnispräsentation das folgende Problem ergeben, so daß dort — um überhaupt temporale Informationen darstellen zu können — ein Provisorium hingenommen werden mußte. Wie zu Beginn
dieses Abschnitts erläutert, ist die Verwendung von objektwertigen Typen über OCI-Datentypen
vorgesehen. Die zugehörigen Definitionen (s. o.) und prinzipielle Verwendung dieser Strukturen
bereitet auch keine Schwierigkeiten: In der Datei oracle.pc existiert dazu die Funktion Test2,
die im Falle statischer Anfragen Zeitstempel entgegennimmt und auswertet. Für dynamische
Anfragen zeigte dieses Vorgehen jedoch keinen Erfolg, wie man der Funktion Test entnehmen
kann. Die Oracle-Dokumentation sieht einen solchen Zugriff zwar ausdrücklich vor, aber dessen
Ausführbarkeit wird nicht anhand eines Beispiels demonstriert (s. [Ora99g]). Sicher ist alternativ
das Auslesen von Objekten in der Datenbank über OCI-Funktionen machbar, jedoch erscheint
für deren Anwendung eine Einarbeitungszeit vonnöten, die im Rahmen dieser Arbeit nicht mehr
zur Verfügung steht.
Es wurde daher — wie oben erwähnt — das folgende Provisorium geschaffen: Die zu übergebenden Zeitstempel werden mit Hilfe einer PL/SQL-Funktion (s. Abschnitt 5.2.3) vor der
Übergabe durch Aufzählung der beteiligten Intervalle in eine Zeichenkette umgewandelt. Diese
Zeichenkette wird wie die anderen nicht-temporalen Typen problemlos übergeben und für die
Bildschirmausgabe entsprechend interpretiert.
5.4
Testdatenbank
In diesem letzten Abschnitt soll nun umfangreiches Datenmaterial zur Verfügung gestellt werden,
um die noch eingeschränkte temporale Funktionalität des Prototypen SQLTE mit realistischeren Anforderungen testen zu können, als dieses etwa mit der oft bemühten Beispieltabelle Ang
möglich ist. Dazu werden mit den unten genannten Anweisungen Daten aus der Institutsbibliothek übernommen.
create table dokument (
doknr number,
titel varchar(20),
verlag varchar(20),
ort varchar(20)) as validtime;
create table literf (
doknr number,
lieferant varchar(10)) as validtime;
insert into dokument (
select RWO_NR_TE_.NEXTVAL, t_stamp_type(t_element_tabl(t_interval_type(
erfdatum, ’31.12.9999 23:59:59’))), doknr, substr(titel, 1, 20),
substr(verlag, 1, 20), substr(ort, 1, 20) from dok);
9803 rows created.
86
KAPITEL 5. IMPLEMENTATION
insert into literf (
select RWO_NR_TE_.NEXTVAL, t_stamp_type(t_element_tabl(t_interval_type(
erfdatum, ’31.12.9999 23:59:59’))), doknr, lieferant from lit);
3379 rows created.
Die beiden beschriebenen Tabellen werden in diesem Testszenario — ohne genaue Kenntnis
des tatsächlichen Verwendungszwecks — als der Buchbestand der Bibliothek (Tabelle dokument)
und der bereits inventarisierte Bestand (Tabelle literf) interpretiert. Die Gültigkeitszeit eines
Dokuments beginnt also mit dem Eintreffen des Schriftstücks in der Bibliothek und die Gültigkeitszeit einer Literaturerfassung beginnt mit seiner korrekten Buchung zum Inventar.
Welche Dokumente vom Lieferanten Teubner waren im Juli 97 eingetragen und auch inventarisiert? — Diese Anfrage liefert die folgenden Kommandos in SQL, SQLTE und in der übersetzten Form. Beim Vergleich der nicht-temporalen und der temporalen Variante muß natürlich
berücksichtigt werden, daß die letztgenannte mehr Funktionalität enthält: Mit Hilfe der Gültigkeitszeit ist es nun möglich, z. B. technisch überholte Bücher wieder aus der Bibliothek zu entfernen, ohne die Informationen aus der Vergangengeit zu löschen.
sql:
select d.doknr from dok d, lit l where d.doknr=l.doknr
and d.erfdatum <= ’31.07.1997’ and l.erfdatum <= ’31.07.1997’
and lieferant = ’Teubner’;
sqlte:
validtime period [’01.07.1997’, ’01.08.1997’)
select d.doknr from dokument d, literf l
where d.doknr = l.doknr and lieferant = ’Teubner’;
ueb:
create table sqlte_temp0_ nested table ts_.ts store as sqlte_temp0_ntabl_ as
(select d.rwo_nr_ as rwo_nr0_, l.rwo_nr_ as rwo_nr1_,
sqlte_.te_intersection(d.ts_, l.ts_) as ts_, d.doknr as ddoknr
from dok d, lit l where lieferant = ’Teubner’ and
(d.ts_.ti_intersection(...) is not null and
l.ts_.ti_intersection(...) is not null and
sqlte_.te_intersection(d.ts_, l.ts_) is not null))
create global temporary table sqlte_temp1_ on commit delete rows as
(select distinct rwo_nr0_, rwo_nr1_, NULL as newrwo_nr_ from sqlte_temp0_)
update sqlte_temp1_ set newrwo_nr_ = rwo_nr_te_.nextval
create table sqlte_temp2_ nested table ts_.ts store as sqlte_temp2_ntabl_ as
(select newrwo_nr_ as rwo_nr_, ts_, ddoknr
from sqlte_temp0_ dt, sqlte_temp1_ rt
where dt.rwo_nr0_ = rt.rwo_nr0_ and dt.rwo_nr1_ = rt.rwo_nr1_)
Kapitel 6
Ausblick
Wie allgemein für Arbeiten dieser Art üblich soll auch das vorliegende Werk mit einem Ausblick
auf mögliche Erweiterungen und Verbesserungen schließen. Auf diese Weise wird einerseits die
geleistete Arbeit klar eingegrenzt und andererseits deutlich gemacht, an welcher Stelle zukünftige
Projekte aufsetzen können.
Die aus Sicht des Autors denkbaren Neuerungen werden nachfolgend unterteilt in Erweiterungen der temporalen Anfragesprache SQLTE und der eigentlichen Implementierung des Übersetzungsprogramms. Weshalb beide Teile recht umfangreich ausfallen, soll vorab kurz erläutert
werden. Zunächst einmal dürfte es unmittelbar einleuchten, daß eine temporale Erweiterung
von SQL, die im Rahmen einer Diplomarbeit formuliert und realisiert wird, dem Vergleich mit
der Funktionsvielfalt von SQL nicht standhalten kann und sich daher auf jeden Fall eine Fülle
von Erweiterungsmöglichkeiten bieten. Darüber hinaus mußten im Verlauf der Arbeit weitere
Einschränkungen vorgenommen werden.
Das liegt für den praktischen Teil der Arbeit zum einen daran, daß eine Vielzahl von neuen
Features des DBMS zum Einsatz gekommen sind, für die vorab erst wenige Erfahrungen gesammelt werden konnten. Der Wechsel auf die neue Version Oracle 8.1.5 erst während der Arbeit
hat dabei den Umfang der neuen Möglichkeiten stärker vergrößert als zunächst angenommen.
Zum anderen haben sich einige dieser neuen Eigenschaften als noch recht unzuverlässig und
fehlerbehaftet herausgestellt. Obwohl durch die Auswahl des einfach geschachtelten Speichermodells objekt-relationale Funktionalität nur in eingeschränktem Umfang zur Anwendung kam,
konnten einige Übersetzungen nicht in der geplanten Form, sondern nur mit Hilfe von syntaktischen Tricks, ausgeführt werden. Die von Oracle derzeit vorgesehene klare Trennung der object
option vom bewährten und zuverlässigen relationalen Teil des Oracle DBMS erscheint in diesem
Zusammenhang durchaus gerechtfertigt.
Für die Formulierung der Sprache im theoretischen Teil der Arbeit hat die oben beschriebene Problematik bewirkt, daß praktische Tests erst spät und nur in eingeschränktem Umfang
ausgeführt werden konnten, so daß Rückschlüsse auf die Praktikabilität von SQLTE leider fehlen.
Insgesamt ergeben sich für den Sprachumfang von SQLTE folgende Erweiterungsmöglichkeiten. Generell wünschenswert ist eine vollständige Aufwärtskompatibilität zum nicht-temporalen
SQL von Oracle. Dafür bedarf es z. B. der Unterstützung von Funktionen und insbesondere von
Aggregationen sowie den Konstrukten group by, order by und having. Weiterhin sind hier
die Formulierung von Sichten und Integritätsbedingungen zu nennen. Alle diese Forderungen
sind auch auf die temporale Funktionalität der Sprache übertragbar, insbesondere fehlt hier die
87
88
KAPITEL 6. AUSBLICK
Formulierbarkeit temporal invarianter Schlüssel.
Bereits in Abschnitt 3.2 wurde auf die Möglichkeit hingewiesen, einen nicht-temporalen Datentyp timestamp einzuführen, der ein temporales Element umfaßt und als Rückgabewert des
validtime-Operators Verwendung finden kann. Ebenso finden sich dort Vorschläge, die Datenmanipulationskommandos der Sprache zu erweitern, so daß Änderungen von Gültigkeitszeitbereichen möglich sind. Als ein weiteres Feld zukünftiger Erweiterungen ergibt sich die Unterstützung
spezieller temporaler Integritätsbedingungen.
Für die Realisierung von SQLTE in der hier vorgestellten Form sind als Ergänzungsmöglichkeiten an erster Stelle die nicht-sequentiellen Anfragen zu nennen. Für diese sind in Kapitel 3
bereits Syntax und Semantik entwickelt worden, und in Kapitel 4 finden sich Ansätze zur Übersetzung. Des weiteren wurde in Abschnitt 4.1 und 4.2 auf die Verwendungsmöglichkeiten von
indexorganisierten Tabellen und Indexen zur effizienteren Auffindung temporaler Informationen
hingewiesen. Darüber hinaus bedarf es auch für die sequentiellen Anfragen einer Vervollkommnung des Funktionsumfangs, in Abschnitt 5.3.6 wurde auf die unvollständigen Übersetzungsregeln für temporale Mengenoperationen und temporale Subqueries hingewiesen.
Als unmittelbare Verbesserungsvorschläge für die Implementation ist eine objektwertige Datenübergabe vom DBMS an das Übersetzungsprogramm zu nennen — die Problematik dabei
ist in Abschnitt 5.3.7 geschildert. Weiterhin ist die Bereitstellung einer grafischen Benutzeroberfläche (GUI) wünschenswert, die neben einer übersichtlicheren Darstellung in einem weiterem Schritt auch Unterstützung bei der Formulierung von SQLTE-Befehlen bieten könnte. Die
Grundlagen dafür sind in Abschnitt 5.3.2 erläutert. Im Rahmen einer verfeinerten Benutzeroberfläche ist auch die Umsetzung einer alternativen Präsentation temporaler Anfrageergebnisse
denkbar, wie dieses in Abschnitt 3.2.5 beschrieben wurde.
Abschließend ist natürlich für die Vervollständigung des Übersetzungsprogramms auch eine Realisierung der vorab genannten theoretischen Spracherweiterungen als wünschenswert zu
erwähnen.
Anhang A
Syntax der temporalen Erweiterung
statement :=
query :=
query
timeflag
sfw
timeflag
;
dml
)
ddl
)
query
UNION
MINUS
INTERSECT
timeflag :=
[
PERIOD
NONSEQUENCED
,
timestamp
timestamp
)
VALIDTIME
timeflag
sfw :=
)
*
SELECT
sfw
FROM
)
alias-id
table-id
scalarexp
...
,
,
WHERE
...
condexp
,
dml :=
)
INSERT INTO
table-id
VALUES
WHERE
DELETE FROM
,
)
column-id
)
condexp
table-id
WHERE
UPDATE
table-id
constant
SET
column-id
=
,
89
scalarexp
condexp
)
90
ANHANG A. SYNTAX DER TEMPORALEN ERWEITERUNG
ddl :=
table-id
CREATE TABLE
(
table-id
column-id
datatype
(
DROP TABLE
AS VALIDTIME
,
datatype :=
timestamp :=
NUMBER
DATE
(
VARCHAR
integerval
scalarexp :=
.
NOW
BEGINNING
FOREVER
dateval
‘
‘
(
column-id
condop :=
alias-id
=
>
<
<>
constant
ABS
BEGIN
END
(
alias-id
timeflag
(
VALIDTIME
>=
<=
OVERLAPS
CONTAINS
MEETS
PRECEDES
(
scalarexp
(
sfw
[
PERIOD
,
scalarexp
(
scalarexp
constant :=
+
*
/
condexp :=
(
condexp
‘
timestamp
integerval
floatval
stringval
(
timeflag
EXISTS
(
(
sfw
NOT
scalarexp
condop
scalarexp
ALL
ANY
SOME
IS
timeflag
(
sfw
(
NULL
NOT
NOT
BETWEEN
IN
OR
AND
AND
scalarexp
(
sfw
timeflag
(
scalarexp
‘
Anhang B
Dokumentation der
Programmdateien
Die in diesem Kapitel beschriebenen Dateien beinhalten die Implementation der temporalen
Anfragesprache SQLTE. Die Darstellung erfolgt nach Modulen sortiert, mit Hilfe der Modulund Funktionsheader aus den Programmdateien. Die Dateien mit den Extensionen .h und .pc
wurden mit Hilfe des Oracle Präprozessors Pro*C/C++ Release 8.1.51 und des GNU C Compilers
GCC Version 2 übersetzt. Vor der ersten Verwendung des so erhaltenen Programms schafft die
Ausführung der .sql-Dateien unter SQL*Plus Release 8.1.5 im gewünschten Datenbankschema
die datenbankseitig benötigten Voraussetzungen. Es bedarf dafür entsprechender Rechte des
Datenbankbenutzers, deren Existenz z. B. durch die Vergabe der vordefinierte Rolle connect
sichergestellt werden.
B.1
Datenbankstrukturen
B.1.1
Hauptskript — sqlte.sql
/*****************************************************************************
*
*
* sqlte.sql - SQL-Skript zur Anlage der Datenbankstrukturen fuer SQLTE
*
*
Definiert temporale Tabellen und Sequenz. Deklariert
*
*
Package und Datentypen. Verwendet die Skriptdateien
*
*
interval.sql, stamp.sql und package.sql um Package und
*
*
Datentypen zu definieren.
*
*
*
* Dieses Skript muss vor der ersten Verwendung des Uebersetzers SQLTE
*
* ausgefuehrt werden, um die benoetigten Datenbankstrukturen zu schaffen.
*
*****************************************************************************/
1
Tatsächlich erfolgte die Programmentwicklung sowohl mit Pro*C/C++ Release 8.1.5 als auch mit dem Präprozessor der Version 8.0.5. Das erstgenannte Produkt hat den leidigen Nachteil, daß z. B. ein fehlendes Semikolon am
Ende einer Zeile der C-Quelldatei durch einen Programmabbruch mit der Nachricht segmentation fault (core
dumped) quittiert wird — ohne Angabe der Fehlerposition. Solche Fehlerpositionen lassen sich dann mit Hilfe der
Version 8.0.5 finden, diese kann jedoch die neuen Funktionen des ANSI dynamic SQL (s. a. Abschnitt 5.3.7) nicht
übersetzen.
91
92
B.1.2
ANHANG B. DOKUMENTATION DER PROGRAMMDATEIEN
Datentyp Intervall — interval.sql
/*****************************************************************************
*
*
* interval.sql - Definiert Body des Datentyps t_interval_type.
*
*
*
* Methoden:
*
* ti_intersection(ti in t_interval_type) return t_interval_type
*
*****************************************************************************/
/******************************************************************************
*
* ti_intersection(ti in t_interval_type) return t_interval_type
*
Liefert den Schnitt zweier temporaler Intervalle oder NULL,
*
falls sich diese nicht ueberlappen.
*
* Eingabeparameter:
*
t_interval_type mit dem aufrufenden Objekt zu schneidendes Intervall
*
* Rueckgabewert:
*
t_interval_type Schnittintervall; NULL, falls kein Ueberlapp
*
*/
B.1.3
Datentyp Zeitstempel — stamp.sql
/*****************************************************************************
*
*
* stamp.sql - Definiert Body des Datentyps t_stamp_type.
*
*
*
* Methoden:
*
* tp_intersection(tp in date) return date
*
* ti_intersection(ti in t_interval_type) return t_stamp_type
*
* te_intersection(te in t_stamp_type) return t_stamp_type
*
* ti_difference(ti in t_interval_type) return t_stamp_type
*
*****************************************************************************/
/******************************************************************************
*
* tp_intersection(tp in date) return date
*
Liefert den Schnitt eines Zeitpunktes mit einem temporalem Element
*
oder NULL, falls der Zeitpunkt nicht ueberlappt wird.
*
* Eingabeparameter:
*
date
mit dem aufrufenden Objekt zu schneidender Zeitpunkt
*
* Rueckgabewert:
*
date
ueberlappter Zeitpunkt; NULL, falls kein Ueberlapp
*
*/
B.1. DATENBANKSTRUKTUREN
/******************************************************************************
*
* ti_intersection(ti in t_interval_type) return t_stamp_type
*
Liefert den Schnitt eines Intervalls mit einem temporalem Element
*
oder NULL, falls sich diese nicht ueberlappen.
*
* Eingabeparameter:
*
t_interval_type mit dem aufrufenden Objekt zu schneidendes Intervall
*
* Rueckgabewert:
*
t_stamp_type
Schnittintervall; NULL, falls kein Ueberlapp
*
*/
/******************************************************************************
*
* te_intersection(te in t_stamp_type) return t_stamp_type
*
Liefert den Schnitt zweier temporaler Elemente
*
oder NULL, falls sich diese nicht ueberlappen.
*
* Eingabeparameter:
*
t_stamp_type
mit dem aufrufenden Objekt zu schneidendes temp. El.
*
* Rueckgabewert:
*
t_stamp_type
Schnittintervall; NULL, falls kein Ueberlapp
*
*/
/******************************************************************************
*
* ti_difference(ti in t_interval_type) return t_stamp_type
*
Bildet die Differenz zwischen aufrufendem Objekt und dem angegebenen
*
Intervall. Liefert NULL, falls das Objekt komplett ueberlappt wird.
*
* Eingabeparameter:
*
t_interval_type das auszublendende Intervall
*
* Rueckgabewert:
*
t_stamp_type
Differenz zwischen aufrufendem Objekt und Intervall
*
oder NULL, falls das Objekt komplett ueberlappt wird
*
*/
B.1.4
Paketfunktionen — package.sql
/*****************************************************************************
*
*
* package.sql - Definiert Body des sqlte-Package.
*
*
*
* Methoden:
*
* te_intersection(te1 in t_stamp_type, ...) return t_stamp_type
*
* ts2string(ts in t_stamp_type) return varchar
*
* ti_subtraction(ti1 in t_interval_type, ti2 in t_interval_type)
*
* return t_stamp_type
*
*****************************************************************************/
93
94
ANHANG B. DOKUMENTATION DER PROGRAMMDATEIEN
/******************************************************************************
*
* te_intersection(te1 in t_stamp_type, ...) return t_stamp_type
*
Liefert den Schnitt bis zu fuenf temporaler Elemente oder NULL,
*
falls sich diese nicht ueberlappen.
*
* Eingabeparameter:
*
t_stamp_type
erstes zu schneidendes temporale Element
*
t_stamp_type
zweites zu schneidendes temporale Element
*
(...)
*
* Rueckgabewert:
*
t_stamp_type
Schnitt der Zeitstempel; NULL, falls kein Ueberlapp
*
*/
/******************************************************************************
*
* ts2string(ts in t_stamp_type) return varchar
*
Wandelt ein temporales Element durch aneinanderreihen der Intervalle
*
in eine Zeichenkette um; als Provisorium zur objektfreien Uebergabe
*
an das Pro*C-Uebersetzungsprogramm.
*
* Eingabeparameter:
*
t_stamp_type
zu konvertierendes temporales Element
*
* Rueckgabewert:
*
varchar
Zeichenkette der Art ’von;bis\von;bis\...’
*
*/
/******************************************************************************
*
* ti_subtraction(ti1 in t_interval_type, ti2 in t_interval_type)
* return t_stamp_type
*
Liefert die Differenz zweier Intervalle als temporales Element,
*
oder NULL, falls das erste Intervall komplett ueberlappt wird.
*
* Eingabeparameter:
*
t_interval_type Minuent
*
t_interval_type Subtrahent
*
* Rueckgabewert:
*
t_stamp_type
Differenz
*
*/
B.1.5
Deinstallationsskript — dropsqlte.sql
Das Deinstallationsskript dropsqlte.sql sorgt für die Löschung aller durch sqlte.sql angelegten Datenbankobjekte. Voraussetzung für eine fehlerfreie Ausführung ist jedoch, daß keine
Typen mehr aus sqlte.sql verwendet werden, d. h. es dürfen z. B. keine temporalen Tabellen
mehr existieren.
B.2. ÜBERSETZUNGSPROGRAMM
B.1.6
95
Test des komplexen Speichermodells — kommode.zip
Diese Archivdatei umfaßt die Testdateien zum zweifach geschachtelten Speichermodell aus Abschnitt 4.2.2. Obwohl dieses Speichermodell keine Anwendung findet, sollen dennoch die zugehörigen Funktionen und Datenstrukturen abgelegt werden, da diese für Weiterentwicklungen
u. U. hilfreich sein können. Enthalten sind die Funktionen und Typdefinitionen aus Abschnitt
4.2.2 sowie einige Skripte zur Generierung von Tabellen und Testdaten.
B.2
B.2.1
Übersetzungsprogramm
Globale Strukturen — sqlte.h
/*****************************************************************************
*
*
* sqlte.h - globale Vereinbarungen fuer SQLTE; verwendet in allen Modulen
*
*
*
* global:
*
* Funktionen
*
* Konstanten
*
* Datentypen
*
*
*
* tdKnoten*
pWurzel;
Wurzelknoten des Parserbaums
*
* tdSymbol*
pSymbol;
zuletzt gescanntes Symbol
*
* tdKommando* pSQL92;
Liste mit uebersetzten Kommandos
*
*****************************************************************************
B.2.2
Hauptprogramm — sqlte.pc
/*****************************************************************************
*
*
* sqlte.pc - Hauptprogramm der temporalen SQL-Erweiterung SQLTE
*
*
*
* global:
*
* int main(int argc, char *argv[])
*
*
*
* tdKnoten*
pWurzel = NULL;
Wurzelknoten des Parserbaums
*
* tdSymbol*
pSymbol = NULL;
zuletzt gescanntes Symbol
*
* tdKommando* pSQL92 = NULL;
Liste mit uebersetzten Kommandos
*
*****************************************************************************/
/******************************************************************************
*
* int main(int argc, char *argv[])
*
Hauptprogramm des SQLTE-Uebersetzers: uebernimmt Anmeldung, ruft
*
Parser, CodeGen und Execute auf.
*
* Eingabeparameter:
*
int argc
Anzahl der Kommandozeilenparameter
*
char *argv[]
Zeiger auf Kommandozeilenparameter
*
-q unterdrueckt Anzeige der SQLTE-Uebersetzung
*
* Rueckgabewert:
*
int
Fehlerrueckgabe: 0, falls fehlerfrei
*/
96
B.2.3
ANHANG B. DOKUMENTATION DER PROGRAMMDATEIEN
Eingabeverarbeitung — eingabe.pc
/*****************************************************************************
*
*
* eingabe.pc - Funktionen zur Eingabeverarbeitung
*
*
*
* global:
*
* void InitEingabe(FILE *InitEingabe)
*
* void KommandoReInit(void)
*
* char KommandoZeichen(void)
*
* BOOL KommandoLeer(void)
*
* char AnmeldungZeichen(void)
*
*
*
* lokal:
*
* static FILE *Eingabe;
*
*****************************************************************************/
/******************************************************************************
*
* void InitEingabe(FILE *InitEingabe)
*
Setzt die lokale(n) Variable(n) (derzeit nur eine) zur Eingabe.
*
* Eingabeparameter:
*
FILE *
Zeiger auf Dateistruktur
*
*/
/******************************************************************************
*
* void KommandoReInit(void)
*
Setzt die Kommandoeingabe zurueck.
*
*/
/******************************************************************************
*
* char KommandoZeichen(void)
*
Liefert das naechste Zeichen der Kommandoeingabe.
*
* Rueckgabewert:
*
char
eingelesenes Zeichen, falls Eingabe nichtleer
*
*/
/******************************************************************************
*
* BOOL KommandoLeer(void)
*
Prueft auf leere Kommandoeingabe.
*
* Rueckgabewert:
*
BOOL
TRUE, falls keine weiteren Kommandozeichen vorhanden
*
*/
B.2. ÜBERSETZUNGSPROGRAMM
/******************************************************************************
*
* char AnmeldungZeichen(void)
*
Liefert das naechste Zeichen der Anmeldung an die Datenbank.
*
* Rueckgabewert:
*
char
eingelesenes Zeichen, falls Eingabe nichtleer
*
*/
B.2.4
Ausgabeverarbeitung — ausgabe.pc
/*****************************************************************************
*
*
* ausgabe.pc - Funktionen zur Ausgabeverarbeitung
*
*
*
* global:
*
* void InitAusgabe(FILE *InitAusgabe)
*
* void ZeigeInfo(char *szInfo)
*
* void ZeigeErgebnis(char *szErgebnis)
*
* void ZeigeFehler(char *szFehler)
*
* void ZeigePrompt(void)
*
*
*
* lokal:
*
* static FILE *Ausgabe;
*
*****************************************************************************/
/******************************************************************************
*
* void InitAusgabe(FILE *InitAusgabe)
*
Setzt die lokale(n) Variable(n) (derzeit nur eine) zur Ausgabe.
*
* Eingabeparameter:
*
FILE *
Zeiger auf Dateistruktur
*
*/
/******************************************************************************
*
* void ZeigeInfo(char *szInfo)
*
Zeigt Zeichenkette zur Benutzerinformation.
*
* Eingabeparameter:
*
char *
Zeiger auf ’\0’-Zeichenkette
*
*/
/******************************************************************************
*
* void ZeigeErgebnis(char *szErgbnis)
*
Zeigt Zeichenkette als Ergebnis einer Datenbankanfrage.
*
* Eingabeparameter:
*
char *
Zeiger auf ’\0’-Zeichenkette
*
*/
97
98
ANHANG B. DOKUMENTATION DER PROGRAMMDATEIEN
/******************************************************************************
*
* void ZeigeFehler(char *szFehler)
*
Zeigt Zeichenkette als Fehlermeldung.
*
* Eingabeparameter:
*
char *
Zeiger auf ’\0’-Zeichenkette
*
*/
/******************************************************************************
*
* void ZeigePrompt(void)
*
Zeigt Eingabeprompt.
*
*/
B.2.5
Lexikalische Analyse — scanner.pc
/*****************************************************************************
*
*
* scanner.pc - Lexikalische Analyse von SQLTE-Kommandos
*
*
*
* global:
*
* BOOL ScannerInit(void)
*
* WORD Scanner(tdKnoten* pKnoten)
*
* BOOL Symbol2Text(char *szText, WORD wKonst)
*
* WORD Text2Symbol(char *szText)
*
*
*
* lokal:
*
* static void leseLeer(void);
*
* static WORD IdentifierTest(char *);
*
* static void ScannerFehler(char *);
*
*
*
* static char EingabeZeichen;
Zuletzt gelesenes Eingabezeichen
*
* static WORD wZeile;
Aktuelle Position in der
*
* static WORD wSpalte;
Benuztereingabe
*
* static tdSymbol sSymbol;
Zuletzt gelesenes Symbol
*
* static struct {
*
*
char szText[STRINGLAENGE];
Klartext eines Symbols
*
*
WORD wKonstante;
Konstante eines Symbols
*
* } sTerminal[];
Uebersetzungstabelle der Symbolkonstanten *
*****************************************************************************/
/******************************************************************************
*
* BOOL ScannerInit(void)
*
Inititalisiert den Scanner, belegt lokale Variablen und
*
liest das erste Zeichen aus der Eingabe.
*
* Rueckgabewert:
*
BOOL
TRUE, falls Initialisierung erfolgreich
*
*/
B.2. ÜBERSETZUNGSPROGRAMM
/******************************************************************************
*
* WORD Scanner(tdKnoten* pKnoten)
*
Liefert das naechste Symbol aus der Benutzereingabe und legt das
*
zuvor gelesene Symbol (sofern vorhanden) im Parserbaum ab.
*
* Eingabeparameter:
*
tdKnoten*
Zeiger auf Knoten im Parserbaum zur Aufnahme des
*
beim letzten Aufruf gelesenen Symbols
*
* Rueckgabewert:
*
WORD
Symbolkonstante des eingelesenen Symbols
*
*/
/******************************************************************************
*
* BOOL Symbol2Text(char *szText, WORD wKonst)
*
Liefert zu einer Symbolkonstante den Klartext.
*
* Eingabeparameter:
*
char *
Zeiger auf Zeichenkette zur Ablage des Ergebnis
*
WORD
zu suchende Symbolskonstante
*
* Rueckgabewert:
*
BOOL
TRUE, falls Suche erfolgreich
*
*/
/******************************************************************************
*
* WORD Text2Symbol(char *szText)
*
Liefert zum Klartext eines Symbols die zugehoerige Symbolkonstante.
*
* Eingabeparameter:
*
char *
Zeiger auf ’\0’-Zeichenkette des Symboltextes
*
* Rueckgabewert:
*
WORD
Symbolkonstante, falls Suche erfolgreich, sonst -1
*
*/
/******************************************************************************
*
* void leseLeer(void)
*
Liest Leereingaben (blank, tab, enter) aus der Benutzereingabe
*
und zaehlt dabei Zeilen und Spalten.
*
*/
99
100
ANHANG B. DOKUMENTATION DER PROGRAMMDATEIEN
/******************************************************************************
*
* WORD IdentifierTest(char *szEingabe)
*
Entscheidet ueber den Inhalt einer Benutzereingabe, die keinem
*
terminalen Symbol zuzuweisen ist.
*
* Eingabeparameter:
*
char *
Zeiger auf zu pruefende Benutzereingabe
*
* Rueckgabewert:
*
WORD
tkIDENTIFIER, tkINTEGERVAL, tkFLOATVAL, tkSTRINGVAL
*
*/
/******************************************************************************
*
* void ScannerFehler(char *szFehler)
*
Gibt Fehlermeldung aus und bricht das Programm ab,
*
da hier interne Ursache (zuviele Symbole, zu lange Strings).
*
* Eingabeparameter:
*
char *
Zeiger auf ’\0’-Zeichenkette mit Fehlermeldung
*
*/
B.2.6
Syntaktische Analyse — parser.pc
/*****************************************************************************
*
*
* parser.pc - Syntaktische Analyse von SQLTE-Kommandos
*
*
*
* global:
*
* WORD Parser(void)
*
*
*
* lokal:
*
* static void pStatement(void);
*
* static void pTimeflag(tdKnoten*);
*
* static void pQuery(tdKnoten*);
*
* static void pSFW(tdKnoten*);
*
* static void pDML(tdKnoten*);
*
* static void pDDL(tdKnoten*);
*
* static void pDatatype(tdKnoten*);
*
* static void pCondExp(tdKnoten*);
*
* static void pCondOp(tdKnoten*);
*
* static void pScalarExp(tdKnoten*);
*
* static void pTimestamp(tdKnoten*);
*
* static void pConstant(tdKnoten*);
*
* static void ParserFehler(WORD);
*
* static tdKnoten* neuerKnoten(void);
*
* static void loescheKnoten(tdKnoten*);
*
* static tdKnoten* verzweigeKnoten(tdKnoten*);
*
*
*
* static WORD wToken;
Zuletzt gescanntes Token
*
* static BOOL bFehler;
TRUE, falls bereits Fehler beim Parsen
*
* static char* szFehlerMeldung[] Fehlermeldungen fuer ParserFehler
*
*****************************************************************************/
B.2. ÜBERSETZUNGSPROGRAMM
/******************************************************************************
*
* WORD Parser(void)
*
Syntaktische Analyse der Benutzereingabe und Aufbau des
*
Parserbaumes ausgehend von der globalen Variable pWurzel.
*
* Rueckgabewert:
*
WORD
0 = leere Eingabe
*
1 = Eingabe syntaktisch fehlerhaft
*
2 = fehlerfreie Eingabe
*
*/
/******************************************************************************
*
* void pStatement(void)
*
Beginnt rekursiven Abstieg des Parsers, initialisiert den
*
Parserbaum durch Verwendung der globalen Variable pWurzel.
*
*/
/******************************************************************************
*
* void pTimeflag(tdKnoten* p)
*
Setzt rekursiven Abstieg des Parsers fort, vgl. Syntaxgraphen.
*
Vergibt ggf. typNONSEQ, typSEQ und belegt den Zeiger pTimeflag.
*
* Eingabeparameter:
*
tdKnoten*
Zeiger auf aktuellen Knoten im Parserbaum
*
*/
/******************************************************************************
*
* void pQuery(tdKnoten* p)
* void pDatatype(tdKnoten* p)
* void pCondExp(tdKnoten* p)
* void pCondOp(tdKnoten* p)
* void pScalarExp(tdKnoten* p)
* void pTimestamp(tdKnoten* p)
* void pConstant(tdKnoten* p)
*
Setzt rekursiven Abstieg des Parsers fort, vgl. Syntaxgraphen.
*
* Eingabeparameter:
*
tdKnoten*
Zeiger auf aktuellen Knoten im Parserbaum
*
*/
/******************************************************************************
*
* void pSFW(tdKnoten* p)
*
Setzt rekursiven Abstieg des Parsers fort, vgl. Syntaxgraphen.
*
Vergibt ggf. typINLINEVIEW, typTABLEID und typJOIN.
*
* Eingabeparameter:
*
tdKnoten*
Zeiger auf aktuellen Knoten im Parserbaum
*
*/
101
102
ANHANG B. DOKUMENTATION DER PROGRAMMDATEIEN
/******************************************************************************
*
* void pDML(tdKnoten* p)
*
Setzt rekursiven Abstieg des Parsers fort, vgl. Syntaxgraphen.
*
Vergibt ggf. typTABLEID.
*
* Eingabeparameter:
*
tdKnoten*
Zeiger auf aktuellen Knoten im Parserbaum
*
*/
/******************************************************************************
*
* void pDDL(tdKnoten* p)
*
Setzt rekursiven Abstieg des Parsers fort, vgl. Syntaxgraphen.
*
Vergibt ggf. typTABLEID, typTEMP und typNONTEMP.
*
* Eingabeparameter:
*
tdKnoten*
Zeiger auf aktuellen Knoten im Parserbaum
*
*/
/******************************************************************************
*
* void ParserFehler(WORD wFehlerNr)
*
Stellt syntaktische Fehler dar, durch Angabe von Text, Zeile, Spalte
*
und eines ’*’ an der fehlerhaften Position der Benutzereingabe.
*
* Eingabeparameter:
*
WORD
Eine Symbolkonstante (z.B. tkSELECT) oder die Nummer
*
einer Fehlermeldung aus szFehlerMeldung[].
*
*/
/******************************************************************************
*
* tdKnoten* neuerKnoten(void)
*
Schafft einen neuen Knoten und verzweigt den angegebenen Knoten des
*
Parserbaumes zu diesem neuen Knoten.
*
* Rueckgabewert:
*
tdKnoten*
Zeiger auf den neu geschaffenen Knoten
*
*/
/******************************************************************************
*
* void loescheKnoten(tdKnoten* p)
*
Loescht einen Knoten des Parserbaumes, d.h. gibt den Speicher frei,
*
der durch den angegebenen Knoten und dessen Nachfolger belegt wird.
*
* Eingabeparameter:
*
tdKnoten*
Zeiger auf den zu loeschenden Knoten
*
*/
B.2. ÜBERSETZUNGSPROGRAMM
/******************************************************************************
*
* tdKnoten* verzweigeKnoten(tdKnoten* pAlt)
*
Schafft einen neuen Knoten und verzweigt den angegebenen Knoten des
*
Parserbaumes zu diesem neuen Knoten.
*
* Eingabeparameter:
*
tdKnoten*
Zeiger auf aktuellen Knoten im Parserbaum, hier wird
*
an der naechsten freien Position die Verzweigung
*
eingetragen.
*
* Rueckgabewert:
*
tdKnoten*
Zeiger auf den neu geschaffenen Knoten
*
*/
B.2.7
Semantische Analyse und Übersetzung — codegen.pc
/*****************************************************************************
*
*
* codegen.pc - Funktionen zur Übersetzung von SQLTE-Kommandos.
*
*
*
* global:
*
* BOOL CodeGen(void)
*
*
*
* lokal:
*
* static BOOL Klartext(tdSymbol**, char *, tdSymbol*, WORD, WORD);
*
* static BOOL tStatement(void);
*
* static BOOL tQuery(tdKnoten*, char*);
*
* static BOOL tDDL(tdKnoten*, char*);
*
* static BOOL tDML(tdKnoten*, char*);
*
* static BOOL tSFW(tdKnoten*, char*);
*
* static BOOL tTimestamp(tdKnoten*, char*);
*
* static BOOL tTimeflag(tdKnoten*, char*);
*
* static BOOL tCondExp(tdKnoten*, char*);
*
* static BOOL ErgaenzeKnoten(tdKnoten*);
*
* static void loescheKommando(tdKommando* p);
*
* static void neuesKommando(char*, BOOL);
*
*
*
* static char szFehlerText[STRINGLAENGE]; temporaer fuer Fehlermeldungen *
* static char szTemp[KOMMANDOLAENGE];
temporaer fuer Kommmandos
*
* static char szSysdate[STRINGLAENGE];
Systemzeit Transaktionsbeginn
*
* static WORD nTempTab;
Zaehler temporaerer Tabellen
*
*
je Transaktion
*
* static WORD nRekursion;
Zaehler fuer Rekursionstiefe
*
*
beim Uebersetzungsvorgang
*
* static char szKommando[KOMMANDOLAENGE]; Hauptkommando der Uebersetzung, *
*
wird an letzter Position in
*
*
Liste pSQL92 eingefuegt
*
*****************************************************************************/
103
104
ANHANG B. DOKUMENTATION DER PROGRAMMDATEIEN
/******************************************************************************
*
* BOOL CodeGen(void)
*
Liefert Uebersetzung der SQLTE-Anfrage aus dem Parserbaum (pWurzel)
*
in der verketteten Liste pSQL92 zurueck. Uebersetzung besteht i.A.
*
aus mehreren SQL92 Anweisungen.
*
* Rueckgabewert:
*
BOOL
Ergebnis der Uebersetzung; TRUE, falls keine Fehler
*
*/
/******************************************************************************
*
* BOOL Klartext(tdSymbol** ppWeiter, char *szKommando, tdSymbol* pSymbol
*
, WORD wAnzahl, WORD wSymbol)
*
Steuert den rekursiven Uebersetzungsvorgang. Dazu werden Symbole in
*
zusammenhaengenden String umgewandelt, jeweils mit nachfolgenden
*
Leerzeichen. Enthalten die Symbole einen Verweis auf einen nach*
folgenden Knoten, so wird dieser rekursiv uebersetzt und zwar, falls
*
vorhanden, mit der Knotenart zugehoerigen Uebersetzungsfunktion oder
*
sonst wieder durch kopieren der Klartexte.
*
Die angegebene Anzahl oder Suche entscheidet ueber Abbruch der
*
Kopierung. Nachfolgerknoten ohne Uebersetzungsfunktion werden
*
komplett kopiert, ohne Suche.
*
* Eingabeparameter:
*
tdSymbol**
Nimmt die Position des naechsten Symbols im Knoten
*
auf, d.h. zeigt auf das erste nicht uebersetzte
*
Zeichen.
*
char *
Zeichenkette zur Aufnahme der Uebersetztung, es
*
wird hier stets angehaengt
*
tdSymbol*
erstes zu kopierendes Symbol
*
WORD
Anzahl der zu kopierende Symbole oder 0, falls
*
nur Abbruch nach Suchkriterium erwuenscht
*
WORD
Zu suchendes Symbol, dieses wird ggf. mit kopiert
*
und danach die Kopierung beendet. 0, falls keine
*
Suchfunktion erwuenscht.
*
* Rueckgabewert:
*
BOOL
Ergebnis der Ausfuehrung; TRUE, falls keine Fehler
*
*/
/******************************************************************************
*
* BOOL tStatement(void)
*
Startet die rekursive Uebersetzung des SQLTE-Kommandos im Parser*
baum. Als Datenquelle dienen dabei die Knoten unterhalb von pWurzel,
*
als Ziel hier die lokale Variable szKommando sowie die Liste pSQL92,
*
sofern mehr als ein Kommando zur Uebersetzung benoetigt wird.
*
Wird einmalig von codegen() aufgerufen.
*
*/
B.2. ÜBERSETZUNGSPROGRAMM
/******************************************************************************
*
* BOOL tQuery(tdKnoten* pK, char*)
* BOOL tDDL(tdKnoten* pK, char*)
* BOOL tDML(tdKnoten* pK, char*)
* BOOL tSFW(tdKnoten* pK, char*)
* BOOL tTimestamp(tdKnoten* pK, char*)
* BOOL tTimeflag(tdKnoten* pK, char*)
* BOOL tCondExp(tdKnoten* pK, char*)
*
Uebernimmt die Uebersetzung eines Knotens der zugehoerigen Art.
*
Ergebnisse der Uebersetzung werden in szKommando bzw. in der Liste
*
pSQL92 abgelegt, sofern mehr als ein Kommando zur Uebersetzung
*
benoetigt wird.
*
* Eingabeparameter:
*
tdKnoten*
Zeiger auf den zu uebersetzenden Knoten
*
char*
hier wird die Uebersetzung angehaengt
*
* Rueckgabewert:
*
BOOL
TRUE, falls Uebersetzung erfolgreich
*
*/
/******************************************************************************
*
* BOOL ErgaenzeKnoten(tdKnoten *p)
*
Ergaenzt Parserknoten um zusaetzliche semantische Informationen,
*
etwa ob Tabellen temporal oder nicht. Ueberprueft die Typvertraeg*
lichkeit von Tabellen/Anfragen, weist ggf. auf noch nicht implemen*
tierte Funktionen der Sprache (z.B. NONSEQ) hin. Wird rekursiv
*
verwendet und von codegen mit dem Knoten pWurzel aufgerufen.
*
* Eingabeparameter:
*
tdKnoten*
erster zu ergaenzender und zu pruefender Knoten
*
* Rueckgabewert:
*
BOOL
Ergebnis der Ausfuehrung; TRUE, falls keine Fehler
*
*/
/******************************************************************************
*
* void loescheKommando(tdKommando* p)
*
Loescht rekursiv die verkettete Listenstruktur zur Aufnahem der
*
uebersetzten Kommandos, d.h. der dynamisch belegte Speicher wird
*
freigegeben.
*
* Eingabeparameter:
*
tdKommando*
Erster zu loeschender Listeneintrag
*
*/
105
106
ANHANG B. DOKUMENTATION DER PROGRAMMDATEIEN
/******************************************************************************
*
* void neuesKommando(char *szText, BOOL)
*
Erzeugt einen neuen Eintrag am Ende der Listenstruktur zur Aufnahme
*
der uebersetzten Kommandos und legt dort die angegebene
*
Zeichenkette ab. Fuehrt das neue Kommando bei Bedarf sofort aus.
*
* Eingabeparameter:
*
char *
Zeiger auf Zeichenkette mit Kommando
*
BOOL
TRUE, falls Kommando sofort ausgefuehrt werden soll
*
*/
B.2.8
Datenbankschnittstelle — oracle.pc
/*****************************************************************************
*
*
* oracle.pc - Funktionen zur Kommunikation mit Oracle fuer SQLTE
*
*
*
* global:
*
* void sqlConnect(char *, char *);
*
* void sqlDisconnect(void);
*
* BOOL sqlExecute(char *);
*
* BOOL sqlCommit(void);
*
* BOOL sqlTEexists(void);
*
* BOOL sqlTableTemporal(char *);
*
* BOOL sqlTableExists(char *);
*
* BOOL sqlStrcatColumns(char *, char *);
*
* void sqlDropTempTab(void);
*
* BOOL sqlGetSysdate(char *);
*
*
*
* lokal:
*
* static void sqlError(void);
*
* static void ZeigeAnfrageErgebnis(void);
*
* static void ZeigeZeitstempel(char*, WORD);
*
* static void VerschmelzeZeitstempel(char*, char*, char*);
*
* static void Test(void);
*
* static void Test2(void);
*
*
*
* static char*
szBuf[MAXSPAL][MAXZEIL] Puffer fuer Anfrageergebnisse
*
* static short
Indi[MAXSPAL][MAXZEIL] Indikator fuer NULL bei Anf.Erg. *
* static VARCHAR username[USER_LEN];
Benutzername zur DB-Anmeldung
*
* static VARCHAR password[PWD_LEN];
Passwort zur DB-Anmeldung
*
* static char*
szOraDynStatement;
dynamische SQL-Kommandos
*
*****************************************************************************/
/******************************************************************************
*
* void sqlConnect(char *szUser, char *szPassword)
*
Stellt Verbindung zur Datenbank her.
*
* Eingabeparameter:
*
char *
Zeiger auf Zeichenkette mit Benutzername@DB-Alias
*
char *
Zeiger auf Zeichenkette mit Passwort
*
*/
B.2. ÜBERSETZUNGSPROGRAMM
/******************************************************************************
*
* void sqlDisconnect(void)
*
Unterbricht Verbindung zur Datenbank, fuehrt COMMIT aus.
*
*/
/******************************************************************************
*
* BOOL sqlExecute(char *)
*
Fuehrt Oracle-Statement dynamisch aus.
*
* Eingabeparameter:
*
char *
Zeiger auf ’\0’-Zeichenkette mit Kommando
*
* Rueckgabewert:
*
BOOL
Ergebnis der Ausfuehrung; TRUE, falls keine Fehler
*
*/
/******************************************************************************
*
* BOOL sqlCommit(void)
*
Fuehrt COMMIT WORK statisch aus.
*
* Rueckgabewert:
*
BOOL
Ergebnis der Ausfuehrung; TRUE, falls keine Fehler
*
*/
/******************************************************************************
*
* BOOL sqlTEexists(void)
*
Prueft auf Existenz der Sequenz rwo_nr_seq_ und stellt damit sicher,
*
dass SQLTE.SQL bereits ausgefuehrt wurde.
*
* Rueckgabewert:
*
BOOL
TRUE, falls Sequenz vorhanden
*
*/
/******************************************************************************
*
* BOOL sqlTableTemporal(char *szTable)
*
Prueft, ob die angegebene Tabelle temporal erweitert wurde, d.h. ob
*
die nested table <table>_ntabl_ im data dictionary existiert.
*
* Eingabeparameter:
*
char *
Zeiger auf Zeichenkette mit Tabellennamen
*
* Rueckgabewert:
*
BOOL
TRUE, falls Tabelle temporal
*
*/
107
108
ANHANG B. DOKUMENTATION DER PROGRAMMDATEIEN
/******************************************************************************
*
* BOOL sqlTableExists(char *szTable)
*
Prueft, ob die angegebene Tabelle im data dictionary existiert.
*
* Eingabeparameter:
*
char *
Zeiger auf Zeichenkette mit Tabellennamen
*
* Rueckgabewert:
*
BOOL
TRUE, falls Tabelle vorhanden
*
*/
/******************************************************************************
*
* BOOL sqlStrcatColumns(char *, char *)
*
liefert die Spalten einer Tabelle in der definierte Reihenfolge,
*
ohne die evtl. vorhandenen internen Spalten RWO_NR_ und TS_
*
* Eingabeparameter:
*
char*
zur Aufnahme der Spaltennamen der Form "c1, c2, ..."
*
char*
Zeichenkette mit Tabellennamen
*
* Rueckgabewert:
*
BOOL
Ergebnis der Ausfuehrung; TRUE, falls keine Fehler
*
*/
/******************************************************************************
*
* void sqlDropTempTab(void)
*
Loescht die temporaeren Tabellen der letzten Transaktion.
*
*/
/******************************************************************************
*
* BOOL sqlGetSysdate(char *szTimestamp)
*
Liefert die aktuelle Systemzeit der Datenbank.
*
* Eingabeparameter:
*
char *
Zeiger auf Zeichenkette zur Zeitaufnahme
*
* Rueckgabewert:
*
BOOL
Ergebnis der Ausfuehrung; TRUE, falls keine Fehler
*
*/
/******************************************************************************
*
* void sqlError(void)
*
Zeigt Oracle-Fehlermeldung aus Oracle-eigener sqlca-Struktur an.
*
Funktion wird durch sqlConnect() als globale SQL-Fehlerprozedur
*
eingerichtet.
*
*/
B.2. ÜBERSETZUNGSPROGRAMM
/******************************************************************************
*
* void ZeigeAnfrageErgebnis(void)
*
Liefert eine einfache formatierte Ausgabe der letzten ueber
*
’output_descriptor’ ausgefuehrten dynamischen Anfrage.
*
Ist die Spalte RWO_NR_ beteiligt, wird von einer temporalen
*
Anfrage ausgegangen: Die Zeilen werden dann bei identischer
*
RWO_NR_ zusammen dargestellt und die Daten des Zeitstempels
*
entsprechend formatiert.
*
*/
/******************************************************************************
*
* void ZeigeZeitstempel(char *szZeitstempel, WORD nLeer)
*
Fuehrt die formatierte Ausgabe eines Zeitstempels aus, beachtet
*
insbesondere die mehrzeilige Ausgabe bei >1 Intervallen.
*
* Eingabeparameter:
*
char *
Zeiger auf Zeichenkette mit temporalem Element
*
(durch die Trennzeichen ; und \ strukturiert)
*
WORD
Position der Intervalle fuer die Bildschirmausgabe
*
*/
/******************************************************************************
*
* void VerschmelzeZeitstempel(char* szZiel, char* szQ1, char*szQ2)
*
Fuegt zwei temporale Elemente in Zeichenkettenform zusammen und
*
nimmt dabei, falls moeglich, Zusammenfassung der Intervalle vor.
*
* Eingabeparameter:
*
char *
Zeiger auf neue Zeitstempel-Zeichenkette
*
char *
Zeiger auf erste alte Zeitstempel-Zeichenkette
*
char *
Zeiger auf zweite alte Zeitstempel-Zeichenkette
*
*/
/******************************************************************************
*
* void Test(void)
*
Diese Funktion probiert die Uebergabe von Objekten - leider erfolglos
*
mit der Meldung ’inconsistent datatypes’ bei fetch. Vielleicht ist
*
die Kombination dynamisches sql/pro*c/objects in Zukunft besser
*
dokumentiert, so dass sich diese Funktion nachruesten laesst. :-)
*
*/
/******************************************************************************
*
* void Test2(void)
*
Diese Funktion probiert die Uebergabe von Objekten bei statischem
*
embedded sql - erhoffte Erkenntnisse fuer den dynamischen Zugriff
*
in der Funktion Test() sind leider ausgeblieben: Der statische
*
Objekt-Zugriff funktioniert hier problemlos.
*
*/
109
Literaturverzeichnis
[BBJ96]
Bair J., Böhlen M. H., Jensen C. S., Snodgrass R. T. (1996): Notions of Upward Compatibility of Temporal Query Languages. Dept. of Mathematics and Computer Science,
University of Aalborg
[Böh94]
Böhlen M. H. (1994): Managing Temporal Knowledge in Deductive Databases. PhD
thesis, Institute for Information Systems, ETH Zürich
[Böh95]
Böhlen M. H. (1995): Temporal Database System Implementations. SIGMOD Record,
Volume 24, Number 4
[Böh97]
Böhlen M. H. (1997): Tiger Reference Manual. Dept. of Mathematics and Computer
Science, University of Aalborg, http://www.cs.auc.dk
[BJ96]
Böhlen M. H., Jensen C. S. (1996): Seamless Integration of Time into SQL. Technical Report R-962049, Dept. of Mathematics and Computer Science, University of
Aalborg, Denmark
[BJS95]
Böhlen M. H., Jensen C. S., Snodgrass R. T. (1995): Evaluating and Enhancing the
Completeness of TSQL2. Technical Report TR 95-05, Computer Science Department,
University of Arizona
[BSS96]
Böhlen M. H., Snodgrass R. T., Soo M. D. (1996): Coalescing in Temporal Databases.
In Vijayaraman T.M., Buchmann A., Mohan C., Sarda N.L. (editors): Proceedings
of the 22nd International Conference on Very Large Data Bases. Mumbai (Bombay),
India: Morgan Kaufmann Publishers
[EJS98]
Etzion O., Jajodia S., Sripada S., Hrsg. (1998): Temporal Databases: Research and
Practice. Lecture Notes in Computer Science 1399. Berlin: Springer-Verlag
[GV85]
Gadia S. K., Vaishnav J. H. (1985): A Query Language for a Homogeneous Temporal
Database. In Proceedings of the International Conference on Management of Data,
1985, p. 51–56
[HP98]
Hohenstein U., Pleßler V. (1998): Oracle 8: Effiziente Anwendungsentwicklung mit
objektrelationalen Konzepten. Heidelberg: dpunkt-Verlag
[JSS93]
Jensen C. S., Soo M. D., Snodgrass R. T. (1993): Unifying Temporal Data Models via
a Conceptual Model. Technical Report TR 93-31, Computer Science Department,
University of Arizona
[Kop96]
Kopka H. (1996): LaTex: Einführung. 2. Auflage, Bonn: Addison-Wesley
110
LITERATURVERZEICHNIS
111
[KL97]
Koch G., Loney K. (1997): Oracle 8: The Complete Reference. Berkeley, CA: Osborne
/ McGraw-Hill
[KR83]
Kernighan B. W., Ritchie D. M. (1983): Programmieren in C. Wien: Hanser
[Myr97]
Myrach T. (1997): TSQL2: Der Konsens über eine temporale Datenbanksprache. Informatik Spektrum 20(3): 143–150
[MS93]
Melton J., Simon A. R. (1993): Understanding the new SQL: A Complete Guide. San
Mateo, CA: Morgan Kaufmann Publishers
[NA93]
Navathe S., Ahmed R. (1993): Temporal Extensions to the Relational Model and SQL.
In [TCG+93], p. 92–109
[Ora98a]
Server Documentation (Rel. 8.0.5): Concepts. Part Number A58227-01
[Ora98b] Server Documentation (Rel. 8.0.5): SQL Reference. Part Number A58225-01
[Ora98c]
Server Documentation (Rel. 8.0.5): Application Developer’s Guide. Part Number
A58241-01
[Ora98d] Server Documentation (Rel. 8.0.5): PL/SQL User’s Guide and Reference. Part Number A58236-01
[Ora98e]
Server Documentation (Rel. 8.0.5): Error Messages. Part Number A58312-01
[Ora99a]
Server Documentation (Rel. 8.1.5): Concepts. Part Number A67781-01
[Ora99b] Server Documentation (Rel. 8.1.5): SQL Reference. Part Number A67779-01
[Ora99c]
Server Documentation (Rel. 8.1.5): Application Developer’s Guide — Fundamentals.
Part Number A68003-01
[Ora99d] Server Documentation (Rel. 8.1.5): PL/SQL User’s Guide and Reference. Part Number A67842-01
[Ora99e]
Server Documentation (Rel. 8.1.5): Error Messages. Part Number A67785-01
[Ora99f]
Server Documentation (Rel. 8.1.5): Getting to Know Oracle 8i. Part Number A6802001
[Ora99g]
Server Documentation (Rel. 8.1.5): Pro*C/C++ Precompiler Programmer’s Guide.
Part Number A68022-01
[Ora99h] Server Documentation (Rel. 8.1.5): Oracle Call Interface Programmer’s Guide. Part
Number A67846-01
[Par91]
Parchmann R. (1991): Skript zu den Vorlesungen Informatik I-IV 2. Auflage, Institut
für Informatik, Universität Hannover
[Pul95]
Pulfer D. (1995): Optimierung von temporalen Queries. Master´s thesis, Institute for
Information Systems, ETH Zürich
[SAA94a] Snodgrass R. T., Ahn I., Ariav G., et al (94): A TSQL2 Tutorial. SIGMOD Record
23:3, p. 27
112
LITERATURVERZEICHNIS
[SAA94b] Snodgrass R. T., Ahn I., Ariav G., et al (94): TSQL2 Language Specification. SIGMOD
Record 23:1, p. 65
[SBJ96a] Snodgrass R. T., Böhlen M. H., Jensen C. S., Steiner A. (1996): Adding Valid Time to
SQL/Temporal. ISO/IEC JTC1/SC21/WG3 DBL MCI-142
[SBJ96b] Snodgrass R. T., Böhlen M. H., Jensen C. S., Steiner A. (1996): Adding Transaction
Time to SQL/Temporal. ISO/IEC JTC1/SC21/WG3 DBL MCI-143
[SBJ97]
Snodgrass R. T., Böhlen M. H., Jensen C. S., Steiner A. (1997): Transitioning Temporal Support in TSQL2 to SQL3. In [EJS98], p. 151–194
[Sno87]
Snodgrass R. T. (1987): The Temporal Query Language TQuel. ACM Transactions on
Database Systems, 12(2): 247–298, June 1987
[Sno95]
Snodgrass R. T. (editor) (1995): The Temporal Query Language TSQL2. Norwell, MA:
Kluwer Academic Publishers
[Sno00]
Snodgrass R. T. (2000): Developing Time-Oriented Database Applications in SQL.
San Francisco, CA: Morgan Kaufmann Publishers
[SQL92]
American National Standards Institute (1992): Information Systems — Database Language — SQL. ANSI X3.135-1992
[SQL99]
International Organization for Standardization (1999): Temporal (SQL/Temporal)
(ISO Working Draft). ISO/IEC JTC 1/SC 21/WG 3
[Ste98a]
Steiner A. (1998): A Generalisation Approach to Temporal Data Models and their
Implementations. Ph. D. Thesis, ETH Zürich
[Ste98b]
Steiner A. (1998): TimeDB 2.0 Documentation. http://www.timeconsult.com
[TCG93] Tansel A. U., Clifford J., Gadia S., Jajodia S., Segev A., Snodgrass R. T. (1993):
Temporal Databases: Theory, Design and Implementation. Redwood City, CA: Benjamin/Cummings
[TJB97]
Torp K., Jensen C. S., Böhlen M. H. (1997): Layered Implementation of Temporal DBMSs — Concepts and Techniques. TimeCenter Technical Report TR-2,
http://www.cs.arizona.edu
[Urm97]
Urman S. (1997): Oracle 8: PL/SQL Programming. Berkeley, CA: Osborne/McGrawHill
[Vos99]
Vossen G. (1999): Datenmodelle, Datenbanksprachen und Datenbank-ManagementSysteme. 3. Auflage, München: Oldenbourg-Verlag
[Wir96]
Wirth N. (1996): Grundlagen und Techniken des Compilerbaus. Bonn: AddisonWesley
[ZCF97]
Zaniolo C., Ceri S., Faloutsos C., Snodgrass R. T., Subrahmanian V.S., Zicari R.
(1997): Advanced Database Systems. San Francisco, CA: Morgan Kaufmann Publishers
Abbildungsverzeichnis
2.1
Ausführung einer temporal aufwärtskompatiblen Anfrage (nach [BJ96]) . . . . .
14
2.2
Beispiel zur Ausführung einer temporal aufwärtskompatiblen Anfrage . . . . . .
15
2.3
Ausführung einer sequentiellen Anfrage (nach [BJ96]) . . . . . . . . . . . . . . .
16
2.4
Ausführung einer nicht-sequentiellen Anfrage (nach [BJ96]) . . . . . . . . . . . .
18
3.1
Nicht-temporale Relationenalgebra . . . . . . . . . . . . . . . . . . . . . . . . . .
25
3.2
Differenz-Operator der temporalen Relationenalgebra (nach [BJ96]) . . . . . . . .
26
3.3
Verschmelzungs-Operator der temporalen Relationenalgebra (nach [BJ96]) . . . .
27
3.4
Syntax der SQLTE-Befehle zur Anfrageformulierung . . . . . . . . . . . . . . . .
31
3.5
Syntax der SQLTE-Befehle zur Datenmanipulation . . . . . . . . . . . . . . . . .
35
3.6
Zeitstempel bei der sequentiellen Update-Operation
. . . . . . . . . . . . . . . .
36
3.7
Syntax der SQLTE-Befehle zur Datendefinition . . . . . . . . . . . . . . . . . . .
38
4.1
Kollektionswertiges Attribut als nested table-Typ . . . . . . . . . . . . . . . . . .
42
4.2
Nested table-Typ als index organized table (IOT) . . . . . . . . . . . . . . . . . .
43
4.3
Zugriff auf Kollektionen unter PL/SQL (nach [Ora98d]) . . . . . . . . . . . . . .
45
4.4
Temporale Tabelle Ang im einfach geschachtelten Speichermodell . . . . . . . . .
49
4.5
Temporale Tabelle Ang im doppelt geschachtelten Speichermodell
. . . . . . . .
54
4.6
Schichtenarchitektur temporaler Datenbanken (nach [TJB97]) . . . . . . . . . . .
61
5.1
Datenflußdiagramm der SQLTE-Implementation . . . . . . . . . . . . . . . . . .
64
5.2
Abhängigkeitsgraph der Module . . . . . . . . . . . . . . . . . . . . . . . . . . . .
67
5.3
Schematische Darstellung eines Parserbaums . . . . . . . . . . . . . . . . . . . .
73
113
Erklärung
Hiermit erkläre ich, die vorliegende Diplomarbeit Realisierung einer temporalen Erweiterung
”
von SQL auf einem objekt-relationalen Datenbankmanagementsystem“ selbständig verfaßt und
keine anderen als die angegebenen Quellen und Hilfsmittel verwendet zu haben.
Hannover, 22. Dezember 1999
114
Herunterladen