*) 3.3 Relationenalgebra Die Beispiele können mit der dem Buch beigefügten Datenbank nachvollzogen werden. Am Schluss dieses Skripts sind die darin enthaltenen Tabellen abgedruckt, sodass man auch ohne Computer die Beispiele nachvollziehen kann. In der Relationenalgebra werden aus vorhandenen Relationen neue Relationen erzeugt. In den Abfragesprachen der kommerziell verfügbaren Datenbanksysteme (z. B. in SQL) sind Konstruktionen enthalten, die die Operationen der Relationenalgebra realisieren. Wir benutzen hier eine kompakte Sprache, die im Gegensatz zu SQL die einzelnen Operationen der Relationenalgebra direkt beschreibt. Diese neuen Relationen können in folgender Weise verwandt werden: Snapshot Es wird eine neue Relation (im Hauptspeicher oder auf der Platte) angelegt, die aus den aktuell gültigen Daten abgeleitet wird. Auf diese Relation kann anschließend zugegriffen werden. Zwischenzeitliche Änderungen in den benutzten Ursprungsdaten bleiben dabei unberücksichtigt. Datenausgabe Die erzeugte Relation wird lediglich auf dem Bildschirm, Drucker oder in eine Datei ausgegeben und kann anschließend vom Datenbanksystem nicht mehr verarbeitet werden. Virtuelle Datensicht Die neue Relation wird nicht sofort angelegt, sondern bei jedem Zugriff auf diese Datensicht wird sie aus den aktuell gültigen Daten erzeugt; diese Relation muss daher auch nicht gespeichert werden. 3.3.1 Die Auswahl (Selektion) Sei R eine Relation, bedingung sei ein logischer Ausdruck (entsprechend einem Booleschen Ausdruck in Programmiersprachen wie Pascal), der aus Attributbezeichnern von R (als Variablen), aus Konstanten, aus den entsprechenden Operatoren und Funktionen, aus den Vergleichsoperatoren =, ≠, <, ≤, > , ≥, aus den logischen Operatoren ∧, ∨, ¬ gebildet wird. Durch Sel [bedingung] (R) wird eine Relation definiert, die genau die Tupel enthält, die die Bedingung erfüllen (d. h. für die der Boolesche Ausdruck den Wert TRUE ergibt). Der einfachste Fall der logischen Bedingung ist der Des weiteren können mehrere Attribute miteinander verglichen werden, z. B. Vergleich eines Attributes mit einer Konstanten der entsprechenden Domäne, z. B. Ankunft < Abfahrt Name = 'Meyer' Listenpreis < 100,00 DM Ankunft < TIME '12:00' Ort ≠ 'Bremen' Listenpreis > Einkaufspreis Wohnort ≠ Arbeitsort *) Dieses Skript ist ein Auszug aus dem Kapitel 3.3 (Seite 79ff) von: Matthiessen, G., Unterstein, M. Relationale Datenbanken und SQL, Addison-Wesley Verlag, 1997, ISBN 3-8273-1167-5 1 Bedingungen können des weiteren mit logischen Operatoren verknüpft werden, z. B. Schließlich können in die Vergleiche auch berechnete Ausdrücke eingehen, wie z. B. Wohnort = 'Bremen' ∨ Arbeitsort 'Bremen' Name = 'Matthiessen' ∧ Vorname 'Günter' Ankunft + TIME'0:08' < Abfahrt Länge < 1,41 * Breite ∧ Länge <235 mm ∧ Breite ≤ 125mm ∧ Höhe ≤ 5mm ∧ Gewicht ≤ 20 g Beispiel Relation, die die Artikel enthält, die nachbestellt werden müssen: Sel[Bestand]< Mindestbestand] (Artikel) Beispiel Relation, die die Kunden aus dem PLZ-Bezirk 23863 sowie die Kunden aus Husum enthält: Sel [PLZ = '23863' v Ort = 'Husum'] (Kunde) Eine Selektion besteht also darin, dass in einer Relation einige Zeilen gestrichen werden und nur die übrigbleiben, die die angegebene Bedingung erfüllen: Im Spezialfall dient die Selektion auch dazu, einen einzelnen Satz zu selektieren, indem ein eindeutiges Merkmal angegeben wird (z. B. der Schlüssel). Falls dieses Merkmal dann nicht in der Relation vorhanden ist, erhalten wir als Ergebnis die leere Relation, die also nur aus den Spaltenüberschriften besteht. Beispiel Relation, die genau das Tupel mit den Kundendaten 103 enthält. Sel[Kunden-Nr = 1031 (Kunde) 2 Beispiel Relation, die genau das Tupel mit den Kundendaten des Kunden 128 enthält. Sel[Kunden-Nr = 1281 (Kunde) Es handelt sich hierbei um eine leere Relation, die aussagt, dass kein Tupel die geforderte Bedingung erfüllt. 3.3.2 Die Projektion Sei R eine Relation und {r1,..., rm} eine Teilmenge der Attribut-Bezeichner von R. Dann ist Proj[r1,..., rm] (R) Eine Relation mit den Attributbezeichnern {r1,..., rm}, die für jedes Tupel t∈R genau ein Tupel [t.r1,..., t.rm] enthält (Tupel, die mehrfach auftreten, werden einfach gezählt!). Das heißt, es zählt nur die Information der angegebenen Attribute - die übrigen Attribute werden ignoriert. Dadurch können Tupel der Relation R zusammenfallen, wenn sie nämlich in allen Attributen r1,..., rm, übereinstimmen, sich aber in mindestens einem Attribut das nicht in der Projektion auftritt, unterscheiden. Beispiel Relation, die für jeden Artikel neben der Artikelnummer und der Bezeichnung den Lagerplatz und den Bestand angibt (Bestandsliste). Proj[Artikel_Nr,Bezeichnung,Lagerplatz,Bestand](Artike1) Beispiel Relation, die alle Orte mit PLZ enthält in denen Kunden wohnen. Hierbei tauchen Orte mit mehreren Kunden nur einmal auf. Proj[plz, ort] (Kunde) 3 Die Projektion besteht also darin, dass gewisse Spalten ausgeblendet werden; nur die angegebenen Spalten bleiben übrig. Dabei kann es als Folgeaktion notwendig werden, bei mehrfach auftretenden identischen Zeilen jeweils alle bis auf eine zu streichen. 3.3.3 Umbenennung von Attributbezeichnern Die Umbenennung von Attributen ist in folgenden Fällen notwendig: 1. Änderung der Überschrift einer auszugebenden Tabelle 2. Zwei Tabellen sollen miteinander verknüpft werden (wird in Kapitel 3.3.6 eingeführt), dazu müssen die korrespondierenden Attribute in den beiden Relationen jeweils denselben Bezeichner haben. 3. Zwei Tabellen sollen miteinander verknüpft werden, dazu müssen die nicht korrespondierenden Attribute in den beiden Relationen jeweils unterschiedliche Bezeichner haben. Wenn a ein Attribut einer Relation R ist und b ein Bezeichner, der nicht als Attributbezeichner von R auftritt, wird durch Ren[b ::= a] (R) eine Relation definiert, in der das Attribut a jetzt b heißt. Falls mehrere Umbenennungen erfolgen, schreiben wir diese hintereinander in eckige Klammern, wie in folgendem Beispiel. Ren [KNr ::= Kunden_Nr, St ::= Status, Lbest ::= letzte_Bestellung, Lwerb ::= letzte_Werbung, ZArt ::= Zahlungsart] (Kunde) 4 Da eine Umbenennung häufig im Zusammenhang mit einer Projektion erfolgt, schreiben wir in Zukunft als Kurzschreibweise die umbenannten Attribute in die Liste der Projektionsattribute mit hinein. Die umbenannten Attribute sind grundsätzlich mit in die Liste der zu projizierenden Attribute einzubeziehen (sonst bräuchten wir sie ja nicht erst umzubenennen). So wird Proj[ArtNr,Bezeichnung,Lager,Ist] ( Ren[ArtNr::=Artikel_Nr, Lager::=Lagerplatz, Ist::=bestand] (Artikel) ) zu Proj[ArtNr::=Artikel_Nr, Bezeichnung, Lager::=Lagerplatz, Ist::= bestand](Artikel) 3.3.4 Adjunktion virtueller Spalten Sei R eine Relation und sei a ein Ausdruck, der aus Attributbezeichnern der Relationen und Operationen, die für die Domänen definiert sind, sowie Konstanten gebildet ist und s sei ein Bezeichner, der nicht als Attributbezeichner in R auftritt; dann wird durch Adj[s ::= a] (R) eine Relation definiert, die eine zusätzliche Spalte enthält, in der als Wert jeweils die Auswertung von a enthalten ist. Diese Spalte wird als virtuelle Spalte bezeichnet. Für ein Tupel t hängt also der Wert von s von den Werten der (echten) Attribute ab. Üblicherweise wird daher der Wert von s nicht mit in der Datenbank gespeichert, sondern bei jedem Aufruf neu berechnet. 5 Auch hier übernehmen wir üblicherweise die adjungierten Spalten mit in die Projektionsattribute, vgl. hierzu das folgende Beispiel. Beispiel Relation, die für jeden Artikel die angegebenen Attribute sowie das Produkt aus Bestand und Listenpreis enthält. Proj[ArtNr, Bezeichnung, Bestand, Listenpreis, Wert] ( Ren[ArtNr ::= Artikel_Nr] ( Adj[Wert ::= Bestand * Listenpreis] (Artikel) ) ) oder kürzer: Proj[ArtNr ::= Artikel_Nr, Bezeichnung, Bestand, Listenpreis, Wert ::= Bestand * Listenpreis] (Artikel) 3.3.5 Gruppierung von Daten Wie wir gesehen haben, werden bei der Projektion gegebenenfalls mehrere Tupel jeweils zusammengefasst: wenn sie in den Projektionsattributen identisch sind, sich aber in anderen Attributen noch unterscheiden. Hier bilden die Tupel, die in den Projektionsattributen identische Werte haben, jeweils eine Datengruppe, wobei die Werte in den nicht projizierten Attributen unterschiedlich sein können. Häufig möchten wir für diese Datengruppen weitere Werte haben, die sich aus den Tupeln der jeweiligen Gruppen berechnen lassen. Im einfachsten Fall möchten wir nur die Anzahl der Tupel der jeweiligen Gruppen haben. Des weiteren möchten wir z.B. die Summen der in den einzelnen Lagern wissen. In diesen Fällen verwenden wir statt der Projektion die Datengruppierung. Zusätzlich zu den zu projizierenden Attributen hat die neue Relation weitere adjungierte Attribute, die durch die Anwendung von Aggregatfunktionen auf die Attribute, die nicht in der Projektion auftreten, entstehen. Formal schreiben wir die Anwendung der Aggregatfunktionen hinter die Projektionsattribute, durch einen senkrechten Strich getrennt. Die Aggregatfunktion COUNT(*)ergibt für jede Datengruppe die Anzahl ihrer Tupel. Die weiteren Aggregatfunktionen haben jeweils eine Spalte oder virtuelle Spalte als Argument. Als Aggregatfunktionen können beliebige Funktionen auftreten, die Mengen von Werten 6 als Argumente annehmen. In SQL sind das neben COUNT(*) folgende Aggregatfunktionen, die als Argument jeweils ein Attribut annehmen (z. B. SUM (Bestand)): COUNT Anzahl definierter Werte COUNT DISTINCT Anzahl verschiedener Werte SUM Summe AVG Average, Durchschnitt MIN Minimum MAX Maximum Als weitere Funktionen (die nicht in SQL definiert sind) kämen z.B. noch das Produkt von numerischen Werten oder statistische Kenngrößen wie Varianz, Median oder geometrisches Mittel in Frage. Beispiel Die folgende Relation gibt für jeden Lagerplatz die Summe der Artikel-Bestände an. Grp[Lagerplatz | Gesamtbestand ::= SUM(Bestand)] (Artikel) Beispiel Relation aller Lagerplätze, die einen Bestand von höchstens 1000 haben: Sel[Gesamtbestand < 1000] (Grp[Lagerplatz | Gesamtbestand::=SUM(bestand)] (Artikel) An diesem Beispiel ist zu beachten, dass hier die Selektion nach der Gruppierung durchzuführen ist. Beispiel Im folgenden Beispiel tritt sowohl eine Selektion auf Tupel-Ebene als auch eine Selektion auf Gruppenebene auf. Es werden nur die selektierten Tupel in die Gruppe aufgenommen und anschließend die relevanten Gruppen ausgewählt: Wir wollen eine Liste der Lager mit dem jeweiligen Gesamtbestand haben, wobei wir uns nur für Artikel interessieren, die mindestens für 5,00 DM verkauft werden und nur für Lager, in denen der Gesamtbestand dieser Artikel höchstens 1000 ist. Sel[Gesamtbestand < 1000] ( Grp[Lagerplatz | Gesamtbestand ::= SUM(bestand)] ( Sel[Listenpreis > 5.00] (Artikel) )) 7 Ein Sonderfall der Datengruppierung tritt auf, wenn kein Gruppierungsattribut angegeben wird, sondern nur Aggregatfunktionen. In diesem Fall werden die Aggregatfunktionen auf die gesamte Tabelle angewandt. So eine Relation hat naturgemäß nur ein Tupel. Grp[ | Gesamtwert ::= SUM(Bestand * Listenpreis), MaxBestand ::= MAX(Bestand), MinPreis ::= MIN(Listenpreis), Sortiment ::= COUNT(Artikel_Nr)] (Artikel) In diesem Fall können wir Varianten der Aggregatfunktionen definieren, die als Ergebnis keine Relation liefern, sondern einen Wert. So ist SUM(Bestand * Listenpreis;Artikel) = 56722,51 DM MAX(Bestand;Artikel) = 2101 MIN(Listenpreis;Artikel) = 0,98 COUNT(Artikel-Nr;Artikel) = 12 Ein weiterer Sonderfall ist gegeben, wenn hinter dem senkrechten Strich keine Anwendung einer Aggregatfunktion steht - in diesem Fall ist die Datengruppierung nichts anderes als die normale Projektion1 3.3.6 Der Verbund (Join) Der Verbund ist eine sehr wichtige Operation im relationalen Datenbankmodell, da durch ihn die Möglichkeit gegeben ist mehrere Relationen miteinander zu verknüpfen. In dieser abstrakten Darstellung des relationalen Datenmodells betrachten wir in erster Linie den sog. natürlichen Verbund (engl. natural join). Hieraus können wir den allgemeineren Gleichheits-Verbund (equi join) erhalten, indem wir gegebenenfalls vorher die entsprechenden Attribute umbenennen. Den noch allgemeineren Verbund (Θ-Join im Sinne von Codd, wobei Θ für eine beliebige zweistellige Operation wie =, <, ≤, >, ≥ steht) behandeln wird hier nicht weiter, da er als Selektion auf dem Kreuzprodukt dargestellt werden kann und außerdem in der Praxis nur eine geringe Rolle spielt. Wie merken hier an, dass der natürliche Verbund, wie wir ihn hier einführen, für die theoretische Definition der Relationenalgebra hervorragend geeignet ist da er einfacher zu definieren ist als die anderen Verbund-Operationen. In der Praxis hat der natürliche Verbund allerdings einen sehr großen Nachteil: er ist empfindlich gegen nachträgliche Änderungen des Datenbankschemas. Da sich die Verknüpfung auf alle Attribute bezieht, die (absichtlich oder zufälligerweise) in beiden Relationen auftreten, kann durch die Einführung eines neuen Attributes (z. B. mit der Bezeichnung Bemerkung), das eigentlich mit der geplanten Verknüpfung nichts zu tun hat, das Ergebnis völlig geändert werden: es werden dann keine Tupel mehr verknüpft, da z. B. die Bemerkungen in der Bestell-Relation nicht mit den Bemerkungen in der Kunden-Relation übereinstimmen. Folgende Voraussetzungen müssen gegeben sein, damit ein Verbund definiert werden kann: 1. R und S sind Relationen; 2. a1,... an sind genau die Attribut-Bezeichner, die in R und S vorkommen (also Durchschnittsmenge der Attributbezeichner); 1 So ist in dem relationalen Datenbanksystem Microsoft Access in einer Abfrage eine Projektion formal als Gruppierung zu beschreiben. 8 3. für i = 1. . n sind die Domänen von ai in R und in S jeweils kompatibel, d. h. sie sind identisch oder es ist zumindest möglich, Werte dieser entsprechenden Domänen auf Gleichheit zu überprüfen; 4. die übrigen Attribut-Bezeichner von R und S sind jeweils verschieden. Die Bedingungen 2 und 4 sind gegebenenfalls durch Umbenennungen von Attributen zu erzwingen. An dieser Stelle ist die Operation der Umbenennung essentiell.2 Es ist dann R Join[a1,... an] S eine neue Relation, die wie folgt definiert ist: 1. Die Attribute von R Join[a1,... an] S sind genau die Attribute von R und S. 2. Die Verbindungsattribute a1,... an werden also genau einmal gezählt. 3. R Join[a1,... an] S enthält für alle Paare von Tupeln von r ∈ R und s ∈ S, die für alle Attribute von a1,... an definiert sind und jeweils identische Werte besitzen, jeweils das Tupel r+s. 4. Da nach der Voraussetzung für zu verknüpfende r und s alle Attribute von a1,... an identische Werte besitzen, sind r und s widerspruchsfrei und somit ist r+s definiert. Ein Tupel r ∈ R kann mit einem Tupel s ∈ S, mit mehreren Tupeln s ∈ S oder mit keinem Tupel s ∈ S verbunden werden. Beispiel Bestellung Join[Kunden_Nr] Kunde Diese Relation enthält für jedes Tupel der Relation »Bestellung« die Angaben (Bestell-Nr, Bestelldatum, Lieferdatum, Rechnungsbetrag) sowie alle Kundendaten für die entsprechenden Kunden. Falls allerdings eine in der Relation »Bestellung« angegebene Kundennummer in der Relation »Kunde« nicht enthalten ist, wird diese Bestellung - ohne Warnung - ignoriert. (Dies kann nicht auftreten, da wir als Konsistenzbedingung eine referentielle Konsistenz angegeben haben - hier sehen wir auch, wozu diese referentielle Konsistenz wichtig ist). Graphisch können wir diese Verknüpfung auch in folgender Form darstellen, indem wir die zu verknüpfenden Attribute durch eine Linie verbinden: 2 In XBase fehlt diese Operation. Die Join-Anweisung enthält zwar die Möglichkeit, entsprechend Punkt 2 die Verbindungsbedingung zu formulieren, aber gleichnamige Feld-Bezeichner können nicht umbenannt werden. Somit sind die Operationen der Relationenalgebra in Xbase nicht vollständig formulierbar (außer durch explizite Programmierung mit verschachtelten Programmschleifen - das können wir aber auch in jeder >~normalen<~ Programmiersprache). 9 Beispiel (Self Join) Verbindungen müssen nicht immer über Schlüsselattribute hergestellt werden. Durch entsprechende Umbenennung können auch Relationen mit sich selber verknüpft werden. Hierzu sei die folgende Relation »Kaiser« gegeben: Kaiser Durch Proj[A_Name::=Name, Vater] (Kaiser) Join[Vaterl Proj[B_Name::=Name, Vater] (Kaiser) bekommen wir die Relation der Brüder - wobei hier allerdings noch jeder, der einen Vater hat, als sein eigener Bruder gilt, d. h. Tupel der Form [A_Name='Ludwig I', B_Name='Ludwig I', Vater='Karl I'] sind zugelassen. Um diese auszuschließen, müssen wir anschließend eine Selektion vornehmen: Sel[A_Name ≠ B-Name] Proj[A_Name::=Name, Vater] (Kaiser) Join[Vater] Proj[B_Name::=Name, Vater] (Kaiser) ) 10 Kommutativgesetz Die Verbund-Operation ist kommutativ, d. h. es kommt nicht auf die Reihenfolge der Relationen an beide Relationen in einem Verbund sind vollständig gleichberechtigt. Es gilt grundsätzlich R Join[rs] S = S Join[rs] R Assoziativgesetz Die Verbund-Operation ist assoziativ, d. h. für drei Relationen R, S, T mit den entsprechenden Mengen von Verbindungsattributen r s und s t gilt stets (R Join[rs] S) Join[st] T = R Join[rs] (S Join[st] T ) Wir können somit vollständig auf die Angabe von Klammern verzichten und den letzteren Ausdruck durch R Join[rs] S Join[st] T darstellen. In der graphischen Darstellung auf Seite 91 bedeutet das, dass auch mehrere Relationen verknüpft werden können und es nur auf die Existenz der Linien ankommt - nicht auf eine irgendwie geartete Pfeilrichtung oder Nummerierung der Verbindungen. Beispiel Die Liste aller Artikel aus Lager 2, die von Kunden aus Kayhude geordert wurden, bekommen wir durch Proj[Artikel_Nr, Bezeichnung] ( Sel[Lagerplatz = 2 ∧ Ort = 'Kayhude'] ( Kunde Join[Kunden_Nr] Bestellung Join[Bestell_Nr] Position Join[Artikel_Nr] Artikel ) ) oder oder Proj[Artikel_Nr, Bezeichnung] ( Sel[Ort = 'Kayhude'] (Kunde) Join[Kunden_Nr] Bestellung Join[Bestell_Nr] Position Join[Artikel_Nr] Sel[Lagerplatz = 2] (Artikel) Proj[Artikel_Nr, Bezeichnung] Sel[Lagerplatz = 2] Join[Artikel_Nr] Position Join[Bestell_Nr] Bestellung Join[Kunden_Nr] Sel[Ort = 'Kayhude'] (Kunde) Alle drei Ausdrücke liefern dasselbe Ergebnis - allerdings ist der Rechenaufwand unterschiedlich. Am höchsten ist er für die erste Formulierung, da hier erst alle Tupel verknüpft werden und die Selektion am Schluss erfolgt. Für die 2. und 3. Formulierung ist a priori nicht klar, welche Formulierung weniger Rechenaufwand erfordert - es ist dort nur die Reihenfolge der Verbund-Verknüpfung unterschiedlich möglicherweise ergibt sich daraus überhaupt kein Unterschied. Dieses Beispiel zeigt auch, dass ein Datenbanksystem sinnvollerweise einen Abfrageoptimierer benötigt. Dieser soll in der Lage sein, eine gegebene Abfrage in eine äquivalente Abfrage (d. h. eine Abfrage mit derselben Ergebnismenge) so umzuformen, dass der Rechenaufwand möglichst gering wird. Hierzu stehen dem Abfrageoptimierer Informationen über die physikalische Speicherorganisation sowie über die Anzahl der Tupel in den beteiligten Relationen und gegebenenfalls Erfahrungen aus vergangenen Abfragen zur Verfügung. Meistens sind die automatisch erzeugten Abfragepläne relativ gut - bei komplexen Anfragen kann gegebenenfalls ein manuelles Tuning überlegen sein. 11 Beispiel Im folgenden soll eine Liste der Bestellungen mit dem Bestelldatum, Lieferdatum und der Kundennummer sowie dem Rechnungsbetrag ausgegeben werden. Grp[Bestell_Nr, Bestelldatum, Lieferdatum, Kunden_Nr | Rechnungsbetrag ::= SUM (Gesamtpreis * (l+ MWSt*0.01) ) ] ( Bestellung Join[Bestell_Nr] Position Join[Artikel_Nr] Artikel 3.3.7 Das Kreuzprodukt In der Datenbankpraxis spielt das Kreuzprodukt nur eine geringe Rolle - es ist aber im ursprünglichen Coddschen Modell vor dem Verbund eingeführt worden, da das Kreuzprodukt mathematisch einfacher ist als der Verbund. Der Verbund kann als Selektion auf dem Kreuzprodukt definiert werden. Umgekehrt ist das Kreuzprodukt ein Spezialfall des natürlichen Verbundes: wenn nämlich die Menge der Verbindungsattribute leer ist, d. h. R x S = R Join[ ] S Die Voraussetzung für das Kreuzprodukt ist also, dass die Attributbezeichner der beteiligten Relationen jeweils verschieden sind - gegebenenfalls sind sie vorher umzubenennen. Beispiel Seien die beiden Relationen Werktag und Unterrichtsblock gegeben. Werktag Unterrichtsblock 12 Werktag X Unterrichtsblock Diese Relation enthält für jeden Tag jeden Unterrichtsblock. Bei 5 Tagen und 6 Unterrichtsblöcken also 5 * 6 = 30 Tupel. Dieses sind genau die möglichen Kästchen auf einem Stundenplan für einen Schüler oder Dozenten. Wenn wir nur die Tupel jeweils durch ein Paar aus den ersten beiden Anfangsbuchstaben des Tages und der Nummer des Blockes beschreiben, und für die beiden Ausgangsrelationen die horizontale bzw. vertikale Achse nehmen, bekommen wir ein Rechteck, das genau die Kombinationen der beiden Ausgangsrelationen enthält, wie in folgender abgewandelter Darstellung sichtbar wird: Allgemeiner: Rl und R2 seien Relationen; alle Attribut-Bezeichner von R1 und R2 seien verschieden. Die Relation Rl × R2 enthält alle Attribut-Bezeichner von R1 und R2. Für jedes Tupel t1 ∈ Rl und jedes t2 ∈ R2 ist t1+t2 ∈ Rl × R2. Insbesondere ist die Anzahl der Tupel von R1 × R2 das Produkt der Anzahlen der Tupel von Rl und R2, d. h. die Zahl kann sehr groß werden. Eine nicht erwünschte Rolle spielt das Produkt aus folgendem Grund: vergisst ein Benutzer, die Verbindungsattribute anzugeben, erhält er statt einer Verbund-Operation das Produkt. Leider sind im Standard SQL-89 die Verbund-Attribute im Zusammenhang mit den Selektionskriterien festzulegen, so dass es leicht vergessen werden kann. In SQL-92 ist dies nicht mehr so problematisch, da hier der Verbund als eigene Operation in der Abfrage-Anweisung auftritt. Falls das Produkt einer Relation mit sich selber gebildet werden soll, sind vorher mindestens in einer der beteiligten Relationen die Attribute umzubenennen. 3.3.8 Der äußere Verbund (Outer Join) Der Nachteil des natürlichen Verbundes ist, dass nur solche Tupel aufgenommen werden, für die explizit (mindestens) eine Verknüpfung existiert. Wenn wir in unserem Versandhandel-Beispiel aber zum Beispiel eine Liste aller Kunden haben wollen, jeweils mit ihren Bestellungen verknüpft, so bekommen wir für die Kunden, für die keine Bestellung eingetragen ist, keine Ausgabe. Dieses ist in der folgenden Relation dargestellt. 13 Beispiel Proj[Name,Bestell_Nr,Bestelldatum] (Kunde Join[Kunden_Nr] Bestellung) Nicht alle Kunden erscheinen in dieser Auflistung, sondern nur die, für die Bestellungen eingetragen sind. Wenn wir dagegen alle Kunden in der Aufstellung sehen wollen, müssen wir den äußeren Verbund benutzen. Es ist allerdings zu berücksichtigen, dass der äußere Verbund nur eingeführt werden kann, wenn ein Konzept von Nullmarken (vgl. Kapitel 3.3.10) eingeführt ist. Einige Attribute haben dann keine Werte, was durch sogenannte Nullmarken gekennzeichnet wird. Für die Relationen, deren Tupel auf jedem Fall erscheinen sollen, werden Sternchen an den eckigen Klammern nach dem Join-Operator angebracht. Somit gibt es drei verschiedene äußere Verbunde: Join*[...], Join[...]* und Join*[...]*. Diese werden als Left Outer Join, Right Outer Join und Full Outer Join bezeichnet. Häufig werden sie auch abgekürzt als Left Join, Right Join und Full Join bezeichnet der oben eingeführte Verbund wird dann als Inner Join bezeichnet. Die obige Abfrage wird dann wie folgt dargestellt: Proj [Name, Bestell_Nr, Datum] (Kunde Join*[Kunden_Nr] Bestellung) Wenn der äußere Verbund mit dem natürlichen Verbund gemischt wird, dann kommt es gegebenenfalls auf die Reihenfolge der Operationen an. Wir können hier also nicht so sorglos auf Klammern verzichten wie bei dem natürlichen Verbund. Beispiel Um die Liste aller Artikel zusammen mit einer Liste aller Kunden zu bekommen, die diese Artikel gekauft haben, können wir folgenden Verbund verwenden: Artikel Join*[Artikel_Nr] (Position Join[Bestell_Nr] Bestellung Join[Kunden_Nr] Kunde) Wir stellen dabei zuerst den inneren Verbund zwischen Position, Bestellung und Kunde her. Damit haben wir alle »gültigen« Positionen mit den uns interessierenden Daten. Der anschließende äußere Verbund mit der Relation »Artikel« stellt sicher, dass alle Artikel in der Liste erscheinen. Von diesem Verbund bilden wir die Projektion auf die interessierenden Attribute und erhalten: Proj[Artikel_Nr, Bezeichnung, Lieferdatum, Kunden_Name::=Name] ( Artikel Join*[Artikel_Nr] (Position Join[Bestell_Nr] Bestellung Join[Kunden_Nr] Kunde) ) 14 Die Klammerung (Artikel Join*[Artikel_Nr] Position) Join[Bestell_Nr] Bestellung Join[Kunden_Nr] Kunde würde für den Fall, dass irgendeine Position nicht zu einer Bestellung gehört (ungültige Bestellung in Positions-Relation) oder dass eine ungültige Kundennummer in der Bestellungs-Relation enthalten ist, die entsprechenden Artikel nicht mit aufführen, da für Artikel ohne Positionen das Verbindungsattribut eine Nullmarke enthält. Hinweis Wie wir eben gesehen haben, ist in der Praxis die Verwendung eines äußere Verbundes unter der Verwendung von mehr als zwei Relationen problematisch. Es ist eine sehr genaue Analyse Voraussetzung, da ein Assoziativgesetz (vgl. Seite 93) für äußere Verbunde nicht gilt. 3.3.9 Mengenoperationen Seien R1 und R2 Relationen vom gleichen Typ (d.h. die Attribut-Bezeichner und ihre entsprechenden Domänen sind gleich, vgl. Kap. 1.4.3). Dann sind durch RI ∪ R2 für die Vereinigungsmenge der Tupel von RI und R2, d. h. die Menge der Tupel, die in Rl oder in R2 oder in beiden Mengen enthalten sind, Rl ∩ R2 für die Durchschnittsmenge, d. h. die Menge der Tupel, die sowohl in R1 und R2 enthalten sind, RI \ R2 für die Differenzmenge, d. h. die Menge der Tupel von R1, die nicht in R2 enthalten sind, rein mengentheoretisch weitere Relationen definiert. Beispiele Sel[Lagerplatz=5] (Artikel) ∪ Sel[Lagerplatz=7] (Artikel) Sel[Verpackung='Karton'] (Artikel) ∩ Sel[Lagerplatz=7] (Artikel) Sel[Lagerplatz=7] (Artikel) \ Sel[Verpackung='Karton'] (Artikel) Diese Beispiele können auch jeweils durch eine einfache Selektion beschrieben werde 15 Sel[Lagerplatz=5 ∨ Lagerplatz=7] (Artikel) Sel[Verpackung = 'Karton^ Lagerplatz=7] (Artikel) Sel[Lagerplatz=7 ∧ Verpackung ≠ 'Karton'] (Artikel) Wenn dagegen zwei Relationen verknüpft werden sollen, die sich nicht von einer gemeinsamen Oberrelation ableiten lassen, können die Mengenoperationen Vereinigung und Differenz nicht durch andere Operationen ersetzt werden. Der Durchschnitt zweier Mengen kann allerdings als natürlicher Verbund dargestellt werden, wobei alle Attribute als Verbindungsattribute zu nehmen sind, vgl. die Relation im folgenden Beispiel 2. So können wir durch Verknüpfung der Relation »Kunde« mit der Relation »Lieferant« u. a. folgende Relationen bilden: Beispiel 1 Proj[Name,Straße,PLZ,Ortl(Kunde) ∪ Proj[Name,Straße,PLZ,Ort] (Lieferant) ist die Relation der Geschäftspartner. Beispiel 2 Proj[Name,Straße,PLZ,Ortl(Kunde) ∩ Proj[Name,Straße,PLZ,Ortl (Lieferant) ist die Relation der Lieferanten, die gleichzeitig Kunden sind. Dieses ist äquivalent zu Proj[Name,Straße,PLZ,Ort] (Kunde) Join[Name,Straße,PLZ,Ort] Proj[Name,Straße,PLZ,Ort] (Lieferant) Beispiel 3 Proj[Name,Straße,PLZ,Ort] (Kunde) \ Proj[Name,Straße,PLZ,Ort] (Lieferant) ist die Relation der Lieferanten, die keine Kunden sind. 3.3.10 Übungsaufgaben Aufgabe 3.2 Beschreiben Sie folgende Fragen durch Verknüpfung der Relationen aus dem Anhang C. a) An welchen (verschiedenen) Lagerplätzen haben Sie Artikel? b) An welchen Orten mit PLZ wohnen Kunden? c) Bei welchen Positionen sind Artikel mit den Nummern 'G001', 'G002' oder 'G003' geliefert worden? d) Bei welchen dieser Positionen ist die Liefermenge größer als 2? e) Bei welchen Positionen sind keine Artikel mit den oben genannten Nummern geliefert worden? f) Welche Kunden sind keine Stammkunden? Geben Sie die Kundennummer, den Namen, den Ort und den Status aus. g) Welche Kunden sind keine Stammkunden und nicht aus Husum? h) Welche Bestellungen sind vom Mai 1996 ? i) Es sollen die Positionen aufgelistet werden, in denen die Artikel 'K001' oder 'G003' vorkommen. j) Bei welchen Artikeln liegt der Listenpreis zwischen 6 und 15 DM? 16 k) Lassen Sie pro MWSt-Satz die Anzahl der davon betroffenen Artikel anzeigen. 1) Geben Sie jeweils die größte Kundennummer der Kunden mit demselben Status aus. m) Zeigen Sie je Lagerplatz den kleinsten, den größten und den durchschnittlichen Listenpreis der dort gelagerten Artikel an. n) Wie viele Kunden haben Sie jeweils mit gleicher Postleitzahl? o) Unter welcher Postleitzahl haben Sie mehr als einen Kunden? p) Zu welcher Zahlungsart haben Sie mehr als zwei Kunden? q) Zeigen Sie zu jedem Artikel den Mehrwertsteuersatz an. r) Zeigen Sie mit Hilfe des Mehrwertsteuersatzes den Bruttopreis je Artikel an. s) Zeigen Sie zu jeder Bestellung den Lieferort an. t) Zeigen Sie zu jeder Bestellposition die Bestellmenge, Bestellnummer und den Lieferort an. u) Zeigen Sie je Ort die Anzahl der bestellten Artikel an. v) Ermitteln Sie den billigsten Artikel. (Nicht nur den Preis des billigsten Artikels!) w) Welcher Kunde hat die letzte Bestellung aufgegeben? Zeigen Sie die Attribute kunden_nr, name und bestell_nr an. x) Welche Bestellungen sind nicht von Kunden aus Kayhude? y) Welche Artikel haben einen höheren Listenpreis als alle Artikel aus Lager 2? 17 Die Datenbank 18 19