Im letzten Kapitel haben Sie gelernt, erste Abfragen zu formulieren. Sie hatten lediglich Einfluss auf die Auswahl der auszugebenden Spalten und die Reihenfolge der auszugebenden Datensätze. Ansonsten waren die Abfragen dadurch gekennzeichnet, stets alle in einer Tabelle vorhandenen Datensätze zurückzugeben. Mithilfe von DISTINCT
konnten Sie lediglich die Ausgabe von Dubletten unterbinden.
In diesem Kapitel sehen wir uns an, wie wir gezielt Datensätze für die Ausgabe auswählen können.
Datensatzauswahl mit WHERE
Für die Formulierung einer Datensatzauswahl nach verschiedenen Kriterien stellt uns SQL die WHERE
-Klausel zur Verfügung. Sie nimmt im SELECT
-Statement, welches einem ganz klaren Schema folgt, einen festen Platz ein, wenn wir diese Klausel verwenden wollen.
Die Syntax des SELECT
-Statements bis hierher, sieht wie folgt aus:
1 2 3 4 5 6 |
SELECT [DISTINCT] select_list FROM table_name WHERE condition [ORDER BY order_expression]; |
Natürlich ist die WHERE
-Klausel optional, wir hätten sie ansonsten bisher nicht auslassen dürfen. Weil sie aber im Mittelpunkt unseres aktuellen Interesses steht, habe ich sie nicht explizit so gekennzeichnet. Selbiges gilt für FROM
. Auch diese Klausel ist, wie Sie bereits wissen, unter Umständen optional.
Mit condition
habe ich die Datensatzauswahlbedingung in der Syntax-Beschreibung bezeichnet. Das ist ein boolescher Ausdruck, ein Wahrheitsausdruck, der bestimmt, ob ein Datensatz zur anzuzeigenden Ergebnismenge gehört oder nicht.
Ich nehme an, Sie haben boolesche Ausdrücke bereits in der Schule kennengelernt. Dort wird man Ihnen erzählt haben, solche Ausdrücke haben lediglich zwei mögliche Werte als Ausprägungen: wahr (TRUE
) und falsch (FALSE
). Bei SQL gibt es einen dritten Wert. Das ist NULL
. Das liegt darin begründet, dass wir diesen Wert in SQL eben haben, wenn etwas nicht bekannt ist. Weiter unten beleuchten wir NULL
-Werte in Verbindung mit logischen Verknüpfungen genauer.
Zuerst sehen wir uns aber an, wie wir grundsätzlich Bedingungen für die Datensatzauswahl formulieren. Die Idee dabei ist einfach. Mithilfe von Vergleichsoperatoren formulieren wir Bedingungen. Werden diese von einem Datensatz der abgefragten Tabelle erfüllt, ist die Bedingung wahr und der Datensatz gelangt in die Ergebnismenge.
Die folgenden Operatoren stehen uns in SQL zur Verfügung:
Operator | Beschreibung |
---|---|
= | gleich |
< | kleiner |
<= | kleiner oder gleich |
> | größer |
>= | größer oder gleich |
<> oder
!= | ungleich |
NOT | Negation |
AND | Konjunktion |
OR | Disjunktion |
IN | TRUE , wenn Wert in Liste |
BETWEEN | TRUE , wenn Wert in Intervall |
LIKE | TRUE , wenn Muster zutreffend |
IS NULL | TRUE , wenn Wert gleich NULL |
IS NULL | TRUE , wenn Wert ungleich NULL |
Bereits ein kurzer Blick auf die Tabelle reicht aus, um zu erkennen, dass NULL
-Werte wohl einer besonderen Behandlung bedürfen. NULL
-Werte können nicht auf Gleichheit, Ungleichheit oder irgendeine Ordnungsrelation hin geprüft werden. WIe auch? – Ihr konkreter Wert ist ja nicht bekannt.
Sehen wir uns die Operatoren und wie sie zu verwenden sind, an je einem Beispiel an.
Beispielabfragen mit den verschiedenen Operatoren
Vergleich auf Gleichheit
Gibt es eine Kundinnen mit dem Vornamen „Pamela“? – Wenn ja, wie heißen sie weiter?
1 2 3 4 5 6 |
SELECT first_name , last_name FROM customer WHERE first_name = 'Pamela'; |
Hinweis: Zeichenketten werden in einfache Anführungszeichen eingeschlossen.
+----------+---------+ |first_name|last_name| +----------+---------+ |Pamela |Baker | +----------+---------+
Vergleiche auf Ungleichheit
Welche Kunden haben einen Nachnamen, der im vorderen Teil des Alphabets liegt?
1 2 3 4 5 6 7 8 |
SELECT first_name , last_name FROM customer WHERE last_name <= 'M' ORDER BY last_name , first_name; |
+----------+---------+ |first_name|last_name| +----------+---------+ |Rafael |Abney | |Nathaniel |Adam | |Kathleen |Adams | |Diana |Alexander| |Gordon |Allard | |Shirley |Allen | ... (326 Datensätze insgesamt)
Ein Kollege soll die Kunden des hinteren Alphabets verarbeiten.
1 2 3 4 5 6 7 8 |
SELECT first_name , last_name FROM customer WHERE last_name > 'M' ORDER BY last_name , first_name; |
+----------+---------+ |first_name|last_name| +----------+---------+ |Steve |Mackenzie| |Ralph |Madrigal | |Matthew |Mahan | |Donald |Mahon | |Clifton |Malcolm | |Joshua |Mark | ... (273 Datensätze insgesamt)
Werte in Wertelisten vergleichen
Gibt es Kundinnen, die „Ann“, „Anna“ oder „Annalena“ heißen?
1 2 3 4 5 6 |
SELECT first_name , last_name FROM customer WHERE first_name IN ('Ann', 'Anna', 'Annalena'); |
+----------+---------+ |first_name|last_name| +----------+---------+ |Anna |Hill | |Ann |Evans | +----------+---------+
Mustervergleich
Eine Liste kann schwer anzugeben sein, wenn wir nur einen kleinen Teil einer zu suchenden Zeichenkette kennen und/oder spezifizieren können. Dann ist LIKE
unser Freund.
Mithilfe von LIKE
können wir einfache Suchmuster definieren. Die Zeichen (oder Zeichenfolgen), die fix vorkommen sollen, geben wir konkret an. Für alle anderen Zeichen verwenden wir Platzhalter. Dabei steht ein %
-Zeichen für ein beliebiges Zeichen, oder auch keines. Ein Unterstrich (_
) steht für genau ein Zeichen.
Wir Suchen alle Kunden, die im Vornamen die Zeichenfolge „ll“ haben. Vor diesen Zeichen sollen beliebig viele Zeichen stehen können. Danach muss aber noch genau ein Zeichen folgen. – Warum auch immer wir das so wollen.
1 2 3 4 5 6 |
SELECT first_name , last_name FROM customer WHERE first_name LIKE '%ll_'; |
Die Platzhalter können mehrfach in einem Suchmuster verwendet werden. Sie entsprechen nicht nur Buchstaben, sondern auch anderen Zeichen.
+----------+---------+ |first_name|last_name| +----------+---------+ |Michelle |Clark | |Kelly |Torres | |Lucille |Holmes | |Danielle |Daniels | |Sally |Pierce | |Holly |Fox | |Ella |Oliver | |Stella |Moreno | |Priscilla |Lowe | |Shelly |Watts | |Billy |Poulin | |Kelly |Knott | +----------+---------+
Mustervergleich ohne Berücksichtigung von Groß-/Kleinschreibung
Es kursieren auch Versionen unserer Beispieldatenbank bei denen die Filmtitel komplett in Großbuchstaben geschrieben sind. Wenn das durchgängig ist, ist das auch kein Problem. Was jedoch tun, wenn wir nach Zeichenketten suchen und dabei die Groß-/Kleinschreibung ignorieren wollen, um z.B. Schreibfehler die Suche nicht behindern zu lassen?
Die Idee ist es dann, das Suchmuster bzw. den Vergleichswert in Groß- oder Kleinschreibung anzugeben und den Wert, welchen wir aus der Tabelle extrahieren entsprechend umzuwandeln, also alle Zeichen in Groß- bzw. Kleinbuchstaben zu transformieren. Leider heißen die entsprechenden Transformationsfunktionen bei den verschiedenen Datenbankmanagementsystemen oft unterschiedlich. Bei PostgreSQL heißen sie lower()
(Argument in Kleinbuchstaben) und upper()
(Argument in Großbuchstaben). Wir können wählen, welche Variante und lieber ist.
Ich verwende hier nochmals LIKE
. Sie können das aber auch mit jedem anderen Vergleichsoperator so machen.
Schauen wir einmal, ob es „Ouch“-Filme gibt. Wir suchen „Ouch“, „OUCH“ oder wie auch immer die Zeichenfolge geschrieben sein mag. Es soll uns auch egal sein, wo sie im Titel eines Films auftaucht.
1 2 3 4 5 |
SELECT title FROM film WHERE upper(title) LIKE '%OUCH%'; |
+--------------------+ |title | +--------------------+ |Untouchables Sunrise| |Usual Untouchables | +--------------------+
Hinweis: PostgreSQL bietet für diese Art den insensitiven Mustervergleichs einen speziellen Operator. Das ist IKLIKE
, ein Abkömmling von LIKE
, der Groß-/Kleinschreibung ignoriert, diesbezüglich insensitiv ist.
1 2 3 4 5 |
SELECT title FROM film WHERE title ILIKE '%OucH%'; |
NULL
-Werte vergleichen
Weil NULL
kein bestimmter Wert ist, auch überall vorkommen kann, bei Zeichenketten, Zahlen etc., können wir Werte nicht auf Gleichheit, Ungleichheit usw. prüfen, wenn sie NULL
sind. Dennoch mage es uns ja interessieren, all die Adressen aus unserem Adressbestand herauszufiltern, bei denen keine Postleitzahl angegeben wurde. Dazu müssen wir prüfen, ob im betreffenden Datenfeld NULL
steht. Mit dem =
-Operator geht dies nicht. Dafür haben wir aber IS
NULL
und IS
NOT
NULL
, wenn wir das Gegenteil prüfen wollen.
1 2 3 4 5 |
SELECT address , postal_code FROM address WHERE postal_code IS NULL; |
+--------------------+-----------+ |address |postal_code| +--------------------+-----------+ |47 MySakila Drive |null | |28 MySQL Boulevard |null | |23 Workhaven Lane |null | |1411 Lillydale Drive|null | +--------------------+-----------+
Umgekehrt können wir uns auch alle Adressen zeigen lassen, bei denen eine Postleitzahl angegeben wurde.
1 2 3 4 5 |
SELECT address , postal_code FROM address WHERE postal_code IS NOT NULL; |
postal_code IS NOT NULL
klingt besser, und ist hier auch so erlaubt, als NOT postal_code IS NULL
. Das wäre die „klassische“ Negation. SQL erlaubt aber hier auch die besser lesbare Variante.
+-----------------+-----------+ |address |postal_code| +-----------------+-----------+ |1913 Hanoi Way |35200 | |1121 Loja Avenue |17886 | |692 Joliet Street|83579 | |1566 Inegl Manor |53561 | ... (599 Datensätze insgesamt)
Intervalle mit BETWEEN
Mittels BETWEEN
können wir zu überprüfende Intervalle definieren. Solche Intervalle können Zahlenintervalle, aber auch Zeichen- oder Enumerationsintervalle sein. Entscheidend ist, dass der Datentyp eine Ordnungsrelation besitzt, deren Werte aufzählbar, im Sinne „1, 2, 3, viele“, ist.
Die Intervallgrenzen, die wir angeben, sind inklusive.
Welche Filme haben eine Spieldauer zwischen 90 und 120 Minuten?
1 2 3 4 5 6 7 8 |
SELECT title , length FROM film WHERE length BETWEEN 90 AND 120 ORDER BY length , title; |
Die Sortierung dient lediglich der übersichtlicheren Ausgabe.
+-----------------+------+ |title |length| +-----------------+------+ |Basic Easy |90 | |Circus Youth |90 | |Forward Temple |90 | |Secret Groundhog |90 | |Vertigo Northwest|90 | ... (223 Datensätze insgesamt)
Negation
Bei der Prüfung von NULL
-Werten habe Sie die Negation (NOT
) schon verwendet.
Grundsätzlich kann jede Bedingung dadurch negiert werden, dass wir ihr ein NOT
voranstellen.
Welche Filme sind für Kinder, Jugendliche unter 17 Jahren, geeignet?
1 2 3 4 5 |
SELECT title , rating FROM film WHERE rating NOT IN ('NC-17', 'R'); |
Hinweis: Wenn Sie die Ausgabe der Altersfreigabe nicht interessiert, dürfen sie die in der Spaltenliste auch gerne weglassen. Für die WHERE
-Bedingung ist nur entscheidend, auf das zu prüfende Attribut zugreifen zu können. Das bestimmt die FROM
-Klausel, welche die zu analysierende Tabelle angibt. Gibt es darin das zu prüfende Attribut, ist den Anforderungen von SQL Genüge getan. Ausgeben brauchen Sie es nicht.
+-----------------+------+ |title |rating| +-----------------+------+ |Bright Encounters|PG-13 | |Academy Dinosaur |PG | |Ace Goldfinger |G | |Affair Prejudice |G | |African Egg |G | |Agent Truman |PG | ... (595 Datensätze insgesamt)
Logische Verknüpfungen
Einfache Bedingungen sind schön und, wie gesagt, einfach. Das Leben spielt aber oftmals anders. Häufig haben wir Bedingungen, die sich auch Teilbedingungen zusammensetzen. Um diese logisch miteinander verknüpfen zu können, haben wir die logischen Operatoren NOT
(oben schon behandelt), AND
und OR
.
Wie diese logischen Operatoren definiert sind, wie sie wirken, machen wir uns am besten an Wahrheitstafeln klar. Sie kennen diese bestimmt aus dem Mathematikunterricht.
Die Idee der Wahrheitstafeln, wenn Sie sie noch nicht kennen, ist es, zwei Aussagen logisch miteinander zu verknüpfen. Dabei kann jede Aussage wahr oder falsch sein. Bei SQL haben wir nun die Besonderheit zu beachten, dass es NULL
-Werte geben kann. Die Ihnen bekannten Wahrheitstafeln werden wur daher um diese Möglichkeit erweitern.
Sie fragen sich, was SQL mit Aussagen zu tun hat? – Nun, eine Bedingung, wie eine Datensatzauswahlbedingung (WHERE
) formuliert eine Aussage mit Variablen. Durch Einsetzen der Werte aus den Datensätzen entstehen so Aussagen, die wahr, falsch oder auch NULL
sein können. Eine solche Aussage, formuliert durch die Datensatzauswahlbedingung, ist wahr, wenn die Bedingung erfüllt ist. Ein Datensatz, der eine Auswahlbedingung erfüllt, gelangt in die Ergebnismenge. Alle anderen Datensätze werden ausgefiltert. – Sie sehen, es macht sehr viel Sinn, sich mit dieser Thematik zu befassen.
Zurück zu den logischen Verknüpfungsoperatoren. Einfache Bedingung sind trivial zu prüfen. Jetzt scheuen wir uns die Verknüpfungen an. Dabei betrachten wir immer zwei Teilaussagen A und B. Das sind unsere Bedingungen, die durch Einsetzen der konkreten Werte eines Datensatzes wahr, falsch oder NULL
werden.
Negation:
A | NOT A |
---|---|
FALSE |
TRUE |
TRUE |
FALSE |
NULL |
NULL |
Konjunktion:
A | B | ||
---|---|---|---|
AND |
FALSE |
TRUE |
NULL |
FALSE |
FALSE | FALSE | FALSE |
TRUE |
FALSE | TRUE | NULL |
NULL |
FALSE | NULL | NULL |
Disjunktion:
A | B | ||
---|---|---|---|
OR |
FALSE |
TRUE |
NULL |
FALSE |
FALSE | TRUE | NULL |
TRUE |
TRUE | TRUE | TRUE |
NULL |
NULL | TRUE | NULL |
Dem einen oder anderen mag die Behandlung von NULL
seltsam erscheinen. Man kann sich das aber ganz einfach anhand eines Mathematiker-Witzes klarmachen:
Kommen zwei Mathematiker in eine Kneipe. Fragt die Wirtin die beiden: „Beide ein Bier?“
Der erste Mathematiker antwortet: „Weiß nicht.“
Der zweite antwortet: „Jau, beide ein Bier.“
Nicht witzig? - Ok, es gibt knalligere Witze. Der Gag ist der, dass wir Mathematikern nachsagen, sie würden alles ganz ernst und wörtlich nehmen. Der erste Mathematiker antwortet mit „Weiß nicht“ (NULL
), weil er nicht weiß, was sein Kollege, der zweite Mathematiker will. - Das ist einfach.
Der zweite Mathematiker versteht die Situation natürlich. Der erste Mathematiker will augenscheinlich ein Bier. Hätte er nämlich keines gewollt, hätte er die Frage der Wirtin mit „Nein“ (FALSE
) beantworten können. Er wäre dann ja sicher gewesen, dass nicht beide ein Bier wollen, egal, wie sein Kollege sich entscheiden würde. Da er das nicht getan hat, weiß der zweite Mathematiker sofort, dass sein Kollege ein Bier möchte. Da er selbst auch gerne einn Bier mag, kann er nun mit Gewissheit „Jau, ...“ antworten.
Sie können es sich auch anders verdeutlichen. Eine logische UND-Verknüpfung von A und B ist nur wahr, wenn beide wahr sind. Sie ist falsch, wenn eine der beide Aussagen falsch ist, oder natürlich auch beide. Daher ergibt FALSE
AND
NULL
(bzw. umgekehrt) den Wert FALSE
. Alle anderen Kombinationen mit NULL
bleiben ungewiss (NULL
).
Bei einer ODER-Verknüpfung reicht es aus, wenn eine der beiden Aussagen wahr ist, um insgesamt wahr zu sein. Daher ergibt TRUE
OR
NULL
(bzw. umgekehrt) den Wert TRUE
. Alle anderen Kombinationen mit NULL
bleiben ungewiss (NULL
).
Die Reihenfolge der Auswertung ist NOT
, AND
und dann erst OR
. Mittels Klammern können wir die Auswertungsreihenfolge beeinflussen. Komplexere Ausdrücke können wir dann schrittweise auswerten. Haben wir beispielsweise A OR B AND C
, überlegen wir uns erst, was B AND C
ergibt. Das Resultat verknüpfen wir dann mit A OR
.
Beachten Sie, dass die Operatoren AND
und OR
assoziativ sind. Es ist also egal, von welcher Seite wir verknüpfen. A AND B
ist gleichbedeutend mit B AND A
. Das Ganze funktioniert in SQL also ganz analog zu dem, was wir aus der Mathematik bereits kennen (sollten).
Einige Beispiele für logische Verknüpfungen
Kunden, die „Leslie Gordon“ heißen
1 2 3 4 5 6 |
SELECT first_name , last_name FROM customer WHERE first_name = 'Leslie' AND last_name = 'Gordon'; |
+----------+---------+ |first_name|last_name| +----------+---------+ |Leslie |Gordon | +----------+---------+
Kunden, mit Vornamen „Leslie“ oder Nachnamen „Gordon“
1 2 3 4 5 6 |
SELECT first_name , last_name FROM customer WHERE first_name = 'Leslie' OR last_name = 'Gordon'; |
+----------+---------+ |first_name|last_name| +----------+---------+ |Leslie |Gordon | |Leslie |Seward | +----------+---------+
Datensatzanzahl begrenzen mit LIMIT
LIMIT
ist im eigentlichen Sinne kein Filter. Damit begrenzen wir lediglich die Anzahl der auszugebenden Datensätze. Weil das auch die natürliche Folge von „echten“ Filtern ist, insofern eine gewisse Ähnlichkeit in der Wirkung besteht, behandle ich LIMIT
an dieser Stelle.
LIMIT
ist natürlich optional zu verwenden. Es besitzt eine ebenfalls optionale Zusatzklausel. Das ist OFFSET
. LIMIT
gibt die Anzahl der auszugebenden Datensätze an. Mit OFFSET
legen wir fest, ab welchem Datensatz der originären Ergebnismenge wir Datensätze ausgeben. - Man verwendet das gerne für die Paginierung von Ergebnissen auf Webseiten o.ä.
1 2 3 4 5 6 7 8 9 |
SELECT select_list FROM table_name ORDER BY sort_expression LIMIT row_count OFFSET rows_to_skip; |
Für verlässliche Resultate sollte immer auch sortiert werden. Andernfalls obliegt es quasi dem Zufall, welche Ergebnisse angezeigt werden, da SQL keine Ordnung der Ergebnisdatensätze einer Abfrage gewährleistet, so diese nicht explizit angegeben ist.
Betrachten wir die ersten 10 Filme, basierend auf deren alphabetischer Ordnung nach Titeln.
1 2 3 4 5 6 |
SELECT title FROM film ORDER BY title LIMIT 10; |
+----------------+ |title | +----------------+ |Academy Dinosaur| |Ace Goldfinger | |Adaptation Holes| |Affair Prejudice| |African Egg | |Agent Truman | |Airplane Sierra | |Airport Pollock | |Alabama Devil | |Aladdin Calendar| +----------------+
Jetzt wollen wir die nächsten 10 Titel ausgeben, also quasi eine Paginierung demonstrieren.
1 2 3 4 5 6 |
SELECT title FROM film ORDER BY title LIMIT 10 OFFSET 10; |
Dazu „überspringen“ wir die ersten 10 Titel, die wir uns bereits ausgegeben haben.
+-------------------+ |title | +-------------------+ |Alamo Videotape | |Alaska Phantom | |Ali Forever | |Alice Fantasia | |Alien Center | |Alley Evolution | |Alone Trip | |Alter Victory | |Amadeus Holy | |Amelie Hellfighters| +-------------------+
Anmerkung: Wenn Sie die letzten 10 Filme ausgeben wollen, dann ordnen Sie in absteigender Reihenfolge. Wollen Sie das Resultat dennoch in aufsteigender Reihenfolge ausgeben, müssen Sie dieses nochmals entsprechend sortieren. Dazu müssen Sie Ihr Resultat als abgeleitete Tabelle nutzen. Das behandeln wir später. Für ganz besonders Wissbegierige unter Ihnen, möchte ich es Ihnen aber kurz zeigen.
1 2 3 4 5 6 7 8 9 10 11 |
SELECT * FROM ( -- suche die letzten 10 Titel SELECT title FROM film ORDER BY title DESC LIMIT 10 ) letzte_zehn -- sortiere sie für die Ausgabe wieder um ORDER BY title; |
Übrigens können Sie Ihre SQL-Codes kommentieren. Einzeile Kommentare werden mit --
eingeleitet und reichen bis ans Zeilenende. Sie haben keinen Einfluss auf die SQL-Ausführung. Mehrzeilige Kommentare werden in /*
und */
eingeschlossen, angelehnt an die Programmiersprache C.
+-----------------+ |title | +-----------------+ |Worst Banger | |Wrath Mile | |Wrong Behavior | |Wyoming Storm | |Yentl Idaho | |Young Language | |Youth Kick | |Zhivago Core | |Zoolander Fiction| |Zorro Ark | +-----------------+