Die SQL-Select-Anweisung Holger Jakobs – [email protected], [email protected] 2011-05-25 Inhaltsverzeichnis 1 Einleitung 1 2 Auswertungsreihenfolge 5 3 Abfragen auf eine Tabelle 3.1 einfache Abfrage . . . . 3.2 Abfrage mit Projektion . 3.3 Abfrage mit Selektion . . 3.4 Selektion und Projektion 3.5 Aggregat-Funktionen . . 3.6 Ähnlichkeitssuche . . . . 5 5 5 5 5 6 7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 Abfragen mit mehreren Tabellen 7 5 Verknüpfung von Relationen mit sich selbst 9 6 Unterabfragen 6.1 Subquery mit IN und = . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2 Subquery mit ALL, ANY und SOME . . . . . . . . . . . . . . . . . . . . . . . 10 10 11 7 Vereinigungsmengenbildung 11 8 Natürlicher Verbund mit ON- oder USING-Klausel 12 9 Korreliertes Subquery 14 10 Bedingte Ausdrücke 15 1 Einleitung Die Structured Query Language SQL ist in erster Linie eine Abfragesprache – wie der Name schon sagt (query = Abfrage). Darüber hinaus dient sie auch der Datenveränderung, 1 1 EINLEITUNG SELECT [ ALL DISTINCT ] { * attribute-list functions } FROM Festlegen der Quell-Tabellen [ alias ] [ , table-name [ alias ] ] ... { table-name } table-name [ [ NATURAL ] [ LEFT | RIGHT ] JOIN table-name ] ... Auswahl von passenden Tupeln [ WHERE condition ] [ GROUP BY column-name [ , column-name ] ... [ ORDER BY Auswahl von passenden Gruppen [ HAVING condition ] column-spec [ ASC | DESC ] [ , ... ] ] ] Sortierung des Ergebnisses Abbildung 1: Ausschnitt aus der SQL-Select-Syntax also dem Einfügen, Ändern und Löschen von Daten (DML = data manipulation language) sowie Erzeugung von Datenbanken, d. h. der Beschreibung der Relationen (Tabellen) mit ihren Attributen (Spalten) und ihren Beziehungen untereinander (DDL = data definition language). Hier soll zunächst die Abfragefunktion, die mit dem SELECT-Kommando ausgeführt wird, erläutert werden. Dieses Kommando ist einerseits das komplexeste, andererseits aber auch das am häufigsten benutzte. Einen Auszug aus der Syntax finden Sie in Abbildung 1. SQL ist Groß- und Kleinschreibung egal – wie den meisten Sprachen aus derselben Zeit. Aus Gründen der Übersichtlichkeit werden die SQL-Schlüsselwörter in diesem Text groß geschrieben, so dass man sie von den Namen der Tabellen und Spalten leicht unterscheiden kann. Innerhalb von Zeichenketten werden Groß- und Kleinschreibung natürlich unterschieden. Das SELECT-Kommando wird sowohl beim interaktiven Arbeiten mit SQL, als auch beim Programmieren mit SQL (embedded SQL, JDBC) verwendet. Mit dem SELECT-Kommando können alle drei Relationen-Operationen (Selektion, Projektion und Verbund) ausgeführt werden. SQL-Kommandos können sich über mehrere Zeilen erstrecken (das Zeilenende hat keine Bedeutung), sie werden meist mit einem Semikolon abgeschlossen. Durch die Auswahl der Spalten, die man statt des Operator Bedeutung Sterns hinter dem Schlüsselwort SELECT angibt, erzeugt = ist gleich man eine Projektion. Statt Spaltennamen der verwen> größer als deten Tabellen können auch Funktionen und Operatio>= größer oder gleich nen verwendet werden, z. B. CURRENT_TIME für die ak< kleiner als <= <> kleiner oder gleich ungleich 2 1 EINLEITUNG tuelle Zeit, sogenannte Aggregatfunktionen (siehe Abschnitt 3.5 auf Seite 6) oder mathematische Ausdrücke z. B. anzahl + 3. Die WHERE-Klausel dient zur Einschränkung des Suchergebnisses auf Tupel (Zeilen), die eine bestimmte Bedingung erfüllen, also eine Selektion. Oft werden dabei Attributwerte mit festen Werten oder Ergebnissen von Funktionen wie CURRENT_TIME verglichen. Die üblichen Operatoren sind vorhanden, siehe Tabelle. Auch Mengenvergleiche sind möglich über den Operator IN (mathematisch ∈ für ist Element von“), z. B. ” WHERE name IN ('Fritz', 'Anna', 'Uta'). Die Menge kann dabei wie hier direkt hingeschrieben werden oder auch Ergebnis einer weiteren SELECT-Abfrage sein, was als Unterabfrage oder Subquery bezeichnet wird (siehe auch Abschnitt 6 auf Seite 10). Wie man in obigem Beispiel sieht, sind Zeichenketten in einfache Hochkommas zu schreiben. Auch Datums- und Zeitwerte werden als Zeichenketten an die Datenbank geschickt und kommen als solche auch von dort zurück, so dass man die Abfrage aller Einträge mit einem bestimmten Datum so schreibt: WHERE datum='2007-04-02' Außer mit festen Werten wird auch oft mit dem NULL-Wert verglichen, d. h. es wird geprüft, ob ein bestimmtes Attribut einen oder keinen Wert hat. Dies geschieht mit der Klausel attributname IS NULL bzw. attributname IS NOT NULL. Dabei muss darauf geachtet werden, dass ein Vergleich mittels des Gleichheitszeichens hier immer falsche Ergebnisse liefert – völlig unabhängig vom tatsächlichen Wert des Attributs, d. h. die Verwendung des Schlüsselworts IS ist bei Vergleichen mit NULL vorgeschrieben. Es sollen zwei Relationen vorliegen: Student (MatrNr, Name, Fach, Semester) und Hört (MatrNr, VorlesungNr) 1. 2. 3. 4. Relationenalgebra Student [Fach='Physik'] Student [Name, Fach] Student × Hört Student ⊗ Hört SQL-SELECT-Statement SELECT * FROM student WHERE fach='Physik'; SELECT name, fach FROM student; SELECT * FROM student, hört; SELECT * FROM student s, hört h WHERE s.matrnr=h.matrnr; SELECT * FROM student JOIN hört USING (matrnr); SELECT * FROM student s JOIN hört h ON s.matrnr=h.matrnr; SELECT * FROM student NATURAL JOIN hört; 1. ist eine Selektion, bei der alle Spalten (*) der Relation ausgewählt werden, aber nur bestimmte Tupel (WHERE fach='Physik'). 2. ist eine Projektion, bei der alle Tupel der Relation ausgewählt werden (keine WHEREKlausel), aber nur bestimmte Attribute (Name, Fach). 3. ist ein einfacher Verbund, bei dem alle Studenten-Tupel mit allen Hört-Tupeln verknüpft werden. Die Ergebnistabelle ist sehr groß hat wenig Aussagekraft (praxisirrelevant). 3 1 EINLEITUNG 4. Meist wird daher der natürliche Verbund verwendet, bei dem die Gleichheit von je einem Attribut gefordert wird. In der Schreibweise der Relationenalgebra wird nicht deutlich, wie die Verknüpfung genau aussieht, d. h. es wird i. a. über das gleichnamige Attribut (bzw. alle gleichnamigen Attribute) verknüpft. Bei SQL gibt es mehrere Möglichkeiten, den natürlichen Verbund darzustellen. Entweder wird eine Bedingung zum einfachen Join angegeben (S.MatrNr=H.MatrNr), was die klassische Schreibweise ist, oder es wird statt des Kommas das Schlüsselwort JOIN verwendet, welches einen der Zusätze USING oder ON benötigt. Hinter USING steht eine Liste aller Attribute, die inhaltlich gleich sein müssen, hinter ON eine Bedingung. Weiterhin gibt es die Möglichkeit, NATURAL JOIN zu schreiben, wodurch eine Gleichheitsbedingung für alle Attribute erzeugt wird, die in beiden Tabellen vorkommen. Diese letzte Variante ist mittlerweile die gängiste geworden. Sehr oft werden Selektion, Projektion und Verbund kombiniert verwendet. Es ist bei SQL nicht vorgesehen, die Ergebnisrelation temporär zu speichern und anschließend mit dieser weiterzuarbeiten. Man muss immer – ausgehend von den in der Datenbank vorliegenden Tabellen oder externen Sichten – alle Operationen auf einmal durchführen. Tatsächlich ist dies auch der bessere Ansatz, weil man bei gespeicherten Ergebnissen erstens viel Speicherplatz benötigen und außerdem mit historischen Daten arbeiten würde. Trotzdem können manche Datenbanken über den Standard hinaus temporäre Tabellen anlegen, die beim Abmelden automatisch gelöscht werden. Für häufig benötigte Abfragen kann man zudem Sichten auf die Daten speichern, die aber als Abfragevorschrift und nicht als Daten abgelegt werden – sogenannte Views. Außerdem kann überall dort, wo die Syntax eine Tabelle erfordert (typischerweise hinter FROM) auch eine beliebig komplexe Unterabfrage (siehe Abschnitt 6 auf Seite 10 eingesetzt werden, der dann allerdings ein Aliasname gegeben werden muss. Eine Tabelle mit den Matrikelnummern und Vorlesungsnummern aller Physikstudenten in Vorlesungsnummernreihenfolge kann man mit folgendem SQL-Kommando bekommen: SELECT matrnr, vorlesungnr FROM student NATURAL JOIN hört WHERE fach='Physik' ORDER BY vorlesungnr; oder SELECT matrnr, vorlesungnr FROM student JOIN hört USING (matrnr) WHERE fach='Physik' ORDER BY vorlesungnr; 4 3 ABFRAGEN AUF EINE TABELLE 2 Auswertungsreihenfolge Die einzelnen Klauseln eines SQL-Kommandos werden immer in einer bestimmten Reihenfolge ausgewertet, die nicht mit der Reihenfolge im Kommando übereinstimmt: FROM WHERE GROUP BY HAVING SELECT ORDER BY definiert die Ausgangstabellen selektiert die Zeilen, die der Bedingung genügen gruppiert Zeilen auf der Basis gleicher Werte in Spalten selektiert Gruppen, die der Bedingung genügen projiziert Spalten sortiert Zeilen auf der Basis von Spaltenwerten. 3 Abfragen auf eine Tabelle 3.1 einfache Abfrage Eine ganz einfache Abfrage zeigt den gesamten Inhalt einer Tabelle: SELECT * FROM spieler; Es werden also keine expliziten Spaltennamen angegeben, sondern statt dessen ein Stern. Der Tabellenname wird hinter FROM genannt. 3.2 Abfrage mit Projektion SELECT spielnr, spielname, vorname FROM spieler; Um nicht alle Spalten zu bekommen, gibt man die gewünschten Spaltennamen hinter SELECT an. Diese Operation nennt man Projektion. 3.3 Abfrage mit Selektion SELECT * FROM spieler WHERE spielnr > 10; Um nicht alle Zeilen zu bekommen, gibt man eine WHERE-Klausel mit einer Bedingung an, die die gewünschten Tupel auswählt. Diese Operation nennt man Selektion. Die Bedingung kann ein Vergleich sein zwischen einem Spaltenwert und einem Literal (wie hier im Beispiel mit dem integer-Literal 10) oder zwischen zwei Spaltenwerten. Darüber hinaus sind weitere Bedingungen möglich – einzige Voraussetzung ist, dass hierbei ein boolescher Wert dargestellt wird. 3.4 Selektion und Projektion Selektion und Projektion werden sehr häufig miteinander kombiniert: SELECT spielnr, spielname, vorname FROM spieler WHERE spielnr > 10; 5 3.5 Aggregat-Funktionen Name SUM(Attribut) COUNT (*) COUNT (Attribut) COUNT (DISTINCT Attribut) AVG (Ausdruck) MIN (Ausdruck) MAX (Ausdruck) 3 ABFRAGEN AUF EINE TABELLE Bedeutung Summe aller Werte Anzahl der Tupel in der Gruppe Anzahl der Tupel mit Wert ungleich NULL in der Gruppe Anzahl der verschiedenen Werte des Attributs in der Gruppe arithmetisches Mittel aller Werte kleinster Wert größter Wert Tabelle 1: Tabelle der Aggregatfunktionen 3.5 Aggregat-Funktionen Aggregat-Funktionen fassen Werte zusammen. Werden sie ohne GROUP BY-Klausel verwendet, so wird nur ein einziges Tupel ausgegeben. In Verbindung mit einer GROUP BY-Klausel wird für jede Gruppe ein Tupel ausgegeben. In Tabelle 1 sind die Aggregatfunktionen aufgeführt. Die Funktionen COUNT, MIN und MAX sind auf alle Arten von Attributen anwendbar, SUM und AVG nur auf numerische Ausdrücke. Bei COUNT kann man entweder ein beliebiges Attribut oder einen Stern (*) als Parameter angeben – das Ergebnis ist gleich, sofern alle Tupel einen Wert in dem genannten Attribut haben. Haben einige Tupel dagegen keinen Wert (also einen sogenannten NULL-Wert – nicht zu verwechseln mit dem Zahlenwert 0), so werden sie nicht mitgezählt. Möchte man nur die Anzahl der verschiedenen Werte haben, so kann man das Wort DISTINCT mit im Parameter angeben. Beispiel: SELECT COUNT (DISTINCT abt_nr) FROM mitarbeiter; gibt aus, wievielen verschiedenen Abteilungen die Mitarbeiter zugeordnet sind. Ohne DISTINCT würde die Anzahl der Mitarbeiter (= Anzahl der Tupel) ausgegeben. Lediglich wenn es Mitarbeiter gibt, bei denen keine Abteilungsnummer eingetragen ist, würden diese nicht mitgezählt. Aggregat-Funktionen können auch in HAVING-Klauseln verwendet werden. Beispiel, das die Namen der Orte ausgibt, in denen genau zwei Spieler wohnen: SELECT ort FROM spieler GROUP BY ort HAVING COUNT (*) = 2; Ein weiteres Beispiel für den Einsatz einer Aggregatfunktion mit Bezug auf die Kundenund Rechnungstabellen (siehe Abbildung 2 auf Seite 8) wäre folgendes: SELECT KName, RNr, RDatum, SUM(Anzahl * APreis) AS RBetrag FROM kunden NATURAL JOIN rechnungen NATURAL JOIN rzeilen NATURAL JOIN artikel; Zeige Kundennamen, die Rechnungsnummern, Rechnungsdatum und Rechnungsbetrag. 6 4 ABFRAGEN MIT MEHREREN TABELLEN 3.6 Ähnlichkeitssuche Die Rechenoperation SUM (Anzahl * APreis) wird ausgeführt und die Spalte wird mit dem Namen RBetrag angezeigt. Zu jeder Rechnungszeile wird also der zugehörige Artikelpreis aus der Artikeltabelle geholt. Alle vier Tabellen werden miteinander über einen natürlichen Verbund verknüpft. Überall dort, wo in der Tabellenabbildung eine Verbindungslinie ist, kann man auch das NATURAL JOIN zwischen die Tabellennamen schreiben. Man kann hier deutlich sehen, dass es wenig sinnvoll wäre, die Rechnungsbeträge bei den Rechnungen zu hinterlegen, weil sie sich aus den einzelnen Rechnungspositionen (Zeilen) leicht errechnen lassen1 . 3.6 Ähnlichkeitssuche SQL bietet die Möglichkeit, nach Zeichenketten auch mit Jokerzeichen (Wildcards) zu suchen. Als Ersatzsymbol für eine beliebige Zeichenkette von 0 oder mehr Zeichen Länge wird das Zeichen '%' verwendet. Man muss allerdings bei Vergleich statt des Zeichens = das Wort LIKE verwenden. Beispiel: SELECT * FROM spieler WHERE ort LIKE '%e%'; gibt alle Angaben zu Spielern aus, die in Orten wohnen, deren Name ein 'e' enthält. Möchte man innerhalb eines LIKE-Vergleichs nach dem Zeichen '%' selbst suchen, muss man es quoten. Das geschieht folgendermaßen: ... LIKE '%!%%' escape '!'; sucht nach einer Zeichenkette, die irgendwo das Zeichen '%' enthält. Das Zeichen '_' steht für genau ein beliebiges Zeichen. ... LIKE '_U_' sucht also nach einer Zeichenkette mit genau drei Zeichen, von denen das mittlere ein 'U' ist. Im Standard gibt es über das recht schwache LIKE hinaus auch noch die Klausel SIMILAR TO, die etwas in Richtung reguläre Ausdrücke hin weiter entwickelt wurde, ohne deren bereits nach POSIX genormte Syntax zu übernehmen. Bei SIMILAR TO können alternative Zeichenketten durch einen senkrechten Strich getrennt werden, die Wiederholungsfaktoren Stern und Pluszeichen funktionieren wie bei regulären Ausdrücken, ebenso kann man mit runden Klammern gruppieren und in eckigen Klammern Zeichenmengen darstellen. Leider gibt es die Klausel SIMILAR TO nicht bei allen Datenbanksystemen; dafür gibt es in manchen darüber hinaus noch wahlweise die regulären Ausdrücke nach POSIX. 4 Abfragen mit mehreren Tabellen Um Daten aus mehreren Tabellen in einer Abfrage darzustellen, verwendet man eine Verbundoperation. Um den Verbund (Join) näher zu erläutern, hier einige Beispiele. Wir gehen davon aus, dass es folgende Tabellen gibt: Artikel Kunden (ANR, ABezeich, APreis) (KNR, KName, KOrt) 1) In der betrieblichen Praxis ist das ein wenig anders, weil sich Preise nach Rechnungsstellung ändern können, die alten Rechnungen aber weiterhin auf den damaligen Preis lauten. Daher werden bei Rechnungsstellung die Preise meist in die Rechnung kopiert. Ein Rechnungsbetrag als Summe aus allen Positionen wird aber dennoch nicht gespeichert, weil das redundant wäre. 7 4 ABFRAGEN MIT MEHREREN TABELLEN Kunden 1 Artikel KNR KName KOrt ANR ABezeich APreis Rechnungen N RNR KNR RDatum LDatum 1 Rechnungs− zeile N RNR N ANR Anzahl 1 Abbildung 2: grafische Tabellendarstellung Kunden & Rechnungen Rechnungen (RNR, KNR, RDatum, LDatum) RZeilen (RNR, ANR, Anzahl) Wie in der Abbildung 2 zu sehen ist, sind Informationen über Artikel (Artikelnummer, Artikelbezeichnung, Artikelpreis), über Kunden (Kundennummer, Kundenname, Kundenort), Rechnungen der Kunden (Rechnungsnummer, Kundennummer, Rechnungsdatum, Lieferdatum) und Rechnungszeilen (Rechungsnummer, Artikelnummer, Anzahl) vorhanden. Hierzu nun einige Standardabfragen mit Erläuterung. Da das Verknüpfungsattribut zwischen den beiden Tabellen in beiden Tabellen gleich heißt und es keine weiteren gleichnamigen Attribute gibt, schreibt man am einfachsten SELECT KNR, KName, RDatum FROM Kunden NATURAL JOIN Rechnungen Die Spalte KNR existiert im Ergebnis nur einmal, weil die beiden KNR-Spalten der Ausgangstabellen automatisch zusammengefasst werden. Eine Variante wäre SELECT K.KNR, KName, RDatum FROM Kunden K, Rechnungen R WHERE K.KNR = R.KNR; Zeige die Kundennummer (die aus der Kundentabelle, aber die aus der Rechnungstabelle ist wegen der WHERE-Bedingung ohnehin identisch), den Kundennamen, das Rechnungsdatum von den Kunden mit den Rechnungen, wobei die Kundennummer aus beiden Tabellen gleich sein muss. Andernfalls würden ja auch Werte aus Rechnungen angezeigt, die gar nicht zu dem Kunden gehören. Die Kundentabelle bekommt 8 5 VERKNÜPFUNG VON RELATIONEN MIT SICH SELBST SELECT R1.*, R2.* FROM R R1, R R2; Aliasnamen alle Spalten aus R2 alle Spalten aus R1 Ausgangsrelation Ergebnisrelation R A B 1 7 2 3 R x R R1.A R1.B R2.A R2.B 1 1 2 2 7 7 3 3 1 2 1 2 7 3 7 3 Abbildung 3: Verbund-Operation einer Relation mit sich selbst den Aliasnamen K, die Rechnungstabelle den Namen R, damit man bei der WHEREund der SELECT-Klausel nicht den ganzen, langen Namen angeben muss. SELECT RDatum, LDatum, ABezeich, Anzahl FROM Rechnungen NATURAL JOIN RZeilen NATURAL JOIN Artikel Auch hier werden die Tabellen wieder über alle gleichnamigen Spalten verbunden, so dass man einfach mehrere NATURAL JOIN-Verbindungen hintereinander durchführen kann. Und wieder die Variante SELECT RDatum, LDatum, ABezeich, Anzahl FROM Rechnungen, RZeilen, Artikel WHERE R.RNR = Z.RNR and Z.ANR = A.ANR; Zeige das Rechnungdatum, das Lieferdatum und die Artikelbezeichnung von allen Rechnungen mit ihren Zeilen. Die Bezeichnung des Artikels muss aus der Artikeltabelle geholt werden – daher wird ein Verbund über drei Tabellen nötig. 5 Verknüpfung von Relationen mit sich selbst Bei einer Verbund-Operation kann auch eine Relation mit sich selbst verknüpft werden. Dann müssen allerdings Aliasnamen für die Relationen gewählt werden, um die Spalten eindeutig bezeichnen zu können. Obiges Beispiel ist natürlich nicht sonderlich interessant. Ein praktischer Anwendungsfall der Verknüpfung einer Relation mit sich selbst wäre folgender: Es liegt eine Tabelle mit Büchern vor: Bücher (Titel, Autor, Preis). Es soll nun ermittelt werden, welche Bücher billiger sind als das Buch SQL“ vom Autor Hoffmann“: ” ” 9 6 UNTERABFRAGEN SELECT b1.* FROM buecher b1, buecher b2 WHERE b2.titel='SQL' AND b2.autor='Hoffmann' AND b1.preis < b2.preis ORDER BY b1.preis; Der hier vorliegende Verbund ist ein θ-Verbund (theta). Die Relation Bücher wird zweimal verwendet: mit den Aliasnamen b1 und b2. Es entsteht also eine neue Relation als Kreuzprodukt der Relation Bücher mit sich selbst. Durch die erste Zeile der WHERE-Klausel werden alle Tupel ausgewählt, die das Buch SQL“ von Hoffmann“ in den b2-Spalten ” ” enthalten. Außerdem muss der Preis in der b1-Spalte kleiner sein als der Preis in der b2Spalte. Jetzt sind in den b1-Spalten alle Bücher übrig, die billiger sind als das SQL-Buch von Hoffmann. Es wird nach dem b1-Preis sortiert. Es werden alle b1-Spalten angezeigt. 6 Unterabfragen Oft möchte man in einer WHERE-Klausel einen Attributwert nicht mit einem festen Wert oder einer festen Menge vergleichen, sondern mit dem Ergebnis einer anderen DatenbankAbfrage. Diese untergeordnete Datenbank-Abfrage nennt man dann Unterabfrage oder Subquery. Es gibt mehrere Varianten, wie man sie einsetzen kann, denn sie ist nicht nur in Vergleichen erlaubt, sondern überall dort, wo eine Tabelle vorkommen kann. 6.1 Subquery mit IN und = Grundsätzlich ist das Ergebnis einer Abfrage eine Menge, so dass man auch Mengenoperatoren verwenden muss, beispielsweise den ist Element von“-Operator (∈) namens IN: ” SELECT * FROM artikel WHERE anr IN (SELECT anr FROM auftraege); Es wird bei jedem einzelnen Tupel der Hauptabfrage geprüft, ob der Wert des Attributs anr im Ergebnis der Unterabfrage vorkommt. Die meisten Datenbanksysteme lassen nur Unterabfragen mit 1 Spalte zu. Natürlich kann man auch die Artikel herausfinden, die in keinem Auftrag vorkommen. Dazu wird die Operation ist kein Element von“ (∈) / verwendet, also heißt es jetzt NOT IN: ” SELECT * FROM artikel WHERE anr NOT IN (SELECT anr FROM auftraege); Sollte das Ergebnis der Unterabfrage nur ein einziges Tupel sein, so kann man den Vergleich auch mit dem Operator = machen. Hier wird dann nicht streng zwischen einem Element und einer Menge mit einem Element unterschieden; mit IN würde es natürlich auch funktionieren. SELECT * FROM sportler WHERE zeit = (SELECT MIN(zeit) FROM sportler); Hinter dem IN-Operator kann man eine Menge auch explizit durch Aufzählung angeben, d. h. ohne Unterabfrage: SELECT spielnr FROM spieler WHERE ort IN ('Krefeld', 'Meerbusch'); 10 7 VEREINIGUNGSMENGENBILDUNG 6.2 Subquery mit ALL, ANY und SOME 6.2 Subquery mit ALL, ANY und SOME Bei Subqueries mit = muss ein einziger Wert herauskommen, bei Subqueries mit IN wird nur auf Existenz des Wertes in der Tabelle geprüft (Mengenoperation ist Element von“, ∈). ” Mit ALL und ANY bzw. SOME sind dagegen auch die anderen Vergleichsoperationen möglich, d. h. man kann fragen, ob ein Wert größer ist als mindestens einer (ANY oder SOME) oder größer als alle (ALL) Werte, die in der Unterabfrage ermittelt wurden. Beispiel 1: Wer aus Abteilung A verdient mehr als alle aus Abteilung B? SELECT persnr FROM personal WHERE abt='A' AND gehalt > all (SELECT gehalt FROM personal WHERE abt='B'); Beispiel 2: Welcher Manager verdient weniger als irgendein Nicht-Manager? SELECT persnr FROM personal WHERE job='Manager' AND gehalt < ANY (SELECT gehalt FROM personal WHERE job <> 'Manager'); 7 Vereinigungsmengenbildung Bei einem natürlichen Verbund von Tabellen werden diejenigen Tupel unberücksichtigt gelassen, bei denen das join-Attribut NULL-Werte enthält bzw. der Wert in einer der Tabellen nicht vorkommt. Beispiel: Aus der Vereinsdatenbank sollen alle Spieler mit der Anzahl ihrer Strafen ermittelt werden. Das klassische SELECT-Kommando (ohne NATURAL JOIN) sieht so aus und erzeugt: spielnr spielname expr SELECT s.spielnr, spielname, COUNT(*) 6 Peters 1 FROM spieler s, strafen st 8 Neuhaus 1 WHERE s.spielnr = st.spielnr 27 Kohl 2 GROUP BY s.spielnr, spielname; 44 Becker 3 104 Maurer 1 Wie man sieht, kommen alle Spieler, die noch keine Strafe erhalten haben, in der Tabelle gar nicht vor. Das liegt daran, dass nur die Tupel ausgegeben werden, bei denen die beiden Spielernummern gleich sind. Kommt eine Spielernummer in einer Tabelle nicht vor oder hat sie einen NULL-Wert, wird das Tupel nicht ausgegeben. Also muss man es nachträglich wieder dazuholen, was man durch ein zweites SELECTKommando macht. Die Ergebnistabelle der beiden SELECT-Kommandos werden dann anschließend zu einer Tabelle zusammengefügt. Da dies eine Mengenoperation ist, wird die Operation UNION (Vereinigung) genannt. Für obiges Beispiel sieht das so aus wie in Abbildung 4 auf der nächsten Seite gezeigt. 11 8 NATÜRLICHER VERBUND MIT ON- ODER USING-KLAUSEL SELECT s.spielnr, spielname, COUNT(*) FROM spieler s, strafen st WHERE s.spielnr = st.spielnr GROUP BY s.spielnr, spielname UNION SELECT spielnr, spielname, 0 FROM spieler WHERE spielnr not in (SELECT spielnr FROM strafen); spielnr 2 6 7 8 27 28 39 44 104 ... spielname Elfers Peters Wiegand Neuhaus Kohl Kohl Bischof Becker Maurer ... expr 0 1 0 1 2 0 0 3 1 ... usw. Abbildung 4: Vereinigungsmengenbildung mit UNION Diese Art von Verbund wird Outer Join“ gegenüber dem normalen Inner Join“ ge” ” nannt. Die beiden mit UNION zu vereinigenden Tabellen müssen in ihrem Aufbau, d. h. in Anzahl und Typen der Spalten, übereinstimmen. Beide SELECTs sind vollständig, nur eine ORDER BY-Klausel darf lediglich ganz am Ende enthalten sein und gilt dann für das gesamte SELECT. Eine bessere Möglichkeit ist der sogenannte NATURAL LEFT JOIN bzw. NATURAL RIGHT JOIN, der es ermöglicht, direkt alle Tupel der linken bzw. rechten Tabelle in die Ergebnistabelle zu übernehmen, auch wenn es kein korrespondierendes Tupel in der anderen Tabelle gibt. Das schreibt man dann so: SELECT spielnr, spielname, COUNT(betrag) FROM spieler NATURAL LEFT JOIN strafen GROUP BY spielnr, spielname; Außer dem Left Outer Join“ und dem Right Outer Join“ gibt es noch den Full Outer ” ” ” Join“, bei dem außer den Tupeln des Inner Join“ alle Tupel aus beiden Tabellen verwendet ” werden, denen kein Tupel aus der jeweils anderen Tabelle zugeordnet werden kann. Diese Art von Outer Join“ ist bei den einigen Systemen nur über UNION erreichbar (obwohl der ” Standard es vorsieht) und kommt in der Praxis nicht sehr häufig vor. 8 Natürlicher Verbund mit ON- oder USING-Klausel Soll ein Join nicht über die Gleichheit aller gleichnamigen Attribute stattfinden, so ist statt der Klausel NATURAL [ LEFT/RIGHT ] JOIN lediglich [ LEFT/RIGHT ] join zu schreiben und dahinter eine ON- oder eine USING-Klausel anzugeben. In einer USING-Klausel steht eine geklammerte Liste all der gleichnamigen Attribute, auf Grund derer der Verbund stattfinden soll. Beispiel: 12 8 NATÜRLICHER VERBUND MIT ON- ODER USING-KLAUSEL SELECT s.spielnr, datum, betrag FROM spieler s LEFT JOIN strafen st ON s.spielnr = st.spielnr AND datum < '2009-01-01'; spielnr 2 6 7 8 27 28 39 44 44 44 57 83 95 100 104 112 datum betrag NULL NULL 2005-12-02 100.00 NULL NULL 2005-12-02 25.00 NULL NULL NULL NULL NULL NULL 2005-12-02 25.00 2006-04-29 75.00 2007-12-02 30.00 NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL SELECT s.spielnr, datum, betrag FROM spieler s LEFT JOIN strafen st ON s.spielnr = st.spielnr WHERE datum < '2009-01-01'; spielnr 8 6 44 44 44 27 datum betrag 2005-12-02 25.00 2005-12-02 100.00 2007-12-02 30.00 2005-12-02 25.00 2006-04-29 75.00 2008-09-03 100.00 Abbildung 5: Vergleich zweier ähnlicher Verbund-Abfragen SELECT w.spielnr, teamnr, liga, gewonnen-verloren FROM wettkaempfe w JOIN teams USING (teamnr); Hier werden die Tabellen wettkaempfe und teams über das Attribut teamnr verbunden; ohne die USING-Klausel (statt dessen dann mit NATURAL JOIN) würde die Werte-Gleichheit sowohl beim Attribut teamnr als auch bei spielnr notwendig, um ein Tupel der Ergebnistabelle zu generieren. Bei Verwendung von USING wird – genauso wie bei NATURAL JOIN – für jedes gleichnamige Attribut in den beiden Tabellen ein einziges Attribut in der Ergebnistabelle erzeugt. Daher muss der Zugriff auf dieses Attribut unqualifiziert erfolgen (ohne Angabe des Tabellen- oder Aliasnamens). In einer ON-Klausel steht eine Bedingung wie in einer WHERE-Klausel. Die Bedingung kann auch aus mehreren Teilen bestehen, die mit den üblichen Operatoren AND, OR, NOT verknüpft werden. Nur Tupel, die der Gesamtbedingung genügen, gelangen in die Ergebnistabelle. Bei Outer Joins“ werden die Tupel für den Inner Join“ durch die ON-Klausel limitiert; alle ” ” 13 9 KORRELIERTES SUBQUERY 1. mit normalem Subquery SELECT spielnr, spielname FROM spieler WHERE spielnr IN (SELECT spielnr FROM strafen); 3. mit Natural Join“ ” SELECT DISTINCT spielnr, spielname FROM spieler NATURAL JOIN strafen; 2. mit klassischem“ natürlichen Verbund: ” SELECT DISTINCT s.spielnr, spielname FROM spieler s, strafen st WHERE s.spielnr=st.spielnr; 4. mit korreliertem Sub-Query: SELECT spielnr, spielname FROM spieler s WHERE exists (SELECT * FROM strafen st WHERE s.spielnr=st.spielnr); Tabelle 2: Vier Arten einer korrelierten Unterabfrage nicht passenden gelangen ggf. in den Outer Join“-Teil der Ergebnistabelle. Der Unterschied ” zwischen einer Bedingung in der ON- und der WHERE-Klausel ist, dass die Bedingung in der ON-Klausel für den Join Gültigkeit haben muss, in der WHERE-Klausel für die Tupel nach der Durchführung aller Joins der Abfrage. Hierzu zwei Abfragen zum Vergleich, die völlig unterschiedliche Ergebnisse liefern, siehe Abbildung 5 auf der vorherigen Seite. Im ersten SELECT werden alle Strafen aller Spieler aufgelistet, wobei die ON-Klausel alle diejenigen Spieler/Strafen-Kombinationen ausschließt, wo das Datum der Strafe vor 2009 liegt. Diese Spieler gelten hier als unbestraft, kommen also in den Outer Join“-Part des ” Left Join“, erscheinen somit im Ergebnis. ” Im zweiten SELECT werden alle Strafen mit den Spielernummern aufgelistet. Nach Ausführung des Join werden alle Strafen vor 2009 mit der WHERE-Klausel entfernt. Diese WHEREKlausel entfernt aber auch alle Tupel, die beim Datum einen NULL-Wert enthalten. Daher werden alle Tupel, die aus dem Outer Join“-Part stammen, entfernt. ” Würde man den Zusatz OR datum IS NULL dazuschreiben, um diese Tupel zu retten, würden aber dennoch einige Tupel fehlen: Die Spieler, die eine Strafe ab 2009-01-01 bekommen haben, kommen mit diesen Strafen in den Inner Join“-Part, werden aber nach ” dem Join von der WHERE-Klausel entfernt, so dass sie im Ergebnis fehlen, während sie in er allerersten Version im Outer Join“-Part als unbestrafte Spieler auftauchen. ” 9 Korreliertes Subquery Die Anzeige aller Spielernamen, die schon einmal eine Strafe erhalten haben, kann man auf vier Arten erreichen (siehe Tabelle 2). Die Besonderheit des korrelierten Subquery ist, dass in der Unterabfrage auf die Tabelle des übergeordneten SELECT zurückgegriffen wird. Dazu wird ein Aliasname der Tabelle aus der Hauptabfrage in der Unterabfrage verwendet. Die meisten Abfragen sind ohne korrelierte Unterabfragen durchführbar. Da bei jedem 14 10 BEDINGTE AUSDRÜCKE Tupel der Hauptabfrage immer die komplette Unterabfrage erneut durchgeführt werden muss, ist die korrelierte Unterabfrage auch besonders aufwendig und daher langsam. Man sollte sie also nach Möglichkeit vermeiden. 10 Bedingte Ausdrücke Um bei Abfragen Werte in verschiedene Klassen einteilen zu können, gibt es CASE. Wenn wir die Strafen in geringe, mittlere und große Strafen einteilen möchten, schreiben wir eine Abfrage so: SELECT spielnr, CASE WHEN betrag < 50 THEN 'wenig' WHEN betrag BETWEEN 50 AND 100 THEN 'mittel' ELSE 'viel' END FROM strafen; Die COALESCE-Funktion kann mehrere Argumente haben und liefert das erste Argument zurück, dessen Wert nicht NULL ist. Hierdurch kann man in Tabellen vorhandene NULLWerte in Ausgaben durch eine passende Zeichenkette ersetzen. Bei allen Spielern ohne Titel bleibt die dritte Spalte nicht leer, sondern enthält die Zeichenkette ’ohne Titel’. SELECT spielnr, spielname, COALESCE (titel, 'ohne Titel') FROM spieler; Die NULLIF-Funktion ist die Umkehrung von COALESCE. Hier wird NULL zurückgegeben, wenn eine Spalte einen bestimmten Wert enthält. Steht also die Zeichenkette ’ohne Titel’ in der Spalte, wird jetzt NULL ausgegeben. SELECT spielnr, spielname, NULLIF (titel, 'ohne Titel') FROM spieler; 15