SQL-Rekursions-Tool - Institut für Informatik

Werbung
Leopold-Franzens-Universität Innsbruck
Institut für Informatik
Datenbanken und Informationssysteme
SQL-Rekursions-Tool
Bachelor-Arbeit
Lucas Ohlicher
betreut von
Michael Tschuggnall, PhD
Univ.-Prof. Dr. Günther Specht
Innsbruck, 20. Juni 2016
Zusammenfassung
Rekursionen sind ein mächtiges und weitverbreitetes Werkzeug in der Informatik und sind teilweise unabdingbar, um gewisse Problemstellungen
zu lösen. Genauso verhält es sich in Sprachen für relationale Datenbanken, wie zum Beispiel SQL. Da SQL als deklarative Sprache keine Schleifenkonstrukte unterstützt, muss für einige Anfragen auf eine Rekursion
zurückgegriffen werden. Rekursionen für einfache Probleme fallen dabei
meist schon relativ komplex aus und wirken für Studenten oftmals unverständlich. Auch gibt es kaum Möglichkeiten, Rekursionen gezielt zu
üben oder verständlicher zu veranschaulichen.
In dieser Arbeit wird ein webbasiertes Tool vorgestellt, mit dem Studenten SQL-artige Abfragen durchführen können. Dabei werden ein Großteil der wichtigsten Funktionen und Befehle, inklusive der Rekursion,
aus SQL unterstützt. Diese wird zusätzlich in einer eigenen Darstellung interaktiv präsentiert, die es dem Benutzer ermöglicht, sich Schritt
für Schritt durch die einzelnen Abschnitte einer rekursiven Abfrage zu
klicken.
Abstract
Recursions are a powerful and commonly used method in computer
science and can sometimes be necessary to solve certain problems. This
also applies to languages that are used in relational databases, such as
SQL. This is the case because it does not support loop constructs and
therefore queries can sometimes only be solved by recursion. Even for
simple problems the resulting query can be rather complex and furthermore for students hard to understand. Moreover, there hardly exist any
means to explicitly practice recursions or display it in a more understandable way.
In this thesis a web-based tool was developed, that allows students to
make SQL-like queries. Most of the important functions and commands
of SQL, including the recursion, are supported. Additionally, the recursion is presented interactively to allow the user to go through the different
parts of a recursive query step by step.
Inhaltsverzeichnis
1 Einleitung
1.1 Rekursion in der Informatik
1.2 Bedeutung der Rekursion in
1.3 Funktionsweise in SQL . . .
1.4 Sinn des Übungstools . . . .
. . . . . . . . . . . . . .
der relationalen Algebra
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
2 Übungstool
2.1 Vorüberlegungen . . . . . . . . .
2.2 Graphische Benutzeroberfläche .
2.3 Parsen von SQL mit PEG.js . . .
2.4 Ausführung von SQL mit SQLike
2.5 Unterstützte Funktionen . . . . .
2.6 Browserkompatibilität . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
1
4
4
9
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
11
11
13
17
19
22
24
3 Rekursive Anfragen im Tool
27
3.1 Erkennung rekursiver Anfragen . . . . . . . . . . . . . . . 27
3.2 Ausführung rekursiver Anfragen . . . . . . . . . . . . . . 28
3.3 Interaktive Darstellung der Rekursion . . . . . . . . . . . 29
4 Zusammenfassung
34
Literaturverzeichnis
38
III
Kapitel 1
Einleitung
Dieses Kapitel dient als Einleitung zum Thema Rekursion in der Informatik. Dabei wird die Funktionalität der Rekursion allgemein erklärt
und es wird auf ihre Einsatzgebiete hingewiesen. Danach wird speziell auf die Rekursion in SQL weiter eingegangen. Am Ende des Kapitels
wird noch erläutert, warum Rekursionen für Neueinsteiger in diesem Gebiet oftmals schwer verständlich und auch, wie dieses Tool dabei helfen
soll.
1.1
Rekursion in der Informatik
In der Informatik gibt es grundsätzlich zwei Ansätze, um eine Aufgabe zu
lösen, die es erfordert, gewisse Schritte beliebig zu wiederholen: Iteration
oder Rekursion. Der iterative Ansatz nutzt verschiedene Schleifenkonstrukte der verwendeten Programmiersprache, um Codeteile so lange zu
wiederholen, bis das gewünschte Ergebnis erreicht wird. Eine rekursive Lösung benötigt keine Schleifenkonstrukte, sondern besteht meistens
aus einer Funktion, die sich wiederholt selbst aufruft, bis eine gewisse
Abbruchbedingung erreicht wird. Das einfachste Beispiel, um Rekursion
und Iteration zu vergleichen, ist die Berechnung der Fakultät. Diese ist
definiert als
n
Y
n! = 1 · 2 · 3 · . . . n =
k
k=1
und ist für negative oder nicht ganze Zahlen nicht definiert. Die Fakultät
lässt sich auch rekursiv definieren:
(
1,
n=0
n! =
n · (n − 1)!, n > 0
Diese Definitionen lassen sich leicht als iterative beziehungsweise rekursive Funktionen implementieren.
1
KAPITEL 1. EINLEITUNG
Listing 1.1: Fakultät iterativ und rekursiv
f u n c t i o n f a c t o r i a l I t e r a t i v e (n){
var r e s u l t = 1 ;
f o r ( var i = 1 ; i <= n ; i ++)
r e s u l t ∗= i ;
return r e s u l t ;
}
f u n c t i o n f a c t o r i a l R e c u r s i v e (n){
i f ( n == 0 )
return 1;
else
r e t u r n n∗ f a c t o r i a l R e c u r s i v e ( n −1);
}
Diese Beispiele beinhalten keine Fehlerbehandlung, die ungültige (nicht
definierte) Werte abfangen soll, sondern nur die Berechnung gültiger
Eingaben. Der Unterschied der beiden Ansätze wird gleich sichtbar: Die
iterative Implementierung durchläuft eine Schleife von 1 bis n und berechnet damit das Ergebnis. Die Rekursion hingegen teilt sich in zwei
Fälle auf, die Abbruchbedingung und die eigentliche Rekursion. Die
Funktion ruft sich solange selbst mit dem Wert n-1 auf, bis die Abbruchbedingung n = 0 erreicht wird. Dann werden die Ergebnisse für
alle Funktionen zurückgegeben und dabei das Endergebnis berechnet.
Die Frage, ob ein Problem nun iterativ oder rekursiv gelöst werden soll,
kann nicht universell beantwortet werden, sondern hängt von mehreren
Faktoren ab:
• Verwendete Programmiersprache
Einige (Programmier-)Sprachen definieren keine Schleifenkonstrukte, sondern verlassen sich einzig auf die Rekursion, wenn wiederholt Code aufgerufen werden soll. Logische oder funktionale Programmierung beschränkt sich oftmals nur auf die Verwendung von
Rekursionen. Die relationale Algebra, die theoretische Basis von
SQL, unterstützt ebenfalls keine Schleifenkonstrukte.
• Formulierung der Problemstellung
Bereits anhand der Problemstellung kann oft festgestellt werden,
ob eine Umsetzung in iterativer oder rekursiver Form sinnvoller
ist. Falls ein Problem bereits in rekursiver Form formuliert ist, ist
im Normalfall auch die Umsetzung als Rekursion einfacher. Des
Weiteren kann auch die verwendete Datenstruktur zur Entscheidung herangezogen werden, da sich verschiedene Datenstrukturen
unterschiedlich gut für die beiden Ansätze eignen. So lassen sich
2
Lucas Ohlicher
KAPITEL 1. EINLEITUNG
Baumstrukturen, zum Beispiel Binärbäume oder Dateisysteme, rekursiv um einiges einfacher und intuitiver durchlaufen. Weitere
typische Beispiele für die Verwendung von Rekursionen sind das
Parsen von Sprachen1 oder verschiedene Sortieralgorithmen basierend auf dem Divide-and-Conquer-Prinzip2 , wie zum Beispiel
Quicksort oder Mergesort.
• Performanz und Speicherverbrauch
Performanz und Speicherverbrauch unterscheiden sich meistens
kaum bei beiden Ansätzen, aber sollten dennoch beachtet werden. Eine Rekursion benötigt mehr Speicher und ist im Allgemeinen langsamer als eine Iteration, da die Funktionsaufrufe auf dem
Stack gespeichert werden müssen, um das Ergebnis zurückgeben
zu können. Dabei kann es bei zu vielen Funktionsaufrufen auch zu
einem Stack Overflow kommen, der das Programm zum Absturz
bringen kann. Allerdings können Rekursionen, sofern es die Programmiersprache unterstützt, optimiert werden. Eine Möglichkeit
zur Optimierung ist es, die Rekursion als sogenannte Endrekursion zu schreiben. Dabei ist der letzte Befehl den die Funktion
ausführt, das rekursive Aufrufen der Funktion. Dadurch kann die
Rekursion vom Compiler als iterativ interpretiert werden. In Listing 1.2 wird die rekursive Berechnung der Fakultät (siehe Listing
1.1) in eine endrekursive Variante umgebaut. Durch das Einführen
einer Hilfsfunktion wird die nachträgliche (beim Zurückgeben der
Ergebnisse) Multiplikation vermieden.
Listing 1.2: Fakultät endrekursiv
func tion helper (n , f a c ){
i f ( n == 0 )
return fac ;
else
r e t u r n h e l p e r ( n−1, f a c ∗n ) ;
}
f u n c t i o n f a c t o r i a l E n d R e c u r s i v e (n){
return helper (n , 1 ) ;
}
Letztlich muss für jede Problemstellung abgewogen werden, ob diese
1
https://en.wikipedia.org/wiki/Recursive_descent_parser, zuletzt besucht
am 08.06.2016
2
https://en.wikipedia.org/wiki/Divide_and_conquer_algorithms,
zuletzt
besucht am 08.06.2016
Lucas Ohlicher
3
KAPITEL 1. EINLEITUNG
iterativ, falls Schleifenkonstrukte unterstützt werden, oder rekursiv umgesetzt werden soll.
1.2
Bedeutung der Rekursion in der relationalen Algebra
Die relationale Algebra ist die theoretische Grundlage für Abfragesprachen in relationalen Datenbanken. SQL ist eine relational vollständige
Sprache, das bedeutet, es werden alle Operationen der relationalen Algebra implementiert. Allerdings gibt es Abfragen, die mit der relationalen Algebra nicht durchgeführt werden können. Es ist zum Beispiel
nicht möglich, die transitive Hülle einer Relation zu berechnen. Die Berechnung dieser Hülle ist für rückbezügliche Relationen notwendig. Diese
finden in verschiedensten Formen ihre Anwendung, zum Beispiel zur Berechnung von Vorfahren basierend auf einer Eltern-Kind-Relation oder
zur Wegberechnung.
1.3
Funktionsweise in SQL
Mit SQL:19993 wurden Common Table Expressions (CTE) dem SQL
Standard hinzugefügt. Eine CTE ist ein temporäres Set, welches das
Ergebnis einer Query enthält. Auf dieses Set kann dann in einer Query
referenziert werden beziehungsweise kann es auch auf sich selbst referenzieren. Mit Hilfe dieser Selbstreferenz ist es möglich, rekursive Abfragen
durchzuführen. Eine Abfrage wird als rekursiv bezeichnet, wenn sie eine
rekursive CTE referenziert. Die Funktionsweise von rekursiven Abfragen
wird unter anderem auch in [KE13] oder [Vos08] behandelt.
1.3.1
Struktur einer rekursiven CTE
Der folgende Abschnitt orientiert sich inhaltlich an der offiziellen Dokumentation von Microsoft (SQL Server 2008 R2)[MSS].
Die Struktur einer rekursiven CTE ist ähnlich zu rekursiven Routinen in
anderen Programmiersprachen. Mit dem Unterschied, dass eine rekursive CTE mehrere Zeilen als Ergebnis zurückgeben kann anstatt eines
einzelnen Wertes.
Eine rekursive CTE besteht aus drei Elementen:
1. Aufruf der Routine
Der erste Aufruf der rekursiven CTE besteht aus einer oder mehreren CTE query definitions verbunden mit UNION ALL, UNION, EXCEPT, oder INTERSECT Operatoren. Das Ergebnis die3
4
https://en.wikipedia.org/wiki/SQL:1999, zuletzt besucht am 08.06.2016
Lucas Ohlicher
KAPITEL 1. EINLEITUNG
ser Query-Definitionen bildet die Basis der CTE-Struktur und wird
als anchor oder Initialisierung bezeichnet.
2. Rekursiver Aufruf der Routine
Ein Aufruf beinhaltet eine oder mehrere CTE query definitions
verbunden mit UNION ALL Operatoren, welche die CTE selbst
referenzieren. Diese werden als recursive members oder als Schrittfall bezeichnet.
3. Abbruchbedingung
Eine Abbruchbedingung ist bereits beinhaltet. Die Rekursion stoppt, wenn ein Aufruf keine Zeilen als Ergebnis zurückgibt. Dabei
muss beachtet werden, dass die Rekursion nicht in eine Endlosschleife gerät. Dies kann zum Beispiel auftreten, wenn die Daten
auf eine bestimmte Abfrage einen Zykel enthält und somit immer
ein Ergebnis liefert. Falls dies eintritt, muss die Abfrage manuell
auf eine maximale Anzahl an Rekursionsschritten eingeschränkt
werden.
1.3.2
Pseudocode und Semantik
Eine rekursive CTE muss mindestens einen anchor (Initialisierung) und
einen recursive member (Schrittfall) enthalten. Folgendes Beispiel beinhaltet jeweils eine Initialisierung sowie einen Schrittfall.
1 WITH cte name ( column name [ , . . . n ] ) AS
2 (
3 C T E q u e r y d e f i n i t i o n −− I n i t i a l i s i e r u n g
4 UNION ALL
5 C T E q u e r y d e f i n i t i o n −− S c h r i t t f a l l
6 )
7 SELECT ∗ FROM cte name −− A b f r a g e d e r CTE
Die Semantik der rekursiven Ausführung lässt sich folgendermaßen erläutern:
1. Aufteilen der CTE in Initialisierung und Schrittfall.
2. Ausführen der Initialisierung ergibt das Basisergebnis (T0 ).
3. Ausführen der Schrittfälle mit Ti als Input und Ti+1 als Output.
4. Wiederholen von Schritt 3 bis ein leeres Ergebnis zurückgegeben
wird.
5. Zurückgeben der Ergebnismenge. Diese ist eine Vereinigung (UNION ALL) von T0 bis Tn .
Lucas Ohlicher
5
KAPITEL 1. EINLEITUNG
abflug
Wien
Frankfurt
Wien
Wien
Venedig
Wien
Zürich
Paris
Madrid
ankunft
Frankfurt
London
Budapest
Venedig
Rom
Zürich
Paris
Madrid
Lissabon
Tabelle 1.1: Beispieltabelle flug
1.3.3
Beispiel
Im folgenden wird die Funktionsweise einer rekursiven CTE an einem
praktischen Beispiel gezeigt. Es basiert auf einer Datenbank bestehend
aus einer Tabelle flug (siehe Tabelle 1.1), die Flugverbindungen beinhaltet. Es sollen alle Flughäfen berechnet werden, die von Wien beginnend
mit beliebig vielen Zwischenschritten erreichbar sind.
1
2
3
4
5
6
7
8
9
WITH ReichweiteWien ( a b f l u g , a n k u f t ) AS
(
SELECT ∗ FROM f l u g WHERE a b f l u g=”Wien”
UNION ALL
SELECT ∗ FROM s t e p . a b f l u g , s t e p . a n k u n f t
FROM ReichweiteWien as r
JOIN f l u g as s t e p ON ( r . a n k u n f t = s t e p . a b f l u g )
)
SELECT ∗ FROM ReichweiteWien
1. Die rekursive CTE ReichweiteWien definiert jeweils eine Initialisierung sowie einen Schrittfall.
2. Die Initialisierung liefert das Basisergebnis T0 . Es beinhaltet alle
Flughäfen, die direkt mit Wien verbunden sind (siehe Tabelle 1.2).
3. Der Schrittfall liefert die weiteren direkten Verbindungen zurück,
die aus dem Ergebnis der Initialisierung hervorgehen. Mit anderen
Worten, die von Wien aus mit einem Zwischenschritt erreichbaren
Flughäfen. Man erhält dieses Ergebnis mit einer Join-Operation
zwischen flug und der CTE ReichweiteWien. Dabei wird die eigentlich Rekursion ausgeführt. Die Verbindungen der CTE ReichweiteWien als Input (Ti ) verknüpft mit dem Join r.ankunft =
6
Lucas Ohlicher
KAPITEL 1. EINLEITUNG
abflug
Wien
Wien
Wien
Wien
ankunft
Frankfurt
Budapest
Venedig
Zürich
Tabelle 1.2: Initialisierung
abflug
Wien
Wien
Wien
Wien
Frankfurt
Venedig
Zürich
ankunft
Frankfurt
Budapest
Venedig
Zürich
London
Rom
Paris
Tabelle 1.3: Zwischenergebnis nach dem 1. Iterationsschritt
step.abflug ergibt als Output (Ti+1 ) die Flughäfen, die von Ti direkt erreichbar sind. Das Ergebnis der ersten Iteration ist in Tabelle 1.3 dargestellt.
4. Der Schrittfall wird wiederholt ausgeführt. Die zweite Iteration
benutzt nun das Ergebnis der ersten Iteration (siehe Tabelle 1.3),
die dritte Iteration das Ergebnis der zweiten und so weiter. Dies
wird so lange fortgesetzt, bis kein neues Ergebnis zurückgegeben
wird (siehe Tabelle 1.4).
5. Das Endergebnis ist eine Vereinigung aller Ergebnisse der durchgeführten Iterationsschritte (siehe Tabelle 1.5).
1.3.4
Logisches Ausführungsmodell
Generell wird eine Rekursion als Schleife umgesetzt, welche solange ausgeführt wird, bis sich das Ergebnis nicht mehr ändert. Dabei gibt es
abflug
Paris
ankunft
Madrid
(a) 2. Iterationsschritt
abflug
Madrid
ankunft
Lissabon
(b) 3. Iterationsschritt
Tabelle 1.4: Weitere Zwischenergebnisse
Lucas Ohlicher
7
KAPITEL 1. EINLEITUNG
abflug
Wien
Wien
Wien
Frankfurt
Venedig
Zürich
Paris
Madrid
ankunft
Frankfurt
Budapest
Venedig
London
Rom
Paris
Madrid
Lissabon
Tabelle 1.5: Endergebnis
grundsätzlich zwei verschiedene Ansätze, wie die entsprechende Iteration ausgeführt wird.
• Naive Iteration
Diese Variante berechnet solange Ergebnisse weiterer Schritte, bis
diese sich nicht mehr verändern. Anhand des eben ausgeführten
Beispiels ReichweiteWien lässt sich dieser Ansatz folgendermaßen
formulieren:
R1 := I n i t ; i :=1
repeat
Ri+1 := ( Ri ◦ V) ∪ Ri ;
u n t i l Ri+1 = Ri
Der erste Schritt (R1 ) ist in diesem Fall die Initialisierung. Für
die weiteren Schritte werden die gültigen Verbindungen (V) basierend auf dem aktuellen Ergebnis berechnet und schließlich damit
vereinigt. Ein Nachteil dieser Umsetzung ist, dass bereits bekannte Ergebnisse immer neu berechnet werden. Um dies zu umgehen,
wird für jeden Schritt jeweils nur ∆Vi neu berechnet.
• Semi-naive Iteration (oder Delta-Iteration)
In dieser Variante wird nicht das gesamte Ergebnis neu berechnet,
sondern immer nur die Teilergebnisse der jeweiligen Schritte (∆i ).
Diese beinhalten die Ergebnisse, die mit dem nächsten Schritt hinzugefügt werden.
R0 := ∅ ; ∆1 := I n i t ; i : = 1 ;
repeat
Ri := Ri−1 ∪ ∆i ;
∆i+1 := ( ∆i ◦ V) − Ri ;
i := i +1
u n t i l ∆i = ∅
8
Lucas Ohlicher
KAPITEL 1. EINLEITUNG
Mit Hilfe der semi-naiven Iteration können diese Berechnungen effizienter ausgeführt werden, da die selben Ergebnisse mit einem geringeren
Rechenaufwand erzielt werden. In SQL, und auch in diesem Übungstool,
wird das Prinzip der semi-naiven Iteration vor allem aus diesem Grund
verwendet.
1.3.5
Beispiel mit expliziter Abbruchbedingung
Rekursionen in SQL können auch vorzeitig abgebrochen werden, falls nur
eine bestimmte Anzahl von Iterationsschritten durchgeführt werden soll
oder eine Endlosschleife verhindert werden soll. Dies lässt sich erreichen,
indem der Schrittfall um eine Bedingung als Where-Statement erweitert
wird. Die Abfrage aus 1.3.3 könnte zum Beispiel abbrechen, sobald ein
beliebiger Flughafen erreicht wurde oder nachdem n Zwischenschritte
ausgeführt wurden. Um eine Rekursion anhand der Anzahl der Schritte
abzubrechen, muss der CTE eine weitere Spalte hinzugefügt werden, die
als Zähler der Schritte fungiert. In 1.3 wird die Rekursion ReichweiteWien so erweitert, dass sie nach drei Zwischenschritten abbricht.
Listing 1.3: Rekursion mit expliziter Abbruchbedingung
1
2
3
4
5
6
7
8
9
10
11
WITH ReichweiteWien ( s c h r i t t , a b f l u g , a n k u f t ) AS
(
SELECT 1 , a b f l u g , a n k u n f t FROM f l u g
WHERE a b f l u g=”Wien”
UNION ALL
SELECT r . s c h r i t t +1, s t e p . a b f l u g , s t e p . a n k u n f t
FROM ReichweiteWien as r
JOIN f l u g as s t e p ON ( r . a n k u n f t = s t e p . a b f l u g )
WHERE r . s c h r i t t < 3
)
SELECT ∗ FROM ReichweiteWien
1.4
Sinn des Übungstools
Rekursionen sind für viele Studenten anfangs schwer verständlich, besonders im Vergleich zu einer iterativen Lösung des selben Problems.
Das liegt oftmals daran, dass oft etwas Zeit benötigt wird, um mit Rekursionen vertraut zu werden und um ihre Eigenheiten zu verstehen.
Iterationen erscheinen einfacher und intuitiver, wenn keine rekursive
Definition des Problems vorliegt, die direkt übernommen werden kann.
Außerdem sind rekursive Programme für Anfänger meist schwieriger zu
lesen und zu verstehen, da diese kompakter ausfallen als ihr iteratives
Gegenstück. Diese Punkte gelten vor allem für imperative ProgrammierLucas Ohlicher
9
KAPITEL 1. EINLEITUNG
sprachen. Im Fall von SQL und anderen relational vollständigen Sprachen gibt es noch weitere Schwierigkeiten zu beachten. Zum Beispiel
arbeitet man mit vollständigen Tabellen, die zuerst entsprechend abgefragt und verbunden werden müssen, anstatt mit einfachen Werten und
Datenstrukturen. Ein weiteres Problem dabei ist es, die drei verschiedenen Abfragen, die für eine Rekursion mindestens notwendig sind, richtig
zu formulieren und die Joins korrekt auszuführen. Letztlich muss auch
eine Abbruchbedingung in den Schrittfall eingebaut werden, um eine
Endlosschleife zu vermeiden.
Rekursionen in SQL sind ein sehr mächtiges Werkzeug, welche mit einer
gewissen Anfangshürde verbunden sind. Dieses Tool soll dabei helfen,
diese Hürde (schneller) zu überwinden. Vor allem die grafische und interaktive Darstellung der Ergebnisse soll dazu führen, dass eine Rekursion in SQL nicht als ein unverständliches Gesamtkonstrukt betrachtet
werden sollte, sondern als eine Kombination ihrer Einzelteile, welche
meistens für sich selbst einfache SQL-Abfragen sind.
10
Lucas Ohlicher
Kapitel 2
Übungstool
Das folgende Kapitel befasst sich mit dem eigentlichen Übungstool. Zuerst werden Design-Entscheidungen diskutiert, die vor der Implementierung getroffen wurden, sowie deren Vor- und Nachteile hervorgehoben. Danach werden die wichtigsten Komponenten des Tools genauer
beschrieben und ihre Funktionalität anhand von Beispielen erklärt.
2.1
Vorüberlegungen
Die Grundidee dieses Tools ist es, ein webbasiertes Übungstool für Studenten zu erstellen, um ihnen die Rekursion in SQL näherzubringen.
Die Aufgabe ist es, eine Webseite zu erstellen, die in irgendeiner Form
fähig ist, SQL-Abfragen inklusive Rekursionen auszuführen. Zusätzlich
ist es wichtig, dass das Tool für den Benutzer möglichst einfach und ohne
größeren Aufwand bedienbar ist. Dabei stehen prinzipiell zwei verschiedene Herangehensweisen zur Auswahl, um das Ausführen von SQL im
Tool zu verwirklichen.
Eine Möglichkeit ist es, das Tool direkt mit einer fixen Datenbank zu
verbinden. Diese Datenbank würde bereits über notwendige Datensätze
verfügen und Abfragen werden direkt an die Datenbank weitergegeben
und von ihr ausgeführt. Somit würde das Tool praktisch als grafische
Datenbank-Schnittstelle, mit eventuell weiteren Funktionen, dienen. Der
Vorteil dieser Herangehensweise ist es, dass die Umsetzung sehr einfach
ist. Allerdings überwiegen dabei die Nachteile stark. Zuerst wird eine
Datenbank benötigt, die möglichst immer erreichbar ist - was nicht garantiert werden kann. Es kann zu Ausfällen oder auch zu Überlastungen,
zum Beispiel durch Endlosschleifen bei missglückten Rekursionen, kommen. Des Weiteren muss die Datenbank manuell mit Daten gefüllt werden und kann auch nur von einem Administrator erweitert werden, womit dem Benutzer nur wenige statische Datensätze zur Verfügung stehen
würden. Dieses Problem könnte behoben werden, indem es dem Benut11
KAPITEL 2. ÜBUNGSTOOL
zer ermöglicht wird, sich mit einer beliebigen, auch lokalen, Datenbank
zu verbinden und diese zu verwenden. Allerdings werden die vorher genannten negativen Eigenschaften lediglich an den Benutzer selbst weitergegeben.
Eine weitere Überlegung bei dieser Implementierung ist es, welches Datenbankmanagementsystem (DBMS) dabei zu verwenden wäre. Die SQL
am naheliegendste Open-Source-Alternative ist MySQL[MyS], welche
allerdings rekursive Anfragen nicht unterstützt. Andere Datenbankmanagementsysteme, die Rekursionen unterstützen, zum Beispiel PostgreSQL[PGS], würden zwar den Anforderungen entsprechen, aber weisen
oft Unterschiede zu SQL auf - vor allem, was die Syntax betrifft. Da das
Tool zur Übung von SQL ausgelegt wird, ist eine abweichende Syntax
nicht zielführend. Auch zu beachten ist, dass Anfragen an Datenbanken oft optimiert werden und dadurch Ergebnisse eventuell in anderer
Reihenfolge als erwartet ausgeben werden können. Hier wird ebenfalls
wieder auf den Übungszweck verwiesen - das Tool soll das Ergebnis
möglichst so ausgeben, wie es sich der Benutzer erwarten würde.
Die andere Möglichkeit zur Implementierung ist es, Abfragen direkt lokal im Browser auszuführen. Somit fallen alle Probleme weg, die mit der
Verwendung eines DBMS zusammenhängen, da zur Benutzung des Tools
lediglich ein Browser benötigt wird. Der Nachteil dabei ist vor allem der
höhere Programmieraufwand - das Tool selbst muss SQL-artige Abfragen parsen sowie auf Arrays ausführen können, inklusive der Rekursion.
Außerdem müssen dabei möglichst viele SQL-Befehle und -Funktionen
implementiert werden, damit sich das Tool für den Benutzer kaum von
einem echten DBMS in Sachen Funktionalität unterscheidet. Der größere
Programmieraufwand rechtfertigt sich vor allem durch die zusätzliche
Flexibilität, die damit erreicht wird. Da der Parser für die Eingabe im
Tool eigens dafür erstellt wird, kann sich die Syntax beliebig an SQL
orientieren und es können auch auf das Tool zugeschnittene Fehlermeldungen generiert werden. Außerdem ist es für den Benutzer möglich,
eigene Datensätze zu verwenden oder an andere weiterzugeben, da diese
lediglich in Textform vorliegen müssen, um für das Tool verwendbar zu
sein. Ein weiterer Punkt, der als Nachteil gesehen werden könnte, ist die
Performance des Tools. Die Ausführung von SQL-artigen Abfragen ist
definitiv langsamer und unoptimierter als die selben Abfragen auf einem
DBMS. Allerdings ist dies für das Tool kaum bis gar nicht relevant, da
es nicht für die Verwendung von großen Datensätzen gedacht ist. Es ist
aus übungstechnischen Gründen nicht sinnvoll, eine Datenbank mit einigen tausend Einträgen pro Tabelle zu verwenden, wenn beispielsweise
zwanzig Einträge pro Tabelle die selbe Wirkung erzielen. Mit kleinen
Datensätzen unterscheiden sich die Abfragezeiten kaum von denen eines
DBMS - besonders wenn zuvor erst eine Verbindung zu einer externen
12
Lucas Ohlicher
KAPITEL 2. ÜBUNGSTOOL
Datenbank hergestellt werden muss.
Aufgrund der vorher genannten Vor- und Nachteile beider Implementierung fiel die Wahl letztlich eindeutig auf eine komplett clientseitige Implementierung. Ein Übungstool sollte möglichst benutzerfreundlich sowie schnell und einfach zu bedienen sein. Der Benutzer soll nach
Aufruf der Seite ohne Aufwand eine Abfrage eingeben und ausführen
können. Dennoch steht es dem Benutzer bei Bedarf zur Verfügung,
eigene Datensätze zu verwenden, ohne sich direkt an den DatenbankAdministrator wenden zu müssen.
2.2
Graphische Benutzeroberfläche
Das Tool besteht aus wenigen, für den Benutzer sichtbaren, Einzelteilen
(Abbildung 2.1). Es gibt ein Menü zur Auswahl der zu verwendenden
Datenbank, einen Überblick über die aktuelle Datenbank (Tabellen- und
Spaltennamen mit Datentyp) sowie einen Editor, mit Syntax-Highlighting
für SQL, zur Eingabe von Abfragen. Im Hintergrund erfolgt das Parsen
der Eingabe und nach einem Klick wird, falls die Eingabe korrekt ist,
die Abfrage ausgeführt und das Ergebnis ausgegeben. Falls eine rekursive CTE als Eingabe erkannt wird, kann diese schrittweise ausgeführt
werden. Dieser Vorgang wird im nächsten Kapitel (3) genauer erläutert.
Abbildung 2.1: Benutzeroberfläche am Beginn; Überblick über aktuellen
Datensatz links, Editor rechts
2.2.1
Auswahl der Daten
Ein Basis-Datensatz wird beim Laden des Tools bereits ausgewählt und
ähnelt dem Datensatz, der in Beispiel 1.3.3 verwendet wird. Des Weiteren bietet der Button ’Select Database’ zwei Möglichkeiten, andere
Lucas Ohlicher
13
KAPITEL 2. ÜBUNGSTOOL
Datensätze zu laden. Zum einen können verschiedene vordefinierte Datensätze verwendet werden, die für alle Benutzer verfügbar sind. Diese liegen lokal vor und können in einem simplen Dropdown-Menü ausgewählt werden (siehe 2.2). Zum anderen können externe Datensätze
in Form von GitHub Gists1 geladen werden. Bereits vorhandene Gists
können per Eingabe der ID in das Tool geladen und werden temporär gespeichert. Das bedeutet, geladene Datensätze stehen solange zur Verfügung,
bis das Tool geschlossen wird.
Abbildung 2.2: Dropdown-Menü zur Datensatz-Auswahl
Erstellung eigener GitHub Gists
Mit GitHub Gists ist es möglich, eigene Datensätze für das Tool zu erstellen. Eine Gist-Datei enthält eine Datenbank in Textform, mit beliebig
vielen Tabellen und Einträgen. Der Benutzer wird allerdings angehalten, das Ausmaß einer solchen Datenbank klein zu halten (max. 100
Einträge), da das Tool nicht für große Datenmenge ausgelegt ist und
auch dem Hauptziel des Tools, dem Übungszweck, entgegenwirkt. Dabei
wird eine Syntax verwendet, die bereits in einem anderen Tool Verwendung findet: Einem Rechner für relationale Algebra, RelaX - relational
algebra calculator [Kes14]. Dadurch ist es möglich, Gist-Dateien in beiden Tools zu verwenden. Anhand eines Beispiel wird nun erläutert, wie
eine solche Datei aufgebaut ist.
group : Flug−Datenbank
d e s c r i p t i o n [ [ E i n f a c h e r B e i s p i e l d a t e n s a t z , [ Databases
and I n f o r m a t i o n Systems ] ( d b i s . uibk . ac . a t ) ] ]
example [ [
/∗ B e i s p i e l −Query 1∗/
SELECT ∗ FROM f l u g WHERE i d > 2 ;
/∗ B e i s p i e l −Query 2 ∗/
SELECT ∗ FROM f l u g h a f e n
]]
flug = {
a b f l u g : s t r i n g , a n k u n f t : s t r i n g , i d : number
’ VIE ’ , ’FRA’ , 0
’FRA’ , ’LHR’ , 1
1
14
https://gist.github.com/, zuletzt besucht am 08.06.2016
Lucas Ohlicher
KAPITEL 2. ÜBUNGSTOOL
’ VIE ’ , ’BUD’ , 2
}
flughafen = {
s t a n d o r t , land , i a t a , i d
’ Wien ’ , ’Ös t e r r e i c h ’ , ’ VIE ’ , 0
’ F r a n k f u r t ’ , ’ Deutschland ’ , ’FRA’ , 1
’ London ’ , ’ England ’ , ’LHR’ , 2
’ Budapest ’ , ’ Ungarn ’ , ’BUD’ , 3
}
• group
Mit diesem Attribut wird der Datensatz mit einem Titel versehen.
Dieser kann beliebige Zeichen enthalten und wird bis zum ersten
Zeilenumbruch eingelesen. Der Titel scheint nach dem Laden im
Dropdown-Menü zur Datenbankauswahl auf.
• description (optional)
Hier kann der Datensatz genauer beschrieben werden und es kann
auf etwaige Quellen verwiesen werden. Falls das Attribut einzeilig
ist, wird die selbe Syntax wie in group verwendet und es wird bis
zum ersten Zeilenumbruch eingelesen. Eine mehrzeilige Beschreibung ist mit Hilfe von doppelten Klammern ebenfalls möglich, wie
im Beispiel zu sehen. Die Beschreibung, wenn vorhanden, erscheint
im Tool unterhalb des Editors. Dieses Attribut unterstützt die
Markdown2 -Syntax um die Beschreibung direkt in äquivalenten
HTML-Code umzuwandeln. Falls keine Beschreibung verwendet
wird, kann das Attribut weggelassen werden.
• example (optional)
Mit diesem Attribut können Beispiel-Abfragen zum Datensatz hinzugefügt werden. Es wird die selbe Syntax wie für die mehrzeilige Beschreibung verwendet und es können beliebig viele Beispiele
angegeben werden. Mehrere Beispiele werden mittels Strichpunkt
getrennt. Diese Beispiele können nach dem Laden des Datensatzes
über Buttons unterhalb des Editors direkt eingefügt werden. Dabei
werden die Abfragen so übernommen, wie diese in der Gist-Datei
angegeben werden, inklusive aller Zeilenumbrüche und Tabulatoren.
• Datensätze
Am Ende folgt die Definition der Datensätze. Die Angabe von Tabellen ähnelt dem Zuweisen von Variablen in diversen Program2
https://de.wikipedia.org/wiki/Markdown, zuletzt besucht am 08.06.2016
Lucas Ohlicher
15
KAPITEL 2. ÜBUNGSTOOL
miersprachen. In diesem Fall wird der Name der Variable als Name der Tabelle verwendet und in den geschwungenen Klammern
werden die Einträge dieser Tabelle definiert. Dabei dient die erste
Zeile dazu, die Spalten nach dem Schema name : datentyp zu beschreiben. Die Angabe des Datentyps ist optional, wie im Beispiel
zu sehen, und dient hauptsächlich zur genaueren Beschreibung der
Spalten. Es werden nur string und number als Datentypen unterstützt, alles andere wird als String behandelt. In den weiteren
Zeilen folgen die eigentlichen Einträge, wobei Strings mit einfachen
Anführungszeichen versehen werden sollten.
Nach Erstellung einer Gist-Datei kann diese nun unter der Verwendung
der zugehörigen ID, zu finden am Ende der URL, ins Tool importiert
werden. Somit ist es auch möglich, Datensätze einfach mit anderen Benutzer zu teilen - es wird nur die ID einer gültigen Gist-Datei benötigt.
2.2.2
SQL-Editor
Der Editor wird mit Hilfe von CodeMirror[Cod] erstellt. CodeMirror ist
ein, in JavaScript implementierter, Texteditor, der sich auf das Bearbeiten von Code spezialisiert. Es stehen sogenannte Modes für diverse Programmiersprachen, wie zum Beispiel SQL, zur Verfügung. Des
Weiteren verfügt CodeMirror über eine umfangreiche API sowie CSSFunktionen, um den Editor an die Anwendung anzupassen. Für das
Tool selbst dient CodeMirror hauptsächlich als visuelle Unterstützung in
Form von Syntax-Highlighting und dem Anzeigen der Zeilennummern.
In den Editor eingegebene Abfragen werden in Echtzeit geparst, auf
korrekte Syntax und Semantik überprüft sowie gegebenenfalls ergänzt.
Dieser Vorgang erfolgt in drei Schritten:
1. Überprüfung der Syntax
Die Überprüfung der Syntax erfolgt mit Hilfe eines Parsers, der
eigens für das Tool erstellt wurde und auf dessen Anforderungen zugeschnitten wurde. Auf den Parser wird in einem späteren
Abschnitt genauer eingegangen (siehe Abschnitt 2.3). Liefert der
Parser keinen Fehler zurück, wird mit dem nächsten Schritt fortgesetzt, ansonsten wird eine entsprechende Fehlermeldung angezeigt.
2. Überprüfung der Semantik
Das Ergebnis des Parsers wird in diesem Schritt mit JavaScript
überprüft. Jede Tabelle oder Spalte, die in der Abfrage referenziert
wird, muss in der aktuellen Datenbank vorhanden sein. Falls alle
Tabellen und Spalten in der Datenbank gefunden werden, oder ein
entsprechender Alias in der Abfrage vorhanden ist, wird mit dem
nächsten Schritt fortgesetzt, ansonsten wird eine Fehlermeldung,
die auf die fehlerhafte Tabelle oder Spalte hinweist, angezeigt.
16
Lucas Ohlicher
KAPITEL 2. ÜBUNGSTOOL
3. Fehlende Tabellen ergänzen
Um dem Benutzer die Eingabe zu erleichtern, ist es nicht notwendig, Tabelle und Spalte immer exakt zu referenzieren. Falls eine
Spalte einer Tabelle eindeutig zugeordnet werden kann, wird diese
im Hintergrund zur Abfrage hinzugefügt. Existiert eine Spalte in
mehreren verwendeten Tabelle, kann diese nicht eindeutig zugeordnet werden und es wird eine Fehlermeldung angezeigt.
Nach erfolgreichem Abschluss dieser Schritte wird der Button unterhalb
des Editors aktiv und die Abfrage kann ausgeführt werden. Diese wird
direkt im Browser ausgeführt (siehe Abschnitt 2.4) und das Ergebnis bei
nicht-rekursiven Abfragen direkt unterhalb ausgegeben. Rekursive Abfragen werden speziell ausgegeben und werden in einem späteren Kapitel
genauer behandelt (siehe Kapitel 3).
Abbildung 2.3: Ergebnis einer nicht-rekursiven Abfrage
2.3
Parsen von SQL mit PEG.js
Da das Tool über keine Anbindung an eine Datenbank verfügt, erfolgt
das Parsen der Eingabe ebenfalls clientseitig im Browser. Sobald sich der
Inhalt des Editors verändert, wird versucht, diese Eingabe zu Parsen.
Dabei wird der Inhalt des Editors an eine Parsing-Funktion übergeben,
die mit Hilfe von PEG.js[Peg] erstellt wurde.
Lucas Ohlicher
17
KAPITEL 2. ÜBUNGSTOOL
2.3.1
PEG.js
PEG.js, entwickelt von David Majda, ist ein freier Parser-Generator für
JavaScript. Ein Parser kann per Kommandozeile oder mit Hilfe einer
JavaScript API generiert werden. Der Parser selbst ist ein JavaScript
Objekt mit einer einfachen API.
PEG.js basiert auf der parsing expression grammar (PEG)[For04]. PEG
gehört zum Typ der analytisch-formalen Grammatiken, das bedeutet,
eine formale Sprache wird mit Hilfe von Regeln zur String-Erkennung
beschrieben. Syntaktisch erinnern PEGs an kontextfreie Grammatiken
(CFG), werden aber unterschiedlich interpretiert. CFGs können mehrdeutig sein, während PEGs immer eindeutig sind. Wenn ein String erfolgreich geparst wird, gibt es genau einen gültigen Syntaxbaum. Daher
eignen sich PEGs sehr gut, um Computersprachen zu parsen, während
sich CFGs eher für natürliche Sprachen anbieten.
Mit PEG.js erstellte Grammatiken ähneln syntaktisch JavaScript, sind
nicht zeilenorientiert und ignorieren Leerzeichen zwischen Tokens. Außerdem ist es möglich, Kommentare wie in JavaScript zu verwenden. Auf
oberster Ebene befinden sich rules, die über einen Namen identifiziert
werden, und eine parsing expression, die das Muster definiert, nach dem
der Input-Text gematcht wird. Der Parsing-Ausdruck kann zusätzlich
JavaScript-Code enthalten, der ausgeführt wird, wenn das Muster erfolgreich gematcht wird. Zusätzlich kann vor der ersten Regel ein sogenannter initializer eingefügt werden. Dieser beinhaltet JavaScript-Code,
der vor dem eigentlichen Parsing-Prozess ausgeführt wird. Alle Variablen
und Funktionen, die im initializer definiert werden, können in den rules
und parsing expressions verwendet werden. Damit können Hilfsfunktionen und Variablen definiert werden, die den Parsing-Prozess vereinfachen sollen.
Der Parser dieses Tools basiert auf einem bereits vorhandenen SQLParser sql.pegjs3 . Dieser wurde an die Anforderungen des Tools angepasst und um Funktionen erweitert, insbesondere wurde die Syntax für
CTEs zum Parser hinzugefügt. Eine korrekte CTE nach diesen Regeln
besitzt im Allgemeinen die Form des Beispiels in 1.3.3. Mit dem Unterschied, dass die Query innerhalb der With-Klausel eine beliebige Form
haben kann und nicht zwingend rekursiv sein muss. Es muss beachtet
werden, dass die Anzahl der Attribute der CTE mit der Anzahl der
Spalten der inneren Query übereinstimmen müssen.
Falls die Eingabe korrekt ist, also der Parser nicht aufgrund eines Fehlers
abbricht, wird ein JavaScript-Objekt zurückgegeben, welches die geparste Abfrage enthält. Dieses Objekt wird in weiterer Folge verwendet, um
die Semantik zu überprüfen sowie die Abfrage auszuführen. Beispielswei3
18
https://github.com/alsotang/sql.pegjs, zuletzt besucht am 08.06.2016
Lucas Ohlicher
KAPITEL 2. ÜBUNGSTOOL
se liefert die Abfrage
SELECT ∗ FROM f l u g WHERE f l u g . a b f l u g=”Wien”
folgendes Objekt zurück:
[ [ { ” stmt ” : ” s e l e c t ” ,
” s e l e c t c o r e s ” : [ { ” r e s u l t s ” : [ { ” column ” : ” ∗ ” } ] ,
” from ” : [ { ” t a b l e ” : ” f l u g ” } ] ,
” where ” : [ { ” column ” : ” a b f l u g ” , ” t a b l e ” : ” f l u g ” } ,
”=” ,
{” l i t e r a l ” : [ ” \ ” ” , [ ”W” , ” i ” , ” e ” , ” n ” ] , ” \ ” ” ] } ] } ] } ]
2.4
Ausführung von SQL mit SQLike
Die Ausführung der Abfrage auf den Arrays wird ebenfalls gänzlich in
JavaScript, mit Hilfe von SQLike[Fra], verwirklicht. SQLike wurde von
Thomas Frank entwickelt und ist eine kleine Query-Engine für JavaScript und ActionScript. Ihre Syntax ist ähnlich zu SQL und kann verwendet werden, um Arrays von Objekten und Arrays von Arrays im Stile
von SQL abzufragen. SQLike beherrscht viele grundlegende Funktionen
und Befehle von SQL und musste für das Tool nur geringfügig erweitert
werden. Damit ist es auch möglich, im Select-Abschnitt der Abfrage zu
Rechnen beziehungsweise Strings zu konkatenieren. Um eine Abfrage
auszuführen, muss die entsprechende Funktion von SQLike aufgerufen
werden, und als Parameter ein Objekt mit der Query übergeben werden. Die Funktion liefert das Ergebnis der Query in Form eines Arrays
zurück.
2.4.1
SQLike Beispiele
Dieser Abschnitt zeigt anhand von Beispielen, wie Objekte für SQLikeQueries aufgebaut sind. Außerdem wird gezeigt, wie die neu hinzugefügten Funktionen verwendet werden. Dies ist lediglich eine kurze
Aufzählung und befasst sich vor allem mit Funktionen, die für rekursive Abfragen unbedingt notwendig sind. Eine Dokumentation mit allen
Funktionen ist auf der Webseite von SQLike zu finden4 .
Der Aufruf der Funktion SQLike.q(queryObject) führt die Abfrage, die
im Parameter als queryObject spezifiziert ist, aus. Die Aufgabe in der
Verwendung von SQLike besteht darin, ein solches Objekt zu erstellen.
Im Folgenden werden Beispiele gezeigt, wie diese aufgebaut werden.
4
http://www.thomasfrank.se/SQLike/, zuletzt besucht am 08.06.2016
Lucas Ohlicher
19
KAPITEL 2. ÜBUNGSTOOL
Listing 2.1: Datensätze in SQLike (Auszug)
var db = { } ;
db . f l u g =
[ { ” a b f l u g ” : ” VIE ” , ” a n k u n f t ” : ”FRA” , ” i d ” : 0 } ,
{” a b f l u g ” : ”FRA” , ” a n k u n f t ” : ”LHR” , ” i d ” : 1 } ,
{” a b f l u g ” : ” VIE ” , ” a n k u n f t ” : ”BUD” , ” i d ” : 2 } ] ;
db . f l u g h a f e n =
[ { ” l a n d ” : ” Ös t e r r e i c h ” ,
” s t a n d o r t ” : ” Wien ” ,
” i a t a ” : ” VIE ” ,
id :0} ,
{” l a n d ” : ” Deutschland ” ,
” standort ”:” Frankfurt ” ,
” i a t a ” : ”FRA” ,
id : 1 } ] ;
Listing 2.2: SQLike Select-From-Where
var q u e r y O b j e c t = { } ;
queryObject . S e l e c t = [ ’ ∗ ’ ] ;
q u e r y O b j e c t . From = db . f l u g ;
q u e r y O b j e c t . Where = f u n c t i o n ( ) { r e t u r n t h i s . i d > 2 ; } ;
var r e s u l t = SQLike . q ( q u e r y O b j e c t ) ;
In Listing 2.1 wird auszugsweise gezeigt, wie Datensätze in SQLike aufgebaut sind. Im Beispiel wird eine Datenbank db mit den Tabellen flug
und flughafen erstellt und je mit Einträgen versehen. In 2.2 wird die
Basis-Funktionalität von SQLike gezeigt. Es wird ein Objekt erstellt
und mit den notwendigen Attributen versehen. Das Select-Attribut ist
ein Array mit allen Spaltennamen, die im Ergebnis angezeigt werden
sollen beziehungsweise ein Stern für alle Spalten, wie in SQL. Das FromAttribut beinhaltet das Array oder das Array von Arrays, in dem die
Daten zu finden sind, im Beispiel wird lediglich die entsprechende Variable referenziert. Where-Bedingungen werden als JavaScript-Funktionen
übergeben und können damit auch sehr komplex werden. Dabei ist zu
beachten, dass JavaScript- und SQL-Syntax unterschiedlich sein kann zum Beispiel gilt && in JavaScript als logisches Und, während in SQL
dafür AND verwendet wird.
20
Lucas Ohlicher
KAPITEL 2. ÜBUNGSTOOL
Listing 2.3: SQLike Join mit On
var q u e r y O b j e c t = { } ;
queryObject . S e l e c t =
[ ’ flug abflug ’ , ’ flug ankunft ’ , ’ flughafen standort ’ ] ;
q u e r y O b j e c t . From = db . f l u g ;
q u e r y O b j e c t . J o i n = db . f l u g h a f e n ;
q u e r y O b j e c t . On = f u n c t i o n ( )
{ r e t u r n t h i s . f l u g . a b f l u g == t h i s . f l u g h a f e n . i a t a } ;
var r e s u l t = SQLike . q ( q u e r y O b j e c t ) ;
Listing 2.4: SQLike Join mit Using
var q u e r y O b j e c t = { } ;
queryObject . S e l e c t =
[ ’ flug abflug ’ , ’ flug ankunft ’ , ’ flughafen standort ’ ] ;
q u e r y O b j e c t . From = db . f l u g ;
q u e r y O b j e c t . J o i n = db . f l u g h a f e n ;
q u e r y O b j e c t . Using = [ ’ id ’ ] ;
var r e s u l t = SQLike . q ( q u e r y O b j e c t ) ;
Wie in den Listings 2.3 und 2.4 zu sehen, werden Joins ebenfalls über Attribute des Query-Objekts realisiert. Dazu wird der Join-Partner sowie
die Join-Bedingung in Form von Arrays angegeben, beziehungsweise im
Fall von Joins mit On als JavaScript-Funktion. Außerdem ist es möglich,
wie in SQL, einen impliziten Join auszuführen und die Join-Bedingung
in das Where-Attribut zu verschieben. Dabei entfällt auch das JoinAttribut und der Join-Partner wird in das From-Attribut als weiteres
Element aufgenommen.
Listing 2.5: SQLike Union All
var q u e r y O b j e c t = { } ;
var query1 = { } ;
var query2 = { } ;
query1 . S e l e c t = [ ’ ∗ ’ ] ;
query1 . From = db . f l u g ;
query1 . Where = f u n c t i o n ( ) { r e t u r n t h i s . a b f l u g==”VIE ” } ;
query2 . S e l e c t = [ ’ ∗ ’ ] ;
query2 . From = db . f l u g ;
query2 . Where = f u n c t i o n ( ) { r e t u r n t h i s . a b f l u g==”FRA” } ;
q u e r y O b j e c t . UnionAll = [ query1 , query2 ] ;
var r e s u l t = SQLike . q ( q u e r y O b j e c t ) ;
Lucas Ohlicher
21
KAPITEL 2. ÜBUNGSTOOL
Listing 2.5 zeigt die Funktionalität von Union All sowie anderer Mengenoperationen (Intersect, Except), die nach dem selben Schema durchgeführt werden. Dabei werden mehrere SQLike-Queries in einem Array
zusammengefasst und als entsprechendes Attribut hinzugefügt.
Listing 2.6: SQLike Funktionen
var q u e r y O b j e c t = { } ;
queryObject . S e l e c t =
[ ’ | concat | ’ , [ abflug , ” − ” , ankunft ] , ’ | as | ’ , ” Strecke ” ,
’ | calc | ’ , [ 2 , ’ ∗ ’ , id ] ] ;
q u e r y O b j e c t . From = db . f l u g ;
var r e s u l t = SQLike . q ( q u e r y O b j e c t ) ;
Die Verwendung von Funktionen wird in Listing 2.6 demonstriert. Dabei
wird im entsprechenden Select-Array zuerst die zu verwendende Funktion angegeben. Damit diese als Funktion von SQLike erkannt wird, wird
sie anhand von Pipes am Anfang beziehungsweise am Ende des Ausdrucks gekennzeichnet. Das nächste Element des Arrays beinhaltet das
Attribut bei Aggregat-Funktionen in Form einer Spalte oder einem Array von Attributen bei Rechnungen und String-Konkatenationen. Die
Alias-Funktion kann nach dem selben Prinzip sowohl in Select- als auch
From-Attributen verwendet werden.
2.5
Unterstützte Funktionen
Da das Tool lokal und ohne Datenbank im Hintergrund arbeitet, ist nicht
alles, was in SQL möglich ist, auch im Tool möglich. Im folgenden Abschnitt werden die vom Tool unterstützten SQL-Funktionen und Befehle
aufgelistet sowie falls notwendig, genauer beschrieben.
• Select (Distinct) - From - Where
• And und Or
• Order By
• Like
• Wildcards
• Between
• Not
• Aliases mit As
• Joins mit Using/On
22
Lucas Ohlicher
KAPITEL 2. ÜBUNGSTOOL
• Left/Right/Full/Cross/Natural Join
• Group By und Having
• Limit
• Union/Except/Intersect (All)
• Aggregat-Funktionen (Avg, Count, Min, Max, Sum)
• String-Konkatenation mit concat() in Select
• Grundrechnungsarten und Modulo in Select
• Einfach-rekursive Abfragen (siehe Kapitel 3)
• Kommentare
Vollständigkeitshalber folgt nun eine Aufzählung verschiedener Funktionen, die entweder aus technischen Gründen nicht implementiert wurden
oder aufgrund der Anforderungen des Tools nicht benötigt werden.
• Subqueries
• In
• Is (Not) Null
• Komplexere Datentypen (zum Beispiel Datum)
• Data Definition Language (zum Beispiel CREATE TABLE)
• Data Manipulation Language (zum Beispiel INSERT INTO oder
UPDATE)
2.5.1
Hinweise zur Verwendung
Um Unklarheiten zu vermeiden, werden einige Funktionen und Befehle
etwas genauer erläutert und anhand von Beispielen erklärt.
• Referenzierung
Es ist nicht notwendig, Tabellen immer exakt anzugeben. Falls
diese eindeutig sind, reicht der Name der Spalte. Der Parser wird
eine entsprechende Fehlermeldung ausgeben, falls eine Spalte nicht
eindeutig zugeordnet werden kann.
Lucas Ohlicher
23
KAPITEL 2. ÜBUNGSTOOL
• Joins
Joins jeglicher Art werden von links nach rechts ausgeführt. Dabei muss, vor allem bei Abfragen mit mehreren Joins, beachtet
werden, dass verschiedene Joins unter Umständen unterschiedliche
Spaltennamen zurückliefern können und somit ein falsches Ergebnis erzeugen können. Zum Beispiel liefert ein Join mit Using nur
eine Spalte zurück, während der selbe Join mit On zwei Spalten
mit dem jeweiligen Präfix der Tabelle zurück gibt. Es wird geraten,
verschiedene Join-Arten nach Möglichkeit nicht zu mischen.
• Group By und Having
Die Verwendung von Having ist nur in Kombination mit Group By
möglich und auch nur dann, wenn Having direkt auf den Group
By-Ausdruck folgt.
• String-Konkatenation mit concat()
Im Select-Teil der Abfrage kann eine Mischung aus Spalten und
Strings beliebig konkateniert werden. Dabei werden der concat()Funktion die Daten als Attribute übergeben.
SELECT
c o n c a t ( ” Das i s t Flug ” , id , ” nach ” , a n k u n f t )
FROM f l u g
• Grundrechnungsarten und Modulo in Select
Rechnungen mit Kommazahlen können direkt als Spalte angegeben werden. Alternativ können Rechnungen auch in eine calc()Funktion, ähnlich zu concat(), verschoben werden.
SELECT 2∗ i d FROM f l u g
SELECT c a l c (2+ i d ) FROM f l u g
• Kommentare
Kommentare können im Editor mit der selben Syntax verwendet
werden wie in SQL.
−− e i n z e i l i g e r Kommentar
/∗
m e h r z e i l i g e r Kommentar
∗/
2.6
Browserkompatibilität
Das Tool basiert komplett auf HTML, CSS und JavaScript und wurde
zum Großteil unter der Verwendung der Frameworks AngularJS[Ang]
24
Lucas Ohlicher
KAPITEL 2. ÜBUNGSTOOL
und Twitter Bootstrap[Boo] erstellt. Des Weiteren werden einige wichtige Funktionen mit Hilfe der JavaScript-Bibliothek jQuery[jQu] umgesetzt. Dadurch hängt die Kompatibilität des Tools vor allem mit der
Kompatibilität des Browsers mit AngularJS, Twitter Bootstrap und
jQuery zusammen.
AngularJS wird aktiv und weitreichend für die aktuellen Versionen von
Chrome, Firefox, Safari und Safari für iOS sowie Internet Explorer der
Versionen 9 bis 11 getestet. Für weitere Browser wird das Framework
zwar nicht aktiv unterstützt, aber es kann ebenfalls fehlerfrei funktionieren - allerdings ohne Garantie. Für weitere Informationen zur Kompatibilität von AngularJS siehe https://docs.angularjs.org/misc/faq.
Die Abbildungen 2.4 und 2.5 zeigen die jeweils von Twitter Bootstrap
beziehungsweise jQuery aktiv unterstützen Browser. Es ist möglich, dass
diese auf älteren Versionen oder anderen Browsern ebenfalls fehlerfrei
funktionieren, allerdings kann dies von den Herstellern nicht garantiert
werden.
Abbildung 2.4: Twitter Bootstrap: Unterstützte Browser. Quelle: http:
//getbootstrap.com/getting-started/#support
Abbildung 2.5: Aktiv unterstützte Browser von jQuery. Quelle: https:
//jquery.com/browser-support/
Wie zu erkennen ist, sind die jeweiligen Einzelteile des Tools im Allgemeinen in allen modernen und häufig verwendeten Browsern lauffähig.
Des Weiteren wurde auch die Gesamt-Funktionalität des Tools in verLucas Ohlicher
25
KAPITEL 2. ÜBUNGSTOOL
Browser
Mozilla Firefox 44
Chromium 37
Google Chrome 50
Microsoft Edge 25
Microsoft Internet Explorer
Google Chrome 50
Mozilla Firefox 46
Opera 37
Microsoft Internet Explorer
Microsoft Internet Explorer
Microsoft Internet Explorer
Microsoft Internet Explorer
11
11
10
9
<9
Plattform
Ubuntu 12.04
Ubuntu 12.04
Windows 10
Windows 10
Windows 10
Windows 7
Windows 7
Windows 7
Windows 7
Windows 7
Windows 7
Windows 7
lauffähig?
ja
ja
ja
ja
ja
ja
ja
ja
ja
ja
eingeschränkt
nein
Tabelle 2.1: Liste getesteter Browser
schiedenen Browsern und Umgebungen getestet. Ein Browser wird dabei
auf die Ausführung rekursiver und nicht-rekursiver Anfragen getestet sowie dem Laden externe Datensätze via GitHub Gists.
Dabei wurde festgestellt, dass das Übungstool auf verschiedenen modernen Browsern mit allen Funktionen verwendbar ist (siehe Tabelle 2.1).
Microsofts Internet Explorer 9 funktioniert nur eingeschränkt, da externe Tabelle via GitHub Gists nicht geladen werden können.
26
Lucas Ohlicher
Kapitel 3
Rekursive Anfragen im
Tool
In den vorherigen Kapiteln wird das Tool im Allgemeinen behandelt
und anhand von nicht-rekursiven Anfragen gezeigt, wie es grundsätzlich
funktioniert. Die Basis beinhaltet dabei das Parsen von SQL, den syntaktischen und semantischen Prüfungen der Eingabe, sowie letztlich das
Ausführen der Query. All diese Teile sind notwendig, um rekursive Anfragen auszuführen und schrittweise darzustellen.
3.1
Erkennung rekursiver Anfragen
Wie in Abschnitt 2.3 erläutert, erkennt der Parser des Tools die grundlegende Syntax einer Common Table Expression (CTE). Damit kann aber
noch nicht festgestellt werden, ob die Eingabe eine rekursive Anfrage ist,
da lediglich die Syntax und nicht die Semantik der Eingabe überprüft
wird. Daher wird, nachdem die drei Schritte des Parsing-Prozesses (siehe
Abschnitt 2.2.2) erfolgreich abgeschlossen wurden, überprüft, ob es sich
um eine Rekursion handelt. Es ist zu beachten, dass das Tool nur einfache Rekursionen behandelt, die nach einem festgelegten Muster (siehe
Abschnitt 1.3.3) aufgebaut sein muss. Eine rekursive Anfrage muss mehrere Anforderungen erfüllen, um vom Tool als solche erkannt zu werden:
1. With-Klausel
Eine CTE ist notwendig, da dies die einzige Möglichkeit ist, um
rekursive Anfragen in SQL zu tätigen. Besitzt die Eingabe keine With-Klausel, dann wird sie vom Tool automatisch als nichtrekursiv eingestuft.
2. Union All
In SQL liefert eine Rekursion nur dann das gewünschte Ergebnis,
wenn Initialisierung und Schrittfall mit dem Union All -Operator
27
KAPITEL 3. REKURSIVE ANFRAGEN IM TOOL
verbunden sind. Falls das Tool keinen Union All -Operator in der
CTE vorfindet, wird diese nicht als rekursiv eingestuft.
3. Rekursive Referenz und Join im Schrittfall
Das zweite Select-Statement in der CTE muss in irgendeiner Weise
auf sich selbst referenzieren - die eigentliche Rekursion. Des Weiteren ist es notwendig, dass dieses Statement einen Join beinhaltet, damit garantiert wird, dass das Ergebnis schrittweise wachsen
kann.
4. Auswahl der With-Klausel
Die Abfrage außerhalb der CTE muss zumindest eine Spalte der
zuvor ausgeführten Rekursion beinhalten, ansonsten kann die Abfrage beliebig durchgeführt werden. Diese Anforderung dient dazu,
um den Benutzer darauf hinzuweisen, dass das eigentliche Ergebnis
der Rekursion erst mit dieser Abfrage ausgegeben werden kann.
Sofern die Eingabe des Benutzers all diese Anforderungen erfüllt, wird
diese als rekursive Abfrage erkannt. Es ist zu beachten, dass mit diesen Punkten noch kein Ergebnis garantiert werden kann - die Aufgabe
einer richtigen Eingabe obliegt weiterhin dem Benutzer. Diese Punkte
dienen dazu, um rekursive und nicht-rekursive Abfragen unterscheiden
zu können. Diese Unterscheidung wird benötigt, da rekursive Anfragen
in anderer Art und Weise ausgeführt und dargestellt werden müssen als
nicht-rekursive Anfragen.
3.2
Ausführung rekursiver Anfragen
Rekursive Anfragen können mit SQLike nicht direkt ausgeführt werden. Daher ist es notwendig, solche Anfragen iterativ zu betrachten und
Schritt für Schritt auszuführen. Dieser Vorgang ist vergleichbar mit dem
Ausführen einer rekursiven Anfrage per Hand. Es wird zuerst die Initialisierung ausgeführt und das Ergebnis zwischengespeichert, danach der
erste Schrittfall. Das Ergebnis dieser beiden Abfragen wird vereinigt
und es wird wieder der Schrittfall ausgeführt und mit dem vorigen Ergebnis vereinigt - so lange, bis dem Ergebnis keine neuen Zeilen mehr
hinzugefügt werden. Dieser Vorgang wird vom Tool folgendermaßen ausgeführt:
1. Aufteilen der Abfrage in Initialisierung, Schrittfall und äußerer
Query. Union All wird dabei ignoriert, da davon ausgegangen wird,
dass Initialisierung und Schrittfall mittels Union All vereinigt werden.
2. Ausführung der Initialisierung mit Hilfe von SQLike. Das Resultat
wird als Ergebnis der Rekursion zwischengespeichert.
28
Lucas Ohlicher
KAPITEL 3. REKURSIVE ANFRAGEN IM TOOL
3. Ausführung des Schrittfalls mit Hilfe von SQLike und Vereinigung (mittels Union All ) mit dem Ergebnis der Rekursion. Dieser
Schritt wird solange wiederholt, bis ein Schrittfall keine neuen Einträge zum Ergebnis hinzufügt.
4. Sobald die Rekursion abgeschlossen ist, wird auf dem Ergebnis
die äußere Query ausgeführt. Diese ergibt das Endergebnis der
rekursiven Anfrage.
3.3
Interaktive Darstellung der Rekursion
Das Ziel dieses Tools ist es, dem Benutzer den Ablauf einer Rekursion in
SQL zu veranschaulichen. Dazu wird als Beispiel eine ähnliche Abfrage
wie in Abschnitt 1.3.3 ausgeführt um erreichbare Flughäfen rekursiv zu
berechnen.
Abbildung 3.1: Eingabe einer korrekten rekursive Abfrage
Abbildung 3.1 zeigt, dass bei Eingabe einer korrekten rekursiven Abfrage der Benutzer eine Rückmeldung erhält, dass diese als rekursive
angesehen wird. Weiters bedeutet dies, dass die interaktive Darstellung
des Ergebnisses zur Verfügung steht.
Sobald die Eingabe mit einem Klick auf den Button ausgeführt wird,
erscheinen unterhalb des Editors die ersten beiden Teile der schrittweisen Lösung (siehe Abbildung 3.2). Es werden die für die Rekursion
benötigten Statements herausgeschrieben, um sie später entsprechend
hervorzuheben. Außerdem ist bereits die Ergebnistabelle der Rekursion
sichtbar (Tabelle ’RecRel’ im Beispiel), die allerdings derzeit noch keine
Einträge besitzt.
Mit einem Klick auf ’Initialization’ kann der erste Schritt ausgeführt
werden (siehe Abbildung 3.3). Dies ist die Initialisierung der Rekursion,
also die Ausführung des ersten Select-Statements. Es wird farblich hervorgehoben, welcher Teil der Query dafür zuständig ist. Außerdem wird
Lucas Ohlicher
29
KAPITEL 3. REKURSIVE ANFRAGEN IM TOOL
Abbildung 3.2: Start der schrittweisen Darstellung
das Ergebnis dieser Abfrage im Ergebnis markiert.
Der nächste Schritt beinhaltet den ersten Join (siehe Abbildung 3.4). Es
wird wieder das zuständige Select-Statement markiert sowie die JoinBedingung hervorgehoben. In diesem Schritt wird auch die Tabelle des
Join-Partners dargestellt und es werden die für den Join zuständigen
Spalten beider Tabellen farblich markiert.
Nun wird das Resultat des Joins mit dem Inhalt der Tabelle ’RecRel’ vereinigt (Abbildung 3.5). Spalten, die in diesem Schritt hinzugefügt werden, sind farblich markiert. Dies entspricht dem Ergebnis der Rekursion
nach dem ersten Schritt. Die nächsten Schritte beinhalten abwechselnd
Join sowie Vereinigung und fügen so dem Ergebnis weitere Schritte hinzu. Dies wiederholt sich solange, bis keine Join-Partner mehr vorhanden
sind und somit keine neuen Ergebnisse mehr hinzugefügt werden können.
Die Abbildungen 3.6 und 3.7 zeigen jeweils den Join für den zweiten
Schritt beziehungsweise das Ergebnis nach zwei Schritten.
Sobald die Rekursion stoppt, enthält die Tabelle ’RecRel’ das vollständige
Ergebnis. Damit kann nun, als letzter Schritt im Tool, die äußere Abfrage durchgeführt werden. Mit dem Button ’show final result’ wird die
äußere Abfrage ausgeführt, womit das Endergebnis berechnet wird. Dieser Button ist von Anfang an verfügbar, um die schrittweise Lösung bei
Bedarf zu überspringen.
Der Benutzer kann in der Darstellung der Rekursion beliebig vor und
zurück navigieren. Die Erklärungen und Hervorhebungen werden in beiden Fällen entsprechend angepasst.
30
Lucas Ohlicher
KAPITEL 3. REKURSIVE ANFRAGEN IM TOOL
Abbildung 3.3: Initialisierung der Rekursion
Abbildung 3.4: Erster Schrittfall
Lucas Ohlicher
31
KAPITEL 3. REKURSIVE ANFRAGEN IM TOOL
Abbildung 3.5: Ergebnis des ersten Schritts
Abbildung 3.6: Zweiter Schrittfall
Abbildung 3.7: Ergebnis nach zwei Schritten
32
Lucas Ohlicher
KAPITEL 3. REKURSIVE ANFRAGEN IM TOOL
Abbildung 3.8: Ende der Rekursion
Abbildung 3.9: Ergebnis der äußeren Query
Lucas Ohlicher
33
Kapitel 4
Zusammenfassung
In dieser Bachelorarbeit wurde ein Übungstool für die Rekursion in SQL
erstellt. Es präsentiert schrittweise das Ergebnis einer rekursiven Abfrage, um es dem Benutzer zu ermöglichen, die einzelnen Teile einer solchen
Abfrage interaktiv mitzuverfolgen. Ziel dabei ist es, Rekursionen besser
zu veranschaulichen und somit verständlicher zu machen.
Das Tool arbeitet komplett clientseitig und alle Berechnungen werden direkt im Browser ausgeführt. Die Eingabe von Abfragen erfolgt in einem
Code-Editor, der über passendes Syntax-Highlighting verfügt. Bereits
während der Eingabe wird diese durch einen Parser, der an die Anforderungen des Tools angepasst wurde, auf syntaktische und semantische
Korrektheit überprüft. Falls keine Fehler angezeigt werden, kann die
eingegebene Abfrage ausgeführt werden. Diese wird direkt im Browser
mit Hilfe von JavaScript berechnet und benötigt keine externe Datenbank. Bei nicht-rekursiven Abfragen wird das Ergebnis direkt angezeigt,
während bei rekursiven Abfragen der Benutzer sich über Buttons Schritt
für Schritt durch die Zwischenergebnisse klicken oder direkt zum Endergebnis springen kann. Des Weiteren werden bei Rekursionen immer die
derzeitigen Schritte farblich hervorgehoben, um den Vorgang sowie die
zuständigen Tabellen und Code-Abschnitte besser zu veranschaulichen.
Es liegen bereits vordefinierte Datensätze vor, die von jedem Benutzer verwendet werden können. Zusätzlich werden Beispiele für Rekursionen zur Verfügung gestellt, die sich über Buttons direkt in den Editor
einfügen lassen. Außerdem ist es möglich, externe oder selbsterstellte Datensätze zu verwenden, welche in Form von Textdateien als GitHub Gists
geladen werden können. Dabei können die Datensätze auch zusätzlich
mit Beispiel-Abfragen versehen werden, die ebenfalls über Buttons direkt eingefügt werden können. Dadurch ist es möglich, Datensätze und
Abfragen einfach mit anderen Benutzern zu teilen und wiederzuverwenden.
34
KAPITEL 4. ZUSAMMENFASSUNG
Ausblick
Das Übungstool unterstützt die Eingabe und Ausführung der meisten
wichtigen SQL Funktionen und Befehle, die sich mit der Abfrage von
Datensätzen befassen. Zusätzlich werden auch rekursive Abfragen unterstützt, die einem bestimmten Muster entsprechen.
Nicht implementierte Funktionen wurden meist aus technischen Gründen
nicht umgesetzt oder weil sie aufgrund des Übungszwecks des Tools nicht
erforderlich waren. So entfällt zum Beispiel der komplette Block der Data Definition Language und der Data Manipulation Language, da Datensätze über andere Weise erstellt und manipuliert werden können. Es
wäre allerdings durchaus vorstellbar, Datensätze auch direkt über den
Editor zu erstellen und zu bearbeiten.
Das Tool unterstützt derzeit nur einfach-rekursive Funktionen, das heißt
es ist nicht möglich, Rekursionen zu verschachteln, um zum Beispiel das
Ergebnis einer Rekursion als Initialisierung einer weiteren Rekursion zu
verwenden. Dies gilt für die Berechnung und die interaktive Darstellung. Das liegt daran, dass sich einfache Rekursionen zur Erklärung
am besten eignen und sich verschachtelte Rekursion von der Funktionsweise kaum von einfachen Rekursionen unterscheiden. Es wäre dennoch wünschenswert, verschachtelte Rekursionen zumindest berechnen
zu können und dabei auf eine interaktive Darstellung zu verzichten, da
diese wahrscheinlich sehr komplex ausfallen würde.
Lucas Ohlicher
35
Literaturverzeichnis
[Ang]
AngularJS - Javascript Webframework by Google, https://
angularjs.org/, [Online; Stand 08. Juni 2016].
[Boo]
Twitter Bootstrap - CSS-Framework, http://getbootstrap.
com/, [Online; Stand 08. Juni 2016].
[Cod]
CodeMirror - A Text Editor for JavaScript, https://
codemirror.net/, [Online; Stand 08. Juni 2016].
[For04] B. Ford: Parsing Expression Grammars: A Recognition-based
Syntactic Foundation, Proceedings of the 31st ACM SIGPLANSIGACT Symposium on Principles of Programming Languages,
POPL ’04, ACM, New York, NY, USA, pages 111–122, URL
http://doi.acm.org/10.1145/964001.964011.
[Fra]
T. Frank: SQLike - a small query engine, http://www.
thomasfrank.se/sqlike.html, [Online; Stand 08. Juni 2016].
[jQu]
jQuery - JavaScript library, https://jquery.com/, [Online;
Stand 08. Juni 2016].
[KE13] A. Kemper and A. Eickler: Datenbanksysteme: Eine Einführung,
9. Auflage, Oldenbourg Verlag, 2013.
[Kes14] J. Kessler: Übungstool für Relationale Algebra, RelationenTupel-Kalkül und SQL, Bachelor Thesis, Department for Computer Science, University of Innsbruck, http://dbis-uibk.
github.io/relax/, 2014, [Online; Stand 08. Juni 2016].
[MSS]
Recursive Queries Using Common Table Expression (Microsoft
SQL Server 2008 R2), https://msdn.microsoft.com/en-us/
library/ms186243.aspx, [Online; Stand 08. Juni 2016].
[MyS]
MySQL, https://www.mysql.de/, [Online; Stand 08. Juni
2016].
[Peg]
PegJS - Parser Generator for JavaScript, http://pegjs.org/,
[Online; Stand 08. Juni 2016].
37
LITERATURVERZEICHNIS
[PGS]
PostgreSQL, https://www.postgresql.org/, [Online; Stand
08. Juni 2016].
[Vos08] G. Vossen: Datenmodelle, Datenbanksprachen und Datenbankmanagementsysteme. 5. Auflage, Oldenbourg Verlag, 2008.
38
Lucas Ohlicher
Herunterladen