SQL-Übungsaufgaben zu JOINs, Aggregation, Unterabfragen und Transaktionen
https://bildung.social/@oerinformatik/112633708392604185
https://oer-informatik.de/sql-uebungsaufgaben
tl/dr; (ca. 60 min Bearbeitungszeit): Am Beispiel einer Rechnungsdatenbank sollen SQL-Befehle mit Unterabfragen, Aggregationen, Joins und Transaktionen erstellt werden. Die Lösungen und Erläuterungen können eingeblendet werden. (Zuletzt geändert am 17.06.2024)
Übungsaufgabe Rechnungsdatenbank:
Gegeben ist das folgende Datenmodell:

1a) Selektion und Projektion (8 Punkte)
Erstelle eine Abfrage, die alle RechnungsNr
des Kunden mit dem Namen “Fantasie GmbH” ausgibt.
Beispielausgabe:
Name | RechnungsNr |
---|---|
Fantasie GmbH | 12345 |
Fantasie GmbH | 56789 |
Fantasie GmbH | 36789 |
1b) Aggregieren (8 Punkte)
Erstelle eine Abfrage, die alle RechnungsNr
ausgibt sowie, falls vorhanden, die jeweiligen Rechnungssummen (die Summe der zugehörigen GesamtpreisNetto
, ohne Berücksichtigung der Rabatte).
Beispielausgabe:
RechnungsNr | Rechnungssumme |
---|---|
12345 | 123,45 |
56789 | 2343,34 |
23223 | NULL |
36789 | 145,45 |
Es ist lediglich ein Aggregat (Summe) und eine Gruppierung nötig:
SELECT RechnungsNr, //1P
SUM(GesamtpreisNetto) AS Rechnungsnumme //3P
FROM Rechnungsposition //2P
GROUP BY RechnungsNr //2P
Häufig werden bei dieser Aufgabe ohne Not mehrere Tabellen verknüpft, aber alle nötigen Attribute sind bereits in Rechnungsposition
enthalten! Daher ist folgende Lösung zwar korrekt, aber unnötig kompliziert (und daher weniger performant):
1c) Bedingtes Aggregieren (8 Punkte)
Erstellen Sie eine Abfrage, die Rechnungsnummer und Anzahl der zugehörigen Rechnungspositionen aller Rechnungen ausgibt, die mehr als 20 Rechnungspositionen haben. Die Liste soll nach Anzahl der Positionen absteigend sortiert sein.
Beispielausgabe:
RechnungsNr | AnzahlPosition |
---|---|
12345 | 321 |
56789 | 200 |
36789 | 15 |
Analog zur vorigen Aufgabe: alle nötigen Felder sind bereits in Rechnungsposition
enthalten. Wichtig zur Reihenfolge der SQL-Befehle:
Sie werden in der Reihenfolge des Merksatzes “Warum geht Herbert oft laufen?” notiert (
WHERE... GROUP... HAVING... ORDER BY... LIMIT...
). Das gilt für MySQL/MariaDB.Sie werden in der Reihenfolge des Merksatzes “Für wen geht Herbert so oft laufen?” ausgeführt (
FROM... WHERE... GROUP... HAVING... SELECT... ORDER BY... LIMIT...
). Auch das gilt für MySQL/MariaDB.
Daher ist der Alias AnzahlPosition
des folgenden Befehls im HAVING
-Clause noch nicht bekannt (dort muss COUNT(...)
genutzt werden), aber im ORDER
-Clause kann er genutzt werden (weil dieser nach SELECT
ausgeführt wird).
SELECT RechnungsNr, //1P
COUNT(RP.RechnungsPositionNr) AS AnzahlPosition //2P
FROM Rechnungsposition //1P
GROUP BY RechnungsNr //1P
HAVING COUNT(RechnungsPositionNr)>20 //2P
ORDER BY AnzahlPosition DESC //1P
Eine Verknüpfung mit Rechnung
verkompliziert die Lösung unnötig und ist weniger performant.
SELECT R.RechnungsNr AS RechnungsNr, //0.5P
COUNT(RP.RechnungsPositionNr) AS AnzahlPosition //1P
FROM Rechnung AS R //0.5P
LEFT JOIN Rechnungsposition AS RP ON R.RechnungsNr = RP.RechnungsNr //1P
GROUP BY R.RechnungsNr //2P
HAVING COUNT(RP.RechnungsPositionNr)>20 //2P
ORDER BY AnzahlPosition DESC //1P
1d) Abfragen aus mehreren Tabellen (12 Punkte)
Erstelle eine Abfrage, die Kundennummern und Namen aller Kunden ausgibt, die Rechnungen mit mehr als 20 Rechnungspositionen haben. Alle Kunden sollen darin höchstens einmal vorkommen.
Beispielausgabe:
Kundennummer | Name |
---|---|
3456 | Fantasie GmbH |
78987 | Beispiel AG |
671235 | Muster KG |
Hier gibt es (mindestens) zwei unterschiedliche Strategien: über JOIN
alle Tabellen zusammentragen, Datensätze Selektieren (HAVING
) und die Ergebnismenge mit DISTINCT
auf Unikate begrenzen oder die Selektion in einer Unterabfrage erledigen (was ich persönlich für eleganter halte):
Variante 1: Unterabfrage zur Datensatz-Selektion
SELECT K2.KundenNr AS Kundenummer, K.Name AS Name //1P
FROM Kunde AS K2 //1P
WHERE K2.KundenNr IN ( //2P
SELECT R.KundenNr, //1P
FROM Rechnung AS R //1P
INNER JOIN Rechnungsposition AS RP ON R.RechnungsNr = RP.RechnungsNr //2P
GROUP BY R.RechnungsNr, R.KundenNr //2P
HAVING COUNT(RP.RechnungsPositionNr)>20) //2P
Variante 2: Erst per JOIN
maximale Information sammeln, die dann per HAVING
und DISTINCT
wieder eindampfen:
SELECT DISTINCT //2P
K.KundenNr AS Kundenummer, K.Name AS Name //1P
FROM Rechnung AS R //1P
INNER JOIN Rechnungsposition AS RP ON R.RechnungsNr = RP.RechnungsNr //2P
INNER JOIN Kunde AS K ON K.KundenNr = R.KundenNr //2P
GROUP BY R.RechnungsNr, K.KundenNr, K.Name //2P
HAVING COUNT(RP.RechnungsPositionNr)>20) //2P
Es spielt hier keine Rolle, ob LEFT JOIN
oder INNER JOIN
verwendet wird, da ohnehin abgefragt wird, ob Rechnungspositionen vorhanden sind.
2a) Unterabfragen mit “vor jeder…” (8 Punkte)
Erstelle eine Abfrage, die alle Rechnungsnummern ausgibt, deren Rechnung (nach numerischen Rechnungsnummern sortiert) vor jeder Rechnung der Fantasie GmbH liegt.
Beispielausgabe:
RechnungsNr |
---|
12345 |
Eigentlich bedeutet ja “nach numerischen Rechnungsnummern sortiert”, dass es eindeutig um die Nummern geht… das wäre dann so:
SELECT R.RechnungsNr AS RechnungsNr, //1P
FROM Rechnung AS R //1P
WHERE R.RechnungsNr < ALL( //2P
SELECT R1.RechnungsNr AS RechnungsNr //1P
FROM Rechnung AS R1 //1P
LEFT JOIN Kunde AS K ON R1.KundenNr = K.KundenNr //1P
WHERE K.Name LIKE "Fantasie GmbH") //1P
… wer es jedoch falsch verstanden hat, der hat das “vor jeder Rechnung…” als eine zeitliche Bedingung gelesen und den Sortierungs-Teil am Ende eingebaut:
SELECT R.RechnungsNr AS RechnungsNr, //0.5P
FROM Rechnung AS R //0.5P
WHERE R.Rechnungsdatum < ALL( //2P
SELECT R1.Rechnungsdatum AS Rechnungsdatum //1P
FROM Rechnung AS R1 //1P
LEFT JOIN Kunde AS K ON R1.KundenNr = K.KundenNr //0.5P
WHERE K.Name LIKE "Fantasie GmbH") //0.5P
ORDER BY RechnungsNr //0.5P
2b) Unterabfragen mit “vor einer…” (8 Punkte)
Erstelle eine Abfrage, die Rechnungsnummern ausgibt, deren Rechnung (nach numerischen Rechnungsnummern sortiert) vor irgendeiner Rechnung der Fantasie GmbH liegt.
RechnungsNr |
---|
67846 |
56789 |
36789 |
Analog zur vorigen Aufgabe:
SELECT R.RechnungsNr AS RechnungsNr, //1P
FROM Rechnung AS R //1P
WHERE R.RechnungsNr < ANY( //2P
SELECT R1.RechnungsNr AS RechnungsNr //1P
FROM Rechnung AS R1 //1P
LEFT JOIN Kunde AS K ON R1.KundenNr = K.KundenNr //1P
WHERE K.Name LIKE “Fantasie GmbH“) //1P
… bei falsch verstandener Bedingung (Teilpunkte)
SELECT R.RechnungsNr AS RechnungsNr, //0.5P
FROM Rechnung AS R //0.5P
WHERE R.Rechnungsdatum < ANY( //2P
SELECT R1.Rechnungsdatum AS Rechnungsdatum //1P
FROM Rechnung AS R1 //1P
LEFT JOIN Kunde AS K ON R1.KundenNr = K.KundenNr //0.5P
WHERE K.Name LIKE “Fantasie GmbH“) //0.5P
ORDER BY RechnungsNr //0.5P
3a) Transaktionen starten (8 Punkte)
Es soll in einem sicheren Transaktions-Umfeld Veränderungen an der Datenbank vorgenommen werden, die sich noch nicht unmittelbar auswirken.
Erstelle alle Befehle, die nötig sind, um eine Transaktion zu starten und die (vorhandene) Rechnung „1234“ dem (vorhandenen) Kunden mit den KundenNr „234“ zuzuweisen.
3b) Transaktionen abbrechen (2 Punkte)
Mit welchem Befehl werden die Befehle aus Aufgabe a nicht zur Anwendung gebracht und die Transaktion abgebrochen?
3c) Transaktionen abschließen (2 Punkte)
Mit welchem Befehl werden die Befehle aus Aufgabe a persistent ausgeführt und die Transaktion abgeschlossen?
Hinweis zur Nachnutzung als Open Educational Resource (OER)
Dieser Artikel und seine Texte, Bilder, Grafiken, Code und sonstiger Inhalt sind - sofern nicht anders angegeben - lizenziert unter CC BY 4.0. Nennung gemäß TULLU-Regel bitte wie folgt: “SQL-Übungsaufgaben zu JOINs, Aggregation, Unterabfragen und Transaktionen” von H. Stein (oer-informatik.de), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/sql-uebungsaufgaben veröffentlicht, die Quelltexte sind in weiterverarbeitbarer Form verfügbar im Repository unter https://gitlab.com/oer-informatik/db-sql/dml-select-queries. Stand: 17.06.2024.
[Kommentare zum Artikel lesen, schreiben] / [Artikel teilen] / [gitlab-Issue zum Artikel schreiben]