483 lines
44 KiB
TeX
483 lines
44 KiB
TeX
%
|
|
% Kapitel Entwurf
|
|
%
|
|
%
|
|
|
|
\chapter{Entwurf}\label{chp:Entwurf}
|
|
In diesem Kapitel werden die Kriterien für die Auswahl der in Kapitel \ref{chp:Implementierung} implementierten Algorithmen dargestellt. Anschließend werden die theoretischen Grundlagen der einzelnen Algorithmen ausführlich beleuchtet.
|
|
|
|
\section{Überblick}\label{sct:Überblick2}
|
|
Die von Kim bzw. Ganski/Wong vorgestellten Algorithmen (siehe \cite{319745,38723}) zur Optimierung geschachtelter Abfragen haben für die Implementierung in \textsc{Secondo} den Vorteil, dass sie sich mit der Einführung weniger weiterer Operatoren in die bestehende \textsc{Secondo}"=Im\-ple\-men\-tie\-rung integrieren lassen. Zur Umsetzung der Algorithmen sind die vorhandenen Stan\-dard-Ope\-ra\-ti\-on\-en Projektion und Selektion sowie die Operationen Anti-Join und Full-Outer-Join notwendig. Im Rahmen dieser Arbeit wurde ein Full-Outer-Join-Operator für den Fall der Attribut-Gleichheit implementiert.
|
|
|
|
Da sich keiner der vorhandenen Join-Algorithmen zur Umsetzung eines effizienten Anti-Join-Algorithmus eignet, wurde auf die Implementierung eines entsprechenden Algorithmus verzichtet. Abfragen mit dem Operator \sql{not in} können mit den ausgewählten Algorithmen nur mit einem Anti-Join-Operator entschachtelt werden. Diese werden daher zwar übersetzt, die entsprechenden Prädikate sind aber keiner weiteren Optimierung zugänglich. D.h. eine Optimierung über Entschachtelung ist in diesem Spezialfall nicht möglich. Nach der Implementierung eines Anti-Join-Operators sollte die Implementierung eines Prädikats zur Entschachtelung mit dem in \cite{319745} vorgestellten Algorithmus möglich sein. Zusätzlich ist die Erweiterung des Optimierers um die Verarbeitung von allgemeinen Join-Prädikaten notwendig, die nicht der Kombination von Kreuzprodukt und Selektion entsprechen. Hierbei sind vor allem der Outer-Join und der Anti-Join für die Optimierung von geschachtelten Abfragen von Bedeutung. Da eine beliebige Vertauschung der Reihenfolge von Outer-Join- und Join-Prädikaten nicht zulässig ist, müssen die für die Vertauschung hinreichenden Bedingungen beim Aufbau des Predicate-Order-Graphen berücksichtigt werden \cite{244812}.
|
|
|
|
Bei der Analyse des Problems wurde klar, dass eine echte Optimierung, d.h. ein Vergleich der geschachtelten und der nicht geschachtelten Ausführungsvariante einer Abfrage, umfangreiche Berechnungen und Simulationen erfordern würde. Um die Kosten der entschachtelten Abfrage ermitteln zu können, müssen die Selektivitäten der Prädikate ermittelt werden. Dies erfolgt durch Auswertung jedes einzelnen Prädikats über Stichproben-Relationen, die aus den eigentlichen Relationen abgeleitet werden. Bei Selektions-Prä\-di\-ka\-ten wird hier nur eine Stichprobenrelation benötigt, bei Join-Prädikaten dementsprechend zwei. Die Stichprobenrelationen werden automatisch erzeugt, sobald sie das erste Mal benötigt werden. Um die Stichprobenrelationen für temporäre Relationen zu erzeugen, müssen die temporären Relationen materialisiert werden. D.h. das Ergebnis der ihnen zugrundeliegenden Abfrage muss vollständig ermittelt werden. Die Erzeugung der temporären Relationen zu diesem Zeitpunkt würde die Laufzeitkosten hierfür aber bei beiden Ausführungsstrategien erhöhen. Ein sinnvoller Vergleich wäre nicht mehr gegeben, da gerade die Erzeugung der temporären Relationen zu einer längeren Laufzeit bei entschachtelter Ausführung führen kann. Da die Prädikate der entschachtelten Abfrage möglicherweise auf temporäre Relationen bezugnehmen, müsste hier die entsprechende Selektivität simuliert werden. Eine Simulation der Selektivitätsbestimmung setzt komplexe statistische Berechnungen voraus, um Selektivität und Kardinalität abzuschätzen. Um an dieser Stelle verlässliche Daten zu erhalten, wäre der Nachbau eines großen Teils der Metadaten-Ermittlung des Optimierers notwendig gewesen. So ist zwar die Abschätzung der Kardinalität einer Ergebnisrelation mit Hilfe der Kardinalitäten der verwendeten Relationen und den Selektivitäten der Prädikate möglich. Aussagen über die Selektivität eines Prädikats über dieser Relation lassen sich jedoch nicht ohne weiteres treffen. Da diese Simulationen den Rahmen dieser Diplomarbeit sprengen würde, wurde auf die Implementierung verzichtet.
|
|
|
|
Zur Veranschaulichung der Algorithmen wird in \cite{319745} eine graphische Repräsentation geschachtelter Abfragen eingeführt. Abfragen werden in Form von Abfragegraphen dargestellt. Jeder Abfrageblock stellt einen Knoten dar. Jeder Knoten wird mit den zur Auswertung notwendigen Relationen markiert. Es gibt zwei Arten von Kanten, die den für die Auswertung notwendigen Informationsfluss darstellen. Gerade Kanten stellen einen Informationsfluss von der Unterabfrage zur äußeren Abfrage dar. D.h. für die Auswertung des äußeren Abfrageblocks sind Informationen aus der Unterabfrage notwendig. Geschwungene Kanten stellen einen Informationsfluss von der äußeren zur inneren Abfrage dar, sie treten somit bei korrelierten geschachtelten Abfragen auf. Die Kanten werden abhängig von der Klassifikation der Schachtelung mit verschiedenen Markierungen versehen. Ein Prädikat vom Typ-A erzeugt eine gerade Kante, die mit \sql{$A(C_k)$} versehen wird. \sql{$C_k$} ist die Projektionsspalte der Unterabfrage.
|
|
|
|
\tikzstyle{place}=[circle,draw=black,thick,inner sep=0pt,minimum size=6mm]
|
|
\tikzstyle{mylabel}=[above,font=\footnotesize]
|
|
|
|
\begin{tikzpicture}[grow=right]
|
|
\path
|
|
(0,0) node[place] (Ri) {$R_i$}
|
|
(2.5,0) node[place,draw](Rj) {$R_j$};
|
|
\draw[-] (Ri) -- node[mylabel] {$A(C_k)$} (Rj);
|
|
\end{tikzpicture}
|
|
|
|
Prädikate vom Typ-N erzeugen eine gerade Kante mit der Markierung \sql{$N(C_k)$}. Da der Operator \sql{not in} einen Sonderfall darstellt, wird bei diesem Operator die Markierung \sql{$Nx(C_k$)} erzeugt.
|
|
|
|
\begin{tikzpicture}[grow=right]
|
|
\path
|
|
(0,0) node[place] (Ri) {$R_i$}
|
|
(2.5,0) node[place,draw](Rj) {$R_j$};
|
|
\draw[-] (Ri) -- node[mylabel] {$N(C_k)$} (Rj);
|
|
\path
|
|
(6,0) node[place] (Ri) {$R_i$}
|
|
(8.5,0) node[place,draw](Rj) {$R_j$};
|
|
\draw[-] (Ri) -- node[mylabel] {$Nx(C_k)$} (Rj);
|
|
\end{tikzpicture}
|
|
|
|
Prädikate vom Typ-J erzeugen zwei Kanten. Eine gerade Kante mit Markierung \sql{$N(C_k)$} bzw. \sql{$Nx(C_k$)} und eine geschwungene Kante, die mit dem korrelierten Prädikat der Unterabfrage markiert wird, also allgemein mit der Markierung \sql{$R_i.C_n \theta R_j.C_m$}. Analog dazu werden bei Prädikaten vom Typ-JA zwei Kanten erzeugt. Die erste Kante ist mit der Markierung \sql{$A(C_k)$} versehen, die zweite, geschwungene Kante wird ebenfalls mit dem korrelierten Prädikat markiert.
|
|
|
|
\begin{tikzpicture}[grow=right]
|
|
\path
|
|
(0,0) node[place] (Ri) {$R_i$}
|
|
(2.5,0) node[place,draw](Rj) {$R_j$};
|
|
\draw[-] (Ri) -- node[mylabel] {$N(C_k)$} (Rj);
|
|
\draw[-]
|
|
(Ri.south east) .. controls (1,-0.5) and (1.5,-0.5) ..
|
|
node[mylabel,below,sloped] {$R_i.C_n \theta R_j.C_m$} (Rj.south west);
|
|
\path
|
|
(6,0) node[place] (Ri) {$R_i$}
|
|
(8.5,0) node[place,draw](Rj) {$R_j$};
|
|
\draw[-] (Ri) -- node[mylabel] {$A(C_k)$} (Rj);
|
|
\draw[-]
|
|
(Ri.south east) .. controls (7,-0.5) and (7.5,-0.5) ..
|
|
node[mylabel,below,sloped] {$R_i.C_n \theta R_j.C_m$} (Rj.south west);
|
|
\end{tikzpicture}
|
|
|
|
Ein Schritt im Optimierungsalgorithmus entspricht immer der Entfernung einer Kante des Abfragegraphen. Das Ergebnis der Abfrage muss dabei erhalten bleiben. Bei Abfragen mit einer Schachtelungstiefe größer 1 kann eine gebogene Kante auch Abfrageblöcke verbinden, die mehr als eine Schachtelungsebene auseinander liegen.
|
|
|
|
\begin{tikzpicture}[grow=right]
|
|
\path
|
|
(0,0) node[place] (R1) {$R_1$}
|
|
(2.5,0) node[place](R2) {$R_2$}
|
|
(5,0) node[place] (R3) {$R_3$};
|
|
\draw[-] (R1) -- node[mylabel] {$N(C_k)$} (R2);
|
|
\draw[-] (R2) -- node[mylabel] {$N(C_l)$} (R3);
|
|
\draw[-]
|
|
(R1.south east) .. controls (2,-0.75) and (3,-0.75) ..
|
|
node[mylabel,below,sloped] {$R_1.C_n \theta R_3.C_m$} (R3.south west);
|
|
\end{tikzpicture}
|
|
|
|
%Da der Operator \sql{in} für Tupel noch nicht implementiert ist, wurde die triviale Umsetzung von \sql{in} in die Disjunktion der angegebenen Konstanten als Übersetzung integriert. Eine Implementierung des \sql{in}-Operators für Tupelströme auf Basis des \secondo{in}- und \secondo{collect\_set}-Operators der \algebra{Collection}-Algebra wäre eine mögliche Vorgehensweise zur performanten Implementierung dieses Operators. Ein Ausdruck \sql{$A_i$ in $(b_1,\dots,b_n)$} wird also in die inhaltlich äquivalente Darstellung \sql{($A_i = b_1\ or\ ( \dots\ or\ (A_i = b_n) \dots ))$} überführt
|
|
|
|
%\begin{itemize}
|
|
% \item keine automatische Entscheidung entschachteln oder nicht
|
|
% \subitem alle Metadaten über temp. Relationen wären notwendig
|
|
% \subitem inkl. Selektivitäten
|
|
% \item \textbf{IN} ohne Subquery -> Übersetzung in geschachtelte \textbf{OR} Prädikate
|
|
%\end{itemize}
|
|
Die Optimierungen von geschachtelten Abfragen, die in dieser Arbeit implementiert wurden, unterliegen aufgrund der verwendeten Algorithmen einigen Einschränkungen. Geschachtelte Abfragen dürfen im inneren Abfrageblock keine \sql{group by/having}-Klausel aufweisen (vgl. \cite{671658}). In korrelierten Abfragen dürfen nur Attribute der Relationen des den Abfrageblock direkt umgebenden Abfrageblocks verwendet werden. D.h. auf Schachtelungsebene n dürfen nur Relationen des inneren Abfrageblocks und Relationen der Schachtelungsebene n-1 verwendet werden. Tritt ein geschachteltes Prädikat in einem Disjunktionsterm auf, so ist es einer Optimierung mit den hier implementierten Algorithmen ebenfalls nicht zugänglich. Algorithmen zur Optimierung geschachtelter Abfragen in Disjunktionen wurden aber z.B. in \cite{Brantner2007} vorgestellt. Bei einer Erweiterung des Optimierers um die Optimierung disjunktiver Terme kann eine entsprechende Optimierung für geschachtelte Abfragen vorgesehen werden. Korrelierte Abfragen in der \sql{select}- und \sql{from}-Klausel können nicht mit einfachen temporären Relationen ersetzt werden. In dieser Arbeit wurde daher nur die Behandlung nicht korrelierter Abfragen in der \sql{select}- und \sql{from}-Klausel implementiert. Geschachtelte Abfragen, die nicht durch Entschachtelungs- und Transformationsalgorithmen optimiert werden können, werden in geschachtelte, iterative Ausdrücke übersetzt.
|
|
|
|
\section{Full-Outer-Join-Operatoren für SECONDO}
|
|
Um die korrekte Umsetzung der Korrekturen zu gewährleisten, die Ganski/Wong an den Algorithmen aus \cite{319745} vorgenommen haben, ist die Full-Outer-Join-Operation notwendig. \textsc{Secondo} kann diese Operation bereits ausführen, da sich der Full-Outer-Join durch andere, bereits implementierte Operatoren darstellen lässt. Die effiziente Berechnung des Full-Outer-Join ist hiermit jedoch nicht möglich, der Full-Outer-Join wird hierbei durch komplexe Verknüpfung von Join, Kreuzprodukt und Mengendifferenz bzw. Mengenvereinigung berechnet. Daher war es notwendig, eine effiziente Implementierung zu erstellen. \textsc{Secondo} enthält bereits mehrere Join-Operatoren. Als Vorlage für die Implementierung der Full-Outer-Join-Operatoren wurden die Operatoren \secondo{sortmergejoin} und \secondo{symmjoin} ausgewählt, da sich die zugrunde liegenden Algorithmen mit wenigen Änderungen in einen Full-Outer-Join umbauen lassen.
|
|
|
|
\subsection{Sort-Merge-Outer-Join}
|
|
Sort-Merge-Join ist ein Algorithmus zur Berechnung von Equi-Joins. Daher wird auch nur der Full-Outer-Join für Equi-Join berechnet. Voraussetzung ist die Sortierung der Relationen bzw. Ströme bezüglich der Join-Attribute. Diese Anforderung kann durch das vorhergehende Sortieren der Relationen erfüllt werden. In \textsc{Secondo} erfolgt diese Sortierung innerhalb des Operators \secondo{sortmergejoin}.
|
|
|
|
Mithilfe zweier Zeiger und eines Puffers werden die sortiert vorliegenden Ströme durchlaufen.
|
|
Zu Beginn des Algorithmus zeigen die beiden Zeiger auf das erste Tupel der jeweiligen Relation und der Puffer ist leer.
|
|
\begin{enumerate}
|
|
\item Jetzt werden alle Tupel der zweiten Relation mit übereinstimmendem Wert im Join-Attribut in den Puffer gelesen und der Zeiger jeweils auf das aktuell gelesene Tupel gesetzt.
|
|
\item Im nächsten Schritt wird das aktuelle Tupel der ersten Relation mit dem aktuellen Tupel der zweiten Relation verglichen.
|
|
\subitem Solange der Wert des Join-Attributs des Tupels der ersten Relation kleiner ist, als der Wert des Join-Attributs des Tupels im Puffer, wird der Zeiger in der ersten Relation um eine Position erhöht.
|
|
\subitem Besteht Gleichheit im Wert des Join-Attributs, so wird ein Tupel der Ergebnisrelation erzeugt, das aus der Aneinanderreihung des Tupels der ersten Relation und des ersten Tupels des Puffers besteht. Anschließend wird das Tupel aus dem Puffer gelöscht. Ist der Puffer erschöpft, wird der Zeiger in der ersten Relation erhöht und mit dem ersten Schritt fortgefahren. Enthält der Puffer noch Tupel, so wird die Erzeugung eines Ergebnistupels mit dem neuen ersten Tupel des Puffers wie gerade beschrieben durchgeführt.
|
|
\subitem Ist der Wert des Join-Attributs des aktuellen Tupels der ersten Relation größer, als der entsprechende Wert des aktuellen Tupels der zweiten Relation, so werden alle Tupel aus dem Puffer gelöscht, der Zeiger in der zweiten Relation erhöht und mit dem ersten Schritt fortgefahren.
|
|
\item Ist eine der beiden Relationen erschöpft, ist der Algorithmus beendet. Verbrauchte Tupel werden aus dem Puffer gelöscht.
|
|
\end{enumerate}
|
|
|
|
Dieser Algorithmus lässt sich auf folgende Art und Weise zum Full-Outer-Join ausbauen. Bei jedem Vergleichsschritt wird jetzt ein Tupel der Ausgaberelation erzeugt. Ist der Wert im ersten Puffer größer als im zweiten, so besteht das Tupel der Ergebnismenge aus einer Verkettung des Tupels aus dem Puffer und einem mit \sql{null}-Werten gefüllten Tupel der zweiten Relation. Auf die gleiche Art wird mit jedem Tupel des Puffers verfahren. Ist der Attribut-Wert gleich, so wird wie beim Sort-Merge-Join verfahren. Ist der Wert kleiner, so wird für jedes Tupel im zweiten Puffer ein Ausgabe-Tupel erzeugt, das aus einer Aneinanderreihung eines NULL-Tupels der ersten Relation und dem Tupel aus dem Puffer besteht. Damit wird für jedes Tupel, das nicht im Join-Ergebnis enthalten wäre, ein Ausgabetupel erzeugt, dessen Attributwerte für die Attribute der jeweils anderen Relation mit \sql{null}-Werten gefüllt sind.
|
|
|
|
\subsection{Symm-Outer-Join}
|
|
Der Algorithmus des Operators \secondo{symmjoin} erlaubt die Berechnung der Join-Operation mit beliebigen Bedingungen. Er ist aufgrund der Verzahnung der Selektions- und Kreuz\-pro\-dukt-Ope\-ra\-ti\-on effizienter als die Verknüpfung dieser beiden Operationen. Die Bedingung wird wie in \textsc{Secondo} üblich durch eine Funktion dargestellt, die zwei Tupel auf einen booleschen Wert abbildet.
|
|
|
|
Die Relationen werden bei diesem Algorithmus mithilfe mehrerer Zeiger, zweier Puffer und eines Flags durchlaufen. Zwei der Zeiger zeigen immer auf das nächste Tupel einer Relation. Der dritte Zeiger zeigt auf das aktuell betrachtete Tupel. Mit dem Flag wird angezeigt, welche Relation als Quelle für das nächste Tupel dient.
|
|
Zu Beginn des Algorithmus sind beide Puffer leer und die Zeiger zeigen auf das jeweils erste Tupel der Relationen. Der Zeiger auf das aktuell betrachtete Tupel zeigt auf das erste Tupel der ersten Relation.
|
|
\begin{enumerate}
|
|
\item Sind beide Relationen erschöpft, so ist der Algorithmus beendet.
|
|
\item Ist das Flag für die erste Relation gesetzt:
|
|
\subitem Zeigt der Zeiger für das aktuelle Tupel auf 0, so wird der Zeiger auf das nächste Tupel der ersten Relation gesetzt und der Zeiger in der ersten Relation erhöht. Ist die erste Relation erschöpft, wird das Flag auf die zweite Relation gesetzt.
|
|
\subitem Wenn der zweite Puffer erschöpft ist, wird das aktuelle Tupel in den ersten Puffer eingefügt. Der Zeiger auf das aktuelle Tupel zeigt auf 0. Das Flag wird auf die zweite Relation geändert. Es wird mit dem ersten Schritt fortgefahren.
|
|
\subitem Ist der zweite Puffer noch nicht erschöpft, so wird die Bedingung über dem aktuellen Tupel und dem ersten Tupel des zweiten Puffers ausgewertet. Wertet die Bedingung zu \sql{true} aus, so wird ein Ergebnistupel erzeugt, das eine Verkettung des aktuellen Tupels und des ersten Tupels des zweiten Puffers ist. Das verwendete Tupel des zweiten Puffers wird unabhängig vom Ergebnis der Auswertung aus dem Puffer gelöscht. Es wird mit Schritt 1 fortgefahren.
|
|
\item Ist das Flag für die zweite Relation gesetzt.
|
|
\subitem Zeigt der Zeiger für das aktuelle Tupel auf 0, so wird der Zeiger auf das nächste Tupel der zweiten Relation gesetzt und der Zeiger in der zweiten Relation erhöht. Ist die zweite Relation erschöpft, wird das Flag auf die erste Relation gesetzt.
|
|
\subitem Ist der erste Puffer erschöpft, wird das aktuelle Tupel in den zweiten Puffer eingefügt. Das aktuelle Tupel wird gelöscht und der Zeiger hierauf wird auf 0 gesetzt. Das Flag wird auf die erste Relation geändert. Es wird mit dem ersten Schritt fortgefahren.
|
|
\subitem Ist der erste Puffer noch nicht erschöpft, so wird die Bedingung über dem ersten Tupel des zweiten Puffers und dem aktuellen Tupel ausgewertet. Wertet die Bedingung zu \sql{true} aus, so wird ein Ergebnistupel erzeugt, das eine Verkettung des ersten Tupels des ersten Puffers des aktuellen Tupels ist. Das verwendete Tupel des ersten Puffers wird unabhängig vom Ergebnis der Auswertung aus dem Puffer gelöscht. Es wird mit Schritt 1 fortgefahren.
|
|
\end{enumerate}
|
|
|
|
Um diesen Algorithmus in einen Full-Outer-Join zu überführen, sind einige Änderungen notwendig. Um über die Tupel Buch zu führen, die bereits im Equi-Join enthalten sind, werden zwei Hashtabellen benutzt, eine Hashtabelle für Tupel der ersten Relation und eine für Tupel der zweiten Relation. Um hier den Speicherbedarf klein zu halten, wird nur die Tupel-Id in der Hashtabelle gespeichert. Die Tupel-Id ist eine eindeutige Identifikation eines Tupels bezogen auf einen Strom von Tupeln. Bei jeder Erzeugung eines Ausgabetupels des \secondo{symmjoin}-Algorithmus werden die Tupel-Ids der beiden für die Ausgabe verwendeten Tupel in die jeweilige Hashtabelle eingetragen. Jedesmal, wenn der Zeiger in einer der beiden Relationen erhöht wird, wird das Tupel in einen Puffer eingetragen. Hierbei gibt es jeweils einen Puffer für die erste und einen für die zweite Relation. Da die Tupel beim Einfügen in einen Puffer nicht kopiert, sondern nur ein Zeiger auf die bestehende Tupeldarstellung erzeugt wird, ist diese Vorgehensweise durchaus effizient. Nachdem der \secondo{symmjoin}-Algorithmus beendet ist, werden die beiden Puffer, die jetzt alle Tupel der jeweiligen Relation beinhalten, mit einem Iterator durchlaufen. Für jedes Tupel wird über die Tupel-Id in der entsprechenden Hashtabelle nachgeschlagen, ob das Tupel bereits für die Ausgabe verwendet wurde. Ist dies nicht der Fall, wird ein Ausgabetupel erzeugt, das aus den Attributwerten des betrachteten Tupels und \sql{null}-Werten für die Attribute der jeweils anderen Relation besteht. Nach dieser Überprüfung wird das Tupel aus dem Puffer gelöscht.
|
|
|
|
%\begin{itemize}
|
|
% \item Beschreibung Sortmergejoin
|
|
% \subitem Zwei Tupelbuffer
|
|
% \subitem sortieren
|
|
% \subitem immer mit dem kleineren Tupel arbeiten und vergleichen
|
|
% \subitem falls Treffer, merge Schritt
|
|
% \subitem sonst Tupel verwerfen und mit nächstem Tupel weiterarbeiten
|
|
%\end{itemize}
|
|
%Basis ist sortmergejoin, nicht \enquote{matchende} Tupel wird mit \sql{null}-Werten aufgefüllt, nur Equi-Outerjoin\\
|
|
%Ausblick: Full-Outer-Join auf Basis von Symmjoin. Die Tupel der beiden Ströme werden um ein Attribut TupelID erweitert. Zu jedem Tupel, das über den Symmjoin-Algorithmus einen Partner gefunden hat, wird die TupelID in einer Hash-Tabelle gespeichert. Dabei werden die TupelIDs für jeden Strom in einer eigenen Hash-Tabelle abgelegt. Nach Beendigung des Symmjoin-Algorithmus wird jeder Tupelpuffer ein zweites Mal durchlaufen. Ist die TupelID eines Tupels nicht in der entsprechenden Hash-Tabelle enthalten, so wird ein Ergebnistupel erzeugt, das die Werte aus dem betrachteten Tupel übernimmt und für die zusätzlichen Spalten mit \sql{null}-Werten aufgefüllt wird. zwei Hash-Tabelle mit TupelIds für alle gefundenen Tupel, Tupelbuffer durchlaufen und ein mit \sql{null}-Werten aufgefülltes Tupel erzeugen für alle nicht gematchten Tupel
|
|
|
|
\section{Behandlung quantifizierter Prädikate}\label{sct:Behandlung quantifizierter Prädikate}
|
|
Prädikate mit den Operatoren \sql{exists}, \sql{all} oder \sql{any} können in ein inhaltlich äquivalentes Prädikat mit Aggregationsfunktion transformiert werden, sofern keine \sql{null}-Werte in den Unterabfragen enthalten sind.
|
|
|
|
Das Prädikat \sql{exists(Q)} ist genau dann wahr, wenn die Ergebnisrelation des Abfrageblocks Q mindestens ein Tupel enthält. Ist \lstinline[mathescape=true]{Q = select $C_n$ from $R_m$ where P}, so lässt sich dies aber auch als \lstinline[mathescape=true]{0 < (select count(*) $\ $from $R_m$ where P)} ausdrücken. Analog dazu kann \lstinline[mathescape=true]{not exists(select $C_n$ from $R_m$ where P)} in \lstinline[mathescape=true]{0 = (select count(*) $\ $from $R_m$ where P)} überführt werden. Da die Aggregation über die ursprüngliche Ergebnisspalte $C_n$ bei \sql{null}-Werten zu falschen Ergebnissen führt, müssen hier alle Tupel gezählt werden. Um die Berechnung der Aggregation zu beschleunigen, kann die Abfrage mit dem Ausdruck \secondo{first 1} noch auf das erste Tupel der Unterabfrage beschränkt werden. Dies verhindert, dass teure Operatoren auf allen Tupeln der Unterabfrage ausgeführt werden müssen.
|
|
|
|
Das Ergebnis von Abfragen mit den Operatoren \sql{all} und \sql{any} bleibt ebenfalls erhalten, wenn man Prädikate mit den beiden Operatoren in Prädikate umwandelt, die einen Vergleich mit dem Maximum bzw. Minimum der Unterabfrage darstellen.
|
|
|
|
\lstinline[mathescape=true]{$C_n$ $\theta$ any(select $C_m$ from $R_i$ where P)} wird für $\theta \in {<,<=,>,>=}$ transformiert in
|
|
|
|
\lstinline[mathescape=true]{$C_n$ $\theta$ (select AGGR($C_m$) from $R_i$ where P)}.
|
|
|
|
Wobei hier gilt
|
|
\begin{math}
|
|
AGGR =
|
|
\begin{cases}
|
|
max&\quad\text{falls}\ \theta \in {<, \leq}, \\
|
|
min&\quad\text{falls}\ \theta \in {>, \geq}.
|
|
\end{cases}
|
|
\end{math}
|
|
|
|
So wird
|
|
|
|
\lstinline[mathescape=true]{select * from $R_i$ where $C_n$ $\leq$ any(select $C_m$ from $R_j$ where P)}
|
|
|
|
in
|
|
|
|
\lstinline[mathescape=true]{select * from $R_i$ where $C_n$ $\leq$ (select $max(C_m)$ from $R_j$ where P)}
|
|
|
|
transformiert.
|
|
|
|
Im Spezialfall \lstinline[mathescape=true]{$C_n$ = any(select $C_m$ from $R_i$ where P)} wird das Prädikat durch das semantisch äquivalente Prädikat \lstinline[mathescape=true]{$C_n$ in (select $C_m$ from $R_i$ where P)} ersetzt.
|
|
|
|
\lstinline[mathescape=true]{$C_n$ $\theta$ all(select $C_m$ from $R_i$ where P)} wird für $\theta \in {<,<=,>,>=}$ transformiert in
|
|
|
|
\lstinline[mathescape=true]{$C_n$ $\theta$ (select AGGR($C_m$) from $R_i$ where P)}.
|
|
|
|
Wobei hier gilt
|
|
\begin{math}
|
|
AGGR =
|
|
\begin{cases}
|
|
min&\quad\text{falls}\ \theta \in {<, \leq}, \\
|
|
max&\quad\text{falls}\ \theta \in {>, \geq}.
|
|
\end{cases}
|
|
\end{math}
|
|
|
|
Mit dem Operator \sql{all} wird analog verfahren. Eine Abfrage mit \sql{all}
|
|
|
|
\lstinline[mathescape=true]{select * from $R_i$ where $C_n$ $\leq$ all(select $C_m$ from $R_j$ where P)}
|
|
|
|
wird in
|
|
|
|
\lstinline[mathescape=true]{select * from $R_i$ where $C_n$ $\leq$ (select $min(C_m)$ from $R_j$ where P)}
|
|
|
|
transformiert.
|
|
|
|
Ein Prädikat \lstinline[mathescape=true]{$C_n$ = all(select $C_m$ from $R_i$ where P)} hat keine sinnvolle Semantik, mehr, sobald die geschachtelte Abfrage mehr als einen Wert für $C_m$ liefert. Die Verwendung von \sql{Epxr = all(Q)} wird in diesem Fall nicht unterstützt.
|
|
|
|
Mit diesen Transformationen lassen sich auch quantifizierte Prädikate als Prädikate der Typen J oder JA betrachten. Dies hat zur Folge, dass sich die im Folgenden beschriebenen Optimierungsalgorithmen auch auf diese Prädikate anwenden lassen. Außerdem sind die Prädikate einer Übersetzung in ausführbare \textsc{Secondo}-Syntax besser zugänglich, da die Aggregation bereits durch Standard-Operatoren implementiert ist. Eine Einführung weiterer Operatoren zur Übersetzung quantifizierter Prädikate ist hierdurch nicht notwendig.
|
|
|
|
|
|
\section{Entschachtelungsalgorithmen}\label{sct:Entschachtelungsalgorithmen}
|
|
\subsection{Algorithmus \textbf{NEST-N-J}}\label{sct:Algorithmus NEST-N-J}
|
|
In \cite{319745} wird gezeigt, dass eine Abfrage der Form
|
|
\begin{lstlisting}
|
|
SELECT §$R_i.C_k$§
|
|
FROM §$R_i$§
|
|
WHERE §$R_i.C_h$§ IN (SELECT §$R_j.C_m$§ FROM §$R_j$§ WHERE P)
|
|
\end{lstlisting}
|
|
|
|
\begin{tikzpicture}[grow=right]
|
|
\path
|
|
(0,0) node[place] (Ri) {$R_i$}
|
|
(2.5,0) node[place,draw](Rj) {$R_j$};
|
|
\draw[-] (Ri) -- node[mylabel] {$N(C_m)$} (Rj);
|
|
\end{tikzpicture}
|
|
|
|
|
|
|
|
inhaltlich äquivalent zu einer Abfrage
|
|
|
|
\begin{lstlisting}
|
|
SELECT §$R_i.C_k$§
|
|
FROM §$R_i, R_j$§
|
|
WHERE §$R_i.C_h = R_j.C_m$§ AND P
|
|
\end{lstlisting}
|
|
|
|
ist.
|
|
|
|
Dabei wird die mengenbasierte Semantik des IN-Operators nur dann erhalten, wenn $R_j$ bezüglich der Spalte $C_m$ duplikatfrei ist. Dies kann aber jederzeit erreicht werden, indem $R_j$ durch eine temporäre Relation $R_{j}^{'}$ ersetzt wird. $R_{j}^{'}$ erhält man, indem $R_j$ auf die Spalte $C_m$ eingeschränkt und projiziert wird. Für skalare Vergleichsoperatoren gilt die Äquivalenz ohne diese Einschränkung.
|
|
|
|
%Skalare Vergleichsoperatoren sind nicht \enquote{duplikats-entfernend}.
|
|
|
|
\begin{lstlisting}
|
|
SELECT §$R_i.C_k$§
|
|
FROM §$R_i$§
|
|
WHERE §$R_i.C_h$§ NOT IN (SELECT §$R_j.C_m$§
|
|
FROM §$R_j$§);
|
|
\end{lstlisting}
|
|
|
|
lässt sich dagegen nicht mit
|
|
|
|
\begin{lstlisting}
|
|
SELECT §$R_i.C_k$§
|
|
FROM §$R_i, R_j$§
|
|
WHERE §$R_i.C_h <> R_j.C_m$§
|
|
\end{lstlisting}
|
|
|
|
übersetzen, da die Semantik und das Ergebnis der beiden Abfragen nicht übereinstimmen. In der geschachtelten Abfrage werden als Ergebnis alle Tupel der äußeren Relation erwartet, zu denen es keine Entsprechung in der inneren Relation gibt. Das Ergebnis der zweiten Abfrage ist die Teilmenge des Kreuzprodukts der beiden Relationen, die in den Werten $C_h$ und $C_m$ nicht übereinstimmen. Die benötigte Operation der Relationenalgebra ist der Anti-Join. In den für \textsc{Secondo} implementierten Algebramodulen für die Relationenalgebra, \algebra{Relation-C++}- und \algebra{ExtRelation-C++}-Algebra ist kein entsprechender Operator enthalten. Eine Implementierung im Rahmen dieser Diplomarbeit erfolgte aus den oben genannten Gründen nicht.
|
|
|
|
Ziel des Algorithmus ist es, ein geschachteltes Prädikat in eine inhaltlich identische entschachtelte Form zu überführen. Basis der Transformation ist die Beobachtung, dass das Ergebnis einer geschachtelten Abfrage vom Typ N oder J und einer Abfrage über allen verwendeten Relationen mit einem zusätzlichen Join-Prädikat identisch ist. Die Entschachtelung entspricht dabei der Verschmelzung der Abfrageblöcke, jede Relation des äußeren und des inneren Abfrageblocks wird erhalten. Die entschachtelte Abfrage enthält alle Prädikate der beiden Abfrageblöcke. Das neue Join-Prädikat hält die Vergleichsbedingung zwischen Attributausdruck des äußeren Abfrageblocks mit den Spalten des inneren Abfrageblocks fest. Da die Join-Operation in SQL Multimengen-Semantik hat, müssen bei Operatoren mit strenger Mengensemantik, z.B. \sql{in}, die inneren Relationen vor der Verwendung in der entschachtelten Variante auf ihre Join-Spalten projiziert und durch die Prädikate der Unterabfrage eingeschränkt werden. Andernfalls liefert das neue Join-Prädikat durch Duplikate möglicherweise falsche Ergebnisse. Da das Ergebnis der Projektion und Selektion maximal die gleiche Mächtigkeit hat, wie die Ursprungsrelationen, wirkt sich diese zusätzliche Operation in den meisten Fällen eher positiv auf die Anzahl der I/O-Operationen aus (siehe auch \cite{319745}). Die Durchführung des Algorithmus entspricht im Abfragegraphen der Entfernung einer geraden Kante mit der Markierung $N(C_k)$. Grundsätzlich kann mit dem hier beschriebenen Algorithmus auch eine gerade Kante mit der Markierung $Nx(C_k)$, d.h. ein geschachteltes Prädikat mit dem \sql{not in}-Operator aufgelöst werden. Der hierfür notwendige Anti-Join-Operator und die für diesen Operator notwendige Sonderbehandlung im Optimierer bleiben aber einer zukünftigen Erweiterung des Optimierers vorbehalten.
|
|
|
|
\begin{algorithm}
|
|
Algorithmus NEST-N-J
|
|
\begin{algorithmic}
|
|
\STATE \textbf{procedure} $nest\_n\_j(Q)$
|
|
\STATE $combine\_from\_clauses(outer\_query\_block, inner\_query\_block)$
|
|
\STATE $combine\_where\_clauses(outer\_query\_block, inner\_query\_block)$
|
|
\STATE $insert\_new\_join\_predicate(Q)$
|
|
\end{algorithmic}
|
|
\end{algorithm}
|
|
|
|
Die \sql{from}-Klausel der entschachtelten Abfrage setzt sich aus der Vereinigung der \sql{from}-Klauseln des äußeren und inneren Abfrageblocks zusammen. Für die neue \sql{where}-Klausel wird die Konjunktion der Prädikate der beiden \sql{where}-Klauseln gebildet. Zusätzlich wird diese um ein Join-Prädikat ergänzt, das aus dem ursprünglich geschachtelten Prädikat abgeleitet ist. Das Prädikat ist der Vergleich des im geschachtelten Prädikat auf der linken Seite auftretenden Attributs mit dem Attribut der \sql{select}-Klausel des inneren Abfrageblocks. Der Vergleichsoperator des neuen Prädikats ist nur für die Operatoren \sql{in} und \sql{not in} abweichend von dem im geschachtelten Prädikat verwendeten Operator. Ist \sql{in} der Operator des geschachtelten Prädikats, so wird der Equi-Join benötigt; der Operator des neuen Join-Prädikats ist dann \enquote{=}. Beim Operator \sql{not in} ist ein Anti-Join notwendig. Eine entsprechende Syntax existiert im SQL-Standard nicht. Die \sql{select}-Klausel der ursprünglichen Abfrage bleibt unverändert.
|
|
|
|
%Alle FROM-Klausel kombinieren. Die resultierende FROM-Klausel umfasst alle Relationen des inneren und des äußeren Abfrageblocks\\
|
|
%Die Konjunktion der \sql{where}-Klauseln bilden.\\
|
|
%Das geschachtelte Prädikat \begin{math}[R_i.C_h \operatorname{op}\ (\texttt{SELECT} R_j.C_m \dots)]\end{math} ersetzen durch ein neues Join-Prädikat \begin{math}[R_i.C_h \operatorname{\emph{new-op}} R_j.C_m]\end{math}, das mit der restlichen \sql{where}-Klausel per Konjunktion verbunden ist\\
|
|
%Die SELECT-Klausel des äußersten Abfrageblocks behalten\\
|
|
|
|
\begin{lstlisting}
|
|
select §$A_1 ,\dots,A_n$§
|
|
from §$R_1 ,\dots,R_m$§
|
|
where §$P_1,\dots,P_l,$§
|
|
§$X\operatorname{\theta}\ ($§
|
|
select §$T_i.B$§
|
|
from §$T_1 ,\dots,T_s$§
|
|
where §$Q_1,\dots,Q_r$§
|
|
§$)$§
|
|
\end{lstlisting}
|
|
|
|
wird transformiert in
|
|
|
|
\begin{lstlisting}
|
|
select §$A_1,\dots,A_n$§
|
|
from §$R_1,\dots,R_m,T_1,\dots,T_s$§
|
|
where §$P_1,\dots,P_l$§,
|
|
§$Q_1,\dots,Q_r$§
|
|
§$X\operatorname{\theta^'}T_i.B$§
|
|
\end{lstlisting}
|
|
|
|
wobei gelten muss
|
|
|
|
\begin{math}
|
|
X \subset\{A_1,\ldots,A_n\} \\[1em]
|
|
\theta \in \{\operatorname{IN},\operatorname{NOT\ IN},=,\not=,>,\geq,<,\leq\} \\[1em]
|
|
\theta^{'} =
|
|
\begin{cases}
|
|
=&\quad\text{falls}\ \theta = \operatorname{IN}, \\
|
|
\triangleright&\quad\text{falls}\ \theta = \operatorname{NOT\ IN}, \\
|
|
\theta&\quad\text{sonst}.
|
|
\end{cases}
|
|
\end{math}
|
|
|
|
$\triangleright$ entspricht hier dem Anti-Join-Operator
|
|
|
|
Der Beweis für die Korrektheit wird in \cite{319745} geführt. Aus der im SQL Standard definierten Semantik für geschachtelte Abfragen, ergibt sich, dass für jedes betrachtete Tupel der äußeren Abfrage die innere Abfrage ausgewertet werden kann. Für den Operator \sql{in} bedeutet dies, dass das geschachtelte Prädikat \sql{true} ist, wenn der Wert $X$ im Ergebnis der inneren Abfrage enthalten ist. Das Join-Prädikat der entschachtelten Abfrage ist genau dann \sql{true}, wenn das geschachtelte Prädikat wahr ist.
|
|
|
|
\subsection{Algorithmus \textbf{NEST-JA2}}\label{sct:Algorithmus NEST-JA2}
|
|
Der Algorithmus basiert auf der Idee, die benötigten Aggregationswerte für jede Ausprägung der Selektion, d.h. für jede Belegung der Attribute der \sql{where}-Klausel, nur einmal zu berechnen. Um die Auswertung der Aggregation nicht bei jedem Tupel der äußeren Relation durchführen zu müssen, wird mit temporären Tabellen gearbeitet. Es wird die Aggregation über die inneren Relationen, gruppiert nach Werten des Join-Attributs der äußeren Spalte, vorausberechnet. Da die temporären Relationen persistent abgelegt werden, fällt der Aufwand für ihr Anlegen nur einmal an. Wird die Abfrage während einer Sitzung erneut aufgerufen, so kann auf die bestehenden temporären Relationen zugegriffen werden, d.h. die Laufzeitkosten für die Erstellung der temporären Relationen fallen während einer Sitzung nur einmal an.
|
|
|
|
\begin{algorithm}
|
|
Algorithmus NEST-JA2
|
|
\begin{algorithmic}
|
|
\STATE \textbf{procedure} $nest\_ja2(Q)$
|
|
\STATE $Rt_1 = project\_outer\_relations(Q)$
|
|
\STATE $Rt_2 = project\_and\_restrict\_inner\_relations(Q)$
|
|
\IF{Aggregationsfunktion ist COUNT}
|
|
\IF{COUNT(*)}
|
|
\STATE \textit{Ersetze * durch Join-Attribut}
|
|
\ENDIF
|
|
\STATE $Rt_3 = outerjoin\_and\_aggregate(Rt_1, Rt_2)$
|
|
\ELSE
|
|
\STATE $Rt_3 = join\_and\_aggregate(Rt_1, Rt_2)$
|
|
\ENDIF
|
|
\STATE $equi\_join(outer\_relations, Rt_3)$
|
|
\end{algorithmic}
|
|
\end{algorithm}
|
|
|
|
Im ersten Schritt werden die benötigten äußeren Relationen auf die in der Unterabfrage verwendeten Attributspalten projiziert und durch den Zusatz \sql{distinct} um Duplikate bereinigt. Hiermit erhält man die erste temporäre Relation $Rt_1$. Die zweite temporäre Relation $Rt_2$ ist die Projektion der inneren Relationen auf die Attribute der korrelierten Prädikate und Restriktion durch alle einfachen Prädikate der Unterabfrage. Die dritte temporäre Relation $Rt_3$ ist ein Join aus den beiden vorhergehenden temporären Relationen. Hierbei wird die Aggregation vorausberechnet und nach den Join-Attributen der äußeren Relationen gruppiert. Handelt es sich bei der Aggregationsfunktion um \sql{count}, so muss ein Full-Outer-Join ausgeführt werden, da hier \sql{null}-Werte das Ergebnis der Aggregation beeinflussen.
|
|
|
|
Wird \sql{count} in der ursprünglichen Abfrage auf Tupelebene ausgeführt (\sql{select count(*)}), so muss die Aggregation bei der Erstellung der temporären Relation auf eine der Join-Spalten geändert werden, um das Ergebnis nicht zu verfälschen. Es dürfen nur die Tupel gezählt werden, die für Spalten, die aus den inneren Relationen stammen, keine \sql{null}-Werte annehmen. Die Projektion der Relation $Rt_3$ erfolgt auf die entsprechenden Attribute der äußeren Relationen, gegebenenfalls müssen die Attribute in der \sql{select}-Klausel umgesetzt werden. Die entschachtelte Version der Abfrage wird gebildet, indem die ursprüngliche \sql{from}-Klausel um die temporäre Relation $Rt_3$ erweitert wird. Das geschachtelte Prädikat der \sql{where}-Klausel wird durch ein neues Join-Prädikat ersetzt, welches auf Übereinstimmung der ursprünglichen linken Seite des geschachtelten Prädikats mit der Aggregationsspalte von $Rt_3$ prüft. Der Algorithmus entspricht der Entfernung einer gebogenen Kante und einer geraden Kante zwischen zwei Knoten im Abfragegraphen.
|
|
|
|
|
|
%1. temporäre Relation = Projektion der äußeren Relation(en) auf die Join-Spalten und Restriktion durch \emph{simple} Prädikate, die in der äußeren \sql{where}-Klausel enthalten sind\\
|
|
%2. temporäre Relation = Join aus 1. temporärer und innerer/en Relationen, falls Aggregationsfunktion = \sql{count}, dann müssen die simplen Prädikate auf die innere Relation vor dem Join angewandt werden. Ist die Aggregationsfunktion \sql{count}(*), dann muss \sql{count} über die/eine Join-Spalte ausgeführt werden. Das Join-Prädikat muss den selben Operator enthalten, wie die ursprüngliche geschachtelte Abfrage. Der Join-Operator in der ursprünglichen Abfrage muss in \emph{=} geändert werden. In der \sql{select}-Klausel muss die Join-Spalte der äußeren Relation anstelle der Inneren Relation verwendet werden\\
|
|
|
|
Der Algorithmus und die einzelnen Zwischenschritte werden hier in SQL-Syntax dargestellt. Die ursprüngliche Abfrage hat die allgemeine Form:
|
|
|
|
\begin{lstlisting}
|
|
select §$A_1, \dots, A_n$§
|
|
from §$R_1, \dots, R_m$§
|
|
where §$P_1 (R_1 ), \dots, P_k (R_1 )$§,
|
|
§$P_1 , \ldots, P_l,$§,
|
|
§$R_i.X \operatorname{\theta}\ ($§
|
|
select §$\operatorname{AGGR}(T_j.A)$§
|
|
from §$T_1 , \dots, T_s$§
|
|
where §$Q_1, \dots, Q_r$§,
|
|
§$R_1.Y \operatorname{op} T_1.Z$§
|
|
§$)$§
|
|
\end{lstlisting}.
|
|
|
|
Die $A_1, \dots, A_n$ sind Attributausdrücke über den Schemata der Relationen $R_1, \dots, R_m$. Die Prädikate $P_1(R_1), \dots, P_k(R_1)$ sind ausschließlich über Attributen der Relation $R_1$ formuliert, sind also \emph{einfache} Prädikate über $R_1$. Die Prädikate $P_1, \dots, P_l$ sind allgemeine Prädikate über Attributen einer oder mehrerer beliebiger Relationen aus $R_1, \dots, R_m$.
|
|
|
|
Die erste temporäre Relation ist die Projektion der äußeren Relation auf die Join-Spalte des korrelierten Prädikats. Es werden bereits alle \emph{einfachen} Prädikate angewandt, da diese auschließlich über Attributen von $R_1$ definiert sind. Die Anwendung der Prädikate bereits zu diesem Zeitpunkt führt zu einer garantiert nicht größeren temporären Relation, da die Selektions-Operation das Ergebnis eines relationalen Ausdrucks nur verkleinern kann. Die temporäre Relation ergibt sich aus der Abfrage als
|
|
|
|
\begin{lstlisting}
|
|
§$Rt_1$§ =
|
|
select distinct §$R_1.Y$§
|
|
from §$R_1$§
|
|
where §$P_1(R_1), \dots, P_k(R_1)$§
|
|
\end{lstlisting}.
|
|
|
|
Für die zweite temporäre Relation werden die inneren Relationen auf die Join-Spalten projiziert und durch die \emph{einfachen} Prädikate eingeschränkt. Es ergibt sich aus der obigen Abfrage folgender Ausdruck:
|
|
|
|
\begin{lstlisting}
|
|
§$Rt_2$§ =
|
|
select §$T_j.A, T_1.Z$§
|
|
from §$T_1, \dots, T_s$§
|
|
where §$Q_1, \dots, Q_r$§
|
|
\end{lstlisting}.
|
|
|
|
Für Aggregationsfunktionen ungleich \sql{count} ergibt sich folgender Ausdruck für die dritte temporäre Relation:
|
|
|
|
\begin{lstlisting}
|
|
§$Rt_3$§ =
|
|
select §$Rt_1.Y,\operatorname{AGGR}(Rt_2.A)$§ as AggrResult
|
|
from $Rt_1, Rt_2$
|
|
where §$Rt_1.Y \operatorname{op} Rt_2.Z$§
|
|
group by
|
|
§$Rt_2.Y$§
|
|
\end{lstlisting}.
|
|
|
|
Die Aggregation wird also für jeden unterschiedlichen Wert von $Rt_2.Y$ genau einmal berechnet.
|
|
|
|
Ist die Aggregationsfunktion \sql{count}, so muss abweichend hiervon der Full-Outer-Join berechnet werden. Die hier angegebene Syntax für den Outer-Join entspricht der Syntax des SQL-Standards. Die momentan im Optimierer implementierte Struktur von SQL-Abfragen lässt eine einfache Erweiterung um diese Outer-Join-Syntax nicht zu. Erschwerend kommt hinzu, dass Join- und Outer-Join-Operationen nur unter eingeschränkten Bedingungen in ihrer Reihenfolge vertauscht werden können \cite{244812}. Die Integration in den zentralen Optimierungsalgorithmus würde daher umfangreiche Veränderungen voraussetzen, um diese Randbedingungen während der Optimierung überprüfen zu können. Daher wurde auf die Integration des Outer-Join-Operators in die SQL-Syntax des Optimierers verzichtet. Die Erzeugung der entsprechenden temporären Relation erfolgt direkt in ausführbarer Syntax.
|
|
|
|
\begin{lstlisting}
|
|
§$Rt_3$§ = (
|
|
select §$Rt_1.Y,\operatorname{AGGR}(Rt_2.A)$§ as AggrResult
|
|
from §$Rt_1$§ full outer join §$Rt_2$§
|
|
on §$Rt_1.Y \operatorname{op} Rt_2.Z$§
|
|
group by
|
|
§$Rt_1.Y$§
|
|
§$)$§
|
|
\end{lstlisting}
|
|
|
|
Für den in dieser Arbeit umgesetzten Operator \secondo{smouterjoin} ergibt sich damit in ausführbarer Syntax
|
|
|
|
\begin{lstlisting}
|
|
§$Rt_3$§ =
|
|
query
|
|
§$Rt_1$§ feed
|
|
§$Rt_2$§ feed
|
|
smouterjoin[Rt_1.Y, Rt_2.Z]
|
|
groupby[AggrResult: group feed §$\operatorname{AGGR}(Rt_2.A)$§]
|
|
project[§$Rt_1.Y$§, AggrResult]
|
|
consume
|
|
\end{lstlisting}.
|
|
|
|
Hierbei gilt allerdings die oben beschriebene Einschränkung, dass das Prädikat \\pred$[Rt_1.Y \operatorname{op} Rt_2.Z]$ die Form $Rt_1.Y = Rt_2.Z$ haben muss.
|
|
|
|
Nach der Berechnung der temporären Tabellen vereinfacht sich die ursprüngliche Abfrage zu
|
|
|
|
\begin{lstlisting}
|
|
select §$A_1, \dots, A_n$§
|
|
from §$R_1, \dots, R_m, Rt_3$§,
|
|
where §$P_1, \dots, P_l,$§
|
|
§$R_i.X \operatorname{\theta} Rt_3$§.AggrResult,
|
|
§$R_1.Y = Rt_3.Z$§
|
|
\end{lstlisting}
|
|
|
|
wobei für den Operator $\theta$ gelten muss
|
|
|
|
\begin{math}\theta \in\{=,\not=,>,\geq,<,\leq\}\end{math}.
|
|
|
|
\subsection{Algorithmus NEST-G}\label{sct:Algorithmus NEST-G}
|
|
Ganski/Wong stellen in \cite{38723} ihren allgemeinen Algorithmus zur Optimierung geschachtelter Abfragen vor. Dieser wendet rekursiv je nach Schachtelungstyp die oben vorgestellten Algorithmen auf geschachtelte Prädikate an. Geschachtelte Prädikate, die keiner der vier Klassen entsprechen, werden wie gewöhnliche Join- oder Selektions-Prädikate behandelt.
|
|
|
|
\begin{algorithm}
|
|
\caption{Algorithmus NEST-G}
|
|
\begin{algorithmic}
|
|
\STATE \textbf{procedure} $nest\_g(Q)$
|
|
\FORALL{Prädikate p in Q}
|
|
\IF{p ist ein geschachteltes Prädikat mit Unterabfrage $Q_u$}
|
|
\STATE $nest\_g(Q_u)$
|
|
\IF{$Q_u$ ist vom Typ A}
|
|
\STATE $nest\_a(Q_u)$
|
|
\ELSIF{$Q_u$ ist vom Typ JA}
|
|
\STATE $nest\_ja2(Q_u)$
|
|
\STATE $nest\_n\_j(Q, Q_u)$
|
|
\ENDIF
|
|
\ENDIF
|
|
\STATE $nest\_n\_j(Q_u)$
|
|
\ENDFOR
|
|
\end{algorithmic}
|
|
\end{algorithm}
|
|
|
|
|
|
Der Algorithmus NEST-A wandelt ein geschachteltes Prädikat einer Abfrage in ein einfaches Prädikat um. Mit Algorithmus NEST-JA2 wird ein geschachteltes Prädikat vom Typ-JA in ein geschachteltes Prädikat vom Typ-J umgewandelt. Durch die Anwendung von Algorithmus NEST-N-J nach NEST-JA2 wird dieses Prädikat in ein einfaches Join-Prädikat transformiert.
|
|
|
|
Damit entfernen die beiden Algorithmen NEST-A und NEST-N-J eine Schachtelungsebene aus einem Prädikat. Da NEST-G rekursiv auf die Unterabfragen angewandt wird wird in jedem Schleifendurchlauf des Algorithmus die Schachtelungstiefe eines Prädikats um eins erniedrigt. Da geschachtelte Prädikate eine Schachtelungstiefe $>= 1$ haben, ist sichergestellt, dass der Algorithmus terminiert. Nach Beendigung des Algorithmus enthält die transformierte Version von Q keine geschachtelten Prädikate der Typen A, N-J oder JA.
|
|
|
|
|
|
%allgemeiner Algorithmus zur Entschachtelung mit temporären Tabellen, auf der Basis von Join- und Outerjoin-Operatoren und temporären Tabellen,
|
|
%
|
|
%
|
|
%Begriffserklärung Schachtelunsgtiefe
|
|
%
|
|
%Schritte des Optimierers
|
|
%\begin{itemize}
|
|
% \item Query-Rewriting
|
|
% \item Schema-Lookup
|
|
% \item Abfrageplan-Ermittlung
|
|
% \item Übersetzung Plan in ausführbare Syntax
|
|
%\end{itemize}
|
|
%
|
|
%
|
|
%durch vorhanden Operatoren in \textsc{Secondo} fast vollständig umsetzbar, nur Erweiterung um Full-Outerjoin-Operator notwendig\\
|
|
%Vergleich nested-iteration/dekorrelierter Plan wird \underline{nicht} implementiert:\\
|
|
%Für den Vergleich der Kosten müsste die komplette Selektivitäts- und Kostenbestimmung für die notwendigen temporären Relationen simuliert werden (**ausführlicher beschreiben**)\\
|
|
%kann nicht für alle Formen von Subqueries genutzt werden (Beispiele)\\
|
|
%Ergebnis sollte eine Abfrage der Schachtelungstiefe 0 sein. Der Optimierer
|
|
%
|
|
%\begin{enumerate}
|
|
% \item Jedes Typ-A Prädikat wird in ein einfaches Prädikat umgewandelt, indem es vollständig ausgewertet wird. Falls das Prädikat ebenfalls geschachtelt ist, wird Algorithmus NEST-G rekursiv angewandt, bis das Prädikat nicht weiter entschachtelt werden kann.
|
|
% \item Jedes Typ-JA Prädikat wird mit dem Algorithmus NEST-JA(G) in eine semantisch äquivalente Abfrage vom Typ-N oder -J umgewandelt.
|
|
% \item \emph{In dieser Arbeit nicht implementiert}: Prädikate vom Typ-D werden in ihre kanonische Form transformiert.
|
|
% \item Die aus den bisherigen Schritten des Algorithmus resultierende Abfrage hat nur noch Prädikate vom Typ-N oder Typ-J. Diese wird mit dem Algorithmus NEST-N-J in ihre kanonische Form überführt.
|
|
%\end{enumerate}
|
|
|
|
\section{Übersetzung in ausführbare Syntax}
|
|
\subsection{IN Operator}
|
|
Die \algebra{Collection}-Algebra stellt mit dem Operator \algebra{in} einen mengenbasierten Operator bereit, der die Semantik des SQL-Operators \sql{in} implementiert. Da der Typ-Konstruktor für \algebra{set} (entspricht einer Menge) über beliebigen Datentypen aus der \emph{Kind} \algebra{DATA} definiert ist, steht die \sql{in}-Operation für beliebige Attributausdrücke zur Verfügung, denn Attribute ebenfalls über \algebra{DATA} definiert sind. Daher wurde dieser Operator für die Übersetzung von Prädikaten mit dem \sql{in}-Operator ausgewählt.
|
|
|
|
\subsection{Geschachtelte Iteration}
|
|
Geschachtelte Prädikate, die nicht durch die oben beschriebenen Algorithmen in flache Abfragen überführt werden können, sollen dennoch vom Optimierer ausgeführt werden können. Die geschachtelte Iteration ist die triviale Umsetzung der Semantik geschachtelter Prädikate. Die Übersetzung der Unterabfragen soll hierbei soweit wie möglich mit den bereits vorhandenen Verfahren des Optimierers erfolgen, um eine effiziente Übersetzung zu gewährleisten. Korrelierte Prädikate können nicht direkt in die Übersetzung der Unterabfrage einbezogen werden. Daher werden die korrelierten Prädikate für die Übersetzung von der Unterabfrage entfernt. Die so entstandene, einfache Abfrage wird mit dem Optimierungsalgorithmus in einen Plan übersetzt. Die korrelierten Prädikate werden dann auf diesen Plan als Selektionsprädikate angewandt, d.h. es werden erst alle einfachen und alle Join-Prädikate ausgeführt und auf deren Ergebnis die korrelierten Prädikate als Selektion ausgeführt. Ist mehr als ein korreliertes Prädikat vorhanden, so werden die Prädikate in der Reihenfolge ihrer Selektivität ausgeführt. Prädikate mit hoher Selektivität werden zuerst ausgeführt, um das noch weiter zu verarbeitende Ergebnis zu minimieren.
|
|
|
|
|
|
%
|
|
% EOF
|
|
%
|
|
% |