Übungsaufgaben zu SQL-Abfragen über mehrere Tabellen (JOIN)

https://bildung.social/@oerinformatik/112633708392604185

https://oer-informatik.de/sql-join-uebungsaufgaben

SQL, JOIN, INNER JOIN, LEFT JOIN, RIGHT JOIN, FULL OUTER JOIN, Anti-Join, Kartesisches Produkt, MariaDB, Übungsaufgaben, Prüfungsvorbereitung

tl/dr; (Bearbeitungszeit insgesamt 154 min, mehrere Aufgaben): Fünf Übungsaufgaben zu Verbünden über mehrere Tabellen – vom einfachen INNER JOIN über LEFT, RIGHT und nachgebauten FULL JOIN bis zu Tabellen in mehreren Rollen, zusammengesetzten Join-Bedingungen, Anti-Joins, Vervielfachung beim Zählen sowie UPDATE und DELETE mit JOIN, außerdem Zeitspannen-Auswertungen, DISTINCT-Abfragen und Verbünde mit pandas. Alle Beispiellösungen sind ausgeblendet und mit Punkteverteilung versehen [AI-Assisted]. (Zuletzt geändert am 26.10.2026)

Dieser Artikel ist Teil einer Artikelreihe über SQL-Abfragen: Selektion und Projektion / Sortieren und Begrenzen (ORDER BY, LIMIT) / Funktionen im SELECT (Single-Row-Functions) / Fallunterscheidungen und NULL (CASE, COALESCE) / Aggregatfunktionen und Gruppierung / Abfragen über mehrere Tabellen (JOIN) / Übungsaufgaben: einfache Abfragen, Aggregatfunktionen, JOIN, Rechnungsdatenbank (gemischt)

Diese Übungsaufgaben gehören zum Artikel SQL: Abfragen über mehrere Tabellen mit JOIN. Wie die Tabellen über Primär- und Fremdschlüssel zusammenhängen, erklärt der Artikel Der logische Datenbankentwurf mit dem Relationenmodell; Gruppieren und Zählen findest Du im Artikel SQL: Aggregierende Funktionen.

Hinweise zur Bearbeitung: Die Aufgaben sind bewusst umfangreich, damit alle typischen Fragestellungen zum Thema vorkommen; die Angabe hinter dem Aufgabentitel nennt die Kompetenzstufe (Stufe 1 Basiskompetenz bis Stufe 4 Expertise). Schreib Deine Lösung zuerst vollständig auf, bevor Du die Beispiellösung aufklappst. Die SQL-Anweisungen sind in MariaDB-Notation formuliert; andere korrekte Lösungen sind ebenfalls richtig (zum Beispiel JOIN statt INNER JOIN oder Tabellennamen statt Aliasen). In den Datenmodellen sind Primärschlüssel unterstrichen und Fremdschlüssel mit # gekennzeichnet. Heute ist in allen Szenarien der 18.10.2026 (CURDATE()). Wo sich die Schreibweise der Datenbanksysteme unterscheidet, zeigen Tabs die Beispiellösung zusätzlich für SQL Server, PostgreSQL, Oracle und SQLite; in Oracle entfällt außerdem das Schlüsselwort AS vor Tabellenaliasen (FROM artikel a). Am Ende findest Du eine SQL-Kurzreferenz.

Aufgabe 1: Bestell- und Fertigungsübersicht im Fahrradshop (Stufe 2 – Kernkompetenz)

(ca. 34 min Bearbeitungszeit)

Du bist Auszubildende*r bei der CSB-Rad GmbH in Bielefeld, die Fahrräder und Komponenten über einen Onlineshop verkauft und Rahmen und Schlösser bei Partnerbetrieben individuell fertigen lässt. Werkstattleiter Bastian Ludwig steuert die Fertigung und braucht für seine Planung einige Listen aus der Shop-Datenbank. Sie enthält unter anderem folgende Tabellen:

  • kunde (kunde_id, kundentyp, firma, vorname, nachname, email, …)

  • adresse (adresse_id, #kunde_id, typ, strasse, hausnr, plz, ort, land)

  • bestellung (bestellung_id, #kunde_id, bestellt_am, status, zahlungsart, #rechnungsadresse_id, #lieferadresse_id, …)

  • bestellposition (#bestellung_id, pos_nr, #artikel_id, menge, einzelpreis_netto, rabatt_prozent, #konfiguration_id)

  • artikel (artikel_id, artikelnr, bezeichnung, #kategorie_id, netto_preis, lagerbestand, …)

  • kategorie (kategorie_id, bezeichnung, #ober_kategorie_id)

  • fertigungspartner (partner_id, name, ort, leistung, schnittstelle, …)

  • fertigungsauftrag (auftrag_id, #bestellung_id, #pos_nr, #partner_id, uebermittelt_am, status, liefertermin, fehlermeldung)

Die Fremdschlüssel rechnungsadresse_id und lieferadresse_id verweisen beide auf adresse. Der Fremdschlüssel (bestellung_id, pos_nr) in fertigungsauftrag verweist auf den zusammengesetzten Primärschlüssel von bestellposition. Derzeit sind 7 Kund*innen und 8 Bestellungen gespeichert.

Auszug aus der Tabelle bestellposition
bestellung_id pos_nr artikel_id menge einzelpreis_netto rabatt_prozent
1 1 3 1 3780.67 0.0
1 2 5 2 62.18 0.0
1 3 7 1 75.63 0.0
2 1 2 1 2436.13 0.0
… … … … … …
6 1 4 1 840.34 0.0
Tabelle fertigungsauftrag (ohne die Spalten uebermittelt_am und fehlermeldung)
auftrag_id bestellung_id pos_nr partner_id status liefertermin
1 1 1 1 in_produktion 2026-10-30
2 1 2 2 geliefert 2026-09-05
3 3 1 1 geliefert 2026-09-24
4 5 1 2 geliefert 2026-05-20
5 6 1 1 fehler NULL

a) Zeige alle Bestellungen mit Bestellnummer, Bestellzeitpunkt, Status sowie Nachname, Vorname und Firma der Kund*innen an. Die neueste Bestellung soll oben stehen. Erstelle dazu die SQL-Abfrage. (4 Punkte)

Ergebnis:

bestellung_id bestellt_am status nachname vorname firma
8 2026-10-16 16:20:00 offen Kühn Hans-Jürgen NULL
6 2026-10-12 19:03:00 bezahlt Berg Felix NULL
7 2026-10-01 07:45:00 storniert Schäfer Ingrid NULL
4 2026-09-15 08:30:00 geliefert Nowak Katarzyna Radhaus Bielefeld-Süd GmbH
3 2026-09-02 22:55:00 versandt Engel Mira NULL
2 2026-08-20 10:12:00 geliefert Kühn Hans-Jürgen NULL
1 2026-08-11 21:40:00 in_fertigung Okafor-Schmidt Chiamaka NULL
5 2026-05-02 14:00:00 geliefert Hartmann Michael Zweckverband Radwege OWL

Bewertung: Spaltenauswahl 1 P, Verbund der beiden Tabellen 1 P, Join-Bedingung Fremdschlüssel = Primärschlüssel 1 P, ORDER BY … DESC 1 P.

b) Bastian Ludwig hat sich in einem Forum eine Abfrage für dieselbe Liste abgeschaut und angepasst:

ba) MariaDB bricht mit der Meldung Column 'kunde_id' in field list is ambiguous ab. Erläutere die Meldung und korrigiere die Abfrage so, dass sie die Schreibweise mit Komma in der FROM-Klausel beibehält und jede Bestellung genau einmal mit dem passenden Nachnamen ausgibt. (3 Punkte)

bb) Ermittle, wie viele Zeilen die Abfrage geliefert hätte, wenn in der SELECT-Liste nur eindeutige Spaltennamen gestanden hätten, und erläutere, wie diese Zeilen zustande kommen. (2 Punkte)

ba) Die Spalte kunde_id gibt es in beiden Tabellen. Ohne Tabellenname oder Alias davor weiß MariaDB nicht, welche gemeint ist, die Angabe ist mehrdeutig (ambiguous). Außerdem fehlt die Verknüpfungsbedingung. Korrigiert mit qualifizierten Spaltennamen und der Join-Bedingung in WHERE (sogenannter impliziter Join):

Das Ergebnis hat 8 Zeilen (eine je Bestellung). Gleichwertig in expliziter Schreibweise: FROM bestellung AS b INNER JOIN kunde AS k ON b.kunde_id = k.kunde_id.

Bewertung: Erläuterung der Mehrdeutigkeit 1 P, qualifizierte Spaltennamen 1 P, Join-Bedingung in WHERE 1 P.

bb) Ohne Verknüpfungsbedingung bildet die Datenbank das kartesische Produkt (Kreuzprodukt): Jede der 8 Bestellungen wird mit jeder der 7 Kund*innen kombiniert, also 8 \cdot 7 = 56 Zeilen. Die meisten davon sind sinnlos, weil Bestellung und Kund*in nicht zusammengehören.

Bewertung: 56 Zeilen 1 P, Erläuterung Kreuzprodukt 1 P.

c) Chiamaka Okafor-Schmidt fragt nach dem Stand ihrer Bestellungen. Bastian kennt ihre Kundennummer nicht, nur den Nachnamen. Erstelle eine SQL-Abfrage, die alle von ihr bestellten Positionen mit Bestellnummer, Positionsnummer, Artikelnummer, Artikelbezeichnung, Kategorie und Menge ausgibt, sortiert nach Bestellung und Position. (6 Punkte)

Ergebnisbeispiel
bestellung_id pos_nr artikelnr artikel kategorie menge
1 1 RAD-LR-03 Lastenrad „Long John“ individuell Lastenräder 1
… … … … … …

Ergebnis:

bestellung_id pos_nr artikelnr artikel kategorie menge
1 1 RAD-LR-03 Lastenrad „Long John“ individuell Lastenräder 1
1 2 SCH-BUE-01 Bügelschloss, gleichschließend Schlösser 2
1 3 LIC-SET-01 Lichtset Nabendynamo StVZO Beleuchtung 1

Bewertung: vier Join-Bedingungen je 0,5 P (2 P), Filter über den Namen statt über die ID 1 P, Spaltenaliase für die beiden Spalten bezeichnung 1 P, Spaltenauswahl 1 P, Sortierung nach zwei Spalten 1 P. Wer kunde_id mit einer Unterabfrage ermittelt, erhält die Punkte für den Filter ebenfalls.

d) Für die Wochenplanung braucht Bastian alle Fertigungsaufträge, die noch nicht geliefert sind.

da) Erstelle eine SQL-Abfrage, die für diese Aufträge die Auftragsnummer, die Bestellnummer, die Bezeichnung des gefertigten Artikels, den Namen des Fertigungspartners, den Status und den Liefertermin ausgibt, sortiert nach Liefertermin. (6 Punkte)

db) Erläutere, was sich am Ergebnis ändert, wenn fertigungsauftrag und bestellposition nur über die Bestellnummer verknüpft werden. (2 Punkte)

da)

Ergebnis:

auftrag_id bestellung_id artikel partner status liefertermin
5 6 Rahmen nach Maß (Stahl, pulverbeschichtet) Rahmenwerk OWL GmbH fehler NULL
1 1 Lastenrad „Long John“ individuell Rahmenwerk OWL GmbH in_produktion 2026-10-30

MariaDB sortiert NULL bei aufsteigender Sortierung nach vorn; der fehlerhafte Auftrag steht damit oben, was für die Planung sogar praktisch ist.

Bewertung: Join-Bedingung über beide Schlüsselspalten 2 P, Verbund mit artikel und fertigungspartner je 1 P, Statusfilter 1 P, Spaltenauswahl und Sortierung 1 P.

db) Bestellung 1 hat drei Positionen. Wird nur bestellung_id verglichen, passt Fertigungsauftrag 1 zu allen drei Positionen; er erscheint dreimal, einmal davon mit dem richtigen Lastenrad und zweimal mit Bügelschloss und Lichtset, die gar nicht bei Rahmenwerk OWL gefertigt werden. Bei einem zusammengesetzten Schlüssel müssen in der Join-Bedingung alle Schlüsselspalten verglichen werden.

Bewertung: Vervielfachung der Zeilen 1 P, Begründung mit dem zusammengesetzten Schlüssel 1 P.

e) Für die Versandplanung sollen alle Bestellungen aufgelistet werden, bei denen Rechnungs- und Lieferadresse verschieden sind. Erstelle eine SQL-Abfrage, die die Bestellnummer, den Nachnamen sowie Straße und Ort der Rechnungsadresse und Straße und Ort der Lieferadresse ausgibt. (5 Punkte)

Ergebnisbeispiel
bestellung_id nachname rechnung_strasse rechnung_ort liefer_strasse liefer_ort
3 Engel Feilenstraße Bielefeld Kurierzentrale, Herforder Straße Bielefeld
… … … … … …

Die Tabelle adresse wird zweimal mit unterschiedlichen Aliasen eingebunden, einmal in der Rolle „Rechnungsadresse“ und einmal in der Rolle „Lieferadresse“:

Ergebnis:

bestellung_id nachname rechnung_strasse rechnung_ort liefer_strasse liefer_ort
3 Engel Feilenstraße Bielefeld Kurierzentrale, Herforder Straße Bielefeld
5 Hartmann Am Bahnhof Bielefeld Betriebshof, Eckendorfer Straße Bielefeld

Bewertung: adresse zweimal mit verschiedenen Aliasen 2 P, beide Join-Bedingungen 1 P, Bedingung für abweichende Adressen 1 P, Spaltenaliase 1 P.

f) Für die Lagerplanung ermittelt Bastian die Artikel, von denen in nicht stornierten Bestellungen insgesamt mehr als 5 Stück verkauft wurden. Erstelle die SQL-Abfrage mit Artikelnummer, Bezeichnung und verkaufter Stückzahl, absteigend nach Stückzahl sortiert. (5 Punkte)

Ergebnis:

artikelnr bezeichnung stueck
LIC-SET-01 Lichtset Nabendynamo StVZO 22
SCH-RAH-01 Rahmenschloss, gleichschließend 12
RAD-CITY-01 Stadtrad „Promenade“ 7-Gang 10

Bewertung: Verbund über drei Tabellen 1 P (der Status steht nur in bestellung), WHERE gegen Stornierungen 1 P, SUM 1 P, GROUP BY mit allen nicht aggregierten Spalten 1 P, HAVING und Sortierung 1 P.

g) Die Lipper SchließTec KG erhöht ihre Preise. Erstelle eine SQL-Anweisung, die den Nettopreis aller Artikel der Kategorie „Schlösser“ um 4 % erhöht und auf zwei Nachkommastellen rundet. Die kategorie_id der Schlösser ist nicht bekannt. (4 Punkte)

MariaDB erlaubt einen Verbund direkt in der UPDATE-Anweisung (multi-table UPDATE):

Gleichwertig und in allen Datenbanksystemen lauffähig ist die Variante mit Unterabfrage:

Betroffen sind 2 Zeilen: Bügelschloss 62.18 → 64.67 und Rahmenschloss 41.18 → 42.83.

Bewertung: UPDATE … SET mit Berechnung 1 P, Verbund oder Unterabfrage zu kategorie 2 P, Filter auf die Bezeichnung 1 P.

Aufgabe 2: Sessionplan und Raumbelegung für ein Barcamp (Stufe 2 – Kernkompetenz)

(ca. 32 min Bearbeitungszeit)

Das Ratz und Rübe Berufskolleg in Bielefeld veranstaltet am 13.03.2027 das Barcamp „Lernen.Digital.Leben 2027“. Du arbeitest als Auszubildende*r im Orga-Team von Informatiklehrer Dimitrios Papadopoulos mit. Emre Aksoy, Leiter des Technikteams, hat schon einige Abfragen geschrieben. Die Orga-Datenbank enthält unter anderem:

  • person (person_id, vorname, nachname, email, organisation, geburtsjahr)

  • anmeldung (anmeldung_id, #veranstaltung_id, #person_id, ticketart, angemeldet_am, essen, bedarf_barrierefreiheit, foto_einwilligung, …)

  • raum (raum_id, bezeichnung, name, gebaeude, etage, plaetze, beamer, stream_faehig, barrierefrei)

  • slot (slot_id, #veranstaltung_id, beginn, ende)

  • session (session_id, #veranstaltung_id, titel, format, status, eingereicht_am, max_plaetze, #raum_id, #slot_id, livestream, live_untertitel)

  • session_person (#session_id, #person_id, rolle)

  • session_buchung (#session_id, #anmeldung_id, gebucht_am)

  • geraet (geraet_id, typ, bezeichnung, mac, ip, #vlan_id, #raum_id, standort, leistung_w, poe)

ticketart hat einen der Werte 'Schueler', 'Lehrkraft', 'Extern', 'Referent', 'Orga' oder 'Aussteller'; barrierefrei ist ein Wahrheitswert. Sessions, die noch nicht eingeplant sind, haben in raum_id und slot_id den Wert NULL.

Auszug aus der Tabelle session
session_id titel status raum_id slot_id
1 KI-Assistenten im Unterricht – was geht, was nicht? angenommen 1 1
2 Reparieren statt wegwerfen: Laptops retten angenommen 7 2
3 Barrierefreie Videos: Untertitel und Gebärdensprache angenommen 5 3
4 Escape-Room mit Mikrocontrollern angenommen 6 4
5 Datenschutz im Schulalltag – Mythen und Fakten eingereicht NULL NULL
6 Barrierefreie Webseiten mit HTML und ARIA angenommen 6 5
7 Krypto-Trading für Einsteiger abgelehnt NULL NULL
Auszug aus der Tabelle raum
raum_id bezeichnung name plaetze barrierefrei
1 A.001 Aula 250 TRUE
2 A.104 NULL 32 TRUE
3 A.105 NULL 32 TRUE
4 B.012 Mensa 180 TRUE
5 B.201 IT-Labor 1 28 TRUE
6 B.202 IT-Labor 2 28 TRUE
7 C.110 Elektrowerkstatt 24 FALSE
8 C.020 Bibliothek 40 TRUE

a) Für die Infomonitore braucht Dimitrios den Sessionplan. Erstelle eine SQL-Abfrage, die für alle angenommenen Sessions Beginn und Ende des Slots, die Raumbezeichnung und den Titel ausgibt, sortiert nach Beginn und bei gleichem Beginn nach Raum. (4 Punkte)

Ergebnis:

beginn ende raum titel
10:00:00 10:45:00 A.001 KI-Assistenten im Unterricht – was geht, was nicht?
11:00:00 11:45:00 C.110 Reparieren statt wegwerfen: Laptops retten
12:45:00 13:30:00 B.201 Barrierefreie Videos: Untertitel und Gebärdensprache
13:45:00 14:30:00 B.202 Escape-Room mit Mikrocontrollern
14:45:00 15:30:00 B.202 Barrierefreie Webseiten mit HTML und ARIA

Bewertung: zwei Join-Bedingungen je 1 P, Statusfilter 1 P, Sortierung nach zwei Spalten 1 P.

b) Dimitrios möchte für die Planungsrunde alle Sessions sehen, die nicht abgelehnt wurden – auch die noch nicht eingeplanten. Er wünscht sich folgendes Ergebnis:

Gewünschtes Ergebnis
session_id titel status raum beginn
1 KI-Assistenten im Unterricht – was geht, was nicht? angenommen A.001 10:00:00
… … … … …
5 Datenschutz im Schulalltag – Mythen und Fakten eingereicht noch offen NULL
6 Barrierefreie Webseiten mit HTML und ARIA angenommen B.202 14:45:00

ba) Begründe, warum die Abfrage aus a) die Session 5 auch ohne den Statusfilter nicht liefern würde. (2 Punkte)

bb) Erstelle die SQL-Abfrage für das gewünschte Ergebnis. (4 Punkte)

ba) Ein INNER JOIN liefert nur Zeilen, zu denen in beiden Tabellen ein passender Partner existiert. Session 5 hat in raum_id und slot_id den Wert NULL. Ein Vergleich mit NULL ist nie wahr, daher findet die Join-Bedingung keinen Raum und keinen Slot, und die Session fällt aus dem Ergebnis.

Bewertung: Eigenschaft des inneren Verbunds 1 P, Bezug auf NULL im Fremdschlüssel 1 P.

bb)

Ergebnis:

session_id titel status raum beginn
1 KI-Assistenten im Unterricht – was geht, was nicht? angenommen A.001 10:00:00
2 Reparieren statt wegwerfen: Laptops retten angenommen C.110 11:00:00
3 Barrierefreie Videos: Untertitel und Gebärdensprache angenommen B.201 12:45:00
4 Escape-Room mit Mikrocontrollern angenommen B.202 13:45:00
5 Datenschutz im Schulalltag – Mythen und Fakten eingereicht noch offen NULL
6 Barrierefreie Webseiten mit HTML und ARIA angenommen B.202 14:45:00

Die Bedingung an den Status darf in WHERE stehen, weil sie die linke Tabelle session betrifft. Der Platzhalter „noch offen“ entsteht mit COALESCE (oder IFNULL).

Bewertung: zwei LEFT JOIN mit session als linker Tabelle 2 P, COALESCE/IFNULL 1 P, Statusfilter und Sortierung 1 P.

c) Emre plant die E-Ink-Türschilder und möchte wissen, wie viele Slots in jedem Raum belegt sind, auch wenn es keine sind. Er hat folgenden Entwurf begonnen:

ca) Ergänze die Lücken ① bis ③ so, dass alle acht Räume mit der richtigen Anzahl erscheinen. (4 Punkte)

cb) Formuliere die Abfrage so um, dass sie mit einem LEFT JOIN dasselbe Ergebnis liefert. (2 Punkte)

ca) ① s.session_id ② RIGHT ③ r.raum_id = s.raum_id

Ergebnis:

bezeichnung name belegte_slots
A.001 Aula 1
A.104 NULL 0
A.105 NULL 0
B.012 Mensa 0
B.201 IT-Labor 1 1
B.202 IT-Labor 2 2
C.020 Bibliothek 0
C.110 Elektrowerkstatt 1

raum steht rechts vom JOIN, alle Räume sollen erscheinen, also RIGHT JOIN. Gezählt werden muss eine Spalte der Tabelle session: Für einen Raum ohne Session erzeugt der äußere Verbund eine Zeile, in der alle Session-Spalten NULL sind. COUNT(*) zählt diese Zeile mit und liefert 1 statt 0, COUNT(s.session_id) zählt nur Nicht-NULL-Werte.

Bewertung: ① 2 P (mit COUNT(*) 0 P), ② 1 P, ③ 1 P.

cb)

A RIGHT JOIN B ist gleichwertig zu B LEFT JOIN A: Die Tabelle, aus der alle Zeilen erscheinen sollen, wird nach links getauscht.

Bewertung: Tabellen getauscht 1 P, LEFT JOIN 1 P.

d) Für die Auswertung der Schüler*innen-Beteiligung soll für jede angenommene Session angezeigt werden, wie viele Buchungen von Schüler*innen (Ticketart 'Schueler') es gibt – auch wenn es keine gibt. Emre hat dazu geschrieben:

Seine Abfrage liefert nur drei der fünf angenommenen Sessions.

da) Erläutere, warum die Sessions „KI-Assistenten im Unterricht“ und „Barrierefreie Videos“ fehlen. (2 Punkte)

db) Korrigiere die Abfrage. Achte darauf, dass wirklich nur Buchungen von Schüler*innen gezählt werden. (4 Punkte)

da) Die LEFT JOIN erzeugen zwar auch für Sessions ohne (passende) Buchung eine Zeile, aber darin ist a.ticketart NULL. Die Bedingung a.ticketart = 'Schueler' in WHERE wird erst nach dem Verbund ausgewertet und ist für NULL nicht wahr. Sie wirft diese Zeilen wieder hinaus; der äußere Verbund wirkt dadurch wie ein innerer. Die KI-Session hat gar keine Buchungen, bei den barrierefreien Videos haben nur Lehrkräfte gebucht.

Bewertung: WHERE filtert nach dem Verbund 1 P, NULL-Zeilen fallen weg 1 P.

db) Die Bedingung an die rechte Tabelle gehört in die ON-Klausel, und gezählt wird eine Spalte von anmeldung:

Ergebnis:

titel schueler_buchungen
KI-Assistenten im Unterricht – was geht, was nicht? 0
Reparieren statt wegwerfen: Laptops retten 2
Barrierefreie Videos: Untertitel und Gebärdensprache 0
Escape-Room mit Mikrocontrollern 2
Barrierefreie Webseiten mit HTML und ARIA 2

Typischer Folgefehler: Wird die Bedingung nach ON verschoben, aber weiter COUNT(sb.anmeldung_id) gezählt, erscheinen zwar alle fünf Sessions, gezählt werden aber auch die Buchungen der Lehrkräfte (Laptops retten 3, Barrierefreie Videos 2). Die Zeile aus session_buchung bleibt ja erhalten, nur die Spalten von anmeldung werden NULL.

Bewertung: Ticket-Bedingung in ON 2 P, COUNT(a.anmeldung_id) 1 P, Statusfilter in WHERE belassen 1 P.

e) Gerhard Vogt, der den Repair-Workshop leitet, ist mit dem Rollator unterwegs und braucht einen Aufzug.

ea) Erstelle eine SQL-Abfrage, die alle Referierenden (alle Rollen in session_person) mit Vor- und Nachname, Sessiontitel, Raumbezeichnung und angegebenem Barrierefreiheitsbedarf auflistet, die einen Bedarf angegeben haben und deren Session in einem nicht barrierefreien Raum stattfindet. Den Bedarf findest Du in der Anmeldung der Person zu derselben Veranstaltung. (5 Punkte)

eb) Dimitrios will die Abfrage künftig vor jeder Änderung am Sessionplan laufen lassen. Nimm Stellung, ob sie damit alle Barrierefreiheitsprobleme der Referierenden zuverlässig findet. (3 Punkte)

ea)

Ergebnis:

vorname nachname titel raum bedarf_barrierefreiheit
Gerhard Vogt Reparieren statt wegwerfen: Laptops retten C.110 Rollator, Aufzug nötig

Bewertung: Verbund über die Zwischentabelle session_person 2 P, Verbund mit anmeldung (über die Person, gern zusätzlich über die Veranstaltung) 1 P, Verbund mit raum 1 P, beide Filterbedingungen 1 P. an.person_id = sp.person_id ist ebenso richtig.

eb) Die Abfrage findet nur einen Fall: eine Person mit irgendeinem Bedarf in einem Raum, der als nicht barrierefrei markiert ist. Sie ist daher eine gute Warnung, aber keine vollständige Prüfung:

  • Der Bedarf ist Freitext. Andere Bedarfe passen nicht zum Merkmal barrierefrei: Lisa Brandt braucht Live-Untertitel; dafür müsste zusätzlich s.live_untertitel geprüft werden.

  • Wer keinen Bedarf eingetragen hat, wird nicht erfasst, auch wenn er oder sie einen hat.

  • Teilnehmende, die eine Session gebucht haben, sind nicht berücksichtigt (dafür ein Verbund über session_buchung).

Fazit: als automatische Warnung sinnvoll, die persönliche Rückfrage bei den Betroffenen ersetzt sie nicht. Die Sofortmaßnahme für Gerhard Vogt: Workshop in einen barrierefreien Raum verlegen.

Bewertung: zwei begründete Einschränkungen je 1 P, nachvollziehbares Fazit 1 P.

f) Jedes E-Ink-Türschild ist in geraet mit dem Typ 'E-Ink-Tuerschild' erfasst. Seit gestern liegt ein Ersatz-Türschild im Orga-Büro, das noch keinem Raum zugeordnet ist:

Liste alle Räume und alle E-Ink-Türschilder in einer Ergebnistabelle auf, sodass sowohl Räume ohne Türschild als auch Türschilder ohne Raum zu sehen sind. Beachte die SQL-Kurzreferenz in der Anlage. (5 Punkte)

Gesucht ist ein vollständiger äußerer Verbund (FULL OUTER JOIN). MariaDB unterstützt ihn in den aktuellen Versionen nicht, deshalb werden ein LEFT JOIN (alle Räume) und ein RIGHT JOIN (alle Türschilder) mit UNION vereinigt. UNION entfernt dabei die doppelten Zeilen der Räume mit Türschild.

Ergebnis:

raum tuerschild
NULL TS-RES-1
A.001 NULL
A.104 TS-A104
A.105 TS-A105
B.012 NULL
B.201 TS-B201
B.202 NULL
C.020 NULL
C.110 NULL

Beachte die Stellung der Typ-Bedingung: Im ersten Teil steht sie in ON, damit Räume mit anderen Geräten (zum Beispiel die Aula mit Kamera und Encoder) nicht herausfallen. Im zweiten Teil darf sie in WHERE stehen, weil sie die Tabelle betrifft, aus der alle Zeilen erhalten bleiben sollen.

Bewertung: Erkennen des vollständigen äußeren Verbunds 1 P, LEFT-Teil mit Typ-Bedingung in ON 1 P, RIGHT-Teil (oder getauschter LEFT JOIN) 1 P, UNION 1 P, Ergebnis vollständig 1 P. Ein FULL OUTER JOIN mit Hinweis, dass MariaDB ihn nicht kennt, erhält 3 P.

Aufgabe 3: Buchungen, Matching und Statistik einer Mitfahr-App (Stufe 2 – Kernkompetenz)

(ca. 34 min Bearbeitungszeit)

Die Auszubildenden des Ratz und Rübe Berufskollegs organisieren ihre Fahrgemeinschaften zur Berufsschule mit der App „RatzFahrt“. Du arbeitest im FIA-Projektteam mit Oksana Melnyk und Jonas Wolff. Andrea Wolf ist die Datenschutzbeauftragte der Schule und administriert die App. Ausschnitt aus dem Datenmodell:

  • nutzer (nutzer_id, pseudonym, vorname, nachname, email_schule, geburtsdatum, #klasse_id, rolle, einwilligung_eltern, fuehrerschein, …)

  • haltepunkt (haltepunkt_id, name, ort, plz, …, entfernung_km)

  • angebot (angebot_id, #fahrer_id, #fahrzeug_id, #haltepunkt_id, richtung, datum, abfahrt, freie_plaetze, kostenbeitrag_eur, status, erstellt_am)

  • gesuch (gesuch_id, #nutzer_id, #haltepunkt_id, richtung, datum, zeit_von, zeit_bis, status, erstellt_am)

  • buchung (buchung_id, #angebot_id, #mitfahrer_id, #gesuch_id, status, angefragt_am, beantwortet_am)

fahrer_id und mitfahrer_id verweisen beide auf nutzer. abfahrt, zeit_von und zeit_bis sind Uhrzeiten (TIME), angefragt_am und beantwortet_am Zeitpunkte (DATETIME).

Tabelle angebot (ohne fahrzeug_id, kostenbeitrag_eur und erstellt_am)
angebot_id fahrer_id haltepunkt_id richtung datum abfahrt freie_plaetze status
1 1 1 zur Schule 2026-10-19 07:00:00 2 offen
2 1 1 nach Hause 2026-10-19 14:20:00 3 offen
3 6 3 zur Schule 2026-10-19 07:10:00 0 voll
4 7 2 zur Schule 2026-10-20 06:50:00 3 offen
5 9 4 zur Schule 2026-10-22 06:45:00 6 offen
6 5 1 nach Hause 2026-10-15 15:30:00 3 durchgefuehrt
7 7 2 nach Hause 2026-10-14 15:20:00 4 abgesagt
Tabelle gesuch (ohne erstellt_am)
gesuch_id nutzer_id haltepunkt_id richtung datum zeit_von zeit_bis status
1 2 1 zur Schule 2026-10-20 07:00:00 08:00:00 offen
2 3 3 zur Schule 2026-10-19 06:45:00 07:30:00 vermittelt
3 4 1 zur Schule 2026-10-19 06:30:00 07:15:00 vermittelt
4 8 4 zur Schule 2026-10-23 07:00:00 08:00:00 offen
5 4 1 nach Hause 2026-10-15 15:00:00 16:00:00 vermittelt
Tabelle buchung
buchung_id angebot_id mitfahrer_id gesuch_id status angefragt_am beantwortet_am
1 3 3 2 bestaetigt 2026-10-14 22:10:00 2026-10-14 22:31:00
2 1 4 3 bestaetigt 2026-10-15 19:45:00 2026-10-15 20:02:00
3 1 8 NULL abgelehnt 2026-10-16 07:00:00 2026-10-16 07:40:00
4 6 4 5 bestaetigt 2026-10-13 12:30:00 2026-10-13 13:05:00
5 4 2 NULL angefragt 2026-10-17 20:15:00 NULL

a) In der App sehen Fahrende und Mitfahrende nur Pseudonyme. Erstelle eine SQL-Abfrage für eine Buchungsübersicht mit Buchungsnummer, Datum und Abfahrtszeit des Angebots, Ort des Haltepunkts, Pseudonym der Fahrerin oder des Fahrers, Pseudonym der Mitfahrerin oder des Mitfahrers und Buchungsstatus, sortiert nach Datum und Abfahrtszeit. (6 Punkte)

Ergebnisbeispiel
buchung_id datum abfahrt ort fahrer mitfahrer status
4 2026-10-15 15:30:00 Gütersloh Grünpfeil Bassline bestaetigt
… … … … … … …

Die Tabelle nutzer wird in zwei Rollen gebraucht und daher zweimal mit verschiedenen Aliasen eingebunden:

Ergebnis:

buchung_id datum abfahrt ort fahrer mitfahrer status
4 2026-10-15 15:30:00 Gütersloh Grünpfeil Bassline bestaetigt
2 2026-10-19 07:00:00 Gütersloh Funkenflug Bassline bestaetigt
3 2026-10-19 07:00:00 Gütersloh Funkenflug Rotkehlchen abgelehnt
1 2026-10-19 07:10:00 Halle (Westf.) Spätzünder Kaffeetasse bestaetigt
5 2026-10-20 06:50:00 Herford Ostwind Morgenmuffel angefragt

Die Reihenfolge der Buchungen 2 und 3 (gleiche Abfahrt) ist nicht festgelegt.

Bewertung: Verbund mit angebot und haltepunkt je 1 P, nutzer zweimal mit verschiedenen Aliasen 2 P, richtige Fremdschlüssel (fahrer_id über angebot, mitfahrer_id über buchung) 1 P, Spaltenaliase und Sortierung 1 P.

b) Oksana entwickelt die Vermittlung. Ein Angebot passt zu einem Gesuch, wenn Haltepunkt, Richtung und Datum übereinstimmen und die Abfahrt im Zeitfenster des Gesuchs liegt (Grenzen eingeschlossen).

ba) Erstelle eine SQL-Abfrage, die zu jedem Gesuch die Gesuchsnummer, den Ort des Haltepunkts, Datum und Zeitfenster sowie Nummer, Abfahrt und Status der passenden Angebote ausgibt. Gesuche ohne passendes Angebot sollen ebenfalls erscheinen. (7 Punkte)

bb) Aylin Kaya (Gesuch 1) wundert sich, dass ihr kein Angebot vorgeschlagen wird, obwohl Ben Richter (Angebot 1) ebenfalls ab Gütersloh zur Schule fährt. Begründe das Ergebnis und gib an, welche Bedingung ergänzt werden muss, damit nur noch tatsächlich buchbare Angebote vorgeschlagen werden. (2 Punkte)

ba) Alle Vergleiche gehören in die ON-Klausel des LEFT JOIN, sonst würden Gesuche ohne Treffer herausfallen:

Ergebnis:

gesuch_id ort datum zeit_von zeit_bis angebot_id abfahrt angebot_status
1 Gütersloh 2026-10-20 07:00:00 08:00:00 NULL NULL NULL
2 Halle (Westf.) 2026-10-19 06:45:00 07:30:00 3 07:10:00 voll
3 Gütersloh 2026-10-19 06:30:00 07:15:00 1 07:00:00 offen
4 Bad Salzuflen 2026-10-23 07:00:00 08:00:00 NULL NULL NULL
5 Gütersloh 2026-10-15 15:00:00 16:00:00 6 15:30:00 durchgefuehrt

Statt BETWEEN geht auch an.abfahrt >= g.zeit_von AND an.abfahrt <= g.zeit_bis.

Bewertung: LEFT JOIN mit gesuch als linker Tabelle 2 P, Join-Bedingung über Haltepunkt, Richtung und Datum 2 P, Zeitfenster 2 P, Verbund mit haltepunkt und Spaltenauswahl 1 P.

bb) Ben Richter fährt am 19.10., Aylin sucht für den 20.10.; die Datumsbedingung ist nicht erfüllt, daher gibt es keinen Treffer. Für buchbare Vorschläge muss zusätzlich AND an.status = 'offen' (und gegebenenfalls AND an.freie_plaetze > 0) in die ON-Klausel. Dann bleibt nur noch Angebot 1 für Gesuch 3 übrig.

Bewertung: Begründung über das Datum 1 P, Statusbedingung in ON 1 P.

c) Die Schülervertretung möchte wissen, wie schnell Fahrende auf Anfragen reagieren. Erstelle eine SQL-Abfrage, die für jede beantwortete Buchung die Buchungsnummer, das Pseudonym der Fahrerin oder des Fahrers, den Status und die Antwortzeit in Minuten ausgibt, die längste Antwortzeit zuerst. (5 Punkte)

Ergebnis:

buchung_id fahrer status antwortzeit_min
3 Funkenflug abgelehnt 40
4 Grünpfeil bestaetigt 35
1 Spätzünder bestaetigt 21
2 Funkenflug bestaetigt 17

Die Fahrerin bzw. der Fahrer steht nicht in buchung, sondern im zugehörigen Angebot; deshalb führt der Weg über angebot zu nutzer.

Bewertung: Verbund über angebot zu nutzer 2 P, TIMESTAMPDIFF(MINUTE, …) mit richtiger Reihenfolge 1 P, Filter auf beantwortete Buchungen 1 P, Sortierung 1 P.

d) Ermittle mit einer SQL-Abfrage alle Haltepunkte (Nummer, Name, Ort), die bisher weder in einem Angebot noch in einem Gesuch verwendet wurden. Verwende dazu keine Unterabfrage. (4 Punkte)

Ein Anti-Join: Mit LEFT JOIN bleiben alle Haltepunkte erhalten; wo es keinen Partner gibt, sind die Schlüssel der rechten Tabellen NULL.

Ergebnis:

haltepunkt_id name ort
5 Kirchplatz Werther (Westf.)
6 Bahnhof Brackwede Bielefeld

Hier muss die Bedingung in WHERE stehen: Gefiltert wird gerade nach den Zeilen, für die der äußere Verbund keinen Partner gefunden hat. Gleichwertig mit Unterabfragen: WHERE NOT EXISTS (SELECT * FROM angebot …) AND NOT EXISTS (SELECT * FROM gesuch …).

Bewertung: zwei LEFT JOIN 2 P, beide IS NULL-Bedingungen auf Schlüsselspalten 2 P.

e) Jonas möchte für die Monatsstatistik je Fahrer*in die Anzahl der Angebote und die Anzahl der bestätigten Mitfahrten ausgeben. Seine Abfrage:

liefert:

Ergebnis der Abfrage von Jonas
pseudonym angebote mitfahrten
Funkenflug 3 2
Grünpfeil 1 1
Ostwind 2 1
Spätzünder 1 1
Turbo 1 0

ea) Funkenflug (Ben Richter) hat aber nur zwei Angebote eingestellt. Erläutere anhand der Zwischenergebnisse des Verbunds, wie die falschen Werte zustande kommen. (3 Punkte)

eb) Korrigiere die Abfrage. (4 Punkte)

ec) Oksana schlägt vor, die beiden Werte stattdessen mit zwei Unterabfragen im SELECT zu ermitteln, zum Beispiel (SELECT COUNT(*) FROM angebot WHERE fahrer_id = n.nutzer_id) AS angebote. Beurteile den Vorschlag. (2 Punkte)

ea) Vor dem Gruppieren erzeugt der Verbund für Funkenflug diese Zeilen:

pseudonym angebot_id buchung_id status
Funkenflug 1 2 bestaetigt
Funkenflug 1 3 abgelehnt
Funkenflug 2 NULL NULL

Angebot 1 hat zwei Buchungen und erscheint deshalb zweimal. COUNT(an.angebot_id) zählt Zeilen mit Angebotsnummer, also 3 statt 2 (Vervielfachung durch den 1:n-Verbund, englisch fan-out). Außerdem zählt COUNT(b.buchung_id) alle Buchungen, auch die abgelehnte Buchung 3 und bei Ostwind die nur angefragte Buchung 5.

Bewertung: Vervielfachung der Angebotszeile 2 P, fehlender Statusfilter 1 P.

eb)

Ergebnis:

pseudonym angebote mitfahrten
Funkenflug 2 1
Grünpfeil 1 1
Ostwind 2 0
Spätzünder 1 1
Turbo 1 0

Die Statusbedingung muss in ON stehen; in WHERE würden Fahrer*innen ohne bestätigte Mitfahrt (Ostwind, Turbo) verschwinden.

Bewertung: COUNT(DISTINCT …) 2 P, Statusbedingung in ON 2 P (in WHERE 1 P).

ec) Der Vorschlag ist richtig und vermeidet die Vervielfachung, weil jede Unterabfrage für sich zählt. Er ist bei mehreren unabhängigen 1:n-Beziehungen oft die übersichtlichere Lösung. Nachteile: Die Unterabfrage wird je Zeile ausgewertet (bei großen Datenmengen ggf. langsamer, moderne Optimierer gleichen das teils aus), und für jede Kennzahl entsteht eine eigene Unterabfrage. Bei zwei Kennzahlen wie hier sind beide Lösungen vertretbar.

Bewertung: Wirkung (keine Vervielfachung) 1 P, begründete Abwägung 1 P.

f) Lina Hartwig (17 Jahre) hat sich ohne Einwilligung ihrer Eltern registriert und eine Mitfahrt angefragt. Andrea Wolf legt fest: Buchungsanfragen von minderjährigen Nutzenden ohne eingetragene Einwilligung werden gelöscht. Erstelle eine SQL-Anweisung, die alle betroffenen Buchungen löscht, ohne die Nutzer-IDs zu kennen. Das Alter am heutigen Tag erhältst Du mit TIMESTAMPDIFF(YEAR, geburtsdatum, CURDATE()). (4 Punkte)

MariaDB erlaubt einen Verbund in der DELETE-Anweisung (multi-table DELETE). Hinter DELETE steht die Tabelle (bzw. ihr Alias), aus der gelöscht wird:

Gleichwertig mit Unterabfrage:

Gelöscht wird eine Zeile: Buchung 3 von Rotkehlchen (Lina Hartwig). Ben Richter ist seit dem 17.01.2026 volljährig und daher nicht betroffen, Aylin Kaya hat eine Einwilligung. Vor dem Löschen empfiehlt sich dieselbe Abfrage als SELECT b.* zur Kontrolle.

Bewertung: DELETE mit Angabe der Zieltabelle 1 P, Verbund oder Unterabfrage zu nutzer 1 P, Altersbedingung 1 P, IS NULL für die Einwilligung 1 P.

Aufgabe 4: Aktivitäten einer Freizeit-App auswerten (Stufe 3 – Profilkompetenz)

(ca. 29 min Bearbeitungszeit)

Du bist Auszubildende*r bei der Let’s Meet GmbH in Bielefeld. Die App bringt Menschen für gemeinsame Freizeitaktivitäten zusammen. Für das Quartalsgespräch möchte Geschäftsführerin Clara Neumann wissen, wie die Aktivitäten angenommen werden. UX-Designerin Anh Nguyen arbeitet zusätzlich mit exportierten CSV-Dateien in Python. Ausschnitt aus dem Datenmodell:

  • benutzer (benutzer_id, vorname, nachname, email, geburtsdatum, plz, ort, status, …)

  • hobby (hobby_id, bezeichnung, kategorie)

  • treffpunkt (treffpunkt_id, name, strasse, plz, ort, barrierefrei, …)

  • aktivitaet (aktivitaet_id, titel, #hobby_id, #ersteller_id, #treffpunkt_id, beginn, dauer_min, max_teilnehmende, mindestalter, status, erstellt_am)

  • teilnahme (#aktivitaet_id, #benutzer_id, angemeldet_am, status)

  • bewertung (bewertung_id, #aktivitaet_id, #bewerter_id, sterne, kommentar, erstellt_am)

aktivitaet.status ist geplant, abgesagt oder stattgefunden; teilnahme.status ist angemeldet, warteliste, abgemeldet, teilgenommen oder nicht_erschienen. bewerter_id verweist auf benutzer; bewerten kann nur, wer teilgenommen hat. beginn und angemeldet_am sind Zeitpunkte (DATETIME).

Tabelle aktivitaet (Auszug)
aktivitaet_id titel hobby_id treffpunkt_id beginn status
1 Brettspielabend: Kooperative Spiele 6 1 2026-09-25 19:00:00 stattgefunden
2 Sonntagswanderung Teutoburger Wald (12 km) 1 2 2026-10-04 10:00:00 stattgefunden
3 Feierabend-Kick 4 3 2026-10-21 18:30:00 geplant
4 Sprachtandem Deutsch–Arabisch 5 4 2026-10-23 17:00:00 geplant
5 Bouldern für Einsteiger*innen 11 5 2026-10-24 14:00:00 geplant
6 Schach im Park 19 6 2026-09-27 15:00:00 abgesagt
7 Fotospaziergang Sparrenburg 2 4 2026-10-31 15:30:00 geplant
8 Kochabend: Mezze mit Weinbegleitung 3 7 2026-10-17 18:00:00 stattgefunden
Tabelle bewertung (ohne kommentar und erstellt_am)
bewertung_id aktivitaet_id bewerter_id sterne
1 1 9 5
2 1 5 4
3 2 6 5
4 2 10 4
5 8 2 5
6 8 6 4

a) Clara Neumann ist keine Informatikerin. Erkläre ihr das Konzept hinter einem JOIN und den Unterschied zwischen einem inneren und einem linken äußeren Verbund am Beispiel der Tabellen aktivitaet und bewertung. (4 Punkte)

Konzept (2 Punkte): Die Daten stehen in getrennten Tabellen. Ein JOIN fügt die Datensätze mehrerer Tabellen zu einer Ergebniszeile zusammen; welche zusammengehören, bestimmt die Verbundbedingung, hier bewertung.aktivitaet_id = aktivitaet.aktivitaet_id (Fremdschlüssel gleich Primärschlüssel). So steht neben jeder Bewertung der Titel der Aktivität.

Unterschied (2 Punkte): Der innere Verbund (INNER JOIN) liefert nur Aktivitäten, zu denen es mindestens eine Bewertung gibt – hier nur die Aktivitäten 1, 2 und 8, jede zweimal. Der linke äußere Verbund (aktivitaet LEFT JOIN bewertung) liefert zusätzlich alle Aktivitäten ohne Bewertung (3 bis 7) mit NULL in den Spalten von bewertung.

b) Erstelle eine SQL-Abfrage, die für alle nicht abgesagten Aktivitäten den Titel, die Bezeichnung des Hobbys, die Anzahl der Bewertungen und den Durchschnitt der Sterne (eine Nachkommastelle) ausgibt. Aktivitäten ohne Bewertung sollen mit der Anzahl 0 erscheinen. Sortiere nach dem Durchschnitt absteigend und bei Gleichstand nach dem Titel. (6 Punkte)

titel hobby bewertungen durchschnitt
Brettspielabend: Kooperative Spiele Brettspiele 2 4.5
Kochabend: Mezze mit Weinbegleitung Kochen 2 4.5
Sonntagswanderung Teutoburger Wald (12 km) Wandern 2 4.5
Bouldern für Einsteiger*innen Klettern 0 NULL
Feierabend-Kick Fußball 0 NULL
Fotospaziergang Sparrenburg Fotografie 0 NULL
Sprachtandem Deutsch–Arabisch Sprachtandem 0 NULL

Bewertung: Verbund mit hobby (1 Punkt), LEFT JOIN auf bewertung (1 Punkt), COUNT(b.bewertung_id) statt COUNT(*) (1 Punkt), AVG mit ROUND (1 Punkt), WHERE und GROUP BY (1 Punkt), Sortierung (1 Punkt). Mit COUNT(*) stünde bei Aktivitäten ohne Bewertung fälschlich 1. NULL steht bei absteigender Sortierung in MariaDB am Ende.

c) Für das Jugendschutzkonzept will Clara wissen, an welchen Treffpunkten schon Aktivitäten mit minderjährigen Teilnehmenden stattfanden oder geplant sind. Erstelle eine SQL-Abfrage, die Name und Ort dieser Treffpunkte jeweils nur einmal ausgibt. Berücksichtige Teilnahmen mit dem Status angemeldet oder teilgenommen; minderjährig ist, wer zu Beginn der Aktivität noch keine 18 Jahre alt ist. (5 Punkte)

name ort
Spielecafé Würfelglück Bielefeld

Bewertung: Verbund über vier Tabellen (2 Punkte), Statusbedingung (1 Punkt), Altersbedingung zum Beginn der Aktivität (1 Punkt), DISTINCT (1 Punkt). Ohne DISTINCT erschiene ein Treffpunkt so oft, wie dort Minderjährige teilnehmen. Gleichwertig: WHERE EXISTS (…) mit einer Unterabfrage. In anderen Datenbanksystemen wird das Alter anders berechnet (z. B. DATEDIFF(YEAR, …) in SQL Server zählt nur Jahreswechsel und ist deshalb ungenau).

d) Clara vermutet, dass sich Menschen für Sportangebote kurzfristiger anmelden als für andere Aktivitäten. Erstelle eine SQL-Abfrage, die je Hobby-Kategorie die Anzahl der Anmeldungen (Status angemeldet oder teilgenommen) und den durchschnittlichen Vorlauf in Stunden zwischen Anmeldung und Beginn der Aktivität (eine Nachkommastelle) ausgibt, sortiert nach dem Vorlauf absteigend. (6 Punkte)

kategorie anmeldungen vorlauf_h
Kreativ 2 364.8
Essen 3 361.9
Spiele 4 317.1
Natur 3 313.1
Sprache 2 256.0
Sport 5 237.8

Bewertung: Verbund über drei Tabellen (2 Punkte), Zeitdifferenz in Minuten bzw. Stunden (1 Punkt), AVG und Umrechnung mit ROUND (1 Punkt), Statusbedingung und GROUP BY (1 Punkt), Sortierung (1 Punkt). Die Vermutung stimmt für diesen Datenbestand: Sportangebote haben mit rund 238 Stunden (knapp zehn Tagen) den kürzesten Vorlauf. TIMESTAMPDIFF(HOUR, …) ist auch richtig, schneidet aber jede Differenz auf volle Stunden ab.

e) Anh Nguyen hat die Tabellen teilnahme und bewertung als CSV-Dateien exportiert. Sie möchte alle Teilnahmen mit der Bewertung derselben Person für dieselbe Aktivität zusammenführen und anschließend die gut bewerteten Teilnahmen herausfiltern. Ihr Skript:

Das Ergebnis von Zeile 4 hat 24 Zeilen, obwohl es nur 23 Teilnahmen gibt; Zeile 5 bricht mit einem ValueError ab.

ea) Erläutere zwei Fehler in Zeile 4. (4 Punkte)

eb) Korrigiere die Zeilen 4 und 5. (4 Punkte)

ec) Gib die SQL-Abfrage an, die der korrigierten Zeile 4 entspricht. (3 Punkte)

ea) Je Fehler 2 Punkte:

  • Verbunden wird nur über aktivitaet_id. Damit wird jede Teilnahme mit jeder Bewertung derselben Aktivität kombiniert, auch mit Bewertungen anderer Personen (Aktivität 1: fünf Teilnahmen × zwei Bewertungen = zehn Zeilen). Die Bewertung gehört zur Person: Zusätzlich muss benutzer_id mit bewerter_id übereinstimmen – die Spalten heißen verschieden, deshalb left_on/right_on.

  • how="inner" lässt alle Teilnahmen ohne Bewertung wegfallen. Für „alle Teilnahmen“ ist ein linker Verbund (how="left") nötig.

eb) Zeile 4 (2 Punkte), Zeile 5 (2 Punkte):

Bedingungen auf DataFrame-Spalten werden mit & (bzw. |) verknüpft, jede in eigenen Klammern; and versucht, eine ganze Spalte als einen einzigen Wahrheitswert zu deuten, und löst den ValueError aus. daten hat danach 23 Zeilen, gut sechs (alle sechs Bewertungen haben mindestens vier Sterne).

ec) (3 Punkte)

Aufgabe 5: Lieferzeiten eines Fahrrad-Onlineshops analysieren (Stufe 3 – Profilkompetenz)

(ca. 25 min Bearbeitungszeit)

Du bist Auszubildende*r bei der CSB-Rad GmbH in Bielefeld. Kund*innen haben sich über lange Lieferzeiten beschwert. Geschäftsführer Cem Sarıkaya bittet Dich, die Bestell- und Versanddaten auszuwerten, bevor er mit den Dienstleistern spricht. Ausschnitt aus dem Datenmodell:

  • kunde (kunde_id, kundentyp, firma, vorname, nachname, email, …)

  • bestellung (bestellung_id, #kunde_id, bestellt_am, status, zahlungsart, …)

  • bestellposition (#bestellung_id, pos_nr, #artikel_id, menge, einzelpreis_netto, …)

  • artikel (artikel_id, artikelnr, bezeichnung, …)

  • rechnung (rechnung_id, rechnungsnr, #bestellung_id, rechnungsdatum, …)

  • versand (versand_id, #bestellung_id, dienstleister, sendungsnr, versandt_am, zugestellt_am)

kundentyp ist privat, haendler oder oeffentlich; firma ist nur bei Händlern und öffentlichen Auftraggebern gefüllt. bestellt_am, versandt_am und zugestellt_am sind Zeitpunkte (DATETIME); zugestellt_am bleibt leer, solange die Sendung unterwegs ist.

Tabelle bestellung (Auszug)
bestellung_id kunde_id bestellt_am status
1 1 2026-08-11 21:40:00 in_fertigung
2 2 2026-08-20 10:12:00 geliefert
3 3 2026-09-02 22:55:00 versandt
4 4 2026-09-15 08:30:00 geliefert
5 5 2026-05-02 14:00:00 geliefert
6 6 2026-10-12 19:03:00 bezahlt
7 7 2026-10-01 07:45:00 storniert
8 2 2026-10-16 16:20:00 offen
Tabelle versand
versand_id bestellung_id dienstleister sendungsnr versandt_am zugestellt_am
1 2 Spedition SP-88412-BI 2026-08-26 13:00:00 2026-08-27 10:40:00
2 3 Paket 00340434160000123456 2026-09-25 15:30:00 NULL
3 4 Spedition SP-90133-BI 2026-09-17 09:00:00 2026-09-18 11:20:00
4 5 Spedition SP-71002-BI 2026-05-20 12:00:00 2026-05-21 08:55:00

a) Erstelle eine SQL-Abfrage, die für alle zugestellten Sendungen die Bestellnummer, den Dienstleister und die Lieferdauer in Tagen vom Bestellzeitpunkt bis zur Zustellung (eine Nachkommastelle) ausgibt, die längste Lieferdauer zuerst. (5 Punkte)

bestellung_id dienstleister lieferdauer_tage
5 Spedition 18.8
2 Spedition 7.0
4 Spedition 3.1

Bewertung: Verbund (1 Punkt), Differenz zwischen den Zeitpunkten aus beiden Tabellen (2 Punkte), Umrechnung in Tage und Runden (1 Punkt), Bedingung und Sortierung (1 Punkt). Die lange Lieferdauer von Bestellung 5 entsteht vor dem Versand (Beschaffung von zwölf gleichschließenden Rahmenschlössern), nicht beim Transport – das zeigt erst der Vergleich mit versandt_am.

b) Erstelle eine SQL-Abfrage, die je Kundentyp die Anzahl der zugestellten Lieferungen und die durchschnittliche Lieferdauer in Tagen (eine Nachkommastelle) ausgibt, die kürzeste zuerst. (5 Punkte)

kundentyp lieferungen durchschnitt_tage
haendler 1 3.1
privat 1 7.0
oeffentlich 1 18.8

Bewertung: Verbund über drei Tabellen (2 Punkte), AVG über die Zeitdifferenz mit Umrechnung (2 Punkte), GROUP BY und Sortierung (1 Punkt). Bei nur einer Lieferung je Kundentyp ist der Durchschnitt wenig aussagekräftig – ein Hinweis darauf, dass für die Auswertung mehr Daten nötig sind.

c) Für das Gespräch braucht Cem eine Übersicht aller nicht stornierten Bestellungen mit Bestellnummer, Kundenname (bei Firmen der Firmenname, sonst der Nachname), Status, Rechnungsnummer, Dienstleister und Zustellzeitpunkt. Bestellungen ohne Rechnung oder ohne Versand sollen ebenfalls erscheinen. Erstelle die SQL-Abfrage, sortiert nach Bestellnummer. (6 Punkte)

bestellung_id kunde status rechnungsnr dienstleister zugestellt_am
1 Okafor-Schmidt in_fertigung NULL NULL NULL
2 Kühn geliefert RE-2026-00412 Spedition 2026-08-27 10:40:00
3 Engel versandt RE-2026-00431 Paket NULL
4 Radhaus Bielefeld-Süd GmbH geliefert RE-2026-00457 Spedition 2026-09-18 11:20:00
5 Zweckverband Radwege OWL geliefert RE-2026-00233 Spedition 2026-05-21 08:55:00
6 Berg bezahlt RE-2026-00499 NULL NULL
8 Kühn offen NULL NULL NULL

Bewertung: innerer Verbund mit kunde (1 Punkt), je ein LEFT JOIN für rechnung und versand (2 Punkte), COALESCE (1 Punkt), Bedingung (1 Punkt), Sortierung (1 Punkt). Mit INNER JOIN auf rechnung oder versand fielen die Bestellungen 1, 6 und 8 bzw. 1 und 8 weg. Hätte eine Bestellung mehrere Sendungen, erschiene sie entsprechend oft.

d) Erstelle eine SQL-Abfrage, die die Artikelnummer und Bezeichnung aller Artikel ausgibt, die schon in nicht stornierten Bestellungen öffentlicher Auftraggeber enthalten waren – jeden Artikel nur einmal, sortiert nach Artikelnummer. (4 Punkte)

artikelnr bezeichnung
RAD-CITY-01 Stadtrad „Promenade“ 7-Gang
SCH-RAH-01 Rahmenschloss, gleichschließend

Bewertung: Verbund über vier Tabellen (2 Punkte), Bedingungen und Sortierung (1 Punkt), DISTINCT (1 Punkt). Gleichwertig ist WHERE EXISTS (…) bzw. WHERE a.artikel_id IN (SELECT …).

e) Erstelle eine SQL-Abfrage, die alle Sendungen ermittelt, die heute (siehe Hinweise zur Bearbeitung) seit mehr als fünf Tagen unterwegs und noch nicht zugestellt sind. Auszugeben sind Bestellnummer, Vor- und Nachname der Kundin bzw. des Kunden, Sendungsnummer und die Anzahl der vollen Tage seit dem Versand. (4 Punkte)

bestellung_id vorname nachname sendungsnr tage_unterwegs
3 Mira Engel 00340434160000123456 22

Bewertung: Verbund über drei Tabellen (1 Punkt), IS NULL (1 Punkt), Tagesdifferenz zum heutigen Datum als Ausgabe und Bedingung (2 Punkte). Ein Vergleich zugestellt_am = NULL wäre nie wahr. Die Ergebnistabelle gilt für den in den Hinweisen festgelegten Stichtag 18.10.2026 (CURDATE()). In SQL Server zählt DATEDIFF(DAY, …) die überschrittenen Tagesgrenzen und liefert deshalb 23.

f) Natalia Wróbel aus der Buchhaltung schlägt vor, die Tabellen künftig als CSV-Dateien zu exportieren und in der Tabellenkalkulation mit einer Verweisfunktion (z. B. XVERWEIS) zusammenzuführen, „dann brauchen wir kein SQL“. Nimm Stellung zu diesem Vorschlag. (3 Punkte)

Für (1 Punkt), z. B.: Für einmalige, kleine Auswertungen ist die Tabellenkalkulation vertraut und schnell; Diagramme lassen sich direkt erstellen.

Gegen (1 Punkt), z. B.: Eine Verweisfunktion liefert je Zeile nur den ersten Treffer – das entspricht einem Verbund nur bei 1:1- bzw. n:1-Beziehungen; 1:n-Beziehungen (mehrere Positionen oder Sendungen je Bestellung) gehen verloren. Exporte veralten sofort, müssen bei jeder Auswertung wiederholt werden und erzeugen Kopien personenbezogener Daten außerhalb des Berechtigungskonzepts der Datenbank.

Begründetes Fazit (1 Punkt), z. B.: Wiederkehrende Auswertungen gehören als Abfrage oder Sicht (View) in die Datenbank; die Tabellenkalkulation kann das Ergebnis darstellen.

Anlage: SQL-Kurzreferenz (MariaDB)

Auszug der in diesen Aufgaben nützlichen Sprachelemente. Eckige Klammern kennzeichnen optionale Teile.

SQL-Kurzreferenz für MariaDB
Syntax Bedeutung
SELECT [DISTINCT] ausdruck [AS alias], … Spalten bzw. Ausdrücke des Ergebnisses
FROM tabelle1 [AS t1], tabelle2 [AS t2] Kreuzprodukt beider Tabellen; mit Bedingung in WHERE ein (impliziter) Verbund
[INNER] JOIN tabelle2 ON bedingung nur Zeilen mit passendem Partner in beiden Tabellen
LEFT [OUTER] JOIN tabelle2 ON bedingung alle Zeilen der linken Tabelle, fehlende Partner als NULL
RIGHT [OUTER] JOIN tabelle2 ON bedingung alle Zeilen der rechten Tabelle, fehlende Partner als NULL
FULL [OUTER] JOIN tabelle2 ON bedingung alle Zeilen beider Tabellen; in MariaDB nicht verfügbar (Ersatz: LEFT JOIN … UNION … RIGHT JOIN)
CROSS JOIN tabelle2 Kreuzprodukt (jede Zeile mit jeder)
WHERE bedingung filtert Zeilen nach dem Verbund und vor dem Gruppieren
GROUP BY ausdruck1 [, ausdruck2 …] bildet Gruppen gleicher Werte
HAVING bedingung filtert Gruppen nach dem Gruppieren (Bedingungen an Aggregate)
ORDER BY ausdruck [ASC] [, …] bzw. … DESC Sortierung (Standard ASC; NULL steht bei ASC vorn)
UNION / UNION ALL hängt Ergebnisse gleich aufgebauter Abfragen aneinander (UNION entfernt Duplikate)
(SELECT … ) Unterabfrage als Wert, Liste (IN) oder abgeleitete Tabelle
[NOT] EXISTS (SELECT …) wahr, wenn die Unterabfrage mindestens eine (bzw. keine) Zeile liefert
UPDATE t1 JOIN t2 ON … SET t1.spalte = wert WHERE … Ändern mit Verbund
DELETE t1 FROM t1 JOIN t2 ON … WHERE … Löschen mit Verbund (gelöscht wird in t1)
COUNT(*) / COUNT(spalte) / COUNT(DISTINCT spalte) Anzahl Zeilen / Nicht-NULL-Werte / verschiedene Nicht-NULL-Werte
SUM(ausdruck), AVG(ausdruck), MIN(…), MAX(…) Summe, Mittelwert, kleinster, größter Wert
ROUND(zahl, stellen) runden
COALESCE(wert1, wert2, …) erster Wert, der nicht NULL ist (IFNULL(a, b) für zwei Werte)
CURDATE(), NOW() heutiges Datum, aktueller Zeitpunkt
TIMESTAMPDIFF(einheit, start, ende) Differenz in vollen Einheiten (MINUTE, HOUR, DAY, MONTH, YEAR)
ausdruck BETWEEN a AND b Bereich einschließlich der Grenzen
ausdruck IS [NOT] NULL Test auf NULL

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: “Übungsaufgaben zu SQL-Abfragen über mehrere Tabellen (JOIN)” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/sql-join-uebungsaufgaben veröffentlicht, die Quelltexte sind in weiterverarbeitbarer Form verfügbar im Repository unter https://gitlab.com/oer-informatik/db-sql/dml-select-queries. Erstellt mit KI-Assistenz; von mir umfassend redaktionell überarbeitet, geprüft und freigegeben. Stand: 26.10.2026.

[Kommentare zum Artikel lesen, schreiben] / [Artikel teilen] / [gitlab-Issue zum Artikel schreiben]

Kommentare gerne per Mastodon, Verbesserungsvorschläge per gitlab issue (siehe oben). Beitrag teilen: