Entwicklung eines Parsers für relationale Anfragen und einer

Werbung
Fakultät für Elektrotechnik und Informatik
Institut für Praktische Informatik
Fachgebiet Datenbanken und Informationssysteme
Universität Hannover
Bachelorarbeit
im Studiengang Informatik
Entwicklung eines Parsers für relationale Anfragen
und einer Verwaltung von Ausführungsplänen
für den Anfragesimulator RELOpt
Alexander Fabig
Matr.-Nr. 2178802
30. September 2005
Erstprüfer: Prof. Dr. Udo Lipeck
Zweitprüfer: Dr. Hans Hermann Brüggemann
Betreuer: Dipl.-Math. Mazeyar E. Makoui
Erklärung
Hiermit versichere ich, Alexander Fabig, dass ich die vorliegende Arbeit und die zugehörige
Implementierung selbständig verfaßt und dabei nur die angegebenen Quellen und Hilfsmittel verwendet habe.
Hannover, am 30. September 2005
Danksagung
An dieser Stelle möchte ich die Gelegenheit nutzen und mich bei allen bedanken, die mich
während der Entstehung dieser Arbeit unterstützt haben.
Besonders bedanken möchte ich mich bei Herrn Prof. Dr. Lipeck, der mir mit seinen Fragen
und Anmerkungen zu meiner Arbeit sehr geholfen hat. Mein Dank geht ebenfalls an Herrn
Dr. Brüggemann für seinen Einsatz als Zweitprüfer.
Für seine andauernde Motivation möchte ich meinem Betreuer Herrn Dipl.-Math. Mazeyar
Makoui danken. Desweiteren bedanke ich mich bei Herrn Radoslaw Rudnicki für die hilfreichen Diskusionen.
Schließlich möchte ich mich bei meinen Eltern, Peter und Delia Lüer, bedanken, die mir
dieses Studium ermöglicht haben.
Hannover, am 30. September 2005
Zusammenfassung
In dieser Arbeit wurde ein relationaler Parser und eine Datenstruktur zur Verwaltung von
Ausführungsplänen für den Anfragesimulator RELOpt entwickelt. Die neue Datenstruktur,
die als Basis für ein Einphasenoptimierungssystem verwendet wird, heißt generalisierter
Baum. In einem solchen Baum wird jede Relation und jede Operation in einem Knoten
gespeichert. Außerdem enthält er die für die Optimierung benötigen Metadaten.
Um einen Ausführungsplan bearbeiten und verändern zu können, werden von einem generaliserten Baum eine Reihe von Methoden bereitgestellt, wie z. B. Methoden zur Ausgabe
der Knotenattribute oder zum Austausch von Teilbäumen.
Als Eingabesprache für das Einphasenoptimierungssystem wurde die relationale Algebra
ausgewählt. Der Parser ist in der Lage aus einer Anfrage einen generalisierten Baum zu
generieren. Da eine veränderte Form des generalisierten Baumes zum Speichern von Termersetzungsregeln verwendet wird, wurde die Sprache erweitert um auch solche variablen und
bedingungsbehafteten Regelterme zu erzeugen.
Inhaltsverzeichnis
1 Einleitung
9
1.1 Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.2 Ziel dieser Arbeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.3 Aufbau dieser Arbeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2 Relationaler Parser
2.1 Sprachelemente . . . . . . . . . . . .
2.1.1 Relationen und Attribute . .
2.1.2 Operatoren . . . . . . . . . .
2.1.3 Parametrisierungen . . . . . .
2.1.4 Zusätzliche Elemente . . . . .
2.2 Entwicklung der Grammatik . . . . .
2.3 Inhaltliche Analyse . . . . . . . . . .
2.3.1 Vereinfachung von komplexen
2.3.2 Operationsvereinfachung . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
Selektionsbedingungen
. . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
13
13
13
14
15
16
17
19
19
22
3 Regelparsing
3.1 Bedingungsarten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.1.1 Existenzbedingungen . . . . . . . . . . . . . . . . . . . . . . . . . .
3.1.2 Eigenschafts- und Zuweisungsbedingungen . . . . . . . . . . . . . .
3.1.3 Aufteilen und Zuweisen von Selektions- oder Verbundbedingungen
3.1.4 Bestimmung von Vergleichsoperatoren . . . . . . . . . . . . . . . .
3.1.5 Kostenbedingungen . . . . . . . . . . . . . . . . . . . . . . . . . .
3.2 Entwicklung einer Grammatik . . . . . . . . . . . . . . . . . . . . . . . . .
3.3 Semantische Überprüfung einer Termersetzungsregel . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
23
23
23
24
25
26
26
27
27
4 Generalisierte Bäume
4.1 Knotenattribute . . . . . . . . . . . . . . . . . . . . . .
4.2 Kostenabhängige Auswahl der physischen Umsetzungen
4.3 Sortierungen . . . . . . . . . . . . . . . . . . . . . . . .
4.4 Indexe . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.5 Pipelining . . . . . . . . . . . . . . . . . . . . . . . . . .
4.6 Verwendung von Regelbäumen in einem Termersetzer .
4.7 Baum-Methoden . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
29
29
30
31
35
35
37
39
.
.
.
.
.
.
41
41
42
45
47
47
47
5 Berechnung der Kosten
5.1 Selektivitätsabschätzungen . . . . . .
5.2 Schätzungen für Attributwertanzahlen
5.3 Kardinalität und Pagegröße . . . . . .
5.4 Kostenfunktionen . . . . . . . . . . . .
5.4.1 Selektion und Projektion . . .
5.4.2 Aggregation . . . . . . . . . . .
7
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
8
INHALTSVERZEICHNIS
5.5
5.4.3 Join . . . . . . . . . . . . . . . . . . . .
5.4.4 Semi-/Antisemijoin . . . . . . . . . . . .
5.4.5 Mengenoperatoren . . . . . . . . . . . .
5.4.6 Erzeugen von Sortierungen oder Indexen
5.4.7 Verwendung von geclusterten Indexen .
Beispiel . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
6 Implementierung
6.1 GUI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.1.1 Hauptfenster . . . . . . . . . . . . . . . . . . . . . . . . . .
6.1.2 Regeleingabe . . . . . . . . . . . . . . . . . . . . . . . . . .
6.1.3 Baumdarstellung . . . . . . . . . . . . . . . . . . . . . . . .
6.2 Packages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.2.1 Package dbs.unihannover.sopt.struc.general . . . . . . . .
6.2.2 Package dbs.unihannover.sopt.struc.general.tree . . . . . .
6.2.3 Package dbs.unihannover.sopt.struc.general.tree.condition
6.2.4 Erweiterungen am Package dbs.unihannover.sopt.gui . . .
6.2.5 Erweiterungen am Package dbs.unihannover.sopt.tree . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
48
49
50
51
51
52
.
.
.
.
.
.
.
.
.
.
55
55
55
57
58
59
59
59
63
67
68
7 Ausblick
69
A Relationaler Parser
A.1 Grammatik . . . . . . . . . . . . .
A.1.1 Nichtterminale Symbole . .
A.1.2 Terminale Symbole . . . . .
A.1.3 Startsymbol . . . . . . . . .
A.1.4 Produktionen . . . . . . . .
A.2 Beweis LL(1)-Bedingung . . . . . .
A.3 Beispiel für einen Ableitungsbaum
B Regelbedingungsparser
B.1 Grammatik . . . . . . . . . . .
B.1.1 Nichtterminale Symbole
B.1.2 Terminale Symbole . . .
B.1.3 Startsymbol . . . . . . .
B.1.4 Produktionen . . . . . .
B.2 Beweis LL(1)-Bedingung . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
71
71
71
71
72
72
73
80
.
.
.
.
.
.
81
81
81
81
81
82
83
Abbildungsverzeichnis
89
Tabellenverzeichnis
91
Literaturverzeichnis
93
Kapitel 1
Einleitung
1.1
Einführung
Anfragen an eine Datenbank werden mit Hilfe von Sprachen, wie z. B. SQL oder der relationalen Algebra, gestellt. Diese Sprachen sind deklarativ, d. h. die gestellten Anfragesätze
geben das gewünschte Ergebnis an, aber nicht die Art und Weise mit der dieses erreicht
werden soll. Um ein Format zu erhalten das von der Datenbank verarbeitet werden kann,
muss das Datenbanksystem aus der Anfrage einen Ausführungsplan erstellen, z. B.:
Anfrage
Ausführungsplan
in Baumdarstellung
Rel
π(P.N
ame,H.Kurse)
(P ROJECT ION (P.N ame, H.Kurse))
(JOIN (P.ID = H.ID) P H))
./Rel−Ind
(P.ID=H.ID)
P
H
Abbildung 1.1: Übersetzung einer Anfrage in einen Ausführungsplan
Eine 1:1-Übersetzung der Eingabe in eine Form, die vom Optimierungssytem bearbeitet
werden kann, wird von einem sogenannten Parser erzeugt. Dieser liest die Eingabe Wort
für Wort und baut daraus die benötigte Baumstruktur auf. Wie der Aufbau einer solchen
Struktur sein muss, hängt vom jeweiligen System ab, z. B. wird in einem Dreiphasenoptimierungssystem erst ein algebraischer Baum generiert, der im Laufe der Optimierung zu
einem konkreten Ausführungsplan wird.
Die drei wichtigsten Bestandteile eines Parsers sind in Abbildung 1.2 dargestellt:
Anfrage als
Zeichenkette
lexikale
Analyse
syntaktische
Analyse
semantische Anfragebaum
Analyse
Abbildung 1.2: Vereinfachter Aufbau eines Parsers
In der lexikalen Analyse wird der übergebene Anfragesatz in eine Menge von Wörtern zerlegt, wobei z. B. überflüssige Leerzeichen entfernt werden. Die syntaktische Analyse prüft,
ob die von der vorherigen Analyse gelieferten Wörter Elemente der Sprache sind und ob ihre
9
10
KAPITEL 1. EINLEITUNG
Anordnung korrekt ist. Außerdem wird in dieser Phase eine Baumstruktur erzeugt, die den
Anfrageplan wiederspiegelt. Anschließend beginnt die semantische Analyse, in der geprüft
wird, ob die Anfrage inhaltlich korrekt ist. Beispielsweise wäre die Anfrage
“AGGREGAT ION (#P.N ame, (count(P.N ame) as X.Count)) P“
syntaktisch richtig, aber nicht semantisch, denn bei einer Aggregation müssen die projizierten Attribute auch zur Gruppierung verwendet werden.
Ist der Vorgang des Parsens abgeschlossen, wird die gewonnene Baumstruktur dem Optimierungssystem übergeben. Dies versucht mit Hilfe von Termersetzungsregeln einen kostengünstigen Ausführungsplan zu erstellen.
1.2
Ziel dieser Arbeit
Bei dem Programm RELOpt handelt es sich um einen Simulator für relationale Anfragen,
welcher nach einem klassischen Dreiphasenoptimierungssystem arbeitet. Er wurde in der Diplomarbeit [Mak03] entworfen und in den folgenden Bachelor- und Studienarbeiten [War03],
[Die03] und [Zle05] weiterentwickelt, z. B. um die Anbindung an eine Oracle-Datenbank zum
Importieren von Tabellen und Statistiken zu ermöglichen. In Zukunft wird dieser Anfragesimulator auf ein Einphasenoptimierungssystem umgestellt. Der strukturelle Aufbau dieses
Systems wird in Abbildung 1.3 beschrieben:
Relationaler
Parser
deklarative
Anfrage
generalisierter
Baum
generalisierter
Baum
GUI
Steuerung
generalisierter
Baum
+
Ersetzungsregeln
generalisierte
Bäume
(Varianten)
Termersetzer
Abbildung 1.3: Aufbau eines Einphasenoptimierungssystems
Vom Benutzer wird über eine GUI eine Anfrage in relationaler Algebra gestellt. Daraus
erzeugt der Parser einen Ausführungsplan, der an die Steuerung übergeben wird. Im Gegensatz zu einem Dreiphasenoptimierer, in dem ein algebraischer und ein physischen Baum
verwendet wird, gibt es hier eine Baumstruktur, in der sämtliche zur Optimierung benötigten Daten gespeichert werden. Diese neue Datenstruktur wird generalisierter Baum genannt.
1.2. ZIEL DIESER ARBEIT
11
In jedem Optimierungsschritt wird von der Steuerung ein Baum oder Teilbaum an den
Termersetzer übergeben. Zusätzlich übergibt sie eine Menge von Termersetzungsregeln mit
deren Hilfe neue Teilbäume generiert werden. Das Ziel ist es, einen kostengünstigeren Anfrageplan zu erhalten. Informationen über den genauen Aufbau der Steuerung und des
Termersetzers sowie über den Ablauf einer Einphasenoptimierung sind in [Rud05] zu finden.
Ziel dieser Arbeit ist die Entwicklung und Implementierung eines relationalen Parsers für
das Programm RELOpt. Dieser Parser wird in der Lage sein, aus einer Anfrage in relationaler Algebra einen generalisierten Baum zu erzeugen.
Die Entwicklung dieser neuer Datenstruktur zum Speichern und Verwalten von Ausführungsplänen ist ein weiterer Bestandteil dieser Arbeit. In einem generalisierten Baum wird jeder
relationale Operator mit den zugehörigen Funktionen zur Berechnung der Kosten in einem Knoten gespeichert. Außerdem werden Methoden bereitgestellt mit deren Hilfe ein
Ausführungsplan durchlaufen und verändert werden kann.
In [War03] wurden Kostenfunktionen für einen Großteil der relationalen Operatoren aufgestellt. Allerdings nicht für Aggregation und Outerjoins. In dieser Arbeit werden die Kostenformeln für diese Operationen entwickelt und das bestehende Kostenmodell der Verbundoperatoren um Formeln für Non-Equijoins erweitert.
Eine veränderte Form des generalisierten Baumes wird zum Speichern von Termersetzungsregeln verwendet. Eine Ersetzungsregel besteht aus einem linken und einem rechten Term, welche selbst generalisierte Bäume sind, allerdings nicht mit konkreten Relationen und Bedingungen, sonderen mit variablen Werten. Außerdem gehören eine Menge
von Überführungsbedingungen zu einer Regel. Wird in einem Ausführungsplan eine Übereinstimmung mit einer der Regelseiten gefunden, wird abhängig von den Bedingung eine
Ersetzung mit der anderen Seite durchgeführt. Dies ist gut an dem folgenden Beispiel einer
Regel aus [Rud05] zu erkennen:
σpRel
./Rel−Rel
q
./Rel−Rel
q
R1
R2
1
2
σpRel
R2
R1
1) falls attr(p) ⊆ sch(R1 )
2) falls cost(τ1 ) < cost(τ2 )
Abbildung 1.4: Beispiel für eine Termersetzungsregel
Der relationale Parser ist in der Lage Term, die Bestandteil von Ersetzungsregeln sind, zu
erzeugen, da diese ebenfalls in einer Form des generalisierte Baumes gespeichert werden.
Um die Erstellung kompletter Regeln zu ermöglichen, wird ein zusätzliches Modul für den
Parser entwickelt, welches für die Eingabe von Regelbedingungen verwendet wird.
In dieser Arbeit verwendete Abkürzungen:
R1 , R2 , R3 , . . .
q, p, p1 , p2 , . . .
l1 , l2
attr(p)
sch(R)
Relationen (z. B. Zwischenergebnisse)
Bedingungen
Attributmengen
die Menge der in p enthaltenen Attribute
Schema der Relation R
12
KAPITEL 1. EINLEITUNG
Operationen:
Γg#f
σp
πl
×
−
∩
∪
./p
.<p
.<p
A./p
./@p
A./@p
1.3
Aggregation einer Relation mit Projektion auf die Attributmenge f
und Gruppierung nach den Attributen der Menge g
Selektion einer Relation durch die Bedingung p
Projektion einer Relation auf die Attributmenge l
Kartesisches Produkt zweier Relationen
Differenz zweier Relationen
Durchschnitt zweier Relationen
Vereinigung zweier Relationen
Join zweier Relationen durch die Bedingung p
Semijoin zweier Relationen durch die Bedingung p
Antisemijoin zweier Relationen durch die Bedingung p
Left-Outerjoin zweier Relationen durch die Bedingung p
Right-Outerjoin zweier Relationen durch die Bedingung p
Left-Right-Outerjoin zweier Relationen durch die Bedingung p
Aufbau dieser Arbeit
Im 2. Kapitel wird der Aufbau des relationalen Parsers und die Entwicklung einer Eingabesprache erläutert. Das 3. Kapitel beschäftigt sich mit der Entwicklung einer Sprache zur
Eingabe von Regelbedingungen und der semantischen Überprüfung von kompletten Termersetzungsregeln.
Die Basis des Einphasenoptimierungssystems, der generalisierte Baum, wird mit seinen Methoden und Knotenattributen in Kapitel 4 beschrieben. Die Kostenfunktionen für die einzelnen relationalen Operatoren die in einem generalisierten Baum gespeichert sind, werden
in Kapitel 5 behandelt.
Im 6. Kapitel wird die Implementierung der aufgestellten Theorien dargestellt. Abschließend
gibt Kapitel 7 einen Ausblick auf Veränderungen und Verbesserungen die zukünftig möglich
wären.
Kapitel 2
Relationaler Parser
Wie bereits in der Einleitung beschrieben, generiert der Parser aus einer Anfrage in relationaler Algebra, einen generalisierten Baum. In diesem Baum wird der Ausführungsplan
und alle Daten, die für eine Optimierung nötig sind, gespeichert. Die Attribute der Knoten
eines Baumes und Methoden zum Bearbeiten eines Ausführungsplanes werden in Kapitel 4
beschrieben.
Das erste Modul des Parsers ist der lexikale Scanner. Dieser zerlegt den Anfragesatz in eine
Folge von Wörtern und entfernt dabei unnötige Zeichen, wie z. B. mehrfache Leerstellen.
Die verwendeten Wörter sind in Kapitel 2.1 aufgeführt und erklärt.
Der nächste Teil ist die syntaktische Analyse, in der geprüft wird, ob die in der Anfrage
verwendeten Wörter Bestandteil der Sprache sind und ob sie in der richtigen Reihenfolge eingegeben wurden. Um diese Überprüfungen machen zu können wird eine Grammatik
benötigt, die den Aufbau der Sprache beschreibt. Wie die Grammatik aufgebaut ist und wie
sie entwickelt wurde, ist in Kapitel 2.2 zu finden.
Anschließend beginnt die semantische Überprüfung, in der analysiert wird, ob der eingegebene Anfragesatz inhaltlich korrekt ist. Kapitel 2.3 beschäftigt sich mit der Analyse des
Inhalts und möglichen Vereinfachungen.
2.1
Sprachelemente
Die vom Parser akzeptierte Sprache ist die Relationale Algebra. Im wesentlichen besteht
sie aus Relationen, Operatoren und Parametrisierungen. In diesem Abschnitt werden die
einzelnen Elemente und ihre Bezeichnung in der Sprache sowie in der Grammatik aufgeführt.
2.1.1
Relationen und Attribute
Eine Relation R besteht aus einem oder mehreren Attributen A1 , ..., An . Diese Menge
wird als Relationenschema bezeichnet. Jedes Attribut Ai speichert eine Menge von Werten wi1 ...wim . Eine Zeile aus der Relation enthält mehrere Werte und wird Tupel genannt.
R
Tupel 1
..
.
A1
w11
..
.
···
···
An
wn1
..
.
Tupel m
w1m
···
wnm
Abbildung 2.1: Darstellung einer Relation
13
14
KAPITEL 2. RELATIONALER PARSER
In der vom Parser akzeptierten Sprache müssen Attribute immer mit dem Namen der dazugehörigen Relation, in der Form RELAT ION.AT T RIBU T , angegeben werden. Die Bezeichnungen von Relationen und Attributen müssen mit einem Buchstaben beginnen und
kürzer als 30 Zeichen sein.
2.1.2
Operatoren
Operatoren arbeiten auf Argumentrelationen, indem sie Tupel oder Attribute auswählen
oder verknüpfen. Das Ergebnis einer Operation ist eine Menge von Tupeln, also wieder eine
Relation, auf der weitere Operationen ausgeführt werden können.
Die in der relationalen Algebra verwendeten Operatoren lassen sich in drei Gruppen einteilen:
• Scanoperatoren
arbeiten auf einer Relation, indem sie Tupel oder Attribute selektieren. Bei einer Aggregation können auch neue Attribute durch die Anwendung von Group-by-Funktionen
hinzukommen. Dies wird genauer in Kapitel 5 erläutert.
Operator
Bezeichnung
Bedeutung
π
P ROJECT ION
Projektion einer Relation auf eine Attributmenge
σ
SELECT ION
Γ
AGGREGAT ION
Selektion von Tupeln aus einer Relation
Aggregation einer Relation mit möglicher Gruppierung und Anwendung von Group-by-Funktionen
Tabelle 2.1: Bezeichnungen für Scanoperatoren
• Mengenoperatoren
verbinden zwei Relationen ohne die Angabe von Verbundbedingungen. Allerdings gibt
es für die Anwendung der Operatoren Voraussetzungen. Zur Formulierung der Anwendungsbedingungen wird eine Funktion sch(R) verwendet, die das Schema der Argumentrelation R wiedergibt.
Operator
Bezeichnung
Bedingung
×
P RODU CT
sch(R1 ) ∩ sch(R2 ) = ∅
∪
U N ION
sch(R1 ) = sch(R2 )
∩
IN T ERSECT
sch(R1 ) = sch(R2 )
−
M IN U S
sch(R1 ) = sch(R2 )
Tabelle 2.2: Bezeichnungen für Mengenoperatoren
• Joinoperatoren
verbinden die Tupel zweier Relationen mit Hilfe von Verbundbedingungen, beispielsweise: “OU T ERJOIN (P.ID = X.ID) P, X“. Ausnahmen sind Semi- und Antisemijoins, da diese Tupel aus der ersten Argumentrelation selektieren und keine Tupel
verbinden. Da diese Operatoren allerdings auf Joinoperatoren basieren, werden sie
hier mit aufgeführt.
2.1. SPRACHELEMENTE
15
Operator
Bezeichnung
Bedeutung
./
JOIN
A./
LEF T OU T ERJOIN
Verbund zweier Relationen mit anschließendem
Hinzufügen der linken, partnerlosen Tupel
./@
RIGHT OU T ERJOIN
Verbund zweier Relationen mit anschließendem
Hinzufügen der rechten, partnerlosen Tupel
A./@
OU T ERJOIN
Verbund zweier Relationen mit anschließendem
Hinzufügen der linken und rechten partnerlosen
Tupel
.<
SEM IJOIN
Selektion von Tupel aus der ersten Relation in
Abhängigkeit von der zweiten
.<
AN T ISEM IJOIN
Selektion von Tupel aus der ersten Relation in
Abhängigkeit von der zweiten
Verbund zweier Relationen
Tabelle 2.3: Bezeichnungen für Verbundoperatoren
2.1.3
Parametrisierungen
Die Parametrisierungen der Operationen beschreiben, welche Tupel oder Attribute ausgewählt oder verbunden werden. In dieser Arbeit werden die Parametrisierungen in vier
Kategorien eingeteilt:
1. Selektionsbedingungen
Diese Art von Parametrisierung wird bei einer Selektion verwendet und gibt an welche Tupel ausgewählt werden sollen. Als Verleichsoperatoren können ≥, >, =, 6=, <, ≤ ,
dargestellt durch >=, >, =, ! =, <, <=, genutzt werden. Selektionsbedingungen lassen
sich verknüpfen durch ∧, ∨ und ¬. Diese Verknüpfungen werden eingegeben durch
AN D, OR und N OT , z. B.:
SELECT ION (AN D((S.Semester >= ’5’), (S.Semester ! = ’6’))) S
Desweiteren sind arithmetische Ausdürcke möglich, wie z. B.:
SELECT ION (((S.Semester/2) + 3) =’5’) S
Für diese Ausdrücke stehen die Operatoren +, −, ∗, und / zur Verfügung. Es muss
bei der Eingabe solcher Bedingungen darauf geachtet werden, dass die einzelnen Berechnungen, wie es in dem Beispiel gezeigt wird, eingeklammert sind.
2. Projektionsparametrisierung
Als Parameter einer Projektion werden Attribute angegeben auf die projiziert werden
soll. Dabei sind tupelweise Berechnungen auf den Attributen der Argumentrelation
möglich, z. B.:
P ROJECT ION (S.M atrN r, ((S.Semester + 15) as S.SEM )) S
Wie man an dem Beispiel sieht, ist es ebenfalls möglich Attribute umzubenennen. Bei
arithmetischen Ausdrücken muss in jedem Fall ein neuer Name angegeben werden,
damit in der Ergebnisrelation eine eindeutige Attributbezeichnung vorhanden ist, wie
es in Kapitel 2.1.1 gefordert wird.
16
KAPITEL 2. RELATIONALER PARSER
3. Aggregationsparametrisierung
Aggregationsparametrisierungen bestehen aus zwei Teilen die durch # getrennt werden. Die linke Seite gibt an, nach welchen Attributen gruppiert werden soll. Auf der
rechten Seite können zusätzlich Group-by-Funktionen, wie z. B. count(∗) angegeben
werden. Bsp.:
AGGREGAT ION (P.N ame#P.N ame, (count(P.Kurse) as A.Count)) P
Für die Attribute, welche durch die Aggregationsfunktionen neu entstehen, muss eine
eindeutige Bezeichnung angegeben werden. Eine Attributbezeichnung muss die Form
RELAT ION.AT T RIBU T haben. Für die Relation kann ein beliebiger, noch nicht
vorhandener Name verwendet werden. Es ist sinnvoll für alle neu entstandenen Attribute den selben Namen für die Relation zu verwenden.
4. Verbundbedingungen
Diese Bedingungen beziehen sich immer auf die Attribute zweier Relationen und geben
an, auf welche Art und Weise diese verbunden werden sollen. Einzelne Joinbedingungen können durch ∧-Verknüpfungen zu komplexen Bedingungen verbunden werden.
Als Vergleichsoperatoren innerhalb einer Bedingung sind ≥, >, =, 6=, <, ≤ zugelassen,
z. B.:
OU T ERJOIN (P.ID <= X.ID) P, X
2.1.4
Zusätzliche Elemente
Zum Umfang der Sprache gehören noch weitere Elemente, wie z. B. das Setzen von Aliasnamen. Aliase können an Basisrelationen, Operationsergebnisse und Attribute vergeben
werden. Dies könnte beispielsweise bei Self-Joins benötigt werden. Das Ändern von Attributbezeichnungen wurde bereits in Kapitel 2.1.3 mit den Projektionsbedingungen erläutert.
Das Vergeben von Aliasnamen an Operationen und Basisrelationen wird beispielhaft im
folgenden dargestellt, wobei das Wort as zur Angabe einer neuen Bezeichnung verwendet
wird:
P ROJECT ION (Z.ID) (SELECT ION (X.ID =’5’) (S as X) as Z)
Der Parser soll zusätzlich in der Lage sein, Terme die Bestandteil von Ersetzungsregeln
sind zu parsen, da diese ebenfalls in einem generalisierten Baum gespeichert werden. Ein
Term einer Regel wird genutzt, um Stellen in einem zu optimierenden Ausführungsplan zu
identifizieren, bei denen eine Termersetzung möglich ist. Der andere gibt eine Struktur vor,
nach der eine gefundenen Stelle umgeformt wird. Genauere Informationen über den Aufbau
von Termersetzungsregeln sind im nächsten Kapitel zu finden.
Die Seiten einer Regel enthalten weder konkrete Parametrisierungen der Operationen, noch
konkrete Relationen. Stattdessen werden Variablen angegeben. Bei der Eingabe muss darauf
geachtet werden, dass für jeden Operator eine physische Umsetzung ausgewählt wird und
die variablen Parametrisierungen und Relationen in eckigen Klammern geschrieben werden.
Somit ist der Parser in der Lage zu erkennen, dass eine Regel geparst werden soll, z. B.:
JOIN rel-ind [p] [R1 ] [R2 ]
Bei den physischen Umsetzungen bezeichnet rel einen Relationenscan, ind einen Indexzugriff, sort steht für die Ausnutzung einer Sortierung und hash für das Verwenden eines
Hash-Algorithmus. Um anzugeben, dass die physische Realisierung beliebig ist, wird das
2.2. ENTWICKLUNG DER GRAMMATIK
17
Wort epsilon verwendet. In einer Regel können noch weitere Operatoren vorhanden sein,
die angeben, dass ein Index oder eine Sortierung erzeugt werden soll. Die Bezeichnungen
und Beschreibungen dieser Operatoren sind in Tabelle 2.4 aufgeführt:
Operation
Beschreibung
IN DEX [l] [R]
Erzeugung eines Index auf den übergebenen Attributen l der Relation R.
SORT [l] [R]
Sortierung der Relation R nach der Attributmenge l.
Tabelle 2.4: Erzeugende Operatoren
Das folgende Beispiel zeigt die Eingabe eines Regelterms, in dem ein Index erzeugt werden
soll, um eine Projektion über einen Indexzugriff zu ermöglichen:
P ROJECT ION ind [l] (IN DEX ([l]) [R])
2.2
Entwicklung der Grammatik
Die Grammatik stellt den Kern des Parsers dar. Eine Grammatik G = (N, T, P, S) wird
definiert durch die Menge ihrer nichtterminalen Symbole N , dies sind Hilfsmittel zum Konstruieren von Sätzen, durch die Menge der terminalen Symbole T , sie sind die Wörter der
Eingabesprache, die Produktionen P , die eine Menge von Ableitungsregeln zur Erzeugung
von Sätzen bilden und der Angabe eines Startsymbols S.
Eine Produktion besteht aus einer linken, einer rechten Seite und einem Überführungspfeil.
Die linke Seite besteht nur aus einem nichtterminalen Zeichen. Auf der rechten Seite können
Nichtterminale und Terminale vorkommen, z. B.:
<operation> → SELECTION <phySingle> (<conditionSelection>) <start> <alias>
An dieser Beispielproduktion wird deutlich auf welche Weise Nichtterminale genutzt werden.
Bei der Verarbeitung eines Satzes werden die nichtterminalen Symbole durch ihre Ableitungen ersetzt.
In dieser Arbeit wird ein deterministischer Top-Down-Parser entwickelt, d. h. nach [Par04]:
1. Man ließt von rechts nach links jedes Wort der Eingabe nur einmal.
2. Parallel wird der Ableitungsbaum von oben nach unten und von links nach rechts
erzeugt. Dies bedeutet, dass eine Linksableitung der Anfrage rekonstruiert wird. Ein
Beispiel für einen Ableitungsbaum ist im Anhang A.3 zu finden.
3. Ist man an einem Knoten des Ableitungsbaumes angekommen, der mit einem nichtterminalen Symbol A markiert ist und der als nächstes gemäß 2. verarbeitet werden muss,
kann man mit Hilfe des nächsten zu lesenden Wortes die anzuwendene A-Produktion
eindeutig bestimmen.
Diese drei Punkte werden mit Hilfe der sogenannten LL(1)-Bedingung überprüft. Ein Definition dieser Bedingung folgt auf den nächsten Seiten.
Für den Parser wurde zuerst eine kontextfreie Grammatik aufgestellt, die den gewünschten
Sprachumfang gewährleistete. Diese konnte allerdings nicht eindeutig geparst werden, da
sie linksrekursive Produktionen und Produktionen mit gemeinsamen Präfixen enthielt. Um
arbeiten zu können, muss der Parser nach jedem gelesenen Wort wissen in welcher Produktion er sich befindet. Dies ist nicht möglich, wenn Produktionen mit der selben linken Seite
18
KAPITEL 2. RELATIONALER PARSER
gemeinsame Präfixe auf der rechten Seite haben. Linksrekursive Produktionen beginnen auf
der rechten Seite mit dem nichtterminalen Symbol der linken, wodurch Endlosschleifen entstehen können.
Unter Anwendung der in [Par04] vorgestellten Methoden wurde die Grammatik in eine
eindeutige Form gebracht:
1. Entfernen gemeinsamer Präfixe:
Seien A → αβ1 |...|αβn |γ1 |...|γm alle A-Produktionen der Grammatik, wobei α 6= ε und
kein γi das Präfix α hat. Ersetze diese Produktionen durch:
A → αA0 |γ1 |...|γm und
A0 → β1 |...|βn
2. Beseitigung linksrekursiver Produktionen:
Seien A → Aα1 |...|Aαn |β1 |...|βm alle A-Produktionen, wobei kein βi mit A beginnt
und alle αi =
6 ε sind. Dann ersetze man diese Produktionen durch:
A → β1 A0 |...|βn A0 und
A0 → α1 A0 |...|αm A0 |ε
wobei A0 ein neues nichtterminales Symbol ist.
Das Entfernen von Linksrekursionen in der Grammatik wird an einem Beispiel demonstriert:
Rekursive Produktionen:
<conditionProjection> → <calcTarget> <alias>
<conditionProjection> → <conditionProjection>, <conditionProjection>
Bereinigte Produktionen:
<conditionProjection> → <calcTarget> <alias><additionalCondition>
<additionalCondition> → , <conditionProjection> | ε
Um abschließend sicherzustellen, dass Anfragen eindeutig geparst werden können, wird die
Grammatik auf die LL(1)-Bedingungen hin überprüft:
• Definition
Eine kontextfreie Grammatik G = (N, T, P, S) heißt LL(1)-Grammatik, falls für alle
Symbole A aus der Menge der Nichtterminalen gilt1 :
Seien A → α1 | . . . |αn alle A-Produktionen in P .
1. First(α1 ),. . . , First(αn ) sind paarweise disjunkt.
2. Ist ε ∈ First(αj ), dann ist Follow(A)∩ First(αi ) = ∅ für 1 ≤ i ≤ n, i 6= j
Die Funktion First(α) bestimmt die Menge der terminalen Symbole die bei einer
Linksableitung von α als erstes auftreten. Dazu kann auch das leere Wort ε gehören.
Follow(α) ist die Menge der Terminale, die bei einer Ableitung, beginnend beim Startsymbol, direkt auf α folgen. Das Ende eines Satzes wird vom Parser mit dem Symbol $
markiert. Wenn α das letzte Wort sein kann, gehört die Endmarkierung $ zu Follow(α).
Algorithmen zur Bestimmung der First- und Followmengen, sind in [Par04] zu finden.
• Behauptung
Die entwickelte Grammatik erfüllt die LL(1)-Bedingung!
1
nach [Par04]
2.3. INHALTLICHE ANALYSE
19
• Beweis
Da der Beweis sehr lang ist und wenig neue Informationen mit sich bringt, ist er in
Anhang A.2 zu finden. Die wichtigsten Erkenntnisse sollen hier aber kurz aufgelistet
werden:
– Bei der Überprüfung der <operation>-Produktionen zeigte sich, dass Probleme
beim Parsen auftreten, wenn die Bezeichnung einer Relation gleich der Bezeichnung einer Operation ist:
First(SELECTION <phySingle> (<conditionSelection>)
<start> <aliasRelation>) = {selection},
First(RELAT ION <aliasRelation>)
Der Ausdruck RELAT ION steht für den Namen einer Relation und ist ein
terminales Zeichen, das zur Laufzeit bestimmt werden muss. Damit die beiden
Firstmengen, wie in der LL(1)-Bedingung gefordert, disjunkt sind, muss der Name der Relation sich von der Operatorbezeichnung unterscheiden.
– Ein ähnliches Problem tritt bei den Produktionen für das nichtterminale Symbol
<start> auf:
First((<operation>)) = {(},
First([<varRelation>]) = {[} und
First(RELAT ION ) = {RELAT ION }
Damit kein Fehler beim Parsen entsteht, darf eine Relationsbezeichnung nicht ’(’
oder ’[’ sein.
Das Ergebnis dieser Arbeit, die fertige Grammatik, ist in Anhang A.1 zu finden.
2.3
Inhaltliche Analyse
Nachdem die syntaktische Analyse abgeschlossen ist, wird der Anfragesatz auf inhaltliche
Korrektheit überprüft. Dabei wird z. B. untersucht, ob Aliasnamen bereits verwendet werden
und ob die Attribute einer Operationsparametrisierung in der bzw. den Argumentrelationen
vorhanden sind.
Bei komplexen Selektionsbedingungen, deren Prädikate mehrfach durch ∧, ∨ oder ¬ verknüpft sind, treten häufig redundante Ausdrücke auf. Außerdem dauert die Optimierung
eines Ausführungsplanes umso länger, je komplexer solche Bedingungen sind. Aus diesen
Gründen wird beim Parsen eine disjunktive Minimalform aus allen Selektionsbedingungen
erstellt.
2.3.1
Vereinfachung von komplexen Selektionsbedingungen
Nachdem geprüft wurde, dass sich die Attribute einer Selektionsbedingung auf die Argumentrelation bzw. Argumentrelationen beziehen, wird der Bedingungsterm mit Hilfe der
Quine-McCluskey-Methode in eine Minimalform transformiert. Dafür wird die Bedingung
zuerst in eine disjunktive Form gebracht, d. h. es wird ein Term aus Disjunktionen von Konjunktionen aufgebaut.
Beispiel:
Atomare Bedingungen seien durch p1 , ... , pn notiert.
p1 ∧ (p2 ∨ p3 ) → (p1 ∧ p2 ) ∨ (p1 ∧ p3 )
20
KAPITEL 2. RELATIONALER PARSER
Mit Hilfe der disjunktiven Form kann eine Wertetabelle aufgebaut werden, die benötigt
wird, um mit dem Quine-McCluskey-Verfahren beginnen zu können.
Nach [Bart03] unterteilt sich der Ablauf des Algorithmus in drei Phasen:
1. Erstellen der ersten Quine’schen Tabelle
Definition: Ordnung
Die Ordung i entspricht den Ergebnissen der i’ten Iteration des Algorithmus. Die
Ausdrücke innerhalb jeder Ordnung sind sortiert nach der Anzahl der in ihnen vorkommenden nicht negierten, atomaren Bedingungen.
Schritt 1: Erzeugung der Ordnung 0
Die Minterme, dies sind Terme für die die Bedingungsfunktion wahr ist, werden nach
der Anzahl der in ihnen vorkommenden, nicht negierten, atomaren Bedingungen geordnet. Sind in der Wertetabelle keine Minterme zu finden, wird der Algorithmus
abgebrochen und f alse ausgegeben.
Schritt 2: Erzeugung der Ordnung i + 1
Es werden jeweils zwei Ausdrücke gesucht, die sich nur in einer Belegung unterscheiden, und durch Ersetzen der unterschiedlichen Belegungen zu einem ’don0 t care T erm’,
zusammengefasst. Die zusammengefassten Ausdrücke bilden die nächste Ordnung.
Durch die Anordnung der Minterme müssen nur Zeilen in benachbarten Gruppen einer
Ordnung verglichen werden. Zwei Ausdrücke aus denen ein neuer entstanden ist, werden markiert, nehmen aber weiterhin an den Vergleichen teil. Entsteht ein Ausdruck
der ausschließlich ’don0 t care T erme’ enthält, endet der Algorithmus und es wird true
ausgegeben.
Beispiel:
In den Tabellen zur Darstellung einer Ordnung, wird die letzte Spalte verwendet, um
Ausdrücke aus denen ein neuer entstanden ist, mit x zu markieren. Die Wertetabelle
ist durch die Selektionsbedingung f in disjunktiver Form gegeben:
f = (¬p2 ∧ ¬p1 ∧ ¬p0 ) ∨ (¬p2 ∧ p1 ∧ p0 ) ∨ (p2 ∧ ¬p1 ) ∨ (¬p2 ∧ p1 )
Bestimmung der 0’ten Ordnung:
Nr.
0
p2
0
p1
0
p0
0
f
1
1
0
0
1
0
2
0
1
0
1
3
0
1
1
1
4
1
0
0
1
5
1
0
1
1
6
1
1
0
0
7
1
1
1
0
Gruppe
0
1
1
2
2
Nr.
0
2
4
3
5
p2
0
0
1
0
1
p1
0
1
0
1
0
p0
0
0
0
1
1
x
x
x
x
x
Bestimmung der 1’ten Ordnung:
Wertetabelle
Gruppe
0
0
1
1
Nr.
0,2
0,4
2,3
4,5
p2
0
0
1
p1
0
1
0
p0
0
0
-
-
2. Bestimmung der Primimplikanten
Es werden die nicht verwendeteten Ausdrücke der ersten Phase in eine boolesche Form
übertragen. Dies sind die Primimplikanten.
2.3. INHALTLICHE ANALYSE
21
Beispiel: (Fortsetzung)
In diesem Beispiel wurden die Ausdrücke mit der Nummer (0,2), (0,4), (2,3) und (4,5)
nicht verwendet:
Ausdruck
Boolesche Form
0-0
¬p2 ∧ ¬p0
-00
¬p1 ∧ ¬p0
01¬p2 ∧ p1
10p2 ∧ ¬p1
3. Bestimmung der Kern-Primimplikanten
Die Primimplikanten werden zusammen mit den Nummern der Minterme, aus denen
sie hervorgegangen sind, in die 2. Quine’sche Tabelle eingetragen. Diese wird auch
Überdeckungstabelle genannt.
Beispiel: (Fortsetzung)
Die Spalten mit der Nummer
heitstabelle.
Ausdruck
¬p2 ∧ ¬p0
¬p1 ∧ ¬p0
¬p2 ∧ p1
p2 ∧ ¬p1
0 bis 7 stehen für die überdeckten Minterme der Wahr0
x
x
1
2
x
3
4
5
6
x
x
x
x
x
7
Kosten
a
b
c
d
Die Aufgabe besteht darin, eine Überdeckung aller Minterme mit möglichst wenigen
Primimplikanten zu erreichen. Die Überdeckungsfunktion entsteht aus der konjunktiven Verknüpfung der Spalten der Minterme, wobei die markierten Ausdrücke disjunktiv verbunden werden. Aus der vereinfachten Überdeckungsfunktion ergibt sich die disjunktive Minimalform bzw. alternative Minimalformen, da das Ergebnis nicht eindeutig sein muss. In solchen Fällen wird die Auswahl des realiserenden Terms abhänging
von einer Kostenfunktion getroffen. In dieser Arbeit wird folgende Funktion verwendet: Negation kostet eine Einheit, jede Verknüpfung kostet eine Einheit.
Beispiel: (Fortsetzung)
Vollständige Überdeckungsfunktion:
u = (a ∨ b) ∧ (a ∨ c) ∧ c ∧ (b ∨ d) ∧ d
Vereinfachte Überdeckungsfunktion:
u = (a ∧ c ∧ d) ∨ (b ∧ c ∧ d)
Auswahl des realisierenden Terms:
Aus der vereinfachten Überdeckungsfunktion ergeben sich zwei Möglichkeiten, um eine
disjunktive Minimalform aufzubauen. Die Auswahl wird mit Hilfe der beschriebenen
Kostenfunktion getroffen:
K(a ∧ c ∧ d) = (2 + 2) + (2 + 1) + (2 + 1) = 10
K(b ∧ c ∧ d) = (2 + 2) + (2 + 1) + (2 + 1) = 10
Da beide Realisierungen die gleichen Kosten verursachen, wird eine beliebige ausgewählt:
DM F (b ∧ c ∧ d) = (¬p1 ∧ ¬p0 ) ∨ (¬p2 ∧ p1 ) ∨ (p2 ∧ ¬p1 )
22
KAPITEL 2. RELATIONALER PARSER
2.3.2
Operationsvereinfachung
Es kann, besonders nach der Minimierung einer Selektionsbedingung vorkommen, dass eine
Operation ausgeführt werden soll, die wenig Sinn macht. Z. B. wäre ein Selektion mit der
Bedingung true schlecht, da die komplette Argumentrelation unnötig gelesen wird, also
ersetzt man σtrue (R) durch R. Diese Art der Vereinfachung kann bei fast allen Operatoren,
unter gewissen Voraussetzungen, ausgeführt werden. In Tabelle 2.5 sind die Operatoren mit
den kritischen Bedingungen und die vereinfachte Form dargestellt:
Anfrage
relationaler Ausdruck
Ersetzung
SELECT ION true R
σtrue (R)
R
SELECT ION f alse R
σf alse (R)
∅
(LEF T /RIGHT OU T ER-)JOIN true R S
R ./true S
R×S
JOIN f alse R S
R ./f alse S
∅
RIGHT OU T ERJOIN f alse R S
R ./@f alse S
S
LEF T OU T ERJOIN f alse R S
R A./f alse S
R
SEM IJOIN true R S
R .<true S
R
SEM IJOIN f alse R S
R .<f alse S
∅
AN T ISEM IJOIN true R S
R .<true S
∅
AN T ISEM IJOIN f alse R S
R .<f alse S
R
Tabelle 2.5: Vereinfachung von Operationen
Eine Selektion mit der Bedingung f alse gibt kein Tupel zurück, d. h. das Ergebnis ist eine
leere Relation, im folgenden Null-Relation genannt. Im Allgemeinen wird aus einer Verbundoperation mit der Bedingung true ein kartesisches Produkt, da jedes Tupel mit jedem
verbunden wird. Bei der Bedingung f alse ergibt sich wieder eine Null-Relation, außer bei
einem Left- bzw. Right-Outerjoin, da hier die Tupel der linken bzw. rechten Relation auch
ohne Joinpartern ausgegeben werden. Weitere Ausnahmen sind Semi- und Antisemijoin,
da sie eher einer Selektion ähneln. Wird ein Semijoin mit true ausgeführt, ist das Ergebnis die komplette linke Argumentrelation. Ist die Bedingung hingegen f alse ergibt sich die
Null-Relation. Für den Antisemijoinoperator gelten die selben Argumentationen nur mit
vertauschten Bedingungen.
Kapitel 3
Regelparsing
Wie in der Einleitung beschrieben wurde, gehören zu einer Termersetzungsregel zusätzlich
zu den beiden Regeltermen eine oder mehrere Regelbedingungen. Diese Bedingungen geben
an, wann eine Termersetzung sinnvoll ist. An der Regel in Abbildung 3.1 ist zu sehen, dass
sie ebenfalls zum Zuweisen von Werten verwendet werden.
τ1 := πlRel (R1 × R2 )
1
τ2 := πlRel
((πlRel
R1 ) × (πlRel
R2 ))
1
2
3
2
1) falls cost(τ2 ) < cost(τ1 ),
2) falls cost(τ1 ) < cost(τ2 ),
l1 = l,
l = l1 ∪ l2 ∪ l3
l2 = l ∩ sch(R1 ),
l3 = l ∩ sch(R2 )
Abbildung 3.1: Termersetzungsregel
Im vorherigen Kapitel wurde beschrieben, wie Regelterme eingegeben werden. Dieses Kapitel beschäftigt sich mit der Eingabe der einzelnen Bedingung und der abschließenden
inhaltlichen Prüfung einer Regel.
In der Arbeit [Rud05] beschäftigt sich Herr Rudnicki mit einem Termersetzer für ein Einphasenoptimierungssystem und den dazugehörigen Termersetzungsregeln. Mit ihm zusammen
wurde der Aufbau von Regelbedingungen und die darin verwendeten Bezeichnungen festgelegt.
3.1
Bedingungsarten
Die in Regeln auftretenen Bedingungen lassen sich in mehrere, voneinander getrennte Klassen einteilen. Die verwendeten Symbole <attributmenge>, <bedingung> und <relation>
stehen für Variablenbezeichnungen wie sie in Termersetzungsregeln vorkommen und nicht
für konkrete Werte. Dabei sind mit <bedingung> Bezeichnungen für Selektions- oder Verbundbedingungen gemeint.
3.1.1
Existenzbedingungen
Mit dieser Art von Regelbedingung kann geprüft werden, ob Sortierungen oder Indexe vorhanden sind und ob sie bei relationalen Operationen genutzt werden können. Eine Existenzbedingung hat immer die Form:
<negation> <existenzf unktion>
23
24
KAPITEL 3. REGELPARSING
• <existenzf unktion>
Es können mehrere Funktionen verwendet werden, deren Ergebnis ein Wahrheitswert,
also true oder f alse, ist:
1. index supported(<attributmenge>, <relation>):
Diese Funktion liefert true, falls auf einer Relation ein Index auf der übergebenen
Attributmenge vorhanden ist.
2. merge supported(<bedingung>, <relation>, <relation>):
Um einen Merge-Join anwenden zu können, ist es nicht ausreichend, dass die Argumentrelationen auf den Joinattributen sortiert sind. Es werden weitere Anforderungen an den Aufbau der Verbundbedingung gestellt. Diese werden genauer
in Kapitel 5.4.3 erläutert. Mit dieser Funktion kann überprüft werden, ob ein
Merge-Join zweier Relationen mit Hilfe einer Verbundbedingung möglich ist.
3. sorted(<attributmenge>, <relation>):
Mit dieser Funktion kann man herrausfinden, ob eine Relation auf einer Attributmenge sortiert ist.
• <negation>
Das Ergebnis der verwendeten Funktionen kann mit dem Wort N OT negiert werden.
Beispiele:
merge supported(p, R1, R2),
N OT index supported(l, R)
3.1.2
Eigenschafts- und Zuweisungsbedingungen
In dieser Bedingungsart wird mit Attributmengen gearbeitet. Es kann der Schnitt und die
Vereinigung zweier Mengen bestimmt werden. Desweiteren kann man in diesen Bedingungen
Variablen erzeugen und ihnen einen Wert zuweisen. Die Form einer solchen Regelbedingung
ist:
<element> <berechnung> <operator> <element> <berechnung>
• <element>
Die Grundelemente sind:
1. attr(<bedingung>):
Mit dieser Funktion werden die in einer Selektions- oder Verbundbedingung enthaltenen Attribute beschrieben.
2. sch(<relation>):
Das Schema einer Relation wird mit dieser Funktion dargestellt.
3. <attributmenge>:
Attributmengenbezeichnungen können ebenfalls als Grundelemente verwendet
werden. Die leere Attributmenge wird mit dem Wort null dargestellt.
• <berechnung>
Es ist möglich den Schnitt oder die Vereinigung der aufgeführten Grundelemente zu
bilden. Die Operatoren ∪ und ∩ werden durch union und intersect eingegeben.
• <operator>
In jeder dieser Bedingungen muss ein Vergleichs- oder Zuweisungsoperator auftreten:
3.1. BEDINGUNGSARTEN
25
1. <vergleichsoperator>:
Um Attributmengen vergleichen zu können werden die Operatoren ⊇, ⊃, =, 6=, ⊂
und ⊆ bereitgestellt. Um zu kennzeichnen, dass ein Vergleich und keine Zuweisung gefordert ist, wird = eingegeben durch ==. Die restlichen Operatoren werden notiert durch ]=, ], !=, [ und [=.
Bei einem Vergleichsoperator können auf beiden Seiten Vereinigungen, Schnitte
und Attributmengen verwendet werden, beispielsweise:
attr(p) intersect sch(R) != null oder attr(q) [= sch(R)
2. <zuweisungsoperator>:
Um Variablen einen Wert zuzuweisen, wird ein einfaches Gleichheitszeichen verwendet.
Bei Zuweisungsbedingungen ist links vom Zuweisungsoperator nur eine Variablenbezeichnung für eine Attributmenge zugelassen. Auf der rechten Seite wird
die Berechnung des Wertes notiert, z. B.:
l = attr(p) union sch(R)
3.1.3
Aufteilen und Zuweisen von Selektions- oder Verbundbedingungen
In einigen Termersetzungsregeln ist es nötig, logisch verknüpfte Selektions- oder Verbundbedingungen aufzuteilen. Um dies zu ermöglichen wird diese Regelbedingungsart zur Verfügung
gestellt. Der Aufbau ist folgendermaßen:
<bedingung> <zuweisungsoperator> <f unktion> <verknüpf ung>
• <bedingung>:
Auf der linken Seite einer solchen Regelbedingung muss eine Variablenbezeichnung für
eine Selektions- oder Verbundbedingung stehen. Darauf folgt ein ’=’ als Zuweisungsoperator.
• <f unktion>:
Die Voraussetzung, um die folgenden Funktionen anwenden zu können, ist, dass die
atomaren Bedingungen ausschließlich durch ∧ verknüpft sind.
1. f etch condition(<bedingung>, <relation>):
Diese Funktion liefert die atomaren Teile einer Selektionsbedingung, welche sich
ausschließlich auf die übergebene Relation beziehen. Beispielsweise:
p := (R1 .A = R2 .D ∧ R1 .A =’5’ ∧ R1 .B ≥’7’ ∧ R2 .D =’10’)
f etch condition(p, R1 ) = (R1 .A =’5’ ∧ R1 .B ≥’7’)
f etch condition(p, R2 ) = (R2 .D =’10’)
2. f etch joinCondition(<bedingung>, <relation>, <relation>):
Mit dieser Methode werden die Teile einer Selektionsbedingung, welche sich als
Verbundbedingung zwischen zwei Relationen eignen, ausgegeben, z. B:
p := (R1 .A = R2 .D ∧ R1 .A =’5’ ∧ R1 .B ≥’7’ ∧ R2 .D =’10’)
f etch joinCondition(p, R1 , R2 ) = (R1 .A = R2 .D)
26
KAPITEL 3. REGELPARSING
3. f etch f irst(<bedingung>):
Diese Funktion bestimmt die erste atomare Formel einer Selektionsbedingung.
4. f etch rest(<bedingung>):
Der Rest der Selektionsbedingung kann mit dieser Methode gefunden werden.
• <verknüpf ung>:
Auf der rechten Seite einer Zuweisungsbedingung können Selektions- und Verbundbedingungen verknüpft werden durch ∧, ∨ und ¬, notiert durch AN D, OR und N OT ,
z. B.:
p = f etch f irst(p) AN D f etch rest(p),
q = p0 OR p1 OR p2
Soll aus einer Bedingung ein Teil entfernt werden, der durch eine der gegebenen Funktionen zuvor identifiziert wurde, kann der Operator ’−’ genutzt werden.
Beispiel:
p1 = f etch joinCondition(p, R1, R2)
p2 = p − p1
3.1.4
Bestimmung von Vergleichsoperatoren
Für manche Termersetzungsregeln ist es nötig die in Selektions- oder Verbundbedingungen verwendeten Vergleichsoperatoren zu kennen. Man erhält sie mit Hilfe der Funktion
op(<bedingung>). Mehrfach vorhandene Operatoren werden nicht mit ausgegeben. Eine
Regelbedingung dieser Art hat immer die Form:
op(<bedingung>) = {<operatoren>}
Innerhalb der geschweiften Klammern können, durch Kommata getrennt, mehrere der Operatoren >=, >, =, ! =, <, <= vorkommen.
3.1.5
Kostenbedingungen
Diese Art von Regelbedingung kann erst überprüft werden, wenn mit Hilfe der Regel eine
Variante erzeugt wurde. Dies ist eine Ausnahme. Die anderen Bedingungsarten können
geprüft werden, ohne einen neuen Term erzeugen zu müssen. Eine Kostenbedingung hat die
Form:
<multiplikator> <kostenf unktion> <vergleich> <multiplikator> <kostenf unktion>
• <kostenf unktion>:
Zur Erzeugung einer solchen Bedingung stehen folgende Funktionen zur Verfügung:
1. card(<relation>):
Mit dieser Funktion wird die Kardinalität einer Relation beschrieben.
2. page(<relation>):
Die Pagegröße einer Relation wird mit dieser Funktion angegeben.
3. cost(<baum>):
Diese Funktion wird genutzt um die Kosten des ursprünglichen Baumes mit denen
des neu entstanden zu vergleichen. Der Baum, der zu der rechten Seite einer Regel
gehört, wird mit right eingegeben. Entsprechend wird der andere Baum mit left
notiert.
3.2. ENTWICKLUNG EINER GRAMMATIK
27
4. sel(<bedingung>, <relation>):
Mit Hilfe dieser Funktion lässt sich die Selektivität einer Selektionsbedingung auf
einer Relation bestimmen.
• <multiplikator>:
Die Ergebnisse der aufgeführten Funktionen lassen sich mit einem Multiplikator gewichten, z. B.:
5 ∗ cost(lef t) <= cost(right)
• <vergleich>:
Für den Vergleichsoperator gibt es die Möglichkeiten ≥, >, =, <, ≤, notiert durch >=
, >, =, <, <=.
3.2
Entwicklung einer Grammatik
Die Sprache zur Eingabe von Regelbedingungen wurde auf die selbe Art und Weise entwickelt wie für den relationalen Parser. Die Elemente der Sprache und der Aufbau der verschiedenen Bedingungsarten wurde im vorherigen Abschnitt dargestellt. Mit dieser Grundlage
wurde eine erste Version einer kontextfreien Grammatik erstellt.
Wie bei der Grammatik des relationalen Parsers traten auch hier linksrekursive Produktionen und Produktionen mit gemeinsamen Präfixen auf. Es wurden erneut die Funktionen
aus [Par04] verwendet um diese Probleme zu beseitigen.
Zum Abschluss wurde diese Grammatik ebenfalls auf die LL(1)-Bedingung hin überprüft.
Im Anhang ist die Überprüfung und die endgültige Version der Grammatik aufgeführt.
3.3
Semantische Überprüfung einer Termersetzungsregel
Wurden die beiden Regelterme und die Regelbedingungen erfolgreich geparst, müssen sie
auf inhaltliche Richtigkeit hin überprüft werden.
Zuerst wird geprüft, ob in beiden Regelbäumen die selben Relationenbezeichnungen verwendet werden. Beispielsweise würde bei der Termersetzungsregeln in Abbildung 3.1 überprüft
werden, ob in den beiden Regeltermen τ1 und τ2 die Bezeichnungen R1 und R2 vorkommen.
Es muss sichergestellt sein, dass bei einer Termersetzung auf beiden Regelseiten jeder Variablen ein Wert zugewiesen werden kann. Entweder wird die selbe Variablenbezeichnung auf
beiden Seiten verwendet, d. h. der Termersetzer kann, falls er in einem Ausführungsplan eine
Übereinstimmung mit einer Regelseite findet, die Werte direkt der entsprechenden Variablen der anderen Seite zuweisen. Zu sehen ist dies in Abbildung 1.4. Dort werden auf beiden
Seiten die selben Variablenbezeichnungen p und l verwendet. Wird einer Variablen nicht auf
diese Weise ein Wert zugewiesen, muss dies in der Regel über eine Zuweisungsbedingung
geschehen, so wie es in der Ersetzungsregel in Abbildung 3.1 der Fall ist.
Abschließend wird überprüft, ob die Parameter der Funktionen innerhalb der Regelbedingungen den richtigen Typ haben, z. B. muss p bei der Funktion attr(p) eine Selektions- oder
Verbundbedingung sein. Ist p in einem Regelbaum als Attributmenge deklariert, entstände
ein Fehler.
28
KAPITEL 3. REGELPARSING
Kapitel 4
Generalisierte Bäume
Für eine Einphasenoptimierung wird als Basis eine Datenstruktur benötigt, die den Ausführungsplan und sämtliche Daten, die für eine Optimierung benötigt werden, speichert.
Diese Struktur ist der generalisierte Baum, in dem jeder relationale Operator in einem Knoten gespeichert wird. Die Attribute der einzelnen Knoten eines Anfragebaumes werden in
den nächsten Abschnitten dargestellt.
Ersetzungsregeln, die in einem Termersetzer verwendet werden, bestehen aus zwei Ausdrücken. Nach dem einen wird in einem Ausführungsplan gesucht und der andere gibt die
Struktur vor, in die der gefundene Teil umgeformt werden soll. Um diese Terme zu speichern
wird ebenfalls eine Form des generalisierten Baumes verwendet. Dieses Thema wird im 6.
Abschnitt dieses Kapitels behandelt.
Damit ein Termersetzer Varianten aus einem Ausführungsplan erzeugen kann, werden Methoden zum Bearbeiten eines generalisierten Baumes benötigt. In Kapitel 4.7 werden diese
aufgezählt und beschrieben.
4.1
Knotenattribute
Jeder Knoten und jedes Blatt im generaliserten Baum enthält eine Reihe von relationenbezogenen Daten, Metainformationen und Funktionen zum Bestimmen dieser. Die folgenden
Attribute werden bei der Erzeugung eines Knoten gesetzt bzw. berechnet. Die dafür benötigten Formeln und ein Beispiel zur Berechnung der Kosten sind in Kapitel 5 zu finden.
Attribute die beim Erzeugen eines Knotens gesetzt werden müssen:
• Kindknoten
Dem erzeugten Knoten muss die Argumentrelation bzw. Argumentrelationen, falls es
sich um einen binären Operator handelt, bekannt sein. Daher kann ein Knoten erst
nach der Erstellung seiner Kinder erzeugt werden.
• Physische Umsetzung
Für jeden relationalen Operator gibt es eine Reihe von physischen Umsetzungen, z. B.
kann eine Selektion mit einem Relationenscan oder einem Indexzugriff realisiert werden. Eine der Möglichkeiten sollte ausgewählt und bei der Erzeugung angegeben werden. Ansonsten wird eine kostenabhängige Auswahl der physischen Realisierung getroffen. Dies wird im nächsten Abschnitt dieses Kapitels genau erklärt.
29
30
KAPITEL 4. GENERALISIERTE BÄUME
• Parametrisierung
Zu den meisten Operatoren wird in einer Anfrage eine Parametrisierung angegeben,
beispielsweise SELECT ION S.Semester=’5’ S. Bei Mengenoperatoren wird keine
Parametrisierung benötigt. Allerdings müssen für ihrer Anwendung andere Vorraussetzungen erfüllt sein, aufgeführt in Kapitel 2.1.2.
Attribute die berechnet werden:
• Attribute der Ergebnisrelation
Es handelt sich hierbei um die Attribute einer Relation, wie sie in Kapitel 2.1.1 beschrieben wurden, und nicht um Knotenattribute. Die Menge der Attribute einer Relation, welche nach Anwendung einer relationalen Operation, z. B. einem Join, vorhanden ist, wird in dem Knoten der ausgeführten Operation gespeichert.
• Kardinalität
Dieser Wert gibt die Anzahl der Tupel der Ergebnisrelation an.
• Attributwertanzahlen
Für jedes Attribut der Ergebnisrelation wird die Anzahl verschiedener Werte nach
Anwendung der entsprechneden Operation abgeschätzt und gespeichert.
• Pagegröße
Die Pagegröße gibt den von der Relation belegten Speicherplatz an. Sie ergibt sich aus
der Anzahl und der Länge der Tupel.
• Kosten
Die CPU- und I/O-Kosten, die eine Operation verursacht, werden ebenfalls mit Funktionen im Knoten berechnet und gespeichert. Die Kostenformeln werden in Kapitel
5.4 aufgeführt und begründet.
• Sortierungen
Nach der Anwendung einer Operation ist es möglich, dass Sortierungen erhalten bleiben oder neu entstehen, die im weiteren Verlauf für eine Optimierung genutzt werden
können. Dies wird von den einzelnen Knoten während der Laufzeit überprüft. Die
Menge der sortierten Attribute wird im zugehörigen Knoten in einer Liste gespeichert.
• Pipelining
Pipelining stellt eine Möglichkeit zur Verringerung der Kosten dar, bei der Zwischenergebnisse nicht gespeichert, sondern tupelweise zum nächsten Operator übergeben
werden. Dadurch entfallen die Input/Output-Kosten für diese Relation bei der folgenden Operation. Es wird von den einzelnen Knoten geprüft, ob Pipelining möglich
ist.
4.2
Kostenabhängige Auswahl der physischen Umsetzungen
Es ist möglich die physische Umsetzung eines Operators, abhängig von den lokalen Kosten,
bestimmen zu lassen. Diese Möglichkeit wird z. B. bei dem vom relationalen Parser gelieferten Baum ausgenutzt, denn so erhält man eine Startkonfiguration mit der im Termersetzer
neuentstandene Ausführungspläne verglichen werden können. Steigen die Gesamtkosten einer Variation zu stark an, kann der entsprechende Anfrageplan ausgeschlossen werden. Die
4.3. SORTIERUNGEN
31
genaue Arbeitsweise des Termersetzers ist ein komplexes Thema und wird in [Rud05] behandelt.
Die Bestimmung der physischen Umsetzungen lässt sich am Besten an einem einfachen Beispiel zeigen. Die Daten über die Basisrelationen und eine detailierte Berechnung der Kosten
sind in Kapitel 5.5 zu finden.
Aus der folgenden Anfrage soll von dem relationalen Parser ein generalisierter Baum erzeugt
und dessen Kosten berechnet werden. Dieses Beispiel beschränkt sich auf I/O-Kosten und
es wird kein Pipelining angewandt.
P ROJECT ION (V.T itel, H.Count)
RIGHT OU T ERJOIN (V.V orlN r = H.V orlN r) V,
(AGGREGAT ION (H.V orlN r#H.V orlN r, (count(H.M atrN r) AS H.Count)) H)
Zuerst wird die Aggregation auf der Relation H erstellt. Als mögliche physische Umsetzungen ergeben sich:
ΓSort
g#f
1. ΓRel
g#f (H): I/O-Kosten= 186
2. ΓSort
g#f (H): I/O-Kosten= 6
H
Da die Kosten von ΓSort
g#f am geringsten sind, wird die 2. Umsetzungsmöglichkeit gewählt. Als
nächstes wird über die Realisierung des Right-Outerjoins entschieden. Zur Auswahl stehen:
1. V
./@Rel−Rel
p
(ΓSort
g#f (H)):
I/O-Kosten= 123
./@pInd−Rel
V
2. V ./@pInd−Rel (ΓSort
g#f (H)): I/O-Kosten= 85
ΓSort
g#f
H
Ausgewählt wird ein Index-Rel-Join, da die entstehenden Kosten bei dieser Variante am
geringsten sind. Im letzten Schritt wird über die physische Umsetzung der Projektion entschieden. Es gibt allerdings nur die Möglichkeit einen Relationenscan auszuführen, da weder
nutzbare Indexe noch Sortierungen vorhanden sind:
1. πlRel (V ./@pInd−Rel (ΓSort
g#f (H))): I/O-Kosten= 257
So entsteht folgender generalisierter Baum mit Gesamtkosten von 6 + 85 + 257 = 348:
Rel
πV.T
itel,H.Count
Ind−Rel
./@(V.V
orlN r=H.V orlN r)
V
ΓSort
(H.V orlN r#H.V orlN r,count(H.M atrN r))
H
4.3
Sortierungen
In diesem Abschnitt sind die Formeln für den Erhalt oder die Entstehung von Sortierungen
aufgeführt.
32
KAPITEL 4. GENERALISIERTE BÄUME
Zur Darstellung wird eine Funktion sorted(R, A) verwendet, die ’true’ liefert, falls die übergebene Relation R auf der Attributmenge A sortiert ist. Sonst ist die Rückgabe ’f alse’.
• Selektion
Bei einem Relationenscan bleiben Sortierungen erhalten, da die Argumentrelation tupelweise von Anfang bis Ende eingelesen wird:
sorted(σpRel (R), A) = sorted(R, A)
Ist ein Indexzugriff möglich, müssen zwei Attributmengen unterschieden werden. Die
Menge der Attribute A die in dem Index enthalten sind und die restliche Attribute B
der Argumentrelation. Die Attribute A werden sortiert ausgegeben, da die Tupel im
Index geordnet gespeichert sind. Bei den Attributen B der Relation kann nicht davon
ausgegangen werden das Sortierungen erhalten bleiben oder gar neu entstehen:
sorted(σpIndex (R), A) = true
sorted(σpIndex (R), B) = f alse
• Projektion
Wird ein Relationenscan für eine Projektion verwendet, gilt die selbe Argumentation
wie bei einer Selektion. Da die Argumentrelation tupelweise gelesen wird, bleiben
Sortierungen auf den projizierten Attributen A erhalten:
sorted(πlRel (R), A) = sorted(R, A)
Um π Sort verwenden zu können, ist Voraussetzung, dass die zu projizierenden Attribute A sortiert sind. Nach Ausführung der Projektion bleiben diese Sortierungen
erhalten. In einem Index sind die Tupel nach ihren Attributwerten geordnet, daher ist
die Ausgabe auf diesen Attributen ebenfalls sortiert:
sorted(πlSort (R), A) = sorted(πlIndex (R), A) = true
• Aggregation
Bei den Attributen A, die durch Aggregations-Funktionen wie z. B. count(∗), neu
entstanden sind, kann nicht davon ausgegangen werden, dass Sortierungen entstehen.
Falls eine Gruppierung verlangt wird und die dafür verwendeten Attribute ebenfalls
projiziert werden, kann eine Sortierung nur auf diesen Attributen B erhalten bleiben.
Eine genaue Beschreibung der Arbeitsweise einer Aggregation ist in Kapitel 5.4.2 zu
finden.
sorted(ΓRel
g#f (R), A) = f alse
sorted(ΓRel
g#f (R), B) = sorted(R, B)
Für ΓSort
g#f (R) gilt das selbe. Da hier Voraussetzung ist, dass die Attribute nach denen
gruppiert wird, sortiert sind, ist eine Sortierung in der Ergebnisrelation vorhanden,
falls auf diese Attributmenge ebenfalls projiziert wird.
sorted(ΓSort
g#f (R), A) = f alse
sorted(ΓSort
g#f (R), B) = true
4.3. SORTIERUNGEN
33
• Join
Im Folgenden seien B ∈ attr(p) ∩ sch(R1 ) die Joinattribute die sich auf R1 und
C ∈ attr(p) ∩ sch(R2 ) diejenigen die sich auf R2 beziehen. A und D sind die Attribute
der einzelnen Relationen ohne die zugehörigen Joinattribute.
Bei einem Nested-Loop-Join wird die Reihenfolge der Tupel aus R1 nicht verändert
und daher bleiben sortierte Spalten weiterhin sortiert:
sorted(R1 ./pN ested−Loop R2 , A) = sorted(R1 , A)
sorted(R1 ./pN ested−Loop R2 , B) = sorted(R1 , B)
Ist R1 auf einem Joinattribut B sortiert und die Joinbedingung p enthält ausschließlich Gleichheitsbedingungen, dann wird das dazugehörige Attribut C aus R2 ebenfalls
im Ergebnis des Joins sortiert sein. Ansonsten kann man nicht annehmen, dass Sortierungen entstehen oder erhalten bleiben. Das selbe gilt für einen Rel-Index-Join.

falls p aus Gleichheitsbedin
 sorted(R1 , B)
gungen besteht
sorted(R1 ./pN ested−Loop R2 , C) =


f alse
sonst
sorted(R1 ./pN ested−Loop R2 , D) = f alse
Damit ein Merge-Join angewandt werden kann, müssen die Argumentrelationen auf
den Joinattributen sortiert sein und die Joinbedingung darf nur Gleichheitsprädikate
enthalten. Da er die Reihenfolge der Tupel nicht ändert, bleiben die Sortierungen der
beiden Relationen erhalten:
sorted(R1 ./pSort−Sort R2 , A) = sorted(R1 , A)
sorted(R1 ./pSort−Sort R2 , B) = true
sorted(R1 ./pSort−Sort R2 , C) = true
sorted(R1 ./pSort−Sort R2 , D) = sorted(R2 , D)
Bei einem Index-Index-Join werden die Tupel nach den Joinattributen sortiert, da
die Werte der Reihe nach von den Indexen geliefert werden. Sortierungen auf NichtJoinattributen gehen dabei im Allgemeinen verloren:
sorted(R1 ./pIndex−Index R2 , A) = f alse
sorted(R1 ./pIndex−Index R2 , B) = true
sorted(R1 ./pIndex−Index R2 , C) =


 true


f alse
falls p aus Gleichheitsbedingungen besteht
sonst
sorted(R1 ./pIndex−Index R2 , D) = f alse
• Anti-/Semijoin
Die Formeln für Semijoins enthalten als zusätzliche physische Realisierung Index-Rel,
da hier die Relationen nicht kommutativ sind. Ansonsten stimmten sie mit den vorherigen Funktionen für ein Join überein. Allerdings bleiben keine Attribute aus R2
erhalten, da diese nur darüber entscheiden, ob ein Tupel aus R1 erhalten bleibt oder
nicht:
34
KAPITEL 4. GENERALISIERTE BÄUME
sorted(R1 .<pN ested−Loop R2 , A) = sorted(R1 , A)
sorted(R1 .<pN ested−Loop R2 , B) = sorted(R1 , B)
sorted(R1 .<pSort−Sort R2 , A) = sorted(R1 , A)
sorted(R1 .<pSort−Sort R2 , B) = true
sorted(R1 .<pIndex−Rel R2 , A) = sorted(R1 .<pIndex−Index R2 , A) = f alse
sorted(R1 .<pIndex−Rel R2 , B) = sorted(R1 .<pIndex−Index R2 , B) = true
Bei einem Index-Rel-Semijoin werden die Tupel aus R1 nach den Joinattributen sortiert ausgegeben, da der Index die Tupel der Reihe nach ausgibt. Vorherige Sortierung
bleiben dabei nicht erhalten. Also unterscheiden sich die Formeln nicht von denen
eines Index-Index-Semijoins.
• Left-/Right-Outerjoin
Bei einem Left-Outerjoin werden die Tupel der linken Relation zum Ergebnis hinzugefügt, auch wenn sie keinen Joinpartner gefunden haben. Daher bleiben Sortierungen,
die auf R1 existieren, bestehen. Da die linke Relation komplett ausgelesen wird, macht
dabei ein Indexzugriff keinen Sinn. Bei der rechten Argumentrelation können durch
das Einfügen von Null-Werten keine Sortierungen entstehen oder erhalten bleiben. Die
Attribute aus R1 werden mit A und die aus R2 werden mit B gekennzeichnet:
sorted(R1 A./pN ested−Loop R2 , A) = sorted(R1 A./Rel−Index
R2 , A) = sorted(R1 , A)
p
sorted(R1 A./pN ested−Loop R2 , B) = sorted(R1 A./Rel−Index
R2 , B) = f alse
p
Um einen Merge-Algortihmus ausnutzen zu können, müssen die Joinattribute der Relationen sortiert sein, d. h. dass die Joinattribute C aus R1 im Ergebnis in jedem Fall
sortiert sind:
sorted(R1 A./pSort−Sort R2 , A) = sorted(R1 , A)
sorted(R1 A./pSort−Sort R2 , C) = true
sorted(R1 A./pSort−Sort R2 , B) = f alse
Für ein Right-Outerjoin gelten die selben Funktionen mit vertauschten Argumentrelationen.
• Full-Outerjoin
Ein NestedLoop-Outerjoin arbeitet wie ein NestedLoop-Left-Outerjoin, nur werden
in einem letzten Schritt die partnerlosen Tupel der zweiten Relation zum Ergebnis
hinzugefügt. Aus diesem Grund gelten die selben Formeln für R1 A./@pN ested−Loop R2 :
sorted(R1 A./@pN ested−Loop R2 , A) = sorted(R1 , A)
sorted(R1 A./@pN ested−Loop R2 , B) = f alse
Da beide Relationen komplett ausgelesen werden, macht weder ein A./@Rel−Ind noch
ein A./@Ind−Ind Sinn. Bei einem Merge-Full-Outerjoin werden beide Relationen seriell
eingelesen. Dabei werden die Tupel ohne Partner direkt zum Ergebnis hinzugefügt.
Daher kann man nicht davon ausgehen, dass Sortierungen erhalten bleiben:
sorted(R1 A./@pSort−Sort R2 , A) = f alse
sorted(R1 A./@pSort−Sort R2 , B) = f alse
4.4. INDEXE
4.4
35
Indexe
In einem generalisierten Baum können normale und geclusterte Indexe vorhanden sein. Es
wird davon ausgegangen, dass ein Index nach Anwendung einer Operation nicht mehr für
deren Ergebnis genutzt werden kann. Der Aufbau von geclusterten Indexen wird in Kapitel
5.4.6 beschrieben.
Informationen über dauerhaft vorhandene Indexe auf Basisrelationen werden beim Erzeugen
eines Baumes in den Blättern gespeichert. Während der Optimierung ist es möglich, dass
zusätzliche temporäre Indexe auf Blättern oder Teilbäumen erzeugt werden. Ist dies der Fall,
wird der komplette Baum durchlaufen und nach übereinstimmenden Stellen durchsucht.
Wird eine Übereinstimmung gefunden, werden die Indexinformationen ebenfalls zu diesem
Knoten hinzugefügt.
4.5
Pipelining
In diesem Abschnitt wird gezeigt, wann Pipelining angewandt werden kann. Ein Indexzugriff
verhindert grundsätzlich diese Optimierungsmöglichkeit.
• Selektion und Projektion:
Bei σpRel können nacheinander alle Tupel der Eingabe direkt auf die Bedingung p hin
überprüft werden.
σp (R)
σpRel
σpIndex
Pipelining von R
ja
nein
πl (R)
Pipelining von R
πlRel
nein
πlSort
ja
πlIndex
nein
Tabelle 4.1: Pipelining bei Projektionen und Selektionen
Für ein Projektion muss im allgemeinen die Argumentrelation mehrfach durchlaufen
werden, um Duplikate zu entfernen. Wenn die Relation sortiert ist, kann die Duplikateleminierung mit einem Durchlauf ausgeführt werden.
• Aggregation:
Werden keine Attribute angegeben, nach denen gruppiert werden soll, ist das Berechnen der Aggregationsfunktionen parallel in einem Durchlauf möglich. Da die Relation
komplett eingelesen werden muss, wird grundsätzlich ein Relationenscan verwendet,
d. h. die Argumentrelation kann tupelweise übergeben werden.
Γ#f (R)
ΓRel
#f
Pipelining von R
ja
Γg#f (R)
Pipelining von R
ΓRel
g#f
nein
ΓSort
g#f
ja
Tabelle 4.2: Pipelining bei einer Aggregation
Wird nach einem oder mehreren Attributen gruppiert, muss bei einem Relationenscan
die Argumentrelation mehrfach durchlaufen werden, also ist keine tupelweise Übergabe
möglich. Ist eine Sortierung auf den entsprechenden Attributen vorhanden, kann das
Ergebnis wieder mit einem Durchlauf bestimmt werden, da zusammengehörige Tupel
36
KAPITEL 4. GENERALISIERTE BÄUME
nur nacheinander auftreten können. Genaue Informationen über die Arbeitsweise einer
Aggregation sind in Kapitel 5.4.2 zu finden.
• Join:
Der Nested-Loop-Join durchläuft in einer Schleife die erste Argumentrelation und für
jedes Tupel aus R1 durchläuft er ein Mal die zweite. Dabei können die Tupel von
R1 der Reihe nach übergeben werden. Die zweite Relation muss mehrfach gelesen
werden, wodurch Pipelining verhindert wird. Die gleiche Argumentation gilt für RelIndex-Joins.
Da ein Merge-Join sequentiell auf R1 und R2 arbeitet um ein Ergebnis zu erziehlen,
kann Pipelining bei beiden Relationen angewandt werden.
R1 ./p R2
Pipelining von R1
Pipelining von R2
./pN ested−Loop
ja
nein
./pSort−Sort
./Rel−Index
p
./pIndex−Index
ja
ja
ja
nein
nein
nein
Tabelle 4.3: Pipelining bei einem Join
• Semi- und Antisemijoin:
Der Semi- und Antisemijoin arbeitet wie eine normaler Join, nur werden keine Werte
aus der zweiten Relation zum Ergebnis hinzugefügt. Mit Hilfe der Tupel aus R2 wird
lediglich entschieden, ob ein Tupel aus R1 die Bedingung p erfüllt.
R1 .<p R2
Pipeling von R1
Pipelining von R2
.<pN ested−Loop
ja
nein
.<pSort−Sort
.<pIndex−Rel
.<Rel−Index
p
Index−Index
.<p
ja
ja
nein
nein
ja
nein
nein
nein
Tabelle 4.4: Pipelining bei Semijoins
Daraus folgt, dass auch die gleichen Möglichkeiten für Pipelining bestehen. Allerdings
gilt in diesem Fall für Index-Rel-Joins nicht das selbe wie für Rel-Index-Joins, da
die beiden Argumentrelationen nicht kommutativ sind. Bei einem Index-Rel-Semijoin
wird auf die erste Relation mit einem Index zugegriffen, wodurch Pipelining verhindert
wird. Da die zweite Relation mehrfach gescannt wird, ist es auch hier nicht anwendbar.
• Outerjoins:
Die Algorithmen für die Umsetzung eines Left-Outerjoins ähneln denen eines normalen
Joins, nur werden die Tupel der linken Relation, die keinen Joinpartner finden, zum
Ergebnis hinzugefügt. Daher gelten die selben Arugmentationen für die Anwendung
von Pipelining bei diesen physischen Realisierungen.
4.6. VERWENDUNG VON REGELBÄUMEN IN EINEM TERMERSETZER
37
Die Tabelle 3.5 gilt ebenfalls für Right-Outerjoins, allerdings müssen die Argumentrelationen vertauscht werden.
R1 A./p R2
Pipeling von R1
Pipelining von R2
A./pN ested−Loop
ja
nein
A./pSort−Sort
A./Rel−Index
p
ja
ja
ja
nein
Tabelle 4.5: Pipelining bei Outerjoins
Bei einem Full-Outerjoin werden beide Relationen komplett ausgelesen. Aus diesem
Grund wird kein A./@Rel−Index
verwendet. Sonst stimmen die Formeln mit denen eines
p
Left-Outerjoins überein.
• Mengenoperatoren:
Schnitt, Differenz und Produkt lassen sich wie folgt darstellen, wobei die Bedingung
p die gleichnamigen Attribute der beiden Argumentrelationen verbindet.
Mengenoperation
Verbundoperation
R1 × R2
R1 ./true R2
R1 − R2
R1 .<p R2
R1 ∩ R2
R1 .<p R2
Tabelle 4.6: Äquivalente Darstellungsformen für Mengenoperatoren
Pipelining ist bei diesen Operatoren möglich, falls es bei den Darstellungen mit Hilfe
von Verbundoperatoren der Fall ist. Eine Vereinigung nutzt Antisemijoin-Algorithmen
zur Berechnung des Ergebnisses. Daher kann Pipelining angewandt werden, wenn es
bei den Antsemijoinoperatoren möglich ist.
R1 ∪ R2
Pipeling von R1
Pipelining von R2
∪N ested−Loop
ja
nein
∪Sort−Sort
ja
ja
∪Rel−Index
ja
nein
nein
nein
∪Index−Index
Tabelle 4.7: Pipelining bei Vereinigungen
4.6
Verwendung von Regelbäumen in einem Termersetzer
Wie in der Einleitung erwähnt, wird eine veränderte Form des generaliserten Baumes zum
Speichern von Termersetzungsregeln verwendet. In diesen Bäumen wird ebenfalls jeder relationale Operator in einem Knoten gespeichert. Allerdings enthalten die Knoten nicht die
selben Attribute wie die eines normalen generaliserten Baumes. Die einzigen vorhandenen
Attribute sind eine physische Umsetzung der Operation und eine Variable für die Parametrisierung. Den Variablen der Regelbäume wird ein Wert von einem Termersetzer zugewiesen,
wenn eine mit der Regel übereinstimmende Stelle in einem konkreten Ausführungsplan gefunden wurde. In diesem Abschnitt wird lediglich beschrieben wie die Regelbäume bei einer
38
KAPITEL 4. GENERALISIERTE BÄUME
Termersetzung verwendet werden. Die genaue Funktionsweise eines Termersetzers in einem
Einphasenoptimierungssystems wird in [Rud05] behandelt.
Beispiel:
Gegeben seinen die beiden Seiten einer Termersetzungsregel τ1 und τ2 und ein Ausführungsplan τ3 . Auf die Angabe einer Regelbedingung wird verzichtet, da nur der prinzipielle Ablauf
einer Termersetzung dargestellt wird.
τ3 :
τ1 :
τ2 :
Rel
σR
0 0
1 .A= 5 ∧R1 .A=R2 .B
./pN0ested−Loop
σpRel
×N ested−Loop
×N ested−Loop
σpRel
1
σpRel
2
R1
X
Y
X
Rel
πR
2 .B
Y
R2
Abbildung 4.1: Regelseiten und Ausführungsplan
Mit Hilfe einer Funktion zum Durchlaufen eines generalisierten Baumes, beschrieben im letzten Abschnitt dieses Kapitels, findet ein Termersetzer eine Übereinstimmung in τ3 mit der
linken Seite der Regel τ1 . Anschließend werden den Variablen in τ1 die im Ausführungsplan
gefundenen Werte zugewiesen:
τ3 :
τ1 :
Rel
σR
0 0
1 .A= 5 ∧R1 .A=R2 .B
Rel
σp:={R
0 0
1 .A= 5 ∧R1 .A=R2 .B}
×N ested−Loop
×N ested−Loop
=⇒
R1
Rel
πR
2 .B
R1
Rel
πR
2 .B
R2
R2
Abbildung 4.2: Setzen der Variablen
Ein Termersetzer überprüft die im Allgemeinen vorhandene Regelbedingung und verteilt
die Werte von τ1 in τ2 . Im generalisierten Baum ist eine Methode vorhanden, die aus einem
gefüllten Regelbaum in einem abschließenden Schritt einen neuen Ausführungsplan erzeugt.
Die genaue Funktionsweise dieser Methode wird im nächsten Abschnitt beschrieben.
τ1 :
τ2 :
Rel
σp:={R
0 0
1 .A= 5 ∧R1 .A=R2 .B}
×N ested−Loop
R1
Rel
πR
2 .B
R2
=⇒
neuer Baum :
./pN0ested−Loop
:={R1 .A=R2 .B}
σpRel
0 0
1 :={R1 .A= 5 }
σpRel
2 :={true}
R1
Rel
πR
2 .B
R2
=⇒
N ested−Loop
./R
1 .A=R2 .B
Rel
σR
0 0
1 .A= 5
Rel
πR
2 .B
R1
R2
Abbildung 4.3: Verteilung der variablen Werte und Erzeugen eines neuen Baumes
4.7. BAUM-METHODEN
4.7
39
Baum-Methoden
Um Informationen aus einem generalisierten Baum zu erhalten oder Veränderungen vorzunehmen, werden eine Reihe von Methoden zur Verfügung gestellt.
• Ausgabe der Knotenattribute
Für die Knotenattribute, die im ersten Abschnitt dieses Kapitels aufgezählt wurden,
exisitieren Methoden zur Ausgabe der Werte. Beispielsweise lässt sich die Kardinalität,
die Pagegröße oder eine Liste der sortierten Attribute ausgeben.
• Berechnung der Gesamtkosten
Die Gesamtkosten setzen sich zusammen aus den I/O- und den CPU-Kosten. Da
sich Datenbanksysteme in der Konfiguration ihrer Hard- und Software unterscheiden,
ist eine Gewichtung der CPU- zu den I/O-Kosten möglich, um das Kostenmodell
individuell anpassen zu können. Die Formel für die Gesamtkosten lautet:
Gesamtkosten := I/O-Kosten + ω · CP U -Kosten
Mit den im Baum vorhandenen Methoden lassen sich die Gesamtkosten eines einzelnen
Knotens oder eines kompletten Teilbaumes bestimmen.
• Kopieren von Teilbäumen
Das Kopieren von Teilbäumen ist recht simpel. Es wird ein Teilbaum übergeben, bei
dem, beginnend mit den Blättern, Kopien der Knoten erstellt und zu einem neuen
Baum zusammengefügt werden.
• Austausch von Teilbäumen
Problematisch ist der Austausch von Teilbäumen. Der Methode werden zwei Bäume
übergeben, von denen der eine durch den anderen ersetzt wird. Nach der Ersetzung
müssen die Attribute der übergeordneten Knoten des Ausgangsbaumes neu berechnet
werden. Für den Fall, dass bei einem dieser Knoten die physische Umsetzung nicht
mehr angewandt werden kann, wird die in Abschnitt 4.2 vorgestellte Möglichkeit genutzt, eine neue Umsetzung kostenabhängig zu bestimmen.
• Umwandlung eines gefüllten Regelbaumes in einen generalisierten Baum
Im vorherigen Abschnitt wurde beschrieben wie in den Variablen der Regelbäume
konkrete Parametrisierungen und Relationen gespeichert werden. Mit Hilfe eines Regelbaumes, der auf diese Weise gefüllt wurde, kann ein neuer Ausführungsplan erzeugt
werden.
Die zur Verfügung gestellte Methode arbeitet ähnlich wie die Methode zum Kopieren
von Teilbäumen. Aus den Blättern des Regelbaumes werden zuerst die Basisrelationen
bzw. die Teilbäume, die im Ursprungsbaum an dieser Stelle vorhanden waren, geholt.
Dann wird er von den Blättern aus weiter nach oben durchlaufen. Dabei wird für
jeden Knoten des Regelbaumes ein konkreter Knoten für den neuen Ausführungsplan
erzeugt. Das Beispiel des vorherigen Abschnittes zeigt im letzten Schritt die Anwendung dieser Methode.
• Durchlaufen von Teilbäumen
Es wird ein Iterator bereitgestellt, mit dem ein Durchlauf eines generalisierten Baumes möglich ist. Jeder next-Aufruf dieses Iterators liefert den nächsten Knoten in
Post-Order Reihenfolge, d. h. es wird jeweils der linke, dann der rechte Teilbäum und
40
KAPITEL 4. GENERALISIERTE BÄUME
anschließend die Wurzel besucht. In der folgenden Abbildung ist der Weg eines solchen
Durchlaufes dargestellt:
Abbildung 4.4: Baumdurchlauf
Jeder Knoten speichert Verweise auf seine Kind- und seinen Elternknoten. Somit konnten Methoden erstellt werden, welche ermöglichen, dass man von einem Knoten aus
in jede Richtung gelangen kann. Auf diese Weise sind individuelle Durchläufe eines
generalisierten Baumes möglich.
Kapitel 5
Berechnung der Kosten
Die Funktionen zur Berechnung der Kosten aus [War03] und [Mak03] wurden erweitert und
ergänzt. Die Bedingungen bei Verbundoperatoren beschränkten sich auf atomare Gleichheitsbedingungen, d. h. sie hatten die Form R1 .A = R2 .B und konnten nicht konjunktiv
verknüpft werden. Die Bedingungen und Kostenformeln wurden erweitert auf allgemeine
Verbundbedingungen. Hinzugekommen sind neue Formeln für die Abschätzung von Kardinalität, Pagegröße, Attributwertanzahlen und Kosten bei Outerjoins und Aggregationen.
5.1
Selektivitätsabschätzungen
Für eine Optimierung ist es wichtig zu wissen, auf wie viele Tupel einer Relation R sich eine
Bedingung p bezieht. Hierzu verwendet man die Selektivitätsabschätzung sel(p, R), da eine
konkrete Aussage ein bereits berechnetes Ergebnis voraussetzten würde.
Prädikat p
Schätzung für sel(p, R)
Standardwert
1
n(A,R)
1
10
A > a, A ≥ a
max(A)−a
max(A)−min(A)
1
3
A < a, A ≤ a
a−min(A)
max(A)−min(A)
1
3
A=a
A=B
min
1
1
n(A,R) , n(B,R)
1
10
A > B, A ≥ B
-
1
3
A < B, A ≤ B
-
1
3
1 − sel(p1 , R)
-
p1 ∧ p2
sel(p1 , R) · sel(p2 , R)
-
p1 ∨ p2
sel(p1 , R) + sel(p2 , R) − sel(p1 ∧ p2 , R)
-
¬p1
Tabelle 5.1: Selektivität von Prädikaten
41
42
KAPITEL 5. BERECHNUNG DER KOSTEN
Für die Abschätzungen in der zweiten Spalte der nächsten Tabelle wird die Anzahl der
verschiedenen Werte eines Attributes benötigt. Dieser Wert wird als Attributwertanzahl
n(A, R) bezeichnet und im nächsten Abschnitt behandelt. In der dritten Spalte befinden
sich Standardwerte für die Abschätzungen.
Wie in Kapitel 2.1.3 beschrieben wurde, sind Selektionsbedingungen, welche arithmetische
Ausdrücke enthalten, ebenfalls möglich. Bei solchen Prädikaten wird der schlechteste Fall
angenommen, und zwar dass die Selektivität gleich 1 ist, d. h. dass alle Tupel ausgewählt
werden.
5.2
Schätzungen für Attributwertanzahlen
Attributwertanzahlen geben an, wie viele verschiedene Werte ein Attribut A einer Relation R
hat und wird mit n(A, R) bezeichnet. Bei der Selektion sind mehrere Fälle zu unterscheiden.
Dabei ist es wichtig, ob das betrachtete Attribut A in der Selektionsbedingung p vorkommt
oder nicht.
Sei A ∈
/ attr(p), r = |σp (R)|, m = n(A, R) , dann gilt:

r
falls r < m

2




r+m
falls m
n(A, σp (R)) =
3
2 ≤r ≤2·m





m
falls r > 2 · m
Abbildung 5.1: Attributwertanzahlen Selektion
Wenn A in attr(p) enthalten ist, wird die folgende Tabelle für die Abschätzung der Attributwertanzahlen einer Selektion verwendet. Die Abschätzungen der anderen Operationen,
wie z. B. Produkt oder Outerjoin, sind in den Tabellen 5.3, 5.4 und 5.5 aufgeführt. In diesen
Tabellen steht A wie gewohnt für ein vorhandenes Attribut der Argumentrelation.
Bedingung p
n(A, σp (R))
AΘa
sel(p, R) · n(A, R)
AΘB
n(A, R)
A=B
min (n(A, R), n(B, R))
p1 ∧ p2
n(A, σp1 (σp2 (R)))
p2 ∨ p2
n(A, σp1 (R) ∪ σp2 (R))
Tabelle 5.2: Schätzungen für Attributwertanzahlen einer Selektion
Bei einer Aggregation können Attribute, durch das Anwenden von Group-by-Funktionen,
wie z. B. count(∗), hinzukommen. Um Attributwertanzahlen dieser Spalten bestimmen zu
können, bräuchte man konkrete Werte der Relation. Dies würde ein bereits berechnetes Ergebnis voraussetzen, dass aber nicht vorhanden ist, da hier ein Anfragesimulator entwickelt
wird.
5.2. SCHÄTZUNGEN FÜR ATTRIBUTWERTANZAHLEN
43
Aus diesem Grund wird von einer Gleichverteilung der Attributwerte der Argumentrelation
ausgegangen, wodurch bei einer Gruppierung gleich große Gruppen entstehen. Somit ist
die Attributwertanzahl eines neu entstandenen Attributes durch die Funktion count() in
jedem Fall 1. Für die anderen Aggregationsfunktionen wird angenommen, dass jede Gruppe einen unterschiedlichen Wert annimmt. Die Anzahl der Gruppen ist abhängig von den
Attributwertanzahlen der Attribute nach denen gruppiert wird.
Operation
Bedingung
n(A,Operation)
Γg#f (R)
A ∈ (g ∩ f )
n(A, R)
A = count()
1
A = sum(), avg(), min(), max()
min(|R|,
Q
X∈attr(g)
n(X, R))
Tabelle 5.3: Attributwertanzahlen Aggregation
Beispiel: Es wird die Anzahl Bestellungen pro Kunde gesucht
n(Kunde, ΓKunde#Kunde,count(Bestellung)VERKÄUFE) = n(Kunde,VERKÄUFE)
n(count(Bestellung), ΓKunde#Kunde,count(Bestellung)VERKÄUFE) = 1
Bei einem Full-Outerjoin bleiben die Attributwertanzahlen der Argumentrelationen erhalten, da auch die Tupel die keinen Joinpartner finden, anschließend zur Tabelle hinzugefügt
werden. Ähnlich verhällt es sich mit Left-Outerjoin und Right-Outerjoin. Hier ändern sich allerdings die Wertanzahlen der Relation, bei der die Tupel ohne Joinpartner nicht betrachtet
werden, wie bei einem normalen Join. Die Formeln zur Berechnung von Attributwertanzahlen bei einem Join, sind in Tabelle 5.5 zu finden.
Operation
Bedingung
n(A,Operation)
R1 A./@p R2
A ∈ sch(R1 )
n(A, R1 )
A ∈ sch(R2 )
n(A, R2 )
A ∈ sch(R1 )
n(A, R1 )
A ∈ sch(R2 )
n(A, σp (R1 × R2 ))
A ∈ sch(R1 )
n(A, σp (R1 × R2 ))
A ∈ sch(R2 )
n(A, R2 )
R1 A./p R2
R1 ./@p R2
Tabelle 5.4: Attributwertanzahlen Outerjoins
In der folgenden Tabelle 5.5 sind die restlichen Funktionen für die übrigen Operatoren aufgeführt. Bei einem kartesischen Produkt ändern sich die Attributwertanzahlen nicht, da
jedes Tupel mit jedem verbunden und kein Tupel entfernt wird. Bei einem Join werden nur
die Tupel selektiert und verbunden, welche die Bedingung p erfüllen.
Semi- und Antisemijoin ähneln sehr einer Selektion. Aus diesem Grund ist es sinnvoll, für die
Attribute A ∈
/ attr(p) die Abschätzung aus Abbildung 5.1 zu verwenden, mit r = |R1 .<p R2 |
und m = n(A, R1 ).
44
KAPITEL 5. BERECHNUNG DER KOSTEN
Operation
Bedingung
n(A,Operation)
πl (R)
A∈l
n(A, R)
R1 × R2
A ∈ sch(R1 )
n(A, R1 )
A ∈ sch(R2 )
n(A, R2 )
n(A, σp (R1 × R2 ))
R1 ./p R2
R1 .<p R2
A ∈ attr(p)
n(A, πsch(R1 ) (R1 ./p R2 ))
R1 .<p R2
A ∈ attr(p)
n(A, R1 ) − n(A, R1 .<p R2 )
R1 − R2
sch(R1 ) = sch(R2 )
n(A, R1 .<p R2 )
R1 ∩ R2
sch(R1 ) = sch(R2 )
n(A, R1 .<p R2 )
R1 ∪ R2
sch(R1 ) = sch(R2 )
n(A, R1 ) + n(A, R2 ) − n(A, R1 ∩ R2 )
Tabelle 5.5: Schätzungen für Attributwertanzahlen
Bei einer Projektion werden Attribute ausgewählt und keine Tupel. Daher bleibt die Anzahl
der verschiedenen Attributwerte erhalten.
Ausnahmen sind Projektionen in denen arithmetische Ausdrücke vorkommen, wie sie in
Kapitel 2.1.3 dargestellt wurden. In solchen Fällen wird davon ausgegangen, dass die Attributwertanzahl erhalten bleibt, falls eine Berechnung mit einem Attribut und einer Zahl
verlangt wird:
Es sei a eine bliebige Zahl und Θ = {+, −, ∗, /}
n(AΘa, πl (R)) = n(A, R)
Falls eine Berechnung mit zwei Attributen ausgeführt werden soll, wird angenommen, dass
jede mögliche Berechnung ein unterschiedliches Ergebnis hat:
n(AΘB, πl (R)) = min(|R|, (n(A, R) · n(B, R)))
Um Abschätungen für die Mengenoperatoren machen zu können, werden die folgenden Darstellungen ausgenutzt, wobei p eine Bedingung ist, welche die gleichnamigen Attribute der
Argumentrelationen vergleicht:
Mengenoperation
Verbundoperation
R1 ∩ R2
R1 .<p R2
R1 − R2
R1 .<p R2
Tabelle 5.6: Darstellungen für Mengenoperatoren
Bei der Vereinigung ergeben sich die Attributwertanzahlen aus der Summe der verschieden
Attributwerte der Argumentrelationen.
5.3. KARDINALITÄT UND PAGEGRÖSSE
5.3
45
Kardinalität und Pagegröße
Mit Hilfe der Selektivitätsabschätzungen lassen sich Kardinalitäten und Pagegrößen, nach
der Anwendung einer Operation, bestimmen. Die Kardinalität gibt die Anzahl der Tupel
wieder und die Pagegröße ist ein Maß für den verwendeten Speicher, da sich ihr Wert aus
der Anzahl Tupel und deren Länge zusammensetzt.
Operation
|Operation|
page(Operation)
σp (R)
sel(p, R) · |R|
sel(p, R) · page(R)
πl (R)
min(|R|,
Q
Al n(A, R))
|πl (R)|
|R|
·
|l|
|sch(R)|
· page(R)
R1 × R2
|R1 | · |R2 |
|R1 | · page(R2 ) + |R2 | · page(R1 )
R1 ./p R2
|σp (R1 × R2 )|
page(σp (R1 × R2 ))
R1 .<p R2
|πsch(R1 ) (R1 ./p R2 )|
|R1 .<p R2 |
|R1 |
· page(R1 )
R1 .<p R2
|R1 | − |R1 .<p R2 |
|R1 .<p R2 |
|R1 |
· page(R1 )
R1 − R2
|R1 .<p R2 |
|R1 −R2 |
|R1 |
R1 ∩ R2
|R1 .<p R2 |
|R1 ∩R2 |
|R1 |+|R2 |
· (page(R1 ) + page(R2 ))
R1 ∪ R2
|R1 | + |R2 | − |R1 ∩ R2 |
|R1 ∪R2 |
|R1 |+|R2 |
· (page(R1 ) + page(R2 ))
· page(R1 )
Tabelle 5.7: Kardinalitäten und Pagegrößen
Der Anteil Tupel der nach einer Selektion bleibt, wird mit Hilfe der Selektivität der Bedingung bestimmt. Um den selben Anteil verringert sich der benötigte Speicherplatz.
Unter der Annahme das eine Projektion grundsätzlich eine Duplikateleminierung ausführt,
ist die Kardinalität abhängig von der Anzahl verschiedener Werte bzw. verschiedener Wertkombinationen der Attribute auf die projiziert wird. Maximal können so viele Tupel zurückgeliefert werden, wie vor der Projektion vorhanden waren. Die Pagegröße ändert sich abhängig
von der Anzahl und der Länge der Tupel.
Bei einem kartesischen Produkt wird jedes Tupel der ersten Relation mit jedem Tupel der
zweiten verbunden. Also ergibt sich die Kardinalität dieser Operation aus dem Produkt der
Kardinalitäten der Argumentrelationen. Für die Pägegröße gilt, das für jedes Tupel einer
Relation der Platzbedarf der anderen benötigt wird.
Eine äquivalente Darstellung eines Joins ist eine Selektion über einem kartesischen Produkt.
Dies wird für die Bestimmung der Kardinalität und Pagegröße ausgenutzt.
Der Semijoinoperator entspricht einem normalen Join mit anschließender Projektion auf die
Attribute der ersten Argumentrelation. Der verwendete Speicherplatz nimmt um den selben
Anteil ab, wie die Anzahl der Tupel nach Anwendung der Operation.
Für die Pagegröße eines Antisemijoins gilt das selbe. Da ein Antisemijoinoperator die Tupel
liefert, die ein Semijoin nicht liefert, bestimmt sich seine Kardinalität durch die Differenz
der Anzahl Tupel der ersten Argumentrelation und der Kardinalität einer Semijoinoperation.
46
KAPITEL 5. BERECHNUNG DER KOSTEN
Für die Berechnung der Mengenoperatoren werden die Äquivalenzen aus Tabelle 4.7 ausgenutzt. Bei der Vereinigung ergibt sich die Kardinalität aus der Summe der verschieden
Tupel der Argumentrelationen.
Zur Bestimmung der Kardinalität und Pagegröße von Outerjoins wird die Darstellung aus
[Lip04] verwendet:
R1 A./@p R2 := (R1 ./p R2 ) ∪ ((R1 .<p R2 ) × {⊥, . . . , ⊥}) ∪ ({⊥, . . . , ⊥} × (R2 .<p R1 ))
Damit und mit den bereits in [War03] aufgestellten Formeln für Join und Antisemijoin
ergeben sich folgende Abschätzungen für Left-Outerjoin, Right-Outerjoin und Left-RightOuterjoin:
Operation
|Operation|
R1 A./p R2
|R1 ./p R2 | + |R1 .<p R2 |
page(σp (R1 × R2 )) +
|R1 .<p R2 |
|R2 |
· page(R2 )
R1 ./@p R2
|R1 ./p R2 | + |R2 .<p R1 |
page(σp (R1 × R2 )) +
|R2 .<p R1 |
|R1 |
· page(R1 )
R1 A./@p R2
|R1 ./p R2 | + |R2 .<p R1 |
page(σp (R1 × R2 )) +
|R2 .<p R1 |
|R2 |
· page(R2 )
+|R1 .<p R2 |
+
page(Operation)
|R1 .<p R2 |
|R1 |
· page(R1 )
Tabelle 5.8: Kardinalitäten und Pagegrößen von Outerjoins
Ein Left-/Right-Outerjoin arbeitet wie ein normaler Join, allerdings werden die Tupel der
linken/rechten Relation, die keinen Joinpartner finden, ebenfalls zu dem Ergebnis hinzugefügt. Der Anteil der zusätzlichen Tupel kann mit Hilfe eines Antisemijoins bestimmt
werden. Das Selbe gilt für ein Full-Outerjoin, nur sind hier die partnerlosen Tupel beider
Argumentrelationen Bestandteil der Ergebnisrelation.
Die Kardinalität einer Aggregation ist abhängig von der Anzahl der gebildeten Gruppen.
Werden keine Attribute angegeben nach denen gruppiert werden soll, liefert eine Aggregation
nur ein Tupel mit den Ergebnissen der Aggregationsfunktionen. Ansonsten ergibt sich die
Anzahl der möglichen Gruppen aus der Menge der verschiedenen Wertkombinationen der
Attribute nach denen gruppiert wird.
Operation
|Operation|
page(Operation)
Γ#f (R)
1
1
Γg#f (R)
min(|R|,
Q
A∈attr(g)
n(A, R))
|Γg#f (R)|
|R|
·
|f |
|sch(R)|
· page(R)
Tabelle 5.9: Kardinalität und Pagegröße einer Aggregation
Für den ersten Fall, in dem nur ein Tupel das Ergebnis ist, wird die Pagegröße ebenfalls
mit 1 angenommen. Wird aber eine Gruppierung ausgeführt, ergibt sich die Pagegröße, wie
bei der Projektion, aus der Anzahl und der Länge der Ergebnistupel.
5.4. KOSTENFUNKTIONEN
5.4
5.4.1
47
Kostenfunktionen
Selektion und Projektion
Eine Selektion kann auf mehrere Arten ausgeführt werden. Einmal über einen Relationenscan σpRel , wobei jedes Tupel aus dem Speicher gelesen und auf die Bedingung p hin überprüft
werden muss.
Eine andere Möglichkeit ist, für den Zugriff einen Index zu verwenden. Die I/O-Kosten
setzen sich dabei aus den vom Index belegten Speicherseiten und den Zugriffen auf die
selektierten Tupel zusammen. Für die CPU-Kosten muss bedacht werden, dass der Index
verarbeitet und eine Auswahl der Tupel zu der entsprechenden TID-Folge gemacht werden
muss. Das Übersetzen der TID in Tupel wird durch |σp (R)| modelliert.
σp (R)
I/O-Kosten
CPU-Kosten
σpRel
page(R)
|R|
σpIndex
page(Indexl (R)) + |σp (R)|
|Indexl (R)| + |σp (R)|
Tabelle 5.10: Selektionskosten
Bei der Projektion kann ein einfacher Relationenscan, ein Scan mit Nutzung einer Sortierung
oder ein Indexzugriff verwendet werden.
πl (R)
I/O-Kosten
page(R)
2
CPU-Kosten
|R|
2
πlRel
page(R) + |R| ·
πlSort
page(R)
|R|
πlIndex
page(Indexl (R))
|Indexl (R)| + |πl (R)|
|R| + |R| ·
Tabelle 5.11: Projektionskosten
Um bei einem relationalen Scan eine Duplikateleminierung auszuführen, muss für jedes neue
Tupel geprüft werden, ob es schon einmal gelesen wurde. Der bereits betrachtete Teil ist
im Mittel die Hälfte der Relation. Wird bei der Projektion eine Sortierung genutzt, können
Duplikate nur nacheinander folgen, wodurch es möglich ist sequentiell zu arbeiten.
Falls ein Index auf den zu projizierenden Attributen vorhanden ist, kann das Projektionsergebnis direkt aus ihm gelesen werden. Die I/O-Kosten und die CPU-Kosten entstehen
daher durch das Lesen des Indexes. Zu den CPU-Kosten muss der Aufwand für die Bestimmung der auftretenen Attributwerte hinzugezählt werden. Diese Kosten werden geschätz
mit |πl (R)|.
5.4.2
Aggregation
Eine Aggregation lässt sich in zwei Typen einteilen. Ist der Teil der Bedingung vor dem Doppelkreuz leer, handelt es sich um eine einfache Aggregation ohne Gruppierung. Bei diesem
48
KAPITEL 5. BERECHNUNG DER KOSTEN
Typ wird ein einzelnes Tupel für die angewendeten Group-by-Funktionen zurückgeliefert,
z. B. enthält das Ergebnistupel der Anfrage
Γ#count(Kunde),max(Betrag) VERKÄUFE
die Anzahl Tupel und den Maximalwert des Attributes Betrag der Relation VERKÄUFE.
Die Group-by-Funktionen können parallel berechnet werden. Im Allgemeinen wird dazu ein
einzelner Durchlauf der Argumentrelation benötigt. Daher wird in solchen Fällen grundsätzlich ein Relationenscan verwendet:
Γ#f (R)
I/O-Kosten
CPU-Kosten
ΓRel
#f
page(R)
|R|
Tabelle 5.12: Kosten einer Aggregation ohne Gruppierung
Eine Aggregation mit Gruppierung ähnelt einer Duplikateleminierung. Wird ein Relationenscan verwendet, muss für jedes neue Tupel geprüft werden, ob es bereits gelesen wurde.
Dabei können die Group-by-Funktionen berechnet werden, ohne weitere Kosten zu erzeugen.
Im Mittel ist der bereits betrachtete Teil der Relation etwa die Hälfte.
Γg#f (R)
I/O-Kosten
ΓRel
g#f
page(R) + |R| ·
ΓSort
g#f
page(R)
page(R)
2
CPU-Kosten
|R| + |R| ·
|R|
2
|R|
Tabelle 5.13: Kosten einer Aggregation mit Gruppierung
Falls in der Relation die einzelnen Attribute aus g sortiert sind oder eine Sortierung über
alle Attribute aus g existiert, kann ein sequentiell arbeitender Merge-Algortihmus genutzt
werden. Es ist recht selten, dass in einer Basisrelation mehr als ein Attribut sortiert ist.
Nach der Anwendung von Verbundoperatoren kann dies aber durchaus der Fall sein.
5.4.3
Join
Im folgenden bezeichnet die Menge A der Joinattribute die sich auf R1 und B die Menge
die sich auf R2 beziehen.
In einem NestedLoop-Join wird jedes Tupel aus der ersten Relation mit jedem Tupel aus
der zweiten verglichen. Das bedeutet, dass der gesamte Inhalt von R1 und für jedes Tupel
daraus der Inhalt von R2 einmal gelesen werden muss. Man erhält in jedem Fall geringere
Kosten wenn die linke Relation die kleinere Kardinalität hat. Aus diesem Grund wird diese
Anordnung automatisch vom generalisierten Baum übernommen.
Bei einen Rel-Index-Join wird die erste Relation und der Index komplett gelesen und für
jedes Tupel aus R1 ein Indexscan gemacht. Um abschätzen zu können, wie viele Tupel
zur gefundenen TID-Folge eingelesen werden müssen, ist es nötig, die Joinbedingung zu
zerlegen. Die Joinattribute die sich auf R2 beziehen, werden in einer Hilfsbedingung mit
einem Attributwert, anstatt mit einem Attribut aus R1 verbunden, z. B. wird R1 .A < R2 .B
zu R1 .A <’a’ und R2 .B >’a’. Das Zerlegen in Hilfsbedingungen wird mit ∧X∈A R1 .XΘa
bzw. ∧X∈B R2 .XΘa dargestellt. Die Anzahl der zu selektierenden Tupel ergibt sich dann
mit |σ∧X∈B R2 .XΘa (R2 )|.
5.4. KOSTENFUNKTIONEN
R1 ./p R2
./pN ested−Loop
erge
./M
p
./Rel−Index
p
./pIndex−Index
49
I/O-Kosten
CPU-Kosten
page(R1 ) + |R1 | · page(R2 )
|R1 | + |R1 | · |R2 |
page(R1 ) + page(R2 )
|R1 | + |R2 |
page(R1 ) + page(IndexB (R2 ))
|R1 | + |R1 |·
+|R1 | · |σ∧X∈B R2 .XΘa (R2 )|
(|IndexB (R2 )| + |σ∧X∈B R2 .XΘa (R2 )|
page(IndexA (R1 ))
+page(IndexB (R2 ))
Q
+min(|R1 |, X∈A n(X, R1 ))
min(|R1 |,
Q
X∈A
n(X, R1 ))
(·|IndexA (R1 )| + |σ∧Y ∈A R1 .Y =a (R1 )|
+|IndexB (R2 )| + |σ∧X∈B R2 .XΘa (R2 )|)
·(|σ∧X∈A R1 .X=a (R1 )|
+|σ∧Y ∈B R2 .Y Θa (R2 )|)
Tabelle 5.14: Kosten eines Joins
Damit ein Merge-Join genutzt werden kann, dürfen ausschließlich Gleichheitsbedingungen
für den Verbund verwendet werden. Enthält die Joinbedingung mehr als ein Prädikat, kann
ein Merge-Algorithmus nur angewandt werden, falls alle Attribute der Bedingung einzeln
sortiert sind oder eine einzige Sortierung über diesen Attributen existiert. Ist der zweite Fall gegeben, muss ebenfalls sichergestellt sein, dass die Bedingungsattribute nach ihrer Ordnung verbunden sind, z. B.: R1 ist sortiert nach ’R1 .A, R2 .B, R3 .C’ und R2 nach
’R2 .C, R2 .A, R2 .B’, dann müssen die Attribute von links beginnend verbunden sein. Mögliche Bedingungen wären
p1 := R1 .A = R2 .C ∧ R1 .B = R2 .A ∧ R1 .C = R2 .B
p2 := R1 .A = R2 .C ∧ R1 .B = R2 .A
p3 := R1 .A = R2 .C
Bei einem Index-Index-Join werden beide Indexe eingelesen. Dann wird für jeden Joinattributwert bzw. Attributwertkombination aus R1 ein Indexscan auf beiden Relationen gemacht.
Dabei wird in der ersten Relation nach dem konkreten Wert gesucht und in der zweiten nach
Tupeln, die die Bedingung mit dem Attributwert erfüllt. Zur Abschätzung der zu lesenden
Tupel werden auch hier Hilfsbedingungen erzeugt.
Diese Kostenfunktionen gelten ebenfalls für die entsprechenden Outerjoinoperatoren, da
ihre Umsetzungen nur Erweiterungen der Join-Algorithmen sind. Bei Outerjoins ist es aber
nicht sinnvoll auf die Relation, deren partnerlose Tupel am Ende zum Ergebnis hinzugefügt
werden, mit einem Index zuzugreifen. Da alle Tupel der Argumentrelation gelesen werden
müssen, würde ein Indexzuriff größere Kosten erzeugen als ein Relationenscan.
5.4.4
Semi-/Antisemijoin
Der Vorteil eines NestedLoop-Semijoins ist, dass ein Durchlauf der inneren Schleife abgebrochen werden kann, wenn ein passendes Tupel gefunden wurde. Der Anteil der qualifizerenden
Tupel ergibt sich aus sel(∧X∈B R2 .XΘa, R2 ). Unter der Annahme, dass die Attributwerte
gleichverteilt und nicht sortiert sind, werden bei einem inneren Schleifendurchlauf durchschnittlich sel(∧X∈B R12 .XΘa,R2 ) , aber maximal |R2 |, Tupel gelesen, bis ein passendes gefunden
wird. Erfüllen z. B. 13 der Tupel die Bedingung, muss man etwa 3 Stück lesen.
50
KAPITEL 5. BERECHNUNG DER KOSTEN
R1 .<p R2
.<pN ested−Loop
I/O-Kosten
CPU-Kosten
page(R1 ) + |R1 |·
|R1 | + |R1 |·
min( sel(∧X∈B R12 .XΘa,R2 ) , |R2 |)·
min( sel(∧X∈B R12 .XΘa,R2 ) , |R2 |)
1
|R2 |
erge
.<M
p
· page(R2 )
page(R1 ) + page(R2 )
|R1 | + |R2 |
.<Rel−Index
p
page(R1 ) + page(IndexB (R2 ))
|R1 | + |R1 | · |IndexB (R2 )|
.<pIndex−Rel
page(IndexA (R1 )) + |R1 .<p R2 |
Q
+min(|R1 |, X∈A n(X, R1 ))·
min( sel(∧X∈B R12 .XΘa,R2 ) , |R2 |)·
1
|R2 |
.<pIndex−Index
min(|R1 |,
Q
X∈A
n(X, R1 ))·
(|IndexA (R1 )| + |σ∧X∈A R1 .XΘa (R1 )|
+ min( sel(∧X∈B R12 .XΘa,R2 ) , |R2 |)
· page(R2 )
page(IndexA (R1 ))
+page(IndexB (R2 ))
Q
+min(|R1 |, X∈A n(X, R1 ))
min(|R1 |,
Q
X∈A
n(X, R1 ))·
(|IndexA (R1 )| + |σ∧Y ∈A R1 .Y =a (R1 )|
+|IndexB (R1 )|)
·|σ∧X∈A R1 .X=a (R1 )|
Tabelle 5.15: Kosten eines Anti-/Semijoins
Falls die Joinbedingung ausschließlich aus Gleichheitsbedingungen besteht, kann ein MergeAlgorithmus angewandt werden, welcher beide Relationen nur einmal zu lesen braucht. Aber
auch hier gelten die selben Bedingungen wie bei einem normalen Join.
Für ein Rel-Index-Semijoin wird die erste Relation gelesen und für jedes Tupel wird im
Index der zweiten Relation geprüft, ob es eine TID-Folge dazu gibt. Ist eine vorhanden,
gehört das betrachtete Tupel zur Ergebnisrelation.
Ein Index-Rel-Semijoin arbeitet ähnlich wie ein Nested-Loop-Semijoin. Allerding müssen
nur die Tupel aus R1 gelesen werden, welche die Joinbedingung p erfüllen. Die Anzahl
Durchläufe der inneren Schleife reduziert sich auf die Anzahl verschiedener Attributwerte
bzw. Attributwertkombinationen, falls die Bedingung mehr als ein Attribut enthält.
Der Unterschied zwischen einem Index-Index-Join und einem Index-Index-Semijoin besteht
lediglich darin, dass keine Tupel aus der zweiten Argumentrelation gelesen werden.
5.4.5
Mengenoperatoren
Mit Ausnahme der Vereinigung wird zur Bestimmung der Kosten der Mengenopertoren erneut die Darstellung aus Tabelle 4.16 verwendet.
Eine Vereinigung setzt sich aus einem Relationenscan und einem Antisemijoin zusammen.
Daher ergeben sich die Kosten aus der Summe der beiden Operationen. Eine Ausnahme
ist der Merge-Algorithmus für eine Vereinigung. Dieser kann ebenfalls sequentiell auf beide
Relationen arbeiten.
5.4. KOSTENFUNKTIONEN
R1 ∪ R2
51
I/O-Kosten
CPU-Kosten
page(R2 ) + page(R1 ) + |R1 |·
min( sel(∧X∈B R12 .XΘa,R2 ) , |R2 |)·
1
|R2 | · page(R2 )
|R2 | + |R1 | + |R1 |·
min( sel(∧X∈B R12 .XΘa,R2 ) , |R2 |)
page(R1 ) + page(R2 )
|R1 | + |R2 |
∪Rel−Index
page(R1 ) + page(IndexB (R2 ))
+page(R2 )
|R2 | + |R1 |
+|R1 | · |IndexB (R2 )|
∪Index−Index
page(R2 ) + page(IndexA (R1 ))
+page(IndexB (R2 ))
Q
+min(|R1 |, X∈A n(X, R1 ))
·|σ∧X∈A R1 .X=a (R1 )|
Q
|R2 | + min(|R1 |, X∈A n(X, R1 ))·
(|IndexA (R1 )| + |σ∧Y ∈A R1 .Y =a (R1 )|
+|IndexB (R1 )|)
∪N ested−Loop
∪M erge
Tabelle 5.16: Kosten einer Vereinigung
5.4.6
Erzeugen von Sortierungen oder Indexen
Es exisiteren Operatoren zum Erzeugen von Indexen oder Sortierungen auf einer Relation.
Die Kosten für diese Operatoren orientieren sich an den bekannten Laufzeiten von Sortieralgorithmen von n · log n:
SORTl (R), IN DEXl (R)
I/O-Kosten
CPU-Kosten
page(R) · log2 (page(R))
|R| · log2 (|R|)
Tabelle 5.17: Kosten für die Erzeugung von Indexen und Sortierungen
5.4.7
Verwendung von geclusterten Indexen
Werden ähnliche Daten nicht nur in einem Index erfasst sondern auch im Speicher nahe
beieinander festgehalten, spricht man von einem geclusterten Index. Der Vorteil gegenüber
einem normalen Index besteht darin, dass auf die Argumentrelation keine einzelnen Tupelzugriffe gemacht werden. Stattdessen werden Blöcke von Tupel eingelesen, wodurch eine
Verringerung der I/O-Kosten erreicht wird.
Operation
I/O-Kosten
σpCl Index
page(IndexA (R)) + page(σp (R))
Index
./Rel−Cl
p
page(R1 ) + page(IndexB (R2 ))
+|R1 | · page(σ∧X∈B R2 .XΘa (R2 ))
Index−Cl Index
./Cl
p
page(IndexA (R1 )) + page(IndexB (R2 ))
Q
+min(|R1 |, X∈A n(X, R1 ))
·(page(σ∧X∈A R1 .X=a (R1 )) + page(σ∧Y ∈B R2 .Y Θa (R2 )))
Tabelle 5.18: Geclusterte Indexe Teil 1
52
KAPITEL 5. BERECHNUNG DER KOSTEN
Operation
I/O-Kosten
./pClIndex−Index
page(IndexA (R1 )) + page(IndexB (R2 ))
Q
+min(|R1 |, X∈A n(X, R1 ))
·(page(σ∧X∈A R1 .X=a (R1 )) + |σ∧Y ∈B R2 .Y Θa (R2 )|)
.<pCl Index−Rel
page(IndexA (R1 )) + page(R1 .<p R2 )
Q
+min(|R1 |, X∈A n(X, R1 ))·
min( sel(∧X∈B R12 .XΘa,R2 ) , |R2 |) ·
1
|R2 |
· page(R2 )
Index−Cl Index
.<Cl
p
page(IndexA (R1 )) + page(IndexB (R2 ))
Q
+min(|R1 |, X∈A n(X, R1 )) · page(σ∧X∈A R1 .X=a (R1 ))
∪Cl Index−Cl Index
page(R2 ) + page(IndexA (R1 )) + page(IndexB (R2 ))
Q
+min(|R1 |, X∈A n(X, R1 )) · page(σ∧X∈A R1 .X=a (R1 ))
Tabelle 5.19: Geclusterte Indexe Teil 2
In einem System können geclusterte und normale Indexe vorkommen. Da bei einem Join die
Argumentrelationen kommutativ sind, genügt die Darstellung eines Cl Index-Index-Join.
Für ein Index-Cl Index-Join werden die selben Formeln, mit vertauschten Relationen, verwendet.
5.5
Beispiel
Als Beispiel wird für folgende Anfrage ein Ausführungsplan erzeugt und dessen Kosten,
mit Hilfe der aufgestellten Formeln, berechnet. Die physischen Umsetzungen der einzelnen
Operatoren werden, wie in Kapitel 4.2 beschrieben, kostenabhängig ausgewählt.
P ROJECT ION (V.T itel, A.Count)
RIGHT OU T ERJOIN (V.V orlN r = H.V orlN r) V,
(AGGREGAT ION (H.V orlN r#H.V orlN r, (count(H.M atrN r) AS A.Count)) H)
Es seinen die Metadaten der nächsten Tabelle gegeben:
R
V
Attribute
{VorlNr, Titel}
|R|
30
page(R)
3
n(A, R)
VorlNr=15, Titel=30
H
{MatrNr, VorlNr}
60
6
MatrNr=60, VorlNr=40
Index
{VorlNr}
Sortierung
{VorlNr}
Tabelle 5.20: Metadaten
Die Kardinalität des Index auf V.V orlN r sei 10 und die Pagegröße sei 1. In diesem Beispiel
wird kein Pipelining verwendet und es beschränkt sich auf die I/O-Kosten.
1. Schritt:
Γ(H.V orlN r#H.V orlN r,count(H.M atrN r))
H
Mögliche Umsetungen für die Aggregation:
• Relationenscan
I/O-Kosten:=page(H) + |H| ·
page(H)
2
= 6 + 60 · 3 = 186
5.5. BEISPIEL
53
• Merge-Aggregation
I/O-Kosten:=page(H) = 6
Da die CPU-Kosten eines Merge-Algorithmus am geringsten sind, wird diese Variante
ausgewählt.
Metadaten:
• Attributwertanzahlen:
n(H.V orlN r, ΓSort
g#f (H)) = n(H.V orlN r, H) = 40
n(H.Count, ΓSort
g#f (H)) = 1
• Kardinalität:
|ΓSort
g#f (H)| = min(|H|,
• Pagegröße:
page(ΓSort
g#f (H)) =
Q
A∈attr(g)
|ΓSort
g#f (H)|
|H|
·
n(A, H)) = min(60, 40) = 40
|f |
|sch(H)|
· page(H) =
40
60
·
2
2
·6=4
• Sortierungen:
In der Ergebnisrelation ist das Attribut H.V orlN r sortiert.
2. Schritt:
./@(V.V orlN r=H.V orlN r)
V
ΓSort
(H.V orlN r#H.V orlN r,count(H.M atrN r))
H
Mögliche Umsetzungen für den Right-Outerjoin:
• NestedLoop-Join:
I/O-Kosten:=page(V ) + |V | · page(ΓSort
g#f (H)) = 3 + 30 · 4 = 123
• Index-Rel-Join:
Sort
I/O-Kosten:=page(IndexV.V orlN r (V ))+page(ΓSort
g#f (H))+|Γg#f (H)|·|σV.V orlN r=0 a0 (V )| =
1 + 4 + 40 ∗ 2 = 85
Als Umsetzung wird ein Index-Rel-Join gewählt, da er die geringsten Kosten verursacht.
Metadaten:
• Kardinalität:
Sort
Sort
|V ./@p (ΓSort
g#f (H))| = |V ./p (Γg#f (H))| + |(Γg#f (H)).<p V | = 30 + 25 = 55
• Attributwertanzahlen:
Sort
n(H.V orlN r, V ./@p (ΓSort
g#f (H))) = n(H.V orlN r, Γg#f (H)) = 40
Sort
n(H.Count, V ./@p (ΓSort
g#f (H))) = n(H.Count, Γg#f (H)) = 1
Sort
n(V.V orlN r, V ./@p (ΓSort
g#f (H))) = min(n(V.V orlN r, V ), n(H.V orlN r, Γg#f (H)))
= 15
n(V.T itel, V ./@p (ΓSort
g#f (H))) = 20
Zur Berechnung der Attributwertanzahlen für V.T itel wurde die Abschätzung aus
Abbildung 5.1, mit r = |V ./p (ΓSort
g#f (H))| und m = n(V.T itel, V ), verwendet.
54
KAPITEL 5. BERECHNUNG DER KOSTEN
• Pagegröße:
Sort
page(V ./@p (ΓSort
g#f (H))) = page(V ./p (Γg#f (H))) +
= 6 + 25
30 · 3 = 9
|(ΓSort
g#f (H)).< p V |
|V |
· page(V )
• Sortierungen:
In der Ergebnisrelation ist das Attribut H.V orlN r sortiert.
3. Schritt:
πV.T itel,H.Count
Ind−Rel
./@(V.V
orlN r=H.V orlN r)
V
ΓSort
(H.V orlN r#H.V orlN r,count(H.M atrN r))
H
Mögliche Umsetzungen für die Projektion:
• π Rel :
Ind−Rel (ΓSort (H))|
I/O-Kosten:=page(V ./@pInd−Rel (ΓSort
g#f (H))) + |V ./@p
g#f
·
page(V ./@Ind−Rel
(ΓSort
p
g#f (H)))
2
= 9 + 55 ·
9
2
= 257
Metadaten:
• Kardinalität:
Ind−Rel (ΓSort (H))|,
|πlRel (V ./@pInd−Rel (ΓSort
g#f (H)))| = min(|V ./@p
g#f
Q
Ind−Rel (ΓSort (H)))) = min(40, 20 · 1) = 20
A∈l n(A, V ./@p
g#f
• Attributwertanzahlen:
n(V.T itel, πlSort (V ./@pInd−Rel (ΓSort
g#f (H))))
Sort
= n(V.T itel, V ./@p (Γp#q (H))) = 20
n(H.Count, πlSort (V ./@pInd−Rel (ΓSort
g#f (H))))
Sort
= n(H.Count, V ./@p (Γg#f (H))) = 1
• Pagegröße:
page(πlRel (V ./@pInd−Rel (ΓSort
g#f (H)))) =
·
|l|
|sch(V ./@Ind−Rel
(ΓSort
p
g#f (H)))|
=
20
55
·
2
4
|πlRel (V ./@Ind−Rel
(ΓSort
p
g#f (H)))|
|V ./@Ind−Rel
(ΓSort
p
g#f (H))|
· page(V ./@pInd−Rel (ΓSort
g#f (H)))
·9=2
Die I/O-Kosten des kompletten Ausführungsplanes ergeben sich aus der Summe der Kosten,
die an den einzelnen Knoten auftreten:
I/O-Kosten gesamt:= 6 + 85 + 257 = 348
Kapitel 6
Implementierung
Die in den vorangegangen Kapiteln aufgeführte Theorie wurde in dem Anfragesimulator RELOpt umgesetzt. In diesem Kapitel wird zuerst die grafische Oberfläche und ihre Benutzung
beschrieben. Anschließend werden die implementierten Packages und Klassen dargestellt.
6.1
GUI
Das Hauptfenster der Einphasenoptimierung ist über einen Knopf aus dem Startfenster von
RELOpt zu erreichen.
6.1.1
Hauptfenster
Die folgende Abbildung zeigt das Hauptfenster der Einphasenoptimierung:
Abbildung 6.1: Hauptfenster der Einphasenoptimierung
Wie in der Abbildung zu sehen ist, unterteilt es sich in drei Hauptbestandteile:
55
56
KAPITEL 6. IMPLEMENTIERUNG
1. Eingabe
Das rot umrandete Fenster dient der Eingabe relationaler Anfragen. Wie in Kapitel
4.2 beschrieben wurde, wird bei dem vom Parser gelieferten generalisierten Baum eine
kostenabhängige Auswahl der physischen Umsetzungen getroffen, um eine Startkonfiguration für den Termersetzer zu erhalten. Die Kosten dieses ersten Baumes werden
im oberen Teil des Eingabefensters angezeigt. Sie sind aufgeteilt in I/O-, CPU- und
Gesamtkosten. Nach der Eingabe einer Anfrage wird der Baum mit den gewählten
physischen Realisierungen im Fenster dargestellt, wie man in Abbildung 6.1 sieht. Mit
dem Knopf “Anfrage“ erhält man wieder die ursprüngliche Anfrage.
2. Ausgabe
Das Ergebnis der Einphasenoptimierung wird in dem blau markierten Teil des Hauptfensters dargestellt. Wie bei dem Eingabefenster befindet sich auch hier am oberen
Rand eine Anzeige der verschieden Kosten.
3. Regelauswahl
Der grün umrandete Teil des Hauptfensters dient der Auswahl von Termersetzungsregeln die bei der Optimierung verwendet werden sollen. Auf der linken Seite werden die
Ersetzungsregeln in Regelgruppen aufgeführt. Auf der rechten Seite kann ausgewählt
werden ob eine Gruppe iteriert werden soll, d. h. diese Regeln werden angewendet bis
keine Veränderungen mehr möglich sind. Sonst wird eine Gruppe nur ein Mal für eine
Termersetzung genutzt. Genaue Informationen über eine Termersetzung und die Iteration von Regeln sind in [Rud05] zu finden.
Soll eine Termersetzungsregel zu einer Regelgruppe hinzugefügt werden, muss die entsprechende Gruppe ausgewählt und der Knopf “Regel hinzufügen“ gedrückt werden.
Anschließend erscheint eine leere Regel wie es in Abbildung 6.2 dargestellt ist. Mit einem Rechtsklick auf eine Ersetzungsregel öffnet sich der ebenfalls in Abbildung 6.2 zu
sehende Dialog. Hier kann eine Regel ausgewählt oder betrachtet werden. Das Fenster
zum Betrachten einer Regel entspricht dem Fenster zur Eingabe einer Regel. Dieses
Fenster wird im nächsten Abschnitt beschrieben. Mit dem Knopf “Regel erzeugen“
gelangt man zu der Regeleingabe.
Abbildung 6.2: Laden einer Regel
Ist noch keine Gruppe vorhanden muss vor dem Hinzufügen einer Regel mit dem
Knopf “Gruppe hinzufügen“ eine erstellt werden. Regeln und Gruppen lassen sich mit
“Gruppe/Regel löschen“ aus der Liste wieder entfernen. Mit den Knöpfen Regelbaum
speichern/laden können die Regelgruppen gespeichert und geladen werden.
Die zwei Textfelder zur Eingabe einer sogenannten statischen und dynamischen Grenze
dienen der Beschränkung der Varianten die bei einer Termersetzung entstehen. Dies
wird ausführlich in [Rud05] behandelt.
6.1. GUI
6.1.2
57
Regeleingabe
Das Fenster zur Regeleingabe besteht aus vier Teilen. Links oben wird der linke Regelbaum
und rechts oben der recht Baum eingegeben. Die Felder links und rechts unten dienen der
Eingabe der Überführungsbedingungen des linken bzw. rechten Regelbaumes. Keines der
Fenster darf leer sein. Wenn ein Baum grundsätzlich ersetzt werden soll, muss als Bedingung
true und wenn er nie ersetzt werden soll, muss f alse eingegeben werden.
Abbildung 6.3: Erzeugen einer Regel
Nach der Eingabe drückt man den Knopf “Regel parsen“. Wenn das Parsen erfolgreich war,
wird der Knopf “Regel speichern“ freigegeben. Vor dem Speichern ist es möglich in dem
Textfeld “Beschreibung“ eine Regelbeschreibung hinzuzufügen. Der Name der beim Speichern angegeben wird, ohne die Dateiendung “.grf“, ist der künftige Name der Regel.
Über den Knopf “Hilfe“ wird ein Hilfedialog für die Regelterm- bzw. Regelbedingungseingabe geöffnet. Dieser ist in Abbildung 6.4 zu sehen:
Abbildung 6.4: Hilfedialog
58
KAPITEL 6. IMPLEMENTIERUNG
6.1.3
Baumdarstellung
Im Hauptfenster findet man zwei Knöpfe mit der Bezeichnung “Baumansicht“. Diese Knöpfe
öffnen eine grafische Darstellung der generalisierten Bäume der Eingabe bzw. Ausgabe. Das
Fenster für die Baumansicht ist in Abbildung 6.5 zu sehen:
Abbildung 6.5: Baumansicht
Jeder Knoten in diesem Baum kann per Mausklick ausgewählt werden. Für einen gewählten
Knoten werden eine Reihe von Informationen auf der rechten Seite angezeigt.
• Kardinalität der Relation
• Pagegröße
• Attributwertanzahlen der einzelnen Attribute
Falls es sich bei dem ausgewählten Knoten um eine Operation und nicht um eine Basisrelation handelt, werden ebenfalls angezeigt:
• die globale Kosten, welche bisher entstanden sind.
• die lokale Kosten, aufgeteilt in I/O-, gewichtete und ungewichtete CPU- und die Gesamtkosten.
• die physische Realisierung der Operation
Werden zwei Knoten mit doppelten anstatt mit einfachen Strichen verbunden, wurde an
dieser Stelle Pipelining angewandt.
6.2
Packages
In der Abbildung 6.6 wird die Paketstruktur des Programmes dargestellt. Das Paket gui
enthält die zur Darstellung des Hauptfensters und der Regeleingabe benötigten Klassen. Die
Klassen zur grafischen Darstellung eines generalisierten Baumes befinden sich in tree.
6.2. PACKAGES
59
de.unihannover.dbs.sopt
struc.general
gui
tree
tree
sun
test
conditions
Abbildung 6.6: Paketstruktur
Die Klasse RELOpt zum Ausführen des Hauptprogrammes liegt in test. In dem Paket
struc.general befinden sich der relationale Parser und die für einen generalisierten Baum
benötigten Klassen.
6.2.1
Package dbs.unihannover.sopt.struc.general
Direkt in diesem Package befindet sich ausschließlich die Klasse GeneralRelationalP arser
zum Umwandeln einer Anfrage in einen generalisierten Baum oder zum Erzeugen einer Termersetzungsregel. Der Aufbau dieses Parsers wurde in Kapitel 2 und Kapitel 3 dargestellt.
Diese Klasse verfügt über folgende Methoden:
• parseTree
Diese Methode erwartet als Parameter eine relationale Anfrage als Zeichenkette, eine
Liste der Basisrelationen und die zugehörigen Metadaten. Die Ausgabe ist ein generalisierter Baum, wie er in Kapitel 4 beschrieben wurde.
• parseRule
Die Übergabewerte an diese Methode sind jeweils zwei Zeichenketten für die Regelterme und die Regelbedingungen. Als Ausgabe erhält man eine komplette Termersetzungsregel.
6.2.2
Package dbs.unihannover.sopt.struc.general.tree
In diesem Paket befinden sich die Klassen für den Aufbau von generalisierten Bäumen. Der
Zusammenhang der Klassen wird in Abbildung 6.7 dargestellt.
Die Klasse GeneralT ree speichert die Knoten des Baumes und bietet folgende Funktionen:
• calculate
Mit dieser Methode kann ein Baum neu berechnet werden, falls sich z. B. die Metadaten
geändert haben.
• countNodes
Die Ausgabe dieser Methode ist die Anzahl Knoten und Blätter im Baum.
• createTreeFromRule
Dies ist die in Kapitel 4.7 beschriebene Funktion zum Erstellen eines generalisierten
Baum aus einem gefüllten Regelterm.
60
KAPITEL 6. IMPLEMENTIERUNG
• deepCopy
Um eine Kopie des Baumes zu erhalten, muss diese Methode aufgerufen werden.
• dump
Mit dieser Funktion kann der Aufbau des Baumes als Zeichenkette ausgegeben werden.
• equals
Mit equals kann überprüft werden, ob zwei Bäume gleich sind.
• getCost
Die Gesamtkosten des Ausführungsplanes werden mit dieser Methode ausgegeben.
• getIOCost
Die I/O-Kosten des Ausführungsplanes werden mit dieser Methode ausgegeben.
• getCPUCost
Die ungewichteten CPU-Kosten werden mit dieser Methode ausgegeben.
• getRoot
Diese Funktion liefert die Wurzel des gespeicherten Baumes.
• postOrderIterator
Den in Kapitel 4.7 beschriebenen Iterator zum Durchlaufen eines Baumes erhält man
mit einem Aufruf dieser Methode.
• postOrderHasNext
Mit dieser Methode wird geprüft, ob ein Post-Order-Iterator weitere Elemente enthält.
• postOrderNext
Die Ausgabe des nächsten Elementes geschieht mit dieser Funktion.
Um später einfach Erweiterungen vornehmen zu können wurde ein Interface entwickelt,
welches jeder Knoten in einem generalisierten Baum implementieren muss. Dieses Interface
heißt GeneralT reeN ode. Es fordert folgende Methoden:
• calculate
- berechnet die Attribute eines Knotens neu
• countNodes
- gibt die Anzahl Knoten in diesem Teilbaum aus
• deepCopy
- liefert eine Kopie des Baumes ab diesem Knoten
• equals
- überprüft, ob zwei Teilbäume gleich sind
• getAlias
- liefert den Alias einer Operation
6.2. PACKAGES
• getAttributeValueCount
- gibt die Attributwertanzahl zu einem Attribut aus
• getChild
- liefert den Kindknoten an der übergebenen Position
• getChildCount
- gibt die Anzahl Kinder eines Knotens an
• getCondition
- gibt die zu der Operation gehörige Parametrisierung aus
• getCost
- liefert die lokalen Gesamtkosten
• getGlobalCost
- liefert die globalen Gesamtkosten
• getCPUCost
- gibt die lokalen CPU-Kosten aus
• getGlobalCPUCost
- gibt die globalen CPU-Kosten aus
• getIOCost
- liefert die entstanden I/O-Kosten an diesem Knoten
• getGlobalIOCost
- liefert die bisher entstanden I/O-Kosten des Teilbaumes
• getIndexAttr
- gibt eine Liste mit Attributmengen auf denen ein Index existiert zurück
• getIndexCard
- liefert die Kardinaliät eines Index
• getIndexPage
- liefert die Pagegröße eines Index
• getName
- gibt den Namen einer Operation oder Relation aus
• getPageCardinality
- liefert die Pagegröße einer Relation
• getParent
- liefert den übergeordneten Knoten
• getPhysOp
- gibt die physische Umsetzung einer Operation aus
61
62
KAPITEL 6. IMPLEMENTIERUNG
• getScheme
- liefert das Schema einer Relation
• getSortedAttributes
- gibt eine Liste von Attributmengen aus, auf den eine Sortierung existiert
• getTupelCardinality
- liefert die Kardinalität einer Relation
• hasIndexOn
- mit dieser kann bestimmt werden, ob auf einer Attributmenge ein Index existiert
• isClusteredIndex
- mit dieser kann bestimmt werden, ob ein Index geclustert ist
• isSortedOn
- mit dieser kann bestimmt werden, ob auf einer Attributmenge eine Sortierung existiert
• pipeliningAtChild
- gibt an, ob bei der Relation an der übergebenen Position Pipelining angewandt
werden kann
• setAlias
- setzt bei einer Relation einen Alias
• setChild
- setzt das Kind an der übergebenen Position
• setParent
- setzt den übergeordneten Knoten
• setPhysOp
- setzt die physische Umsetzung
Dieses Interface wird direkt in der Klasse GeneralT able, welche eine Basisrelation darstellt,
umgesetzt. Für die anderen Operatoren wurden zur Unterscheidung von variablen und normalen Knoten abstrakte Oberklassen implementiert, welche das Interface nutzen.
Für die normalen Operatoren existieren die abstrakten Klassen AbstractBinaryOperation
für binäre Operatoren und AbstractU naryOperation für Operatoren mit nur einer Argumentrelation. Diese Klassen implementieren bereits einen Großteil der von GeneralT reeN ode
geforderten Methoden. Die einzelnen Knoten müssen noch die folgenden Funktionen umsetzen:
calculate, deepCopy, dump, equals, getName, setPhysOp
Für variable Knoten wurde einmal die abstrakte Klasse AbstractV ariable eingeführt. Sie
implementiert einen großen Teil von dem Interface GeneralT reeN ode, führt aber weitere
Methoden ein, welche von variablen Knoten umgesetzt werden müssen:
• getTreeNode
- liefert einen konkreten Knoten indem er die gefüllten Werte des Regelknotens verwendet
6.2. PACKAGES
63
• getRealCondition
- gibt bei einem gefüllten Knoten den Wert der Variablen aus
• setRealCondition
- setzt den Wert für die variable Parametrisierung
Zur Unterscheidung von binären und einfachen variablen Operationen wurden ebenfalls zwei
Klassen, AbstractU naryV ariable und AbstractBinaryV ariable, eingeführt. In der folgenden Abbildung ist dies dargestellt:
<<interface>>
GeneralTreeNode
+calculate(MetaData): void
+countNodes(): int
+deepCopy(): GeneralTreeNode
+dump(int): void
+equals(Object): boolean
+getAlias (): String
+getAttributeValueCount(Attribute,MetaData): long
+getChild(int): GeneralTreeNode
+getChildCount(): int
+getCondition(): ConditionTree
+getCost(double): long
+getGlobalCost(double): long
+getCPUCost(): long
+getGlobalCPUCost(): long
+getIOCost(): long
+getGlobalIOCost(): long
+getIndexAttr(): Vector<AttributeSet>
+getIndexCard(AttributeSet,MetaData): long
+getIndexPage(AttributeSet,MetaData): long
+getName(): String
+getPageCardinality(MetaData): long
+getParent(): GeneralTreeNode
+getPhysOp(): String
+getScheme(): AttributeSet
+getSortedAttributes(MetaData): Vector<AttributeSet>
+getTupelCardinality(MetaData): long
+hasIndexOn(AttributeSet:MetaData): boolean
+isClusteredIndex(AttributeSet,MetaData): boolean
+isSortedOn(AttributeSet,MetaData): boolean
+pipeliningAtChild(int): booelan
+setAlias(String): void
+setChild(int,GeneralTreeNode): void
+setParent(GeneralTreeNode): void
+setPhysOp(String): void
GeneralTable
VarRelation
AbstractVariable
AbstractBinaryOperation
AbstractUnaryOperation
GeneralJoin
GeneralSemiJoin
+getRealCondition(): ConditionTree
+setRealCondition(ConditionTree): void
+getTreeNode(MetaData): GeneralTreeNode
Sort
GeneralAntiSemiJoin
Index
AbstractBinaryVariable
GeneralOuterJoin
GeneralSelection
VarJoin
GeneralProjection
VarSemiJoin
AbstractUnaryVariable
GeneralLeftOuterJoin
GeneralRightOuterJoin
GeneralAggregation
VarAntiSemiJoin
GeneralProduct
VarSort
VarIndex
VarOuterJoin
VarSelection
GeneralUnion
VarLeftOuterJoin
VarProjection
GeneralIntersection
VarRightOuterJoin
VarAggregation
GeneralDifference
VarProduct
VarUnion
VarIntersection
VarDifference
Abbildung 6.7: Klassendiagramm dbs.unihannover.sopt.struc.general.tree
64
KAPITEL 6. IMPLEMENTIERUNG
Die Operatoren Sort und Index übernehmen die Knotenattribute ihres Kindknotens, fügen
aber zu den Metadaten die Information über die erzeugten Indexe oder Sortierungen hinzu.
Wird ein Index erzeugt, müssen für die Kostenabschätzungen in Kapitel 5 |Index| und
page(Index) bekannt sein. Zur Abschätzung dieser Werte wurden die bereits in RELOpt
implementierten Funktionen aus der Klasse de.unihannover.dbs.sopt.struc.phys.M etaData
genutzt.
6.2.3
Package dbs.unihannover.sopt.struc.general.tree.condition
Die Klassen zur Erstellung von Parametrisierungen für Operationen sind in dem Package
.tree.condition zu finden. Um arithmetische Ausdrücke, wie sie in Kapitel 2.1.3 beschrieben
sind, speichern zu können, wurde eine Baumstruktur für die Operatoren und Operanden
entworfen. Jeder Knoten in dieser Struktur muss das Interface ArithmeticT erm implementieren:
Plus
Minus
<<Interface>>
ArithmeticTerm
Times
+addChild(ArithmeticTerm): void
+deepCopy(): ArithmeticTerm
+getAlias(): Attribute
+getAttributes(): AttributeSet
+getChildAt(int): ArtihmeticTerm
+getChildCount(): int
+removeChildAt(int): void
+setAlias(String): void
+toString(): String
Divide
Column
Digit
+getValue(): int
Abbildung 6.8: Klassendiagramm arithmetische Terme
Die Klassen Plus, Minus, Times und Divide stellen die Operatoren +, −, ·, / dar. Die
Klasse Column wird genutzt zum Speichern von Attributen und die Klasse Digit zum
Speichern von Zahlen. Folgende Methoden werden von dem Interface ArithmeticT erm verlangt:
• addChild
- fügt einen Kindknoten hinzu
• deepCopy
- liefert eine Kopie des Baumes ab diesem Knoten
• getAlias
- liefert den Alias für einen arithmetischen Term
• getAttributes
- gibt die in dem Teilbaum vorhandenen Attribute aus
• getChildAt
- liefert den Kindknoten an der übergebenen Position
6.2. PACKAGES
65
• getChildCount
- gibt die Anzahl Kinder zurück
• removeChildAt
- entfernt einen Kindknoten
• setAlias
- setzt den Alias für einen arithmetischen Ausdruck
• toString
Da Selektions- und Verbundbedingungen logisch verknüpft werden können, wurde eine
Baumstruktur zur einheitlichen Speicherung der Parametrisierungen verwendet.
<<Interface>>
ConditionAggregation
ConditionTreeNode
+getAliases(): Vector<Attribute>
+getFunctions(): Vector<String>
+getFunctionAttr(): Vector<Attribute>
+getGroupAttr(): Vector<Attribute>
+getProjAttr(): Vector<Attribtue>
+addChild(ConditionTreeNode): void
+countNodes(): int
+deepCopy(): ConditionTreeNode
+equals(Object): boolean
+getAttributes(): AttributeSet
+getChildAt(int): ConditionTreeNode
+getChildCount(): int
+getParent(): ConditionTreeNode
+removeChildAt(int): void
+replaceChildAt(int,ConditionTreeNode): void
+setParent(ConditionTreeNode): void
+toString(): String
VarCondition
ConditionProjection
+getArithmeticAttr(): Vector<ArithmeticTerm>
AbstractCondition
+getSelektivity(GeneralTreeNode,MetaData): double
+getThetas(): Vector<Theta>
NOT
AtomicCondition
OR
AND
Abbildung 6.9: Klassendiagramm Parametrisierungen
Jeder Knoten in einem solchen Baum muss das Interface ConditionT reeN ode implementieren. Gefordert werden von dem Interface folgende Methoden:
• addChild
- setzt den Kindknoten bei Verknüpfungen wie z. B. AND
• countNodes
- zählt die Knoten in einem Teilbaum
• deepCopy
- kopiert einen Teilbaum
• equals
- prüft, ob zwei Objekte gleich sind
• getAttributes
- liefert die Attribute auf die sich eine Parametrisierung bezieht
66
KAPITEL 6. IMPLEMENTIERUNG
• getChildAt
- liefert den Kindknoten an der übergebenen Position
• getChildCount
- gibt die Anzahl Kindknoten aus
• getParent
- liefert den übergeordneten Knoten
• removeChildAt
- entfernt einen Kindknoten
• replaceChildAt
- tauscht einen Kindknoten aus
• setParent
- setzt den übergeordneten Knoten
• toString
- liefert die Parametrisierung als Zeichenkette
Die Klasse AtomicCondition wird verwendet zum Speichern von Selektions- und Verbundbedingungen. Objekte dieser Klasse können durch AN D, OR und N OT verknüpft werden.
Das selbe gilt für die Klasse V arCondition, welche für variable Parametrisierungen in Regeltermen verwendet wird. Dabei sind AN D und OR nicht auf zwei Argumente beschränkt,
sondern können beliebig viele Kinder haben.
Die Klassen ConditionP rojection und ConditionAggregation dienen der Speicherung von
Projektions- bzw. Aggregationsparametrisierungen. Objekte dieser Klassen können nur als
einzelne Elemente verwendet und nicht durch AN D, OR oder N OT verknüpft werden.
Die Knoten werden in der Klasse ConditionT ree geseichert. Diese Klasse stellt folgende
Methoden zur Verfügung:
• countNodes
- zählt die Knoten in einem Teilbaum
• deepCopy
- kopiert einen Teilbaum
• equals
- prüft, ob zwei Objekte gleich sind
• fetchCondition
- realisiert die in Kapitel 3.1 vorgestellte Funktion
• fetchJoinCondition
- setzt die in Kapitel 3.1 vorgestellte Funktion um
• minus
- implementiert die in Kapitel 3.1 vorgestellte Funktion
6.2. PACKAGES
67
• getAttributes
- liefert die Attribute auf die sich eine Parametrisierung bezieht
• getRoot
- gibt die Wurzel des Baumes zurück
• setRoot
- setzt die Wurzel eines Baumes
• toString
- liefert den Baum als Zeichenkette
Das Paket .condition enthält außerdem die Klassen für das in Kapitel 2.3.1 beschriebene
Verfahren von Quine-McCluskey zur Minimierung von logisch verknüpften Selektionsbedingungen.
1. ValueTable
Mit dieser Klasse wird eine Wertetabelle dargestellt. Sie bietet folgende Methoden:
- fill: Dieser Methode wird eine Selektionsbedingung in disjunktiver Form übergeben,
aus der die Tabelle aufgebaut wird.
- getMinTerms: Die Minterme der Wertetabelle können mit dieser Funktion ausgegeben werden.
2. FirstQuineTable
Diese Klasse repräsentiert die erste Quine’sche Tabelle. Bei der Erzeugung eines Objektes dieser Klasse werden die Minterme, welche man aus V alueT able erhält, übergeben. Mit der Funktion getPrimterms können anschließend die gefundenen Primterme
ausgegeben werden.
3. SecoundQuineTable
Mit den Primtermen aus F irstQuineT able kann die zweite Quine’sche Tabelle aufgebaut werden. Dazu wird die Klasse SecoundQuineT able verwendet. Die Funktion
getDMF wird genutzt um abschließend die disjunktive Minimalform ausgeben zu
lassen.
4. ConditionMinimizer
Die Klasse ConditionM inimizer bekommt bei der Erzeugung eines Objektes eine logisch verknüpfte Selektionsbedingung übergeben, welche in eine disjunktive Form gebracht wird. Anschließend werden Objekte der Klassen V alueT able, F irstQuineT able
und SecoundQuineT able erzeugt, um eine disjunktive Minimalform zu erhalten.
6.2.4
Erweiterungen am Package dbs.unihannover.sopt.gui
Die Klassen zum Aufbau der grafischen Benutzerschnittstelle der Einphasenoptimierung
befinden sich in diesem Paket. Neu hinzugekommen sind die Klassen:
• GeneralEinPhasEdit
- realisiert das Hauptfenster der Einphasenoptimierung
• GeneralRuleDialog
- realisiert das Fenster zur Regeleingabe
68
KAPITEL 6. IMPLEMENTIERUNG
• GeneralRulePanel
- bietet einen Container für die Darstellung der Termersetzungsregeln im Hauptfenster
• GeneralRuleTable
- dient zum Anzeigen der Regelgruppen
• GeneralRuleTreeTableModel
- implementiert das Verhalten der Tabelle GeneralRuleT able in Bezug auf das Anzeigen von Daten oder Benutzereingaben
• OperatorInfoDialog4
- realisiert den Hilfedialog für die Eingabe von Regeltermen
• RuleInfoDialog2
- realisiert den Hilfedialog für die Eingabe von Regelbedingungen
Die Klasse T reeT extArena wurde angepasst, um die Hilfedialoge OperatorInf oDialog4
und RuleInf oDialog2 in dem Fenster für die Regeleingabe öffnen zu können.
6.2.5
Erweiterungen am Package dbs.unihannover.sopt.tree
Die Klassen in diesem Package wurden erweitert um eine grafische Darstellung von generalisierten Bäumen zu ermöglichen. Folgende Klassen mussten angepasst werden:
• Node
Diese Klasse repräsentiert einen allgemeinen Knoten in dem grafisch dargestellten
Baum.
• TreeView und TreeFrame
Mit diesen Klassen werden Objekte die von N ode abstammen in einem Fenster positioniert und gezeichnet.
Kapitel 7
Ausblick
• Späte Duplikateneleminierung
Eine Projektion in RELOpt ist momentan eine Projektion bei der grundsätzlich Duplikate entfernt werden. Um dem Optimierungssystem die Möglichkeit zu geben kostspielige Duplikateleminierungen erst beim Endergebnis auszuführen, wäre es sinnvoll
eine nicht-duplikateleminierende Projektion zu implementieren.
• Erweiterung der mitgeführten Metadaten und Verfeinerung des Kostenmodells
Um eine verbesserte inhaltliche Analyse einer Anfrage und verfeinerte Kostenabschätzungen zu ermöglichen, sollten weitere Metadaten bei der Optimierung mitgeführt werden. Beispielsweise könnten Typen, Bytelängen oder minimale und maximale Werte
der einzelnen Attribute einer Relation gespeichert werden.
Die Information über den Attributtyp hilf bei der semantischen Analyse. Pagegrößen
könnten genauer mit vorhanden Bytelängen bestimmt werden und das Speichern von
minimalen und maximalen Attributwerten würde z. B. eine Verbesserung der Kostenfunktion für Merge-Joins ermöglichen.
• Hash-Algorithmen
Momentan verfügt das erstellte Kostenmodell nicht über Abschätzungen für HashJoins. Diese sollten in Zukunft hinzugefügt werden.
• Erweiterung der Sprache zur Erzeugung von Regelbedingungen
Eine große Menge von möglichen Bedingungen bei einer Termersetzungsregel ist die
Grundlage um ein großes Spektrum an Optimierungsstrategien zu ermöglichen. Aus
diesem Grund wäre es denkbar weitere Funktionen zum vorhandenen Wortschatz hinzuzufügen, z. B. eine Funktion zum bestimmen von Attributwertanzahlen.
69
70
KAPITEL 7. AUSBLICK
Anhang A
Relationaler Parser
A.1
A.1.1
Grammatik
Nichtterminale Symbole
N ={
<start>, <operation>, <operatorSet>, <operatorJoin>,
<groupby>, <additionalTarget>, <function>, <additionalFunction>,
<conditionProjection>, <conditionSelection>, <conditionOperator>,
<innerCS>, <innerFunction>, <additionalCS>, <additionalCO>,
<phySingle>, <phyBinary>, <tree>, <bigLetter>,
<aliasRelation>, <alias>, <calcOrTerm>, <calcTarget>, <numeric>,
<calcOperand>, <calculation>, <target>, <letter>, <followTerm>,
<term>, <additionalCondition>, <varRelation>, <varCondition>,
<valueOperator>, <functionOperator>, <followNum>,
<additionalTarget>
},
A.1.2
Terminale Symbole
Zu den terminalen Zeichen kommen zur Laufzeit noch die Namen von Relationen und Attributen hinzu. Diese werden in der Grammatik durch RELAT ION und AT T RIBU T E
gekennzeichnet und müssen beim parsen auf ihr Vorhandensein geprüft werden.
T ={
+, -, *, /, ≤, > , =, ! =, <, ≥
0, 1, ... , 9,
a, b, ... , z,
A, B, ... , Z,
%, , #, ,,’,
RELAT ION , AT T RIBU T E,
and, or, not, as,
min, max, count, sum, avg, (, )
AGGREGATION, PROJECTION, SELECTION,
JOIN, SEMIJOIN, ANTISEMIJOIN,
LEFTOUTERJOIN, RIGHTOUTERJOIN, OUTERJOIN,
UNION, INTERSECT, MINUS, PRODUCT,
rel, ind, sort, rel-rel, ind-rel,
rel-ind, ind-ind, sort-sort, hash-hash
},
71
72
ANHANG A. RELATIONALER PARSER
A.1.3
Startsymbol
S={
<start>
}
A.1.4
Produktionen
P ={
<start> → (<operation>)
<start> → RELAT ION
<start> → [<varRelation>]
<operation> → aggregation <phySingle> (<groupby>#<function>)
<start> <aliasRelation>
<operation> → projection <phySingle> (<conditionProjection>)
<start> <aliasRelation>
<operation> → selection <phySingle> (<conditionSelection>)
<start> <aliasRelation>
<operation> → <operatorJoin> <phyBinary> (<conditionOperator>)
<start>, <start> <aliasRelation>
<operation> → <operatorSet> <phyBinary> <start>, <start> <aliasRelation>
<operation> → <tree> (<target><additionalTarget>) <start>
<operation> → RELAT ION <aliasRelation>
<phySingle> → rel | ind | sort | ε
<phyBinary> → rel-rel | ind-rel | rel-ind | ind-ind | sort-sort |
hash-hash | ε
<varRelation> → <bigLetter><followTerm>
<varCondition> → <letter><followTerm>
<conditionProjection> → <calcTarget> <alias><additionalCondition>
<conditionProjection> → [<varCondition>]
<conditionSelection> → <calcTarget> <valueOperator> <calcOrTerm>
<conditionSelection> → [<varCondition>]
<conditionSelection> → <innerCS>
<conditionOperator> → <target> <valueOperator> <target>
<conditionOperator> → [<varCondition>]
<conditionOperator> → and (<conditionOperator>,
<conditionOperator><additionalCO>)
<groupby> → <target><additionalTarget>| [<varCondition>] | ε
<function> → <innerFunction><additionalFunction> | [<varCondition>]
<aliasRelation> → as <varRelation> | ε
<alias> → as <varRelation>.<letter><term> | ε
<innerFunction> → sum(<target>) as <alias> | count(<target>) as <alias> |
max(<target>) as <alias>| avg(<target>) as <alias>|
min(<target>) as <alias>| <target>
A.2. BEWEIS LL(1)-BEDINGUNG
73
<innerCS> → not (<conditionSelection>)
<innerCS> → and (<conditionSelection>, <conditionSelection><additionalCS>)
<innerCS> → or (<conditionSelection>, <conditionSelection><additionalCS>)
<additionalCondition> → , <conditionProjection> | ε
<additionalFunction> → , <function> | ε
<additionalCO> → , <conditionOperator> | ε
<additionalCS> → , <conditionSelection> | ε
<additionalTarget> → , <target><additionalTarget> | ε
<calcOrTerm> → <calcTarget> | ’<term>’
<calcTarget> → <calcOperand> <calculation>
<calcOperand> → (<calcTarget>) | <numeric> | <target>
<calculation> → <functionOperator> <calcOperand> | ε
<target> → RELAT ION .AT T RIBU T E
<tree> → SORT | INDEX
<term> → 0<followTerm> | ... | 9<followTerm> | <followTerm>
a<followTerm> | ... | Z<followTerm> | %<followTerm>
<followTerm> → <term> | ε
<operatorSet> → PRODUCT | UNION | INTERSECT | MINUS
<operatorJoin> → JOIN | SEMIJOIN | ANTISEMIJOIN | LEFTOUTERJOIN |
RIGHTOUTERJOIN | OUTERJOIN
<letter> → a | ... | z
<bigLetter> → A | ... | z
<numeric> → 0<followNum> | ... | 9<followNum>
<followNum> → <numeric> | ε
<valueOperator> → >= | > | = | ! = | < | <=
<functionOperator> → + | - | * | /
}
A.2
Beweis LL(1)-Bedingung
1. Bedingung
• <start>
First((<operation>)) = {(}, First([<varRelation>]) = {[} und First(RELAT ION )
müssen disjunkt sein.
Aus den Produktionen geht hervor, dass der Name einer Relation nicht ’( ’ oder ’[ ’
sein darf. Daher sind die beiden Menge in jedem Fall disjunkt!
• <operation>
First(AGGREGATION <phySingle> (<groupby>#<function>) <start>
<aliasRelation>) = {AGGREGATION},
74
ANHANG A. RELATIONALER PARSER
First(PROJECTION <phySingle> (<conditionProjection>) <start>
<aliasRelation>) = {projection},
First(SELECTION <phySingle> (<conditionSelection>) <start>
<aliasRelation>) = {selection},
First(<operatorJoin> <phyBinary> (<conditionOperator>) <start>, <start>
<aliasRelation>) = {
JOIN, SEMIJOIN, ANTISEMIJOIN, LEFTOUTERJOIN,
RIGHTOUTERJOIN, OUTERJOIN
},
First(<operatorSet> <phyBinary> <start>, <start> <aliasRelation>) = {
product, union, intersect, minus
} und
First(<tree> (<target><additionalTarget>) <start>) = {SORT, INDEX}
sind paarweise disjunkt!
Damit auch First(RELAT ION <aliasRelation>) zu den anderen paarweise disjunkt ist, darf eine Relation nicht den Namen eines Operators oder ’[’ haben!
• <phySingle>
First(rel) = {rel},
First(ind) = {ind},
First(ind) = {sort},
First(ε) = {ε}
sind paarweise disjunkt!
• <phyBinary>
First(rel-rel) = {rel-rel},
First(rel-ind) = {rel-ind},
First(ind-rel) = {ind-rel},
First(ind-ind) = {ind-ind},
First(sort-sort) = {sort-sort},
First(hash-hash) = {hash-hash},
First(ε) = {ε}
sind paarweise disjunkt!
• <conditionProjection>
First(<calcTarget> <alias><additionalCondition>) = {
(, a, ... , Z, 1, ... , 9
},
First([<varCondition>]) = {[}
sind disjunkt!
• <conditionSelection>
First(<calcTarget> <valueOperator> <calcOrTerm>) = {
(, a, ... , Z, 1, ... , 9
},
First(<innerCS>) = {and, or, not},
A.2. BEWEIS LL(1)-BEDINGUNG
First([<varCondition>]) = {[}
sind paarweise disjunkt!
• <conditionOperator>
First(<target> <valueOperator> <target>) = {
A, ... , Z
},
First(and (<conditionOperator>,
<conditionOperator><additionalCO>)) = {and},
First([<varCondition>]) = {[}
sind paarweise disjunkt!
• <groupby>
First(<target><additionalTarget>) = {A,... , Z},
First(ε) = {ε},
First([<varCondition>]) = {[}
sind paarweise disjunkt!
• <function>
First(<innerFunction><additionalFunction>) = {
sum, min, max, count, avg, A, ... , Z
},
First([<varCondition>]) = {[}
sind paarweise disjunkt!
• <aliasRelation>
First(as <varRelation>) = {as},
First(ε) = {ε}
sind disjunkt!
• <alias>
First(as <varRelation>.<letter><term>) = {as},
First(ε) = {ε}
sind disjunkt!
• <innerFunktion>
First(sum(<target> as <alias>)) = {sum},
First(count(<target> as <alias>)) = {count},
First(max(<target> as <alias>)) = {max},
First(min(<target> as <alias>)) = {min},
First(avg(<target> as <alias>)) = {avg},
First(<target>) = {A, ... , Z}
sind paarweise disjunkt!
75
76
ANHANG A. RELATIONALER PARSER
• <innerCS>
First(not (<conditionSelection>)) = {not},
First(and (<conditionSelection>,
<conditionSelection><additionalCS>)) = {and},
First(or (<conditionSelection>,
<conditionSelection><additionalCS>)) = {or}
sind paarweise disjunkt!
• <additionalCondition>
First(, <conditionProjection>) = {,},
First(ε) = {ε}
sind disjunkt!
• <additionalFunction>
First(, <function>) = {,},
First(ε) = {ε}
sind disjunkt!
• <additionalCS>
First(, <conditionSelection>) = {,},
First(ε) = {ε}
sind disjunkt!
• <additionalCO>
First(, <conditionOperator>) = {,},
First(ε) = {ε}
sind disjunkt!
• <additionalTarget>
First(, <target><additionalTarget>) = {,},
First(ε) = {ε}
sind disjunkt!
• <calcOrTerm>
First(<calcTarget>) = {(, A, ... , Z, 0, ... , 9},
First(’<term>’) = {’}
sind disjunkt!
• <calcOperand>
First((<calcTarget>)) = {(},
First(<numeric>) = {0, ... , 9},
First(<target>) = {a, ... , Z}
sind paarweise disjunkt!
A.2. BEWEIS LL(1)-BEDINGUNG
77
• <calculation>
First(<functionOperator> <calcOperand>) = {+, -, *, /},
First(ε) = {ε}
sind disjunkt!
• <tree>
First(SORT) = {SORT},
First(INDEX) = {INDEX}
sind paarweise disjunkt!
• <term>
First(0<followTerm>) = {0}, ... , First(9<followTerm>) = {9},
First(a<followTerm>) = {a}, ... , First(Z<followTerm>) = {Z},
First( <followTerm>) = { }, First(%<followTerm>) = {%}
sind paarweise disjunkt!
• <followTerm>
First(<term>) = {0, ... , 9, a, ... , Z, , %},
First(ε) = {ε}
sind disjunkt!
• <operatorSet>
First(PRODUCT) = {PRODUCT},
First(UNION) = {UNION},
First(INTERSECT) = {INTERSECT},
First(MINUS) = {MINUS}
sind paarweise disjunkt!
• <operatorJoin>
First(JOIN) = {JOIN},
First(SEMIJOIN) = {SEMIJOIN},
First(ANTISEMIJOIN) = {ANTISEMIJOIN},
First(LEFTOUTERJOIN) = {LEFTOUTERJOIN},
First(RIGHTOUTERJOIN) = {RIGHTOUTERJOIN},
First(OUTERJOIN) = {OUTERJOIN}
sind paarweise disjunkt!
• <numeric>
First(0<followNum>)
First(2<followNum>)
First(4<followNum>)
First(6<followNum>)
First(8<followNum>)
=
=
=
=
=
sind paarweise disjunkt!
{0},
{2},
{4},
{6},
{8},
First(1<followNum>)
First(3<followNum>)
First(5<followNum>)
First(7<followNum>)
First(9<followNum>)
=
=
=
=
=
{1},
{3},
{5},
{7},
{9}
78
ANHANG A. RELATIONALER PARSER
• <followNum>
First(<numeric>) = {0, ... , 9},
First(ε) = {ε}
sind disjunkt!
• <letter>
First(a) = {a}, ... , First(z) = {z}
sind paarweise disjunkt!
• <bigLetter>
First(A) = {A}, ... , First(Z) = {Z}
sind paarweise disjunkt!
• <valueOperator>
First(>=) = {>=}, First(>) = {>}, First(=) = {=},
First(! =) = {! =}, First(<) = {<}, First(<=) = {<=}
sind paarweise disjunkt!
• <functionOperator>
First(+) = {+}, First(-) = {-},
First(*) = {*}, First(/) = {/}
sind paarweise disjunkt!
2. Bedingung
• <phySingle>
Follow(<phySingle>) = {(},
First(rel) = {rel},
First(ind) = {ind},
First(sort) = {sort}
sind paarweise disjunkt!
• <phyBinary>
Follow(<phyBinary>) = {(, RELAT ION , [},
First(rel-rel) = {rel-rel},
First(rel-ind) = {rel-ind},
First(ind-rel) = {ind-rel},
First(ind-ind) = {ind-ind},
First(sort-sort) = {sort-sort},
First(hash-hash) = {hash-hash}
sind paarweise disjunkt, falls keine Relation die Bezeichnung einer physischen Umsetzung hat!
• <groupby>
Follow(<groupby>) = {#},
A.2. BEWEIS LL(1)-BEDINGUNG
First(<target><additionalTarget>) = {A, ... , Z} und
First([varCondition]) = {[}
sind paarweise disjunkt!
• <aliasRelation>
Follow(<aliasRelation>) = {), ,, $},
First(as <varRelation>.<bigLetter><term>) = {as}
sind disjunkt!
• <alias>
Follow(<alias>) = {), ,},
First(as <varRelation>) = {as}
sind disjunkt!
• <additionalCondition>
Follow(<additionalCondition>) = {)},
First(, <conditionProjection>) = {,}
sind disjunkt!
• <additionalTarget>
Follow(<additionalTarget>) = {#, )},
First(, <target><additionalTarget>) = {,}
sind disjunkt!
• <additionalFunction>
Follow(<additionalFunction>) = {)},
First(, <function>) = {,}
sind disjunkt!
• <additionalCO>
Follow(<additionalCO>) = {)},
First(, <conditionOperator>) = {,}
sind disjunkt!
• <additionalCS>
Follow(<additionalCS>) = {)},
First(, <conditionSelection>) = {,}
sind disjunkt!
• <calculation>
Follow(<calculation>) = {)},
First(<functionOperator> <calcOperand>) = {+, -, *, /}
sind disjunkt!
79
80
ANHANG A. RELATIONALER PARSER
• <followTerm>
Follow(<followTerm>) = {’, ,, +, -, *, /, )},
First(<term>) = {0, ... , 9, a, ... , Z, , %}
sind disjunkt!
• <followNum>
Follow(<followNum>) = {), ,, +, -, *, /},
First(<numeric>) = {0, ... , 9}
sind disjunkt!
Damit erfüllt obere Grammatik die LL(1)-Bedingung.
A.3
Beispiel für einen Ableitungsbaum
Für die Anfrage
SELECT ION (S.Semester =’5’) S
ergibt sich folgender Ableitungsbaum:
Abbildung A.1: Beispiel für einen Ableitungsbaum
Anhang B
Regelbedingungsparser
B.1
B.1.1
Grammatik
Nichtterminale Symbole
N ={
<start>, <bedingung>, <mengenOperation>, <zuweisungsOperation>,
<vergleichsOperation>, <kostenOperationt>, <boolscheOperation>, <element>,
<berechnung>, <operator>, <variableBedingung>, <variableRelation>
<variableAttributmenge>, <mengenOperator>, <vergleichsOperator>, <firstElement>
<teilmengenOperator>, <zuweisungsOperator>, <funktion>, <verknüpfung>,
<negation>, <verknüpfungAND>, <verknüpfungOR>, <verknüpfungMINUS>,
<operatoren>, <operatorTyp1>, <weitereOperatoren>, <groessenOperator>,
<multiplikator>, <kostenfunktion>, <baum>, <existenzfunktion>, <zahl>
},
B.1.2
Terminale Symbole
T ={
+, -, *, /, >=, > , =, ! =, <, <=,
], ]=, [, [=,
0, 1, ... , 9,
a, b, ... , z,
A, B, ... , Z,
and, or, not, null,
union, intersect, attr, sch,
fetch condition, fetch joinCondition,
fetch first, fetch rest,
op, card, page, sel, cost,
left, right, index supported,
merge supported, sorted
},
B.1.3
Startsymbol
S={
<start>
}
81
82
ANHANG B. REGELBEDINGUNGSPARSER
B.1.4
Produktionen
P ={
<start> → <bedingung>
<bedingung>
<bedingung>
<bedingung>
<bedingung>
<bedingung>
→
→
→
→
→
<zuweisungsOperation>
<vergleichsOperation>
<mengenOperation>
<kostenOperation>
<boolscheOperation>
<zuweisungsoperation> → <variableBedingung> <zuweisungsOperator> <funktion>
<verknüpfung>
<funktion> → <negation> <variableBedingung>
| fetch condition(<variableBedingung>, <variableRelation>)
| fetch first(<variableBedingung>) | fetch rest(<variableBedingung>)
| fetch joinCondition(<variableBedingung>,<variableRelation>,
<variableRelation>)
<verknüpfung> → <verknüpfungAND> | <verknüpfungOR> | <verknüpfungMINUS>
<verknüpfungAND> → AND <funktion> <verknüpfungAND> | ε
<verknüpfungOR> → OR <funktion> <verknüpfungOR> | ε
<verknüpfungMINUS> → − <funktion> <verknüpfungMINUS> | ε
<negation> → NOT | ε
<vergleichsOperation> → op(<variableBedingung>) <vergleichsOperator>
{<operatoren>}
<operatoren> → <operatorTyp1> <weitereOperatoren>
<weitereOperatoren> → , <operatoren> | ε
<operatorTyp1> → <groessenOperator> | <zuweisungsOperator>
<mengenOperation> → <firstElement> <berechnung> <operator>
<element> <berechnung>
<firstElement> → attr(<variableBedingung>) | sch(<variableRelation>)
<element> → attr(<variableBedingung>) | sch(<variableRelation>) |
<variableAttributmenge> | null
<berechnung> → <mengenOperator> <element> | ε
<operator> → <vergleichsOperator> | <teilmengenOperator> | <zuweisungsOperator>
<mengenOperator> → union | intersect
<kostenOperation> → <multiplikator> <kostenfunktion> <operatorTyp1>
<multiplikator> <kostenfunktion>
<multiplikator> → <zahl> ∗ | ε
<kostenfunktion> → card(<variableRelation>) | page(<variableRelation>)
| sel(<variableBedingung>, <variableRelation>) | cost(<baum>)
<baum> → left | right
<boolscheOperation> → <negation> <existenzfunktion>
<existenzfunktion> → sorted(<variableAttributmenge>, <variableRelation>)
| index supported(<variableAttributmenge>, <variableRelation>)
| merge supported(<variableBedingung>, <variableRelation>,
<variableRelation>)
B.2. BEWEIS LL(1)-BEDINGUNG
<vergleichsOperator> → == | ! =
<teilmengenOperator> → [= | [ | ] | ] =
<groessenOperator> → >= | > | < | <=
<zuweisungsOperator> → =
<variableBedingung> → <buchstabe><term>
<variableRelation> → <grossBuchstabe><term>
<variableAttributmenge> → <buchstabe><term>
<buchstabe> → a | ... | z
<grossBuchstabe> → A | ... | z
<term> → 0<followTerm> | ... | 9<followTerm> |
a<followTerm> | ... | Z<followTerm>
<followTerm> → <term> | ε
<zahl> → 0<follow> | ... | 9<follow>
<follow> → <zahl> | ε
}
B.2
Beweis LL(1)-Bedingung
1. Bedingung
• <bedingung>
First(<zuweisungsOperation>) = {a,... ,z},
First(<vergleichsOperation>) = {op},
First(<mengenOperation>) = {attr, sch, null},
First(<kostenOperation>) = {0,... ,9, sel, card, page, cost},
First(<boolscheOperation>) = {
NOT, sorted, index supported,
merge supported
}
sind paarweise disjunkt!
• <funktion>
First(<negation> <variableBedingung>) = {NOT, a,... ,z},
First(fetch condition(<variableBedingung>,<variableRelation>))
= {fetch condition},
First(fetch joinCondition(<variableBedingung>,<variableRelation>,
<variableRelation>)) = {fetch joinCondition},
First(fetch first(<variableBedingung>)) = {fetch first},
First(fetch rest(<variableBedingung>)) = {fetch rest}
sind paarweise disjunkt!
• <verknüpfung>
First(<verknüpfungAND>) = {AND, ε},
First(<verknüpfungOR>) = {OR, ε},
83
84
ANHANG B. REGELBEDINGUNGSPARSER
First(<verknüpfungMINUS>) = {-, ε}
sind paarweise disjunkt!
• <verknüpfungAND>
First(AND <funktion> <verknüpfungAND>) = {AND},
First(ε) = {ε}
sind disjunkt!
• <verknüpfungOR>
First(OR <funktion> <verknüpfungOR>) = {OR},
First(ε) = {ε}
sind disjunkt!
• <verknüpfungMINUS>
First(- <funktion> <verknüpfungAND>) = {-},
First(ε) = {ε}
sind disjunkt!
• <negation>
First(NOT) = {NOT},
First(ε) = {ε}
sind disjunkt!
• <weitereOperatoren>
First(, <operator>) = {,},
First(ε) = {ε}
sind disjunkt!
• <operatorTyp1>
First(<groessenOperator>) = {>=, >, <, <=},
First(<zuweisungsOperator>) = {=}
sind disjunkt!
• <firstElement>
First(sch(<variableRelation>)) = {sch},
First(attr(<variableBedingung>)) = {attr}
sind paarweise disjunkt!
• <element>
First(sch(<variableRelation>)) = {sch},
First(attr(<variableBedingung>)) = {attr},
First(<variableAttributmenge>) = {a,... ,z},
First(null) = {null}
sind paarweise disjunkt!
B.2. BEWEIS LL(1)-BEDINGUNG
• <berechnung>
First(<mengenOperator> <element>) = {union, intersect},
First(ε) = {ε}
sind disjunkt!
• <operator>
First(<vergleichsOperator>) = {==, ! =},
First(<teilmengenOperator>) = {] =, ], [, [=},
First(<zuweisungsOperator>) = {=}
sind paarweise disjunkt!
• <mengenOperator>
First(union) = {union},
First(intersect) = {intersect}
sind disjunkt!
• <multiplikator>
First(<zahl> ∗) = {0,... ,9},
First(ε) = {ε}
sind disjunkt!
• <kostenfunktion>
First(card(<variableRelation>)) = {card},
First(page(<variableRelation>)) = {page},
First(sel(<variableBedingung>, <variableRelation>)) = {sel},
First(cost(<baum>)) = {cost}
sind paarweise disjunkt!
• <baum>
First(left) = {left},
First(right) = {right}
sind paarweise disjunkt!
• <existenzfunktion>
First(sorted(<variableAttributmenge>,<variableRelation>)) = {sorted},
First(index supported(<variableAttributmenge>,<variableRelation>))
= {index supported},
First(merge supported(<variableAttributmenge>,<variableRelation>,
<variableRelation>)) = {merge supported}
sind paarweise disjunkt!
• <vergleichsOperator>
First(==) = {==},
First(! =) = {! =}
85
86
ANHANG B. REGELBEDINGUNGSPARSER
sind disjunkt!
• <teilmengenOperator>
First(] =) = {] =},
First(]) = {]},
First([) = {[},
First([=) = {[=}
sind paarweise disjunkt!
• <groessenOperator>
First(>=) = {>=},
First(>) = {>},
First(<) = {<},
First(<=) = {<=}
sind paarweise disjunkt!
• <buchstabe>
First(a) = {a}, ... , First(z) = {z}
sind paarweise disjunkt!
• <grossBuchstabe>
First(A) = {A}, ... , First(Z) = {Z}
sind paarweise disjunkt!
• <term>
First(0<followTerm>) = {0}, ... , First(9<followTerm>) = {9},
First(a<followTerm>) = {a}, ... , First(Z<followTerm>) = {Z}
sind paarweise disjunkt!
• <followTerm>
First(<term>) = {0, ... , 9, a, ... , Z},
First(ε) = {ε}
sind disjunkt!
• <zahl>
First(0<follow>) = {0}, First(1<follow>) = {1},
First(2<follow>) = {2}, First(3<follow) = {3},
First(4<follow>) = {4}, First(5<follow>) = {5},
First(6<follow>) = {6}, First(7<follow>) = {7},
First(8<follow) = {8}, First(9<follow>) = {9}
sind paarweise disjunkt!
• <follow>
First(<zahl>) = {0, ... , 9},
B.2. BEWEIS LL(1)-BEDINGUNG
87
First(ε) = {ε}
sind disjunkt!
2. Bedingung
• <verknüpfung>
Follow(<verknüpfung>) = {$},
First(AND <funktion> <verknüpfungAND>) = {AND},
First(OR <funktion> <verknüpfungOR>) = {OR},
First(- <funktion> <verknüpfungMINUS>) = {-},
sind paarweise disjunkt!
• <verknüpfungAND>
Follow(<verknüpfungAND>) = {$},
First(AND <funktion> <verknüpfungAND>) = {AND}
sind disjunkt!
• <verknüpfungOR>
Follow(<verknüpfungOR>) = {$},
First(OR <funktion> <verknüpfungOR>) = {OR}
sind disjunkt!
• <verknüpfungMINUS>
Follow(<verknüpfungMINUS>) = {$},
First(- <funktion> <verknüpfungMINUS>) = {-}
sind disjunkt!
• <negation>
Follow(<negation>) = {a,... , z, sorted, index supported, merge supported},
First(NOT) = {NOT}
sind disjunkt!
• <weitereOperatoren>
Follow(<weitereOperatoren>) = {}},
First(, <operator>) = {,}
sind disjunkt!
• <berechnung>
Follow(<berechnung>) = {==, ! =, =, ] =, ], [, [=, $},
First(<mengenOperator> <element>) = {union, intersect}
sind disjunkt!
• <multiplikator>
Follow(<multiplikator>) = {card, page, sel, cost},
88
ANHANG B. REGELBEDINGUNGSPARSER
First(<zahl> ∗) = {0,... , 9}
sind disjunkt!
• <followTerm>
Follow(<followTerm>) = {,, ), =, ==, !=, -, AND, OR, union, intersect,
]=, ], [, [=},
First(<term>) = {0, ... , 9, a, ... , Z}
sind paarweise disjunkt!
• <follow>
Follow(<follow>) = {∗},
First(<zahl>) = {0, ... , 9}
sind paarweise disjunkt!
Abbildungsverzeichnis
1.1
1.2
1.3
1.4
Übersetzung einer Anfrage in einen Ausführungsplan
Vereinfachter Aufbau eines Parsers . . . . . . . . . .
Aufbau eines Einphasenoptimierungssystems . . . .
Beispiel für eine Termersetzungsregel . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
2.1
Darstellung einer Relation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3.1
Termersetzungsregel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
4.1
4.2
4.3
4.4
Regelseiten und Ausführungsplan .
Setzen der Variablen . . . . . . . .
Verteilung der variablen Werte und
Baumdurchlauf . . . . . . . . . . .
5.1
Attributwertanzahlen Selektion . . . . . . . . . . . . . . . . . . . . . . . . . . 42
6.1
6.2
6.3
6.4
6.5
6.6
6.7
6.8
6.9
Hauptfenster der Einphasenoptimierung . . . . . . . . . . .
Laden einer Regel . . . . . . . . . . . . . . . . . . . . . . . .
Erzeugen einer Regel . . . . . . . . . . . . . . . . . . . . . .
Hilfedialog . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Baumansicht . . . . . . . . . . . . . . . . . . . . . . . . . .
Paketstruktur . . . . . . . . . . . . . . . . . . . . . . . . . .
Klassendiagramm dbs.unihannover.sopt.struc.general.tree
Klassendiagramm arithmetische Terme . . . . . . . . . . . .
Klassendiagramm Parametrisierungen . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
Erzeugen eines neuen Baumes
. . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. 9
. 9
. 10
. 11
.
.
.
.
.
.
.
.
.
.
.
.
.
38
38
38
40
55
56
57
57
58
59
64
65
65
A.1 Beispiel für einen Ableitungsbaum . . . . . . . . . . . . . . . . . . . . . . . . 80
89
90
ABBILDUNGSVERZEICHNIS
Tabellenverzeichnis
2.1
2.2
2.3
2.4
2.5
Bezeichnungen für Scanoperatoren . .
Bezeichnungen für Mengenoperatoren
Bezeichnungen für Verbundoperatoren
Erzeugende Operatoren . . . . . . . .
Vereinfachung von Operationen . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
14
14
15
17
22
4.1
4.2
4.3
4.4
4.5
4.6
4.7
Pipelining bei Projektionen und Selektionen . . . . . .
Pipelining bei einer Aggregation . . . . . . . . . . . .
Pipelining bei einem Join . . . . . . . . . . . . . . . .
Pipelining bei Semijoins . . . . . . . . . . . . . . . . .
Pipelining bei Outerjoins . . . . . . . . . . . . . . . .
Äquivalente Darstellungsformen für Mengenoperatoren
Pipelining bei Vereinigungen . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
35
35
36
36
37
37
37
5.1
5.2
5.3
5.4
5.5
5.6
5.7
5.8
5.9
5.10
5.11
5.12
5.13
5.14
5.15
5.16
5.17
5.18
5.19
5.20
Selektivität von Prädikaten . . . . . . . . . . . . . . . .
Schätzungen für Attributwertanzahlen einer Selektion .
Attributwertanzahlen Aggregation . . . . . . . . . . . .
Attributwertanzahlen Outerjoins . . . . . . . . . . . . .
Schätzungen für Attributwertanzahlen . . . . . . . . . .
Darstellungen für Mengenoperatoren . . . . . . . . . . .
Kardinalitäten und Pagegrößen . . . . . . . . . . . . . .
Kardinalitäten und Pagegrößen von Outerjoins . . . . .
Kardinalität und Pagegröße einer Aggregation . . . . . .
Selektionskosten . . . . . . . . . . . . . . . . . . . . . .
Projektionskosten . . . . . . . . . . . . . . . . . . . . . .
Kosten einer Aggregation ohne Gruppierung . . . . . . .
Kosten einer Aggregation mit Gruppierung . . . . . . .
Kosten eines Joins . . . . . . . . . . . . . . . . . . . . .
Kosten eines Anti-/Semijoins . . . . . . . . . . . . . . .
Kosten einer Vereinigung . . . . . . . . . . . . . . . . .
Kosten für die Erzeugung von Indexen und Sortierungen
Geclusterte Indexe Teil 1 . . . . . . . . . . . . . . . . .
Geclusterte Indexe Teil 2 . . . . . . . . . . . . . . . . .
Metadaten . . . . . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
41
42
43
43
44
44
45
46
46
47
47
48
48
49
50
51
51
51
52
52
91
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
92
TABELLENVERZEICHNIS
Literaturverzeichnis
[Mak03]
M. E. Makoui: Heuristische Anfrageoptimierungen in Relationalen Datenbanken.
Diplomarbeit, Institut für Informationssysteme, Universität Hannover, 2003
[War03]
H. Warnecke: Erweiterung eines Simulators für relationale Anfrageoptimierungen. Bachelorarbeit, Institut für Informationssysteme, Universität Hannover,
2003
[Die03]
M. Diehle: Erweiterung eines relationalen Anfragesimulators für eine regelbasierte Steuerung von physischen Optimierungsregeln. Bachelorarbeit, Institut für
Informationssysteme, Universität Hannover, 2003
[Zle05]
A. Zlenko: Implementierung von Termersetzungsregeln zur regelgesteuerten Anfragteoptimierung in relationalen Datenbanken. Studienarbeit, Institut für Informationssysteme, Universität Hannover, 2005
[Rud05]
R. Rudnicki: Entwicklung eines bedingten Termersetzungssystems und Umsetzung von Optimierungsregeln für den Anfragesimulator RELOpt. Bachelorarbeit,
Institut für Informationssysteme, Universität Hannover, 2005
[Mak05]
M. E. Makoui: Anfrageoptimierung in objektrelationelen Datenbanken mit bedingten Termersetzungen. Grundlagen von Datenbanken 2005: 83-88
[Par04]
R. Parchmann: Programmiersprachen und Übersetzer. Skript zur Vorlesung. Universität Hannover, 2004
[Lip04]
U. Lipeck: Datenbanksysteme I. Skript zur Vorlesung. Universität Hannover,
2004
[Bart03]
R. Barton: Verfahren der Technischen Informatik-Hilfeseiten zum Übungsbetrieb.
Universität Hannover, 2003
93
94
LITERATURVERZEICHNIS
Herunterladen