Vorwort

SQL (Structured Query Language) ist eine der meist genutzten Datenbanksprachen für relationale Datenbanken und findet sich in den unterschiedlichsten Branchen und Anwendungen wieder. Der Begriff relationale Datenbank beschreibt dabei eine Sammlung von Tabellen, die in Datensätzen abgespeichert werden und verknüpft werden können.

In diesem Tutorial werden die grundlegenden SQL Befehle und Funktionen anhand einer fiktiven Datenbank erklärt. Um den Einstieg so einfach wie möglich zu machen, wird dabei der DB Browser for SQLite verwendet.


Folgende Ressourcen müssen wir uns für dieses Tutorial herunterladen:

Eine Liste weiterer optionaler Ressourcen für den Einstieg in SQL befinden sich im letzten Kapitel.

1 Setup

Im Folgenden werden wir uns zunächst einen Browser für SQLite und einen fiktiven Datensatz herunterladen. SQLite ist ein auf der Programmiersprache C beruhendes Datenbankmanagementsystem, das ohne Serveranbindung auf unserem Endsystem verwendet werden kann und uns daher ein wenig Aufwand erspart.

Bei vielen anderen Anwendungsfällen von SQL werden jedoch Datenbanksysteme genutzt, die auf Server zurückgreifen und daher erst eine speziellere Konfiguration benötigen. Da mittlerweile viele verschiedene Unternehmen oder Open Source Communities solche Datenbanksysteme auf Basis von SQL Standards entwickelt haben, gibt es leichte Unterschiede in der Syntax. Die zugrunde liegende Logik und die Funktionen sind jedoch weitgehend deckungsgleich, sodass man mit Kenntnissen in einem Datenbanksystem problemlos auf andere SQL-Systeme umsteigen kann.

Die am stärksten verbreiteten Datenbanksysteme umfassen:

  • MySQL - Open Source von Oracle
  • SQL Server - von Microsoft
  • PostgreSQL - Open Source
  • Oracle Database - von Oracle

1.1 DB Browser for SQLite Installation

Für einen einfachen Einstieg nutzen wir den kostenlosen DB Browser for SQLite, der uns ohne großen Aufwand ermöglicht Datenbanken zu durchsuchen, Daten zu erstellen, Einträge zu bearbeiten, SQL Abfragen durchzuführen und Ergebnisse zu exportieren.

Der kostenfreie Download für Windows, macOS und Linux befindet sich hier:

Anschließend muss der Browser installiert werden.

1.2 sTunes Database

Als praktisches Beispiel für diese Einführung dient uns die Datenbank eines fiktiven Onlinestores für Musik. Wir simulieren also ein Szenario, in dem wir als Analysten für dieses fiktive Unternehmen tätig sind und im Auftrag unserer Stakeholder (Management, Investoren, Kunden, …) beispielsweise Daten zu Alben, Artists, Kunden, Mitarbeitern, Genres, Umsätzen oder Bestelldetails sammeln und aufbereiten müssen. Wie genau diese Datenbank aufgebaut ist, werden wir uns noch anschauen.

Zunächst können wir die Datenbank unter folgendem Link mit dem Passwort “IJKHannover”herunterladen:

Die zip-Datei können wir nun entpacken und die Datenbank-Datei in einem neuen Ordner abspeichern, in dem wir auch unser Projekt anlegen.

1.3 Öffnen der Datenbank

Jetzt können wir den DB Browser for SQLite öffnen und uns mit dem Programm vertraut machen. Um unsere sTunes Datenbank zu öffnen, können wir auf Datenbank öffnen klicken und unsere sTunes.db Datei auswählen.

2 Datenbankstruktur

Nun sehen wir unter dem Reiter Datenbankstruktur welche Tabellen und dementsprechend welche Daten unsere Datenbank erhält. Wir sehen, dass die Datenbank 13 Tabellen enthält und können uns für jede Tabelle anschauen, welche Variablen unter welchem Namen gespeichert sind. So enthält unsere Tabelle albums die Variablen AlbumID, Title und ArtistID.

Die Spalte Typ zeigt uns an, welches Format die Daten in der jeweiligen Tabellenspalte besitzen. Die Spalte Schema zeigt uns, über welche Befehle die jeweiligen Tabellen und Spalten erstellt worden sind.

Wir haben folglich also eine Übersicht zu allen Tabellen und ihren Variablen und können zum Beispiel folgende Rückschlüsse ziehen:

  • Die Variable AlbumID in der Tabelle albums ist vom Typ INTEGER, also eine ganze Zahl.
  • Die Variable Title in der Tabelle albums ist vom Typ NVARCHAR, also eine Zeichenfolge mit variabler Länge und durch die Angabe (160) auf Maximal 160 Zeichen begrenzt.
  • Die Variable UnitPrice in der Tabelle invoice_items ist vom Typ NUMERIC, also eine Zahl und durch die Angabe (10,2) auf insgesamt 10 Stellen begrenzt, wobei 2 dieser Stellen Nachkommastellen sind.

Eine Übersicht zu weiteren Datentypen ist hier zu finden:
SQL Datatypes

Um unsere Datenbankstruktur besser nachvollziehen zu können, ist im Downloadordner der Datenbank ein sogenanntes Datenbankschema hinterlegt:

3 SQL-Abfragen

Nachdem wir uns einen Überblick über die Datenbankstruktur verschafft haben, können wir damit anfangen, Informationen aus unserer Datenbank abzufragen. Dazu müssen wir in unserem DB Browser auf den Reiter SQL ausführen wechseln und können dann unsere Befehle in das Fenster schreiben. Durch den Play-Button lassen sich diese dann ausführen.


3.1 SELECT

Um Daten abzufragen, können wir das sogenannte SELECT Statement verwenden. Durch den folgenden Befehl können wir alle Einträge mit allen Spalten aus der Tabelle artists ausgeben lassen.

SELECT
  *
FROM
  artists;


Durch das Sternchen haben wir alle Variablen ausgewählt. Möchten wir genauer festlegen, welche Variablen wir abfragen möchten, können wir dies zwischen SELECT und FROM definieren:

SELECT
  Name
FROM
  artists;


Dabei können wir beliebig viele Variablen wählen, indem wir sie durch ein Komma trennen. Nur bei der letzten Variable darf kein Komma stehen:

SELECT
  FirstName,
  LastName,
  Email
FROM
  customers;


Um die Namen der Spalten in unserer Ausgabe zu verändern, können wir die ausgewählten Variablen in unserem SELECT Statement neu betiteln. Dabei reicht es bei einzelnen Worten, den neuen Titel hinter die Zuordnung AS zu schreiben, während mehrere getrennte Worte wie im folgenden Beispiel durch ’’ oder [ ] umschlossen werden müssen:

SELECT
  FirstName AS Vorname,
  LastName AS Nachname,
  Email AS 'Aktuelle Emailadresse',
  City AS [Aktueller Wohnort]
FROM
  customers;

3.2 ORDER BY

Um unsere Ergebnisse nun auch zu soriteren, können wir den Befehl ORDER BY verwenden, die entsprechende Variable wählen und zwischen ASC für ascending (aufsteigend) und DESC für descending (absteigend) wählen. Verzichten wir auf die Angabe von ASC und DESC, wird immer ASC ausgewählt. Zur besseren Lesbarkeit empfiehlt es sich dennoch die Angabe zu definieren.

SELECT
  FirstName AS Vorname,
  LastName AS Nachname,
  Email AS 'Aktuelle Emailadresse',
  City AS [Aktueller Wohnort]
FROM
  customers
 ORDER BY
    FirstName ASC;

3.3 LIMIT

Möchten wir die Ausgabe nun auch auf eine bestimmte Anzahl von Ergebnissen beschränken, können wir dies mit dem Befehl LIMIT tun:

SELECT
  FirstName AS Vorname,
  LastName AS Nachname,
  Email AS 'Aktuelle Emailadresse',
  City AS [Aktueller Wohnort]
FROM
  customers
 ORDER BY
    FirstName ASC
LIMIT 3;

3.4 WHERE

Um Daten genauer filtern zu können und bestimmte Informationen ausfindig zu machen, können wir den Befehl WHERE und verschiedene Operatoren nutzen.

Zu den wichtigsten Operatoren zählen:

Vergleiche:
= gleich
> größer als
< kleiner als
>= größer oder gleich
<= kleiner oder gleich
<> ungleich

und Logische Operatoren:
BETWEEN
IN
LIKE
AND
OR

Möchten wir zum Beispiel wissen, welche Rechnungen aus unserer invoices Tabelle einen Gesamtbetrag von genau 1.98$ (Total = 1.98) aufweisen, wann die Bestellungen getätigt worden sind (InvoiceDate), aus welcher Stadt (BillingCity) und mit welcher Rechnungsadresse (BillingAddress), können wir folgenden Befehl ausführen:

SELECT
    InvoiceDate AS Bestelldatum,
    BillingAddress AS Rechnungsadresse,
    BillingCity AS Stadt,
    Total AS Betrag
FROM
    invoices
WHERE
    Total = 1.98
ORDER BY
    InvoiceDate;


Ebenso können wir mit dem BETWEEN Operator herausfinden, welche Rechnungen einen Gesamtbetrag zwischen 1.98 und 5.00 aufweisen:

SELECT
    InvoiceDate AS Bestelldatum,
    BillingAddress AS Rechnungsadresse,
    BillingCity AS Stadt,
    Total AS Betrag
FROM
    invoices
WHERE
    Total BETWEEN 1.98 AND 5.00
ORDER BY
    InvoiceDate;


Um nach Text-Daten zu filtern, müssen wir den entsprechenden Text lediglich mit Anführungszeichen umschließen. So lassen sich wie folgt alle Bestellungen aus Stuttgart abrufen:

SELECT
  InvoiceDate AS Bestelldatum,
  BillingAddress AS Rechnungsadresse,
  BillingCity AS Stadt,
  Total AS Betrag
FROM
  invoices
WHERE
  BillingCity = 'Stuttgart'
ORDER BY
  InvoiceDate;

Dabei können wir ganz gleich ob es sich um Zahlenwerte oder Text handelt auch mehrere Kriterien verwenden. Möchten wir z.B. alle Rechnungen aus Stuttgart mit einem Betrag von mehr al 5$, können wir die zweite Bedingung durch AND hinzufügen:

SELECT
  InvoiceDate AS Bestelldatum,
  BillingAddress AS Rechnungsadresse,
  BillingCity AS Stadt,
  Total AS Betrag
FROM
  invoices
WHERE
  BillingCity = 'Stuttgart' AND Total > 5
ORDER BY
  InvoiceDate;


Durch den Operator IN können wir zudem festlegen, dass mehrere Ausprägungen einer Variable in Betracht gezogen werden sollen. So lassen sich wie folgt alle Rechnungen aus Stuttgart oder Berlin abfragen. Wir erhalten praktisch alle Einträge, bei denen der Wert der Variable BillingCity in unserer Liste vorkommt.

SELECT
  InvoiceDate AS Bestelldatum,
  BillingAddress AS Rechnungsadresse,
  BillingCity AS Stadt,
  Total AS Betrag
FROM
  invoices
WHERE
  BillingCity IN ('Stuttgart','Berlin')
ORDER BY
  InvoiceDate;


Wenn wir nach Ergebnissen suchen, die ein Kriterium nur teilweise erfüllen sollen, zum Beispiel alle Rechnungen von Städten, die mit “B” anfangen, können wir den Operator LIKE verwenden. Dabei ist durch ein %-Zeichen zu definieren, ob die Buchstaben vor, hinter oder sowohl vor als auch hinter dem gewünschten Inhalt beliebig sein können:

Durch BillingCity LIKE ‘B%’ erhalten wir alle Städte die mit B beginnen. (Brüssel, etc.)
Durch BillingCity LIKE ‘%T’ erhalten wir alle Städte die auf T enden. (Frankfurt, etc.)
Durch BillingCity LIKE ‘%k%’ erhalten wir alle Städte die ein K beinhalten. (Stockholm, etc.)
Durch BillingCity LIKE ‘O%O’ erhalten wir alle Städte die mit O beginnen und enden. (Oslo, etc.)

SELECT
  InvoiceDate AS Bestelldatum,
  BillingAddress AS Rechnungsadresse,
  BillingCity AS Stadt,
  Total AS Betrag
FROM
  invoices
WHERE
  BillingCity LIKE '%K%'
ORDER BY
  InvoiceDate;


Nützlich wird dies vor allem in Situationen, in denen wir vielleicht nicht wissen wie genau die Eingabe der Daten erfolgt ist oder wir nach kurzen Inhalten in langen Texten filtern möchten. Suchen wir beispielsweise alle Rechnungen mit der Adresse “Berger Straße” und wissen dabei nicht, ob das Wort Straße in der Eingabe ausgeschrieben oder gekürzt wurde und um welche Hausnummer es sich handelt, können wir mit der folgenden Abfrage alle Einträge erhalten, in denen das Wort Berger in der Rechnungsadresse vorgekommen ist:

SELECT
  InvoiceDate AS Bestelldatum,
  BillingAddress AS Rechnungsadresse,
  BillingCity AS Stadt,
  Total AS Betrag
FROM
  invoices
WHERE
  BillingAddress LIKE '%Berger%'
ORDER BY
  InvoiceDate;


3.5 Nutzung mehrerer Operatoren

In der Realität werden diese Filter meist komplexer und verwenden mehrere Operatoren wie AND und OR zugleich. Dabei gilt es bestimmte Reihenfolgen und Regeln zu beachten, um nicht das falsche Ergebnis zu erhalten. Ohne die Verwendung von Klammern werden die Bedingungen auf beiden Seiten des AND automatisch zusammengefasst. Dies entspricht jedoch nicht immer unserem Vorhaben.

Möchten wir beispielsweise eine Liste aller Rechnungen mit einem Betrag über 1.50$ aus Städten die entweder mit D oder P beginnen, könnten wir die Bedingung Total > 1.50 über AND mit BillingCity Like ‘p%’ OR BillingCity Like ‘d%’ verbinden. Ohne Klammern erhalten wir jedoch das folgende Ergebnis:

SELECT
  InvoiceDate AS Bestelldatum,
  BillingAddress AS Rechnungsadresse,
  BillingCity AS Stadt,
  Total AS Betrag
FROM
  invoices
WHERE
  Total > 1.50 AND BillingCity LIKE 'p%' OR BillingCity LIKE 'd%'
ORDER BY
  Total;


Wie wir an den ersten beiden Einträgen mit Betrag unter 1.50$ sehen können, hat SQL unseren Befehl anders verstanden, als wir es uns gewünscht haben. Die beiden Kriterien vor dem OR wurden zusammengefasst, während alles nach dem OR einzeln und damit ohne Rücksicht auf den Betrag betrachtet wurde. Wir haben also alle Einträge erhalten, die entweder der Bedingung “größer als 1.50$ und aus Stadt mit p am Anfang” oder der Bedingung “Stadt mit d am Anfang” entsprechen.

Um das korrekte Ergebnis zu erhalten, müssen wir Klammern so setzen, dass alle Bedingungen korrekt interpretiert werden, also so, dass zunächst alle Rechnungen mit den gewünschten Beträgen gesucht und erst anschließend die weiteren Bedingungen angewendet werden.

SELECT
  InvoiceDate AS Bestelldatum,
  BillingAddress AS Rechnungsadresse,
  BillingCity AS Stadt,
  Total AS Betrag
FROM
  invoices
WHERE
  Total > 1.50 AND (BillingCity LIKE 'p%' OR BillingCity LIKE 'd%')
ORDER BY
  Total;

4 Mehrere Tabellen verbinden

Eine der wichtigsten Eigenschaften von SQL ist die Fähigkeit Daten aus mehreren Tabellen zusammen zu verarbeiten. Wie wir in unserem Datenbank-Schema und dem Reiter Datenbankstruktur bereits festgestellt haben, befinden sich 13 Tabellen in unserer Datenbank. Diese können wir, wie in den folgenden Abschnitten genauer erläutert wird, über die PRIMARY und FOREIGN KEYS verknüpfen.

4.1 Entity-Relationship-Modell

Um das Schema der Datenbankstruktur zu veranschaulichen, habe ich die Tabellen mit ihren Variablen und den Beziehungen als sogenanntes Entity-Relationship-Modell, kurz ERM dargestellt. Dieses Modell hilft uns dabei, die Beziehungen der einzelnen Tabellen und ihrer Variablen zu veranschaulichen. Das Schema ist in unserem Downloadlink der Datenbank zu finden:

Wir sehen hier, welche Tabellen in unserer Datenbank vorhanden sind, welche Variablen diese besitzen und wie diese Tabellen zueinander in Beziehung stehen.

4.2 Primary & Foreign Keys

Wir stellen fest, dass einige Variablen - in jeder Tabelle mindestens eine - mit einem Schlüsselsymbol versehen sind. Dieses Symbol zeigt uns, dass diese Variablen der sogenannte PRIMARY KEY in der jeweiligen Tabelle sind und als eindeutige Kennung (unique identifier) für die einzelnen Einträge in der Tabelle gelten.

So wissen wir beispielsweise, dass jedes einzelne Album in der Tabelle albums, also jeder einzelne Eintrag, eine eindeutige AlbumId besitzt, über die das jeweilige Album identifiziert werden kann. Welche Variable als PRIMARY KEY dient, wird bei der Erstellung einer jeden Tabelle festgelegt. Ohne die Grafik würden wir die Info zu den Keys in unserer Übersicht zur Datenbankstruktur in der Spalte Schema durch Hinweise wie [AlbumId] INTEGER PRIMARY KEY finden. Wie wir anhand der Tabelle playlist_track feststellen können, ist es auch möglich mehrere PRIMARY KEYS in einer Tabelle zu nutzen.

Folgen wir den Verbindungen der PRIMARY KEYS, sehen wir, dass diese auch mit Variablen anderer Tabellen in Beziehung stehen, den sogenannten FOREIGN KEYS.

Ein FOREIGN KEY einer Tabelle ist eine Variable, die in einer anderen Tabelle als PRIMARY KEY verwendet wird. Diese Zuteilung ermöglicht es uns, die Daten aus verschiedenen Tabellen miteinander zu verbinden und auszuwerten.

Wir sehen beispielsweise, dass der PRIMARY KEY ArtistID in der Tabelle artists ebenso in der Tabelle albums als FOREIGN KEY vorkommt. Durch diese Verknüpfung ist es beispielsweise möglich, den Namen aus der artists Tabelle an die Daten der albums Tabelle zu hängen, um eine Übersicht zu allen Alben und ihren Künstlern zu erstellen.

Die Zahl 1 und das Unendlichkeitszeichen an den Verbindungen der Keys weisen zudem auf die Art der Beziehung hin. Während jeder Artist in der artists Tabelle nur einmal vorkommt und eine eindeutige ArtistId erhält, um Dopplungen zu vermeiden, kann diese ArtistId logischerweise bei unendlich vielen Alben vorkommen. Hat eine Künstlerin vier Alben gemacht, werden in der albums Tabelle vier Einträge mit eindeutigen AlbumIds vorkommen, die jedoch alle die selbe ArtistId verwenden.

Zu beachten ist außerdem, dass ein PRIMARY KEY in einer Tabelle auch mit einem FOREIGN KEY mit anderem Namen in einer anderen Tabelle verbunden sein kann. So tritt der PRIMARY KEY EmployeeId der employees Tabelle als FOREIGN KEY mit dem Namen SupportRepId in der customers Tabelle auf. Während wir in der Personaltabelle vielleicht von Personalnummer sprechen, kann die gleiche Nummer in der Kundentabelle beispielsweise als Nummer der Betreuungsperson betitelt werden.

4.3 Inner Joins

Wie wir in unserem Datenbank-Schema feststellen konnten, besteht unsere Tabelle albums aus den Variablen AlbumId, Title und ArtistId. Möchten wir nun zu dem Albumtiteln auch Namen der Interpreten sehen, ist dies ohne die artists Tabelle nicht möglich.

Da die Variable ArtistId in der albums Tabelle jedoch als FOREIGN KEY mit dem PRIMARY KEY ArtistId in der artists Tabelle übereinstimmt, können wir sie mit der folgenden Abfrage verbinden:

Wir verbinden die beiden Tabellen albums und artists bei allen Einträgen, bei denen die ArtistId in der Albums-Tabelle der ArtistId in der Artists-Tabelle entspricht.

SELECT
    *
FROM
    albums
INNER JOIN
    artists
ON
    albums.ArtistId = artists.ArtistId;


Dabei können wir den Code einfacher und übersichtlicher darstellen, indem wir den beiden Tabellen innerhalb der Abfrage ein Alias geben. So müssen wir die Tabellennamen im Befehl nicht erneut ausschreiben:

SELECT
    *
FROM
    albums AS a
INNER JOIN
    artists AS n
ON
    a.ArtistId = n.ArtistId;


Wenn wir nun die Auswahl der Variablen eingrenzen, können wir dies ebenfalls mit dem Alias machen, selbst wenn dieser erst in den darauf folgenden Zeilen definiert wird:

SELECT
    a.Title,
    n.Name
FROM
    albums AS a
INNER JOIN
    artists AS n
ON
    a.ArtistId = n.ArtistId;


Zu beachten ist, dass INNER JOINS ausschließlich übereinstimmende Einträge wiedergeben.

In unserem Beispiel passt es logischerweise, dass alle Alben jemandem zugeordnet werden können. Gibt es jedoch für einen Eintrag keine Übereinstimmung, wird dieser Eintrag bei einem INNER JOIN fallen gelassen. Durch unsere Abfrage erhielten wir alle Alben und die zum Album gehörenden Artists, da jedoch nicht von allen Artists ein Album in unserem Store ist, wurden einige Artists bei dieser Abfrage ausgeschlossen - wie wir gleich sehen werden.

4.4 Outer Joins

Outer Joins geben eine Tabelle vollständig wieder und fügen die übereinstimmende Einträge einer anderen Tabelle der ersten hinzu. Man unterscheidet dabei zwischen sogenannten LEFT OUTER JOINS und RIGHT OUTER JOINS.

Bei LEFT OUTER JOINS werden sozusagen allen Einträgen der “linken Tabelle” die passenden Einträge der “rechten Tabelle” hinzugefügt und alle Einträge ohne Übereinstimmung erhalten für die Variablen der rechten Tabelle Null-Werte.

Für RIGHT OUTER JOINS gilt dies auch, nur in umgekehrter Reihenfolge. Diese werden in SQLite nicht unterstützt, lassen sich aber logischerweise durch einen LEFT OUTER JOIN und Tauschen der Seiten der Tabellen replizieren.

Nehmen wir erneut das Beispiel mit den Alben und Artists. Möchten wir unsere artists Tabelle um Albuminformationen erweitern, auch wenn nicht alle Artists ein Album in unserem Store aufweisen, können wir den LEFT OUTER JOIN nutzen. Dabei nehmen wir die artists Tabelle auf die linke und die albums Tabelle auf die rechte Seite.

SELECT
    n.Name AS 'Artist-Name',
    a.Title AS 'Album-Titel'
FROM
    artists AS n
LEFT OUTER JOIN
    albums AS a
ON
    n.ArtistId = a.ArtistId;
Scrollen wir jetzt ein wenig durch die Ergebnisse, sehen wir zum einen, dass wir für Künstler mit mehreren Alben auch mehrere Einträge haben, gleichzeitig jedoch immernoch alle Künstler ohne Album vorhanden sind. Überall, wo keine Übereinstimmung mit der albums Tabelle gefunden worden ist, wurden Null-Werte hinzugefügt:


4.5 Nutzung mehrerer Joins

Bei Betrachtung unseres Datenbank-Schemas zeigt sich, dass sich noch mehr als nur zwei Tabellen miteinander verknüpfen lassen. Möchten wir beispielsweise wissen, welche Songs in einer Playlist vorkommen und wie Song und Playlist jeweils heißen, müssen wir die Tabellen playlists, playlist_track und tracks verbinden.


Durch einen INNER JOIN zwischen der PlaylistId in der Tabelle playlists und der PlaylistID in der Tabelle playlist_track können wir zunächst alle Track-IDs, die in den jeweiligen Playlists vorkommen, ausfindig machen.

Anschließend können wir diese TrackID aus der Tabelle playlist_track für einen weiteren INNER JOIN nutzen, um über die TrackId in der Tabelle tracks an die Variable Name des Tracks heranzukommen. Wir können also auf einen Join den nächsten folgen lassen:

SELECT
    p.name AS Playlistname,
    p.PlaylistId,
    t.TrackId,
    t.name AS Songname,
    t.Composer
FROM
    playlists AS p
INNER JOIN
    playlist_track AS pt
ON
    p.PlaylistId = pt.PlaylistId
INNER JOIN
    tracks AS t
ON 
    pt.TrackId = t.TrackId
ORDER BY
    p.PlaylistId DESC;


So können wir nun sehen, welche Songs von welchen Künstlern in welcher Playlist vorkommen.

5 Erweiterte SQL-Abfragen

In der Praxis wird man uns natürlich nicht nur damit beauftragen, bestimmte Einträge aus der Datenbank zu ziehen, sondern diese auch auszuwerten, gesondert aufzubereiten und zum Beispiel für das Management relevante Schlussfolgerungen anzustellen. Als abschließenden Ausblick, schauen wir uns nun ein paar erweiterte SQL-Abfragen in Kurzform an.

5.1 Aggregatfunktionen

Ein wichtiges Element in der Arbeit mit Datenbanken sind Aggregatfunktion, also Funktionen mit denen wir Daten zusammenfassen oder mathematische Berechnungen anstellen können. Die folgenden fünf grundlegenden Aggregatfunktionen sind dabei nur ein kleiner Teil der in SQL verwendbaren Funktionen:

  • AVG()
  • COUNT()
  • MAX()
  • MIN()
  • SUM()

Angewendet werden diese Funktionen wie folgt:

SELECT
    SUM(Total) AS Summe,
    AVG(Total) AS Durchschnittsbetrag,
    MAX(Total) AS 'Höchster Betrag',
    MIN(Total) AS 'Geringster Betrag',
    COUNT(*) AS 'Anzahl der Belege'
FROM
    invoices;


COUNT() beachtet dabei nur Werte die nicht Null sind. In unserem Beispiel haben wir jedoch mit dem * in der Klammer alle Null und Error-Werte mitgezählt.

Wie wir anhand des Durchschnittsbetrags sehen können, hat SQL das Ergebnis nicht gerundet. Zum Runden können wir die Funktion ROUND(X,Y) verwenden, wobei X für den Wert der gerundet werden soll und Y für die Anzahl der Nachkommastellen steht.

SELECT
    SUM(Total) AS Summe,
    AVG(Total) AS Durchschnittsbetrag,
    ROUND(AVG(Total), 2) AS 'Gerundeter Durchschnittsbetrag',
    MAX(Total) AS 'Höchster Betrag',
    MIN(Total) AS 'Geringster Betrag',
    COUNT(*) AS 'Anzahl der Belege'
FROM
    invoices;


5.2 GROUP BY

In den meisten Fällen werden wir jedoch nicht einfach nach dem Durchschnittswert der gesamten Tabelle, sondern eher nach dem Durchschnitt in bestimmten Kategorien oder Gruppen gefragt.

Um Werte für bestimmte Gruppen berechnen zu können, lässt sich der Befehl GROUP BY verwenden. So können wir wie im folgenden Beispiel die Durchschnittsbeträge der Rechnungen je nach Stadt darstellen:

SELECT
    BillingCity AS Standort,
    AVG(Total) AS 'Durchschnittlicher Rechnungsbetrag'
FROM
    invoices
GROUP BY
    BillingCity;


Möchten wir dabei wie in den vorherigen Kapiteln nach bestimmten Eigenschaften filtern, können wir auch hier den Befehl WHERE einsetzen. So können wir uns wie folgt die Durchschnittsbeträge für alle Städte mit F als Anfangsbuchstaben ausgeben lassen:

SELECT
    BillingCity AS Standort,
    AVG(Total) AS 'Durchschnittlicher Rechnungsbetrag'
FROM
    invoices
WHERE
    BillingCity LIKE 'F%'
GROUP BY
    BillingCity;


Sollten wir jedoch nach dem Ergebnis unserer Aggregatfunktion filtern wollen, können wir WHERE nicht verwenden. Möchten wir z.B. nur Städte sehen, dessen Durchschnittsbetrag größer als 6 ist und definieren dies wie folgt durch WHERE, erhalten wir eine Fehlermeldung:

SELECT
    BillingCity AS Standort,
    AVG(Total) AS 'Durchschnittlicher Rechnungsbetrag'
FROM
    invoices
WHERE
    AVG(Total) > 6
GROUP BY
    BillingCity;


Um nach dem Ergebnis der Aggregatfunktion filtern zu können, müssen wir den Ausdruck HAVING nach dem GROUP BY Befehl einsetzen:

SELECT
    BillingCity AS Standort,
    AVG(Total) AS 'Durchschnittlicher Rechnungsbetrag'
FROM
    invoices
GROUP BY
    BillingCity
HAVING
    AVG(Total) > 6;


Wir können festhalten, dass WHERE als Filter für direkte Daten aus unseren Tabellen und HAVING als Filter für aggregierte Daten genutzt werden kann. Der Befehl HAVING führt ohne GROUP BY zu einem Error.

Selbstverständlich können wir beide Befehle auch zusammen verwenden. Beispielsweise um uns alle Städte mit Anfangsbuchstabe F (Werte aus der Tabelle) mit einem Durchschnittsbetrag über 6 (Werte die wir neu berechnen) ausgeben zu lassen. Hierbei ist die Reihenfolge der Befehle FROM, WHERE, GROUP BY und HAVING entscheidend:

SELECT
    BillingCity AS Standort,
    AVG(Total) AS 'Durchschnittlicher Rechnungsbetrag'
FROM
    invoices
WHERE
    BillingCity LIKE 'B%'
GROUP BY
    BillingCity
HAVING
    AVG(Total) > 6;


5.3 CASE

In manchen Szenarien möchten wir die Fälle in unserer Tabelle aufgrund bestimmter Werte zu neuen Gruppen zuteilen. Beispielsweise um zwischen kleinen, mittleren und großen Bestellungen zu unterscheiden. Durch den Befehl CASE können wir diese Zuteilung vornehmen. Der Befehl wird in unserer Auswahl der Variablen im Befehl SELECT platziert und definiert die Zuteilung zu einer bestimmten Gruppe je nach Wert einer anderen Variable.

So wählen wir wie im folgenden Codeblock zunächst die Variablen unserer Tabelle, die wir uns anzeigen lassen möchten und platzieren dann unseren Befehl CASE. Ohne die darauf folgenden Zeilen mit einem Komma zu trennen, wie es bei den Variablen der Fall ist, definieren wir dann mit dem Wort WHEN, unter welchen Umständen (Total < 2.00) der jeweilige Fall zu einer neuen Kategorie (Kleine Bestellung) hinzugefügt werden soll. Das Wort ELSE teilt dann alle Fälle, die den vorherigen WHEN Bedingungen nicht entsprechen einer weiteren Kategorie zu. Der CASE Befehl wird dann durch END AS mit dem Namen unserer neuen Spalte beendet.

SELECT
    InvoiceDate AS Bestelldatum,
    BillingAddress AS Rechnungsadresse,
    Total AS Rechnungsbetrag,
    CASE
    WHEN Total < 2.00 THEN 'Kleine Bestellung'
    WHEN Total BETWEEN 2.00 AND 7.00 THEN 'Mittlere Bestellung'
    ELSE 'Große Bestellung'
    END AS Bestellungsumfang
FROM
    invoices;


Möchten wir anschließend nurnoch die großen Bestellungen betrachten, können wir wieder durch WHERE filtern:

SELECT
    InvoiceDate AS Bestelldatum,
    BillingAddress AS Rechnungsadresse,
    Total AS Rechnungsbetrag,
    CASE
    WHEN Total < 2.00 THEN 'Kleine Bestellung'
    WHEN Total BETWEEN 2.00 AND 7.00 THEN 'Mittlere Bestellung'
    ELSE 'Große Bestellung'
    END AS Bestellungsumfang
FROM
    invoices
WHERE
    Bestellungsumfang = 'Große Bestellung';


6 Erstellung von Datenbanken

Die letzten Grundlagen, mit denen man sich beschäftigen sollte, bevor man sich mit komplexeren Abfragen und Funktionen auseinandersetzt, umfassen das Erstellen von Datenbanken und Tabellen.

Die Datenbankerstellung im DB Browser for SQLite ist etwas anders, als bei der Nutzung gängiger Datenbanksysteme, weshalb das folgende Beispiel im DB Browser nicht anwendbar ist.

In gängigen Datenbanksystemen lässt sich eine neue Datenbank einfach über den Befehl CREATE DATABASE in Kombination mit dem Namen der neuen Datenbank erstellen:

CREATE DATABASE datenbankname;

Anschließend lässt sich eine neue Tabelle in der Datenbank über den Befehl CREATE TABLE erstellen. Dabei müssen für jede Spalte, die wir erstellen wollen, der Spaltenname und der Datentyp der jeweiligen Spalte definiert werden:

CREATE TABLE Playlistkuratoren (
  KuratorId INT NOT NULL,
  Personalnummer INT,
  Vorname NVARCHAR(40),
  Nachname NVARCHAR(40),
  Geburtsdatum DATE,
  PlaylistAnzahl NUMERIC(4),
  PRIMARY KEY (KuratorId),
  FOREIGN KEY (Personalnummer) REFERENCES employees(EmployeeId)
)

In diesem Beispiel erstellen wir eine neue Tabelle, in der wir Daten über die Kuratoren von Playlisten sammeln.

In der ersten Spalte soll sich unsere KuratorId befinden. Diese ist vom Typ INT - also Integer, eine ganze Zahl. Durch NOT NULL legen wir fest, dass dieses Feld für keinen Eintrag in der Tabelle leer sein darf, denn mit der ID wollen wir den jeweiligen Eintrag immer identifizieren können.

Da einige Kuratoren eventuell direkt bei uns angestellt sind, möchten wir zudem eine Spalte für mögliche Personalnummern mit dem Datentyp INT anlegen.

Für Vor- und Nachname nehmen wir den Datentyp NVARCHAR. Es kann also ein beliebig langer Text eingegeben werden, der jedoch durch den Zusatz (40) auf maximal 40 Zeichen begrenzt ist.

Für das Geburtsdatum wählen wir den Datentyp DATE.

Durch den PRIMARY KEY legen wir nun fest, welche Variable als Primary Key dienen soll und damit jeder Wert der Variable nur einmal vorkommen kann. Zusätzlich legen wir fest, dass unsere Variable Personalnummer als FOREIGN KEY für die Variable employeeId in der Tabelle employees dient.

Nun ist unsere neue Tabelle erstellt und wir müssen diese nurnoch mit Einträgen befüllen. Dies geschieht mit dem Befehl INSERT INTO, bei dem wir die entsprechende Playlist und die zu befüllenden Variablen auswählen und anschließend über VALUES die Daten eingeben:

INSERT INTO Playlistkuratoren (KuratorId, Personalnummer, Vorname, Nachname, Geburtsdatum, PlaylistAnzahl)
VALUES (2,1,'Andrew', 'Adams', '1962-02-18',3);

Möchten wir nun beispielsweise wissen, wie die Emailadressen von Mitarbeitern sind, die unsere Playlisten zusammenstellen, können wir den folgenden INNER JOIN benutzen:

SELECT
    p.KuratorId,
    p.Personalnummer,
    p.PlaylistAnzahl,
    e.FirstName,
    e.LastName,
    e.Email
FROM
    employees AS e
INNER JOIN
    Playlistkuratoren AS p
ON
    p.Personalnummer = e.EmployeeId;


Logischerweise sehen wir, dass es nur den einen, neu hinzugefügten Mitarbeiter gibt, der als Playlistkurator tätig ist.

7 Weitere Ressourcen

Dieses Tutorial hat nur einen kleinen Einblick in die Funktionsweisen und Möglichkeiten von SQL gegeben. Hier sind weitere nützliche Ressourcen für die Arbeit mit SQL zu finden:

Hilfestellungen / Dokumentationen:


Umfangreicheres Tutorial: