Block 01 · Datenbanken – Lösungen

Immer 9/9 Prüfungen · Ø 54 von 200 Punkten · 36 Aufgaben · 345 Punkte · Zurück zu den Aufgaben: 01_Datenbanken.html
Hinweise zu den Lösungen Die Bewertungshinweise folgen dem Stil der IHK-Lösungen. Andere fachlich richtige Lösungen und Darstellungsformen sind ebenfalls mit voller Punktzahl zu bewerten. Alle SQL-Lösungen wurden gegen die Daten aus Anlage 2/3 getestet (Skript sql/01_Datenbanken.sql, ausgeführt mit SQLite; MySQL-Funktionen wie YEAR(), MONTH(), DATEDIFF(), LEFT() wurden für den Test in SQLite-Funktionen übersetzt). Die angegebenen Ergebnistabellen sind die tatsächlichen Testergebnisse. Bei DML-Aufgaben gilt jeweils der Ausgangsbestand der Anlagen.

DB-01 · ER-/Relationenmodell

Lösung DB-01.1 (6 Punkte)

a)

Nr.KardinalitätBegründung
11:n (Station 1 : n Fahrrad)Eine Station hat mehrere Fahrräder, ein Fahrrad gehört zu genau einer Station.
21:1Eine Station hat genau einen Leiter, ein Mitarbeiter leitet höchstens eine Station.
3m:nEine Ausleihe umfasst mehrere Zubehörteile, ein Zubehörteil wird bei vielen Ausleihen gebucht.

b)

Kategorie(KategorieNr, Bezeichnung, Tagespreis)

Fahrrad(FahrradNr, Modell, Kaufpreis, ↑KategorieNr)

Der Fremdschlüssel kommt bei einer 1:n-Beziehung in die Tabelle der n-Seite (Fahrrad): Jedes Fahrrad hat genau eine Kategorie, also steht im Feld KategorieNr genau ein Wert. Würde man umgekehrt die FahrradNr in Kategorie speichern, bräuchte eine Kategorie mehrere Werte in einem Feld (Verstoß gegen die 1NF).

Bewertung: a) je Zeile 1 P (Kardinalität mit stimmiger Begründung; Kardinalität ohne Begründung ½ P). b) Kategorie mit PK 1 P, Fahrrad mit PK und FK 1 P, Begründung 1 P. Schreibweise „1:n“, „n:1“ oder „Station 1 – n Fahrrad“ gleichwertig.

Erklärung: Immer beide Richtungen fragen. Bei Nr. 2 kommt der FK in eine der beiden Tabellen (z. B. Station.LeiterPersNr) und wird zusätzlich UNIQUE; bei Nr. 3 braucht man eine Zwischentabelle (vgl. DB-08.3 d).

Lösung DB-01.2 (12 Punkte)
Station StationNr Name startet an 1 n Tour Mitarbeiter PersNr Name Telefon leitet 1 n Kunde KundenNr Name E-Mail nimmt teil an n m bezahlt Bewertung TourNr Titel Datum LaengeKm Schwierigkeit ist Vorgesetzter von 1 n Vorgesetzter Mitarbeiter
Lösung DB-01.2: ER-Modell „Geführte Touren“

Zur Kontrolle die daraus folgende relationale Umsetzung (nicht verlangt):

Mitarbeiter(PersNr, Name, Telefon, ↑VorgesetzterPersNr)

Station(StationNr, Name) · Kunde(KundenNr, Name, EMail)

Tour(TourNr, Titel, Datum, LaengeKm, Schwierigkeit, ↑StationNr, ↑PersNr)

Teilnahme(↑TourNr, ↑KundenNr, bezahlt, Bewertung)

Bewertung: je Entitätstyp mit Attributen und unterstrichenem Schlüssel 1 P (4 P). Je Beziehung mit korrekter Kardinalität 2 P (8 P): „startet an“ (Station 1 : n Tour), „leitet“ (Mitarbeiter 1 : n Tour), „nimmt teil an“ (Tour n : m Kunde) mit den Attributen bezahlt und Bewertung an der Raute (hängen sie an einer Entität: nur 1 P), „ist Vorgesetzter von“ als rekursive 1:n-Beziehung (zwei Linien zwischen Mitarbeiter und Raute). Andere sinnvolle Beziehungsnamen werden akzeptiert.

Erklärung: „bezahlt“ und „Bewertung“ beschreiben weder den Kunden allein (er nimmt an vielen Touren teil) noch die Tour allein (sie hat viele Teilnehmer) – sie gehören zur Teilnahme, also zur Beziehung. Typischer Fehler bei der rekursiven Beziehung: eine zweite Entität „Vorgesetzter“ anlegen. Der Vorgesetzte ist aber selbst ein Mitarbeiter.

Lösung DB-01.3 (22 Punkte)

a) Relationenmodell in 3NF

Kunde KundenNr (PK) Vorname Nachname Email Buchung BuchungsNr (PK) Buchungsdatum Abholdatum Rueckgabedatum KundenNr (FK) AbholStationNr (FK) RueckgabeStationNr (FK) GutscheinCode (FK, UNIQUE) Station StationNr (PK) Name Ort Gutschein Code (PK) RabattProzent GueltigBis Buchung_Zubehoer BuchungsNr (PK, FK) ZubehoerNr (PK, FK) Anzahl Zubehoer ZubehoerNr (PK) Bezeichnung Tagespreis 1 n 1 n Abholung 1 n Rückgabe 1 1 1 n n 1
Lösung DB-01.3 a: Relationenmodell in 3NF

Kunde(KundenNr, Vorname, Nachname, Email)

Station(StationNr, Name, Ort)

Gutschein(Code, RabattProzent, GueltigBis)

Buchung(BuchungsNr, Buchungsdatum, Abholdatum, Rueckgabedatum, ↑KundenNr, ↑AbholStationNr, ↑RueckgabeStationNr, ↑GutscheinCode)

Zubehoer(ZubehoerNr, Bezeichnung, Tagespreis)

Buchung_Zubehoer(↑BuchungsNr, ↑ZubehoerNr, Anzahl)

Bewertung (18 P): je Tabelle mit allen Attributen und PK 1 P (6 P); je Beziehung mit Kardinalität 1 P (6 P: Kunde–Buchung 1:n, Station–Buchung „Abholung“ 1:n, Station–Buchung „Rückgabe“ 1:n, Gutschein–Buchung 1:1, Buchung–Buchung_Zubehoer 1:n, Zubehoer–Buchung_Zubehoer 1:n); je FK 1 P (6 P: KundenNr, AbholStationNr, RueckgabeStationNr, GutscheinCode, BuchungsNr und ZubehoerNr in der Zwischentabelle).
Ebenfalls volle Punktzahl: Surrogatschlüssel in Buchung_Zubehoer (dann BuchungsNr + ZubehoerNr als FKs); FK zum Gutschein auf der anderen Seite (Gutschein.BuchungsNr, UNIQUE); Kardinalität Gutschein–Buchung als „1:c“. Wird die m:n-Beziehung nicht aufgelöst, gibt es für diese Beziehung und die zugehörigen FKs keine Punkte.

b)

Der Fremdschlüssel GutscheinCode in Buchung erhält zusätzlich die Einschränkung UNIQUE. Damit kann jeder Code höchstens in einer Buchung vorkommen. Er darf NULL sein, weil nicht jede Buchung einen Gutschein hat (mehrere NULL-Werte sind bei UNIQUE erlaubt). Alternativ: FK BuchungsNr in Gutschein mit UNIQUE.

Bewertung: UNIQUE auf dem Fremdschlüssel 2 P; nur „Fremdschlüssel“ ohne Eindeutigkeit 1 P.

c)

Die Anzahl hängt von der Kombination aus Buchung und Zubehör ab (voll funktional abhängig von BuchungsNr und ZubehoerNr). In Buchung müsste eine Buchung mit mehreren Zubehörteilen mehrere Anzahlen in einem Feld speichern (Verstoß gegen 1NF); in Zubehör gälte die Anzahl für alle Buchungen gleichzeitig, obwohl jede Buchung eine andere Anzahl hat. Sie gehört deshalb in die Zwischentabelle Buchung_Zubehoer.

Bewertung: je stimmige Begründung (Buchung / Zubehör) 1 P.

Erklärung: Die Abhol- und die Rückgabestation sind zwei verschiedene Beziehungen zwischen denselben Tabellen – das gibt zwei FKs mit sprechenden Namen und zwei Linien. Wer nur einen StationNr-Fremdschlüssel einträgt, verliert eine Beziehung und einen FK. Die 1:1-Beziehung zum Gutschein wird wie eine 1:n umgesetzt, nur dass der FK eindeutig sein muss.

DB-02 · Normalisierung

Lösung DB-02.1 (6 Punkte)

a)

Ein Attribut B ist funktional abhängig von A (A → B), wenn zu jedem Wert von A genau ein Wert von B gehört. Beispiel aus Kunde: KundenNr → Nachname – zur KundenNr 1001 gehört immer der Nachname „Hoffmann“ (ebenso KundenNr → Email).

Bewertung: Erläuterung 1 P, passendes Beispiel 1 P (z. B. auch KundenNr → Ort).

b)

Nr.verletzte NFGrund
11NFFeld nicht atomar (zwei Werte in einem Feld)
23NFtransitive Abhängigkeit AusleiheNr → KundenNr → Nachname
32NFBezeichnung hängt nur von einem Teil des zusammengesetzten Schlüssels ab

Bewertung: je richtige Zuordnung 1 P.

c)

Die 2NF ist verletzt, wenn ein Nichtschlüsselattribut nur von einem Teil des Primärschlüssels abhängt. Ein einspaltiger Schlüssel hat keinen echten Teil – jedes Nichtschlüsselattribut ist also automatisch voll funktional vom gesamten Schlüssel abhängig.

Bewertung: 1 P.

Erklärung: Merkhilfe für b): Mehrere Werte in einem Feld → immer 1NF. Hängt ein Attribut nur von einem Teil des zusammengesetzten Schlüssels ab → 2NF. Hängt es von einem anderen Nichtschlüsselattribut ab → 3NF. Die Verwechslung von 2NF und 3NF ist der häufigste Fehler.

Lösung DB-02.2 (12 Punkte)

a)

Redundanz bedeutet, dass dieselbe Information mehrfach gespeichert ist. Beispiel: Name und Telefonnummer von Tom Weber (PersNr 12) stehen in drei Zeilen; „Hauptbahnhof, Freiburg“ zur StationNr 1 steht in vier Zeilen; die Dauer „6,0“ steht bei jeder Frühschicht.

Bewertung: Erläuterung 1 P, konkretes Beispiel aus Anlage 5 1 P.

b)

Bewertung: je Anomalie 2 P (1 P Erläuterung, 1 P konkretes Beispiel aus Anlage 5).

c)

Mitarbeiter(PersNr, Vorname, Nachname, Telefon)

Station(StationNr, Name, Ort)

Schicht(Schichtart, Stunden)

Einsatz(Datum, ↑PersNr, ↑StationNr, ↑Schichtart)

Bewertung: je Tabelle mit korrektem PK/FK 1 P. Volle Punktzahl für Mitarbeiter nur, wenn der Name in Vor- und Nachname zerlegt ist. Statt Schichtart als PK auch SchichtNr möglich; Stunden dürfen alternativ aus Beginn/Ende der Schicht berechnet werden.

Erklärung: Schlüssel der Liste ist (Datum, PersNr), da ein Mitarbeiter höchstens eine Schicht pro Tag hat. PersNr → Name, Telefon ist eine Teilabhängigkeit (2NF-Verstoß); StationNr → Station, Ort und Schicht → Stunden sind transitive Abhängigkeiten (3NF-Verstöße). „Weber, Tom“ ist nicht atomar (1NF).

Lösung DB-02.3 (20 Punkte)

a)

Nicht gespeichert werden die berechneten Angaben: Positionsbetrag „Gesamt“ (Menge × Einzelpreis), Nettobetrag (Summe der Positionen), Umsatzsteuerbetrag (Netto × 19 %) und Rechnungsbetrag (Netto + USt). Sie lassen sich jederzeit aus den gespeicherten Werten berechnen; speichert man sie, entsteht Redundanz mit der Gefahr widersprüchlicher Werte.

Bewertung: zwei berechnete Angaben 1 P, Begründung 1 P.

b) Relationenmodell in 3NF

Kunde KundenNr (PK) Anrede Vorname Nachname Strasse Hausnummer PLZ Ort Telefon Kundenfahrrad KundenfahrradNr (PK) Rahmennummer Hersteller Modell KundenNr (FK) Mechaniker MechanikerNr (PK) Vorname Nachname Rechnung RechnungsNr (PK) Rechnungsdatum Zahlungsart KundenfahrradNr (FK) MechanikerNr (FK) Artikel ArtikelNr (PK) Bezeichnung Einheit Preis Rechnungsposition RechnungsNr (PK, FK) PosNr (PK) ArtikelNr (FK) Menge Einzelpreis 1 n 1 n 1 n 1 n 1 n
Lösung DB-02.3 b: Relationenmodell „Werkstattrechnung“ in 3NF

Kunde(KundenNr, Anrede, Vorname, Nachname, Strasse, Hausnummer, PLZ, Ort, Telefon)

Kundenfahrrad(KundenfahrradNr, Rahmennummer, Hersteller, Modell, ↑KundenNr)

Mechaniker(MechanikerNr, Vorname, Nachname)

Rechnung(RechnungsNr, Rechnungsdatum, Zahlungsart, ↑KundenfahrradNr, ↑MechanikerNr)

Artikel(ArtikelNr, Bezeichnung, Einheit, Preis)

Rechnungsposition(↑RechnungsNr, PosNr, ↑ArtikelNr, Menge, Einzelpreis)

Bewertung (18 P): je Tabelle mit vollständigen Attributen und PK 1 P (6 P); je Beziehung mit Kardinalität 1 P (5 P); je FK 1 P (5 P); Einzelpreis (Preis zum Rechnungszeitpunkt) in der Positionstabelle 1 P; Name und Anschrift atomar zerlegt 1 P.
Ebenfalls korrekt: PK der Position (RechnungsNr, ArtikelNr); Rahmennummer als PK von Kundenfahrrad; zusätzliche Tabellen Hersteller, Zahlungsart, Einheit oder Ort(PLZ, Ort); USt-Satz als Attribut von Rechnung oder Artikel; KundenNr zusätzlich in Rechnung, wenn begründet (z. B. Rechnungsempfänger kann vom Fahrradbesitzer abweichen).

Erklärung: Aus dem Gespräch folgt: Kunde 1:n Fahrrad, Fahrrad 1:n Rechnung, Mechaniker 1:n Rechnung. Rechnung und Artikel stehen in einer m:n-Beziehung, die über die Rechnungsposition (mit Menge und Einzelpreis) aufgelöst wird. Arbeitsleistungen (L-100, Einheit Std.) sind einfach Artikel. Der Einzelpreis in der Position ist trotz Artikel.Preis keine Redundanz, weil er den historischen Preis festhält.

DB-03 · SQL: SELECT mit JOIN

Lösung DB-03.1 (6 Punkte)

a)

SELECT KundenNr, Vorname, Nachname
FROM   Kunde
WHERE  Email IS NULL;
KundenNrVornameNachname
1003MiraSchulze
1007EmmaVogel

b)

SELECT FahrradNr, Modell, Kaufpreis
FROM   Fahrrad
WHERE  YEAR(Kaufdatum) = 2024
ORDER BY Kaufpreis DESC, FahrradNr;
FahrradNrModellKaufpreis
107Cargo L25390.00
103Tour T73199.00
104Tour T73199.00

c)

SELECT KundenNr, Nachname, PLZ, Ort
FROM   Kunde
WHERE  PLZ LIKE '791%';
KundenNrNachnamePLZOrt
1002Albrecht79102Freiburg
1005Keller79100Freiburg
1009Krüger79194Gundelfingen
1010Neumann79108Freiburg

Bewertung: je Teilaufgabe SELECT/FROM 1 P, WHERE (bei b mit ORDER BY) 1 P. Alternativ b) WHERE Kaufdatum BETWEEN '2024-01-01' AND '2024-12-31'.

Erklärung: Email = NULL liefert nie ein Ergebnis. LIKE funktioniert, weil PLZ als Text gespeichert ist (vgl. Kunde 1008 mit PLZ 01067).

Lösung DB-03.2 (9 Punkte)

a)

SELECT a.AusleiheNr, a.Beginn, k.Nachname, f.Modell
FROM   Ausleihe a
  INNER JOIN Kunde k   ON k.KundenNr  = a.KundenNr
  INNER JOIN Fahrrad f ON f.FahrradNr = a.FahrradNr
WHERE  a.Beginn BETWEEN '2026-07-01' AND '2026-08-31'
ORDER BY a.Beginn;
AusleiheNrBeginnNachnameModell
50092026-07-18HoffmannCargo L2
50102026-08-08BrandtTrail X9
50112026-08-22VogelUrban C5

Bewertung: SELECT-Liste 1 P, Join Kunde 1 P, Join Fahrrad 1 P, Zeitraumbedingung 1 P, ORDER BY 1 P. Alternativ: WHERE YEAR(a.Beginn) = 2026 AND MONTH(a.Beginn) IN (7, 8) oder Join-Bedingungen im WHERE (FROM Ausleihe a, Kunde k, Fahrrad f WHERE …) – getestet, gleiches Ergebnis.

b)

SELECT f.FahrradNr, f.Modell, w.Datum, w.Art
FROM   Fahrrad f
  LEFT JOIN Wartung w ON w.FahrradNr = f.FahrradNr
ORDER BY f.FahrradNr, w.Datum;
FahrradNrModellDatumArt
101Urban C52025-11-20Inspektion
102Urban C5NULLNULL
103Tour T72026-02-03Inspektion
104Tour T72026-09-15Reifen
105Trail X92025-11-25Bremsen
105Trail X92026-04-20Akku-Check
105Trail X92026-09-30Inspektion
106Trail X9NULLNULL
107Cargo L22026-01-14Inspektion
107Cargo L22026-07-01Bremsen
108Cargo L2NULLNULL
109Kiddy 202026-05-07Reifen
110Urban C6NULLNULL

Bewertung: SELECT 1 P, LEFT JOIN mit Fahrrad als linker Tabelle 2 P (INNER JOIN: 0 P für diesen Teil, da 102, 106, 108 und 110 fehlen), ORDER BY 1 P. Wartung w RIGHT JOIN Fahrrad f ist gleichwertig.

Erklärung: „Auch Fahrräder, die nie gewartet wurden“ → LEFT JOIN. Fahrrad 105 erscheint dreimal, weil es drei Wartungen hat.

Lösung DB-03.3 (14 Punkte)

a)

SELECT a.AusleiheNr, k.Nachname, f.Modell, kat.Bezeichnung,
       DATEDIFF(a.Ende, a.Beginn) + 1 AS Mietdauer,
       kat.Tagespreis,
       (DATEDIFF(a.Ende, a.Beginn) + 1) * kat.Tagespreis AS Sollbetrag
FROM   Ausleihe a
  INNER JOIN Kunde k       ON k.KundenNr      = a.KundenNr
  INNER JOIN Fahrrad f     ON f.FahrradNr     = a.FahrradNr
  INNER JOIN Kategorie kat ON kat.KategorieNr = f.KategorieNr
WHERE  YEAR(a.Beginn) = 2026
  AND  a.Ende IS NOT NULL
ORDER BY Sollbetrag DESC;
AusleiheNrNachnameModellBezeichnungMietdauerTagespreisSollbetrag
5008BrandtTour T7Trekking-E-Bike730.00210.00
5006KellerCargo L2E-Lastenrad345.00135.00
5007AlbrechtTrail X9E-Mountainbike339.00117.00
5010BrandtTrail X9E-Mountainbike239.0078.00
5011VogelUrban C5City-E-Bike325.0075.00
5004HoffmannTour T7Trekking-E-Bike230.0060.00
5009HoffmannCargo L2E-Lastenrad145.0045.00
5005SchulzeKiddy 20Kinderrad112.0012.00

Bewertung: SELECT-Liste mit Aliasen 1 P, Mietdauer mit +1 1 P, Sollbetrag 1 P, Joins über vier Tabellen 1 P, WHERE (2026 und abgeschlossen) 1 P, ORDER BY 1 P. Mit IHK-Belegsatz-Syntax ebenso richtig: DATEDIFF(DAY, a.Beginn, a.Ende) + 1. Ohne „+ 1“ 0 P für die Mietdauer.

Erklärung: DATEDIFF liefert die Differenz (Ausleihe 5009: 0 Tage), deshalb +1. Die Kontrolle ergibt: Alle Sollbeträge stimmen mit den gespeicherten Beträgen überein. Die laufende Ausleihe 5012 fällt durch Ende IS NOT NULL heraus (ohne die Bedingung stünde sie mit Mietdauer NULL im Ergebnis).

b)

SELECT k.KundenNr, k.Nachname, a.AusleiheNr, a.Beginn
FROM   Kunde k
  LEFT JOIN Ausleihe a ON a.KundenNr = k.KundenNr
                      AND YEAR(a.Beginn) = 2026
ORDER BY k.KundenNr, a.Beginn;
KundenNrNachnameAusleiheNrBeginn
1001Hoffmann50042026-03-21
1001Hoffmann50092026-07-18
1002Albrecht50072026-05-30
1003Schulze50052026-04-11
1004WagnerNULLNULL
1005Keller50062026-05-01
1005Keller50122026-10-08
1006Brandt50082026-06-14
1006Brandt50102026-08-08
1007Vogel50112026-08-22
1008RothNULLNULL
1009KrügerNULLNULL
1010NeumannNULLNULL

Bewertung: SELECT/FROM Kunde 1 P, LEFT JOIN 2 P, Jahresbedingung in der ON-Klausel 1 P, ORDER BY 1 P. Statt YEAR auch AND a.Beginn >= '2026-01-01' AND a.Beginn < '2027-01-01'.

c)

Es fehlen 1004 Wagner, 1008 Roth, 1009 Krüger und 1010 Neumann. Die WHERE-Klausel wird erst nach dem Join ausgewertet: Für 1008, 1009 und 1010 erzeugt der LEFT JOIN eine Zeile mit NULL in den Ausleihe-Spalten; YEAR(NULL) = 2026 ist nicht wahr, die Zeile wird verworfen. Für 1004 entsteht eine Zeile mit der Ausleihe 5003 aus 2025, die die Bedingung ebenfalls nicht erfüllt. Der LEFT JOIN wirkt dadurch wie ein INNER JOIN.

KundenNrNachnameAusleiheNrBeginn
1001Hoffmann50042026-03-21
1001Hoffmann50092026-07-18
1002Albrecht50072026-05-30
1003Schulze50052026-04-11
1005Keller50062026-05-01
1005Keller50122026-10-08
1006Brandt50082026-06-14
1006Brandt50102026-08-08
1007Vogel50112026-08-22

Bewertung: fehlende Kunden genannt 1 P (alle vier; auch Wagner, der nur 2025 ausgeliehen hat), Begründung (WHERE nach dem Join, NULL bzw. anderes Jahr erfüllt Bedingung nicht → wie INNER JOIN) 2 P.

DB-04 · SQL: Aggregatfunktionen, GROUP BY, HAVING

Lösung DB-04.1 (6 Punkte)

a)

SELECT COUNT(*) AS Anzahl, SUM(Kilometer) AS SummeKm, AVG(Kilometer) AS DurchschnittKm
FROM   Ausleihe
WHERE  Ende IS NOT NULL;
AnzahlSummeKmDurchschnittKm
1188580.4545

Bewertung: Aggregatfunktionen 1 P, Bedingung „abgeschlossen“ 1 P. Statt COUNT(*) mit WHERE auch COUNT(Kilometer) ohne WHERE richtig.

Erklärung: Ohne WHERE liefert COUNT(*) 12 (inklusive der laufenden Ausleihe 5012), SUM und AVG bleiben gleich, weil sie NULL ignorieren. MySQL zeigt AVG mit vier Nachkommastellen (80.4545).

b)

WHERE filtert einzelne Datensätze vor der Gruppierung und darf keine Aggregatfunktionen enthalten. HAVING filtert die Gruppen nach dem GROUP BY und kann sich auf Aggregatfunktionen beziehen (z. B. HAVING COUNT(*) >= 2).

Bewertung: je Klausel 1 P.

c)

COUNT(*)COUNT(Kosten)
98

COUNT(*) zählt alle 9 Zeilen, COUNT(Kosten) nur die Zeilen, in denen Kosten nicht NULL ist. Die Wartung 9 (noch nicht abgerechnet) hat Kosten = NULL.

Bewertung: Ergebnis 1 P, Begründung 1 P.

Lösung DB-04.2 (10 Punkte)

a)

SELECT kat.Bezeichnung, COUNT(*) AS Anzahl, SUM(a.Betrag) AS Umsatz
FROM   Ausleihe a
  INNER JOIN Fahrrad f     ON f.FahrradNr     = a.FahrradNr
  INNER JOIN Kategorie kat ON kat.KategorieNr = f.KategorieNr
GROUP BY kat.KategorieNr, kat.Bezeichnung
ORDER BY Umsatz DESC;
BezeichnungAnzahlUmsatz
E-Lastenrad3315.00
Trekking-E-Bike3270.00
E-Mountainbike3234.00
City-E-Bike2125.00
Kinderrad112.00

Bewertung: Joins 2 P, COUNT und SUM 1 P, GROUP BY 1 P, ORDER BY 1 P.

Erklärung: Tandem hat keine Ausleihen und erscheint wegen des INNER JOIN nicht – genau so verlangt. Bei Trekking zählt die laufende Ausleihe 5012 mit (Anzahl 3), ihr Betrag NULL wird von SUM ignoriert.

b)

SELECT k.KundenNr, k.Vorname, k.Nachname, COUNT(*) AS Anzahl
FROM   Kunde k
  INNER JOIN Ausleihe a ON a.KundenNr = k.KundenNr
GROUP BY k.KundenNr, k.Vorname, k.Nachname
HAVING COUNT(*) >= 2
ORDER BY Anzahl DESC, k.Nachname;
KundenNrVornameNachnameAnzahl
1001LenaHoffmann3
1002JonasAlbrecht2
1006TimBrandt2
1005SofiaKeller2

Bewertung: Join 1 P, GROUP BY mit allen Nicht-Aggregat-Spalten 1 P, HAVING 2 P (Bedingung im WHERE: 0 P), ORDER BY 1 P.

Lösung DB-04.3 (16 Punkte)

a)

SELECT s.StationNr, s.Name, COUNT(f.FahrradNr) AS AnzahlFahrraeder
FROM   Station s
  LEFT JOIN Fahrrad f ON f.StationNr = s.StationNr
GROUP BY s.StationNr, s.Name
ORDER BY s.StationNr;
StationNrNameAnzahlFahrraeder
1Hauptbahnhof3
2Messe2
3Titisee3
4Kaiserstuhl2
5Schauinsland0

Bewertung: LEFT JOIN mit Station links 2 P, COUNT(f.FahrradNr) 2 P (mit COUNT(*) nur 1 P, weil Schauinsland dann 1 statt 0 erhält), GROUP BY 1 P. ORDER BY nicht verlangt.

b)

SELECT f.FahrradNr, f.Modell, COUNT(*) AS AnzahlWartungen, SUM(w.Kosten) AS Kosten
FROM   Fahrrad f
  INNER JOIN Wartung w ON w.FahrradNr = f.FahrradNr
WHERE  YEAR(w.Datum) = 2026
GROUP BY f.FahrradNr, f.Modell
HAVING SUM(w.Kosten) > 50
ORDER BY Kosten DESC;
FahrradNrModellAnzahlWartungenKosten
107Cargo L22287.00
103Tour T7189.00
104Tour T7155.00

Bewertung: Join 1 P, WHERE mit Jahr 1 P, COUNT und SUM 1 P, GROUP BY 1 P, HAVING 1 P, ORDER BY 1 P.

Erklärung: Ohne HAVING kämen noch 109 (42.90) und 105 (2 Wartungen, Summe 35.00 – die Inspektion vom 30.09. hat Kosten NULL) hinzu. Die Jahresbedingung betrifft Einzelzeilen → WHERE; „Summe über 50 €“ betrifft die Gruppe → HAVING.

c)

Fehler 1: Die Aggregatfunktion COUNT steht in der WHERE-Klausel. WHERE wird vor der Gruppierung ausgewertet; die Bedingung gehört in HAVING.
Fehler 2: k.Nachname steht im SELECT, aber nicht im GROUP BY (Verstoß gegen die GROUP-BY-Regel).

SELECT k.KundenNr, k.Nachname, COUNT(a.AusleiheNr) AS Anzahl
FROM   Kunde k
  LEFT JOIN Ausleihe a ON k.KundenNr = a.KundenNr
GROUP BY k.KundenNr, k.Nachname
HAVING COUNT(a.AusleiheNr) = 0;
KundenNrNachnameAnzahl
1008Roth0
1009Krüger0
1010Neumann0

Mit COUNT(*) wäre das Ergebnis leer: Nach dem LEFT JOIN hat jeder Kunde ohne Ausleihe trotzdem genau eine Zeile (mit NULL in den Ausleihe-Spalten). COUNT(*) zählt diese Zeile, liefert also 1 und nie 0.

Bewertung: je Fehler 1 P, korrigierte Abfrage 2 P, Erläuterung zu COUNT(*) 1 P. Ebenso richtig: Lösung mit WHERE NOT EXISTS oder WHERE KundenNr NOT IN (SELECT KundenNr FROM Ausleihe).

Dialekt-Hinweis: MySQL 8 lässt Fehler 2 hier ausnahmsweise durchgehen, weil Nachname funktional vom Primärschlüssel KundenNr abhängt. SQL Server, Oracle und die IHK-Lösungen erwarten aber alle Nicht-Aggregat-Spalten im GROUP BY.

DB-05 · SQL: INSERT, UPDATE, DELETE

Lösung DB-05.1 (5 Punkte)

a)

INSERT INTO Kunde (KundenNr, Vorname, Nachname, PLZ, Ort, Email)
VALUES (1011, 'Clara', 'Fischer', '79104', 'Freiburg', 'c.fischer@mail.de');

Bewertung: INSERT INTO mit Tabelle und Spaltenliste 1 P, Werte vollständig und in passender Reihenfolge (Texte und PLZ in Anführungszeichen) 1 P. Ohne Spaltenliste richtig, wenn die Werte in Tabellenreihenfolge stehen.

b)

UPDATE Kunde
SET    Email = 'paul.wagner@posteo.de'
WHERE  KundenNr = 1004;

Bewertung: 1 P (ohne WHERE 0 P – dann hätten alle Kunden diese Adresse).

c)

Auf den Kunden 1004 verweist noch die Ausleihe 5003 über den Fremdschlüssel Ausleihe.KundenNr. Die referenzielle Integrität verhindert das Löschen (Standardverhalten RESTRICT), sonst entstünde eine Ausleihe zu einem nicht existierenden Kunden.

Möglichkeiten: zuerst die abhängigen Ausleihen archivieren bzw. löschen und danach den Kunden löschen; oder den Fremdschlüssel mit ON DELETE CASCADE definieren (löscht die Ausleihhistorie mit); oder den Kunden statt zu löschen anonymisieren bzw. als inaktiv markieren (Soft Delete).

Bewertung: Grund (FK/referenzielle Integrität mit Bezug auf Ausleihe 5003) 1 P, eine sinnvolle Möglichkeit 1 P.

Erklärung: Die PLZ ist Text und steht deshalb in Anführungszeichen ('79104'). In c) reicht „der Kunde wird noch verwendet“ nicht – erwartet wird der Bezug zum Fremdschlüssel bzw. zur referenziellen Integrität. Ob die Ausleihhistorie gelöscht werden darf, ist eine fachliche Frage (Aufbewahrungspflichten); Archivieren oder Anonymisieren ist deshalb meist besser als CASCADE.

Lösung DB-05.2 (10 Punkte)

a)

UPDATE Kategorie
SET    Tagespreis = Tagespreis * 1.10
WHERE  KategorieNr IN (SELECT f.KategorieNr
                       FROM   Ausleihe a
                         INNER JOIN Fahrrad f ON f.FahrradNr = a.FahrradNr
                       WHERE  YEAR(a.Beginn) = 2026
                       GROUP BY f.KategorieNr
                       HAVING COUNT(*) >= 2);

Ausleihen 2026 je Kategorie: City 1, Trekking 3, E-Mountainbike 2, E-Lastenrad 2, Kinderrad 1. Ergebnis nach dem UPDATE (Tabelle Kategorie):

KategorieNrBezeichnungTagespreis
1City-E-Bike25.00
2Trekking-E-Bike33.00
3E-Mountainbike42.90
4E-Lastenrad49.50
5Kinderrad12.00
6Tandem35.00

Bewertung: UPDATE … SET mit Berechnung (+10 %) 2 P, Unterabfrage mit Join und Jahresbedingung 2 P, GROUP BY/HAVING in der Unterabfrage 1 P.

b)

UPDATE Fahrrad
SET    StationNr = (SELECT StationNr FROM Station WHERE Name = 'Messe')
WHERE  KategorieNr = (SELECT KategorieNr FROM Kategorie WHERE Bezeichnung = 'E-Lastenrad');

-- gleichwertig in MySQL: UPDATE mit JOIN
UPDATE Fahrrad f
  INNER JOIN Kategorie k ON k.KategorieNr = f.KategorieNr
  INNER JOIN Station s   ON s.Name = 'Messe'
SET    f.StationNr = s.StationNr
WHERE  k.Bezeichnung = 'E-Lastenrad';
FahrradNrModellKategorieNrStationNr
107Cargo L242
108Cargo L242

Bewertung: SET mit Unterabfrage auf Station 2 P, WHERE mit Unterabfrage auf Kategorie 2 P, vollständige Syntax 1 P. Feste Werte (SET StationNr = 2 WHERE KategorieNr = 4) entsprechen nicht der Aufgabenstellung: max. 2 P.

Erklärung: Betroffen sind die Fahrräder 107 (bisher Station 1) und 108 (stand schon an der Messe). Die skalaren Unterabfragen sind zulässig, weil Name bzw. Bezeichnung jeweils genau eine Zeile liefern.

Lösung DB-05.3 (16 Punkte)

a)

START TRANSACTION;

INSERT INTO Ausleihe_Archiv (AusleiheNr, KundenNr, FahrradNr, Beginn, Ende, Kilometer, Betrag)
SELECT AusleiheNr, KundenNr, FahrradNr, Beginn, Ende, Kilometer, Betrag
FROM   Ausleihe
WHERE  Ende < '2026-01-01';

DELETE FROM Ausleihe
WHERE  Ende < '2026-01-01';

COMMIT;

Ergebnis: Die Ausleihen 5001, 5002 und 5003 stehen jetzt in Ausleihe_Archiv (11 Zeilen), Ausleihe enthält noch 5004–5012 (9 Zeilen). Die laufende Ausleihe 5012 bleibt erhalten, weil NULL < '2026-01-01' nicht wahr ist.

AusleiheNrKundenNrFahrradNrBeginnEndeKilometerBetrag
400110011012024-05-102024-05-115550.00
400210091052024-06-222024-06-238178.00
400310021022024-07-062024-07-063125.00
400410041092024-08-152024-08-172036.00
400510091072024-09-282024-09-281845.00
400610061032024-10-122024-10-1310460.00
400710031042025-04-192025-04-206660.00
400810011012025-06-072025-06-086450.00
500110011012025-08-022025-08-036450.00
500210021052025-09-132025-09-133839.00
500310041072025-10-042025-10-0641135.00

Bewertung: INSERT INTO … SELECT (ohne VALUES) mit passenden Spalten 2 P, korrekte Bedingung 2 P, DELETE mit derselben Bedingung 2 P. WHERE YEAR(Ende) < 2026 ist gleichwertig; INSERT INTO Ausleihe_Archiv SELECT * FROM Ausleihe … ist wegen gleicher Struktur ebenfalls richtig.

b)

Transaktion: Beide Befehle bilden eine logische Einheit. Bricht das System nach dem INSERT ab, würde ohne Transaktion das DELETE fehlen (Datensätze doppelt); mit Transaktion wird alles zurückgesetzt (ROLLBACK) bzw. nur gemeinsam bestätigt (COMMIT).
Reihenfolge: Erst kopieren, dann löschen. Umgekehrt wären die Daten nach dem DELETE weg und könnten nicht mehr ins Archiv übernommen werden.

Bewertung: Transaktion 1 P, Reihenfolge 1 P.

c)

INSERT INTO Fahrrad_Archiv (FahrradNr, Rahmennummer, Modell, Kategorie, Kaufdatum, Ausmusterung, GesamtKm)
SELECT f.FahrradNr, f.Rahmennummer, f.Modell, k.Bezeichnung, f.Kaufdatum, '2026-10-10',
       (SELECT SUM(a.Kilometer)  FROM Ausleihe a         WHERE a.FahrradNr  = f.FahrradNr)
     + (SELECT SUM(aa.Kilometer) FROM Ausleihe_Archiv aa WHERE aa.FahrradNr = f.FahrradNr)
FROM   Fahrrad f
  INNER JOIN Kategorie k ON k.KategorieNr = f.KategorieNr
WHERE  f.FahrradNr = 105;
FahrradNrRahmennummerModellKategorieKaufdatumAusmusterungGesamtKm
95WAW-21-0004Urban C4City-E-Bike2021-04-012025-12-314120
98MTN-21-0102Trail X8E-Mountainbike2021-06-152026-01-152875
105MTN-22-0456Trail X9E-Mountainbike2022-05-202026-10-10195

GesamtKm = 38 + 76 (Ausleihe 5002, 5010) + 81 (Archiv 4002) = 195. Das Ergebnis ist dasselbe, wenn a) vorher ausgeführt wurde (dann liegt 5002 im Archiv) – getestet.

Bewertung: INSERT INTO … SELECT mit Spaltenliste 1 P, Join auf Kategorie (Bezeichnung statt Nummer) 1 P, Ausmusterungsdatum 1 P, Kilometersumme aus beiden Tabellen 2 P (nur aus einer Tabelle: 1 P). Statt '2026-10-10' auch CURDATE().

Robuster: Hat ein Fahrrad in einer der Tabellen keine Ausleihe, liefert SUM dort NULL und die Summe wird NULL. Abhilfe: COALESCE((SELECT SUM(…) …), 0) + COALESCE((SELECT SUM(…) …), 0) oder die Kilometer beider Tabellen per UNION ALL in einer Unterabfrage zusammenfassen und einmal summieren.

d)

DELETE FROM Wartung  WHERE FahrradNr = 105;
DELETE FROM Ausleihe WHERE FahrradNr = 105;
DELETE FROM Fahrrad  WHERE FahrradNr = 105;

Wartung und Ausleihe verweisen über Fremdschlüssel auf Fahrrad. Solange dort noch Datensätze zu Fahrrad 105 existieren, verhindert die referenzielle Integrität das Löschen des Fahrrads (getestet: DELETE FROM Fahrrad WHERE FahrradNr = 105 zuerst → Fehler „FOREIGN KEY constraint failed“). Daher zuerst die abhängigen Datensätze (Reihenfolge Wartung/Ausleihe beliebig), dann das Fahrrad. Ausleihe_Archiv hat keinen Fremdschlüssel und bleibt unverändert.

Bewertung: drei DELETE-Anweisungen 1 P, Reihenfolge 1 P, Begründung 1 P.

DB-06 · Datenbank-Theorie

Lösung DB-06.1 (6 Punkte)

a)

Bewertung: je Eigenschaft (Begriff und Erläuterung) 1 P.

b)

Vorteil: schnelleres Suchen, Sortieren und Verknüpfen (kein Durchsuchen der ganzen Tabelle). Nachteil: zusätzlicher Speicherbedarf bzw. langsamere INSERT-, UPDATE- und DELETE-Operationen, weil der Index mitgepflegt werden muss.

Bewertung: je 1 P.

Erklärung: Bei „Nennen und erläutern“ gibt es den Punkt nur mit Erläuterung – die vier Begriffe allein reichen nicht. Merkhilfe: A = alles oder nichts, C = Regeln bleiben eingehalten, I = keine gegenseitige Störung, D = bleibt gespeichert.

Lösung DB-06.2 (10 Punkte)

a)

Eine Transaktion ist eine Folge von Datenbankoperationen, die als logische Einheit vollständig oder gar nicht ausgeführt wird (START TRANSACTION … COMMIT bzw. ROLLBACK).

Stürzt der Server ohne Transaktion nach Schritt 1 ab, ist die Ausleihe bereits abgeschlossen (Ende, Kilometer, Betrag gesetzt), aber es gibt keine Rechnung – der Kunde wird nicht abgerechnet, der Umsatz fehlt. Außerdem fehlt ggf. der Wartungsauftrag, sodass ein wartungsbedürftiges Fahrrad weiter verliehen wird. Die Daten sind inkonsistent; eine Wiederholung der Rückgabe ist nicht möglich, weil die Ausleihe schon beendet ist. Mit Transaktion würde Schritt 1 beim Neustart zurückgesetzt und die Rückgabe könnte komplett wiederholt werden.

Bewertung: Erläuterung Transaktion 2 P, Folgen des Absturzes 2 P.

b)

Referenzielle Integrität: Jeder Wert im Fremdschlüssel Ausleihe.FahrradNr muss als Primärschlüssel in Fahrrad.FahrradNr existieren. Eine Ausleihe für ein Fahrrad 999 kann nicht eingefügt werden, und das Fahrrad 103 kann nicht einfach gelöscht werden, solange die Ausleihen 5004 und 5012 darauf verweisen.

Reaktionsmöglichkeiten beim Löschen: RESTRICT / NO ACTION – das Löschen wird abgewiesen; CASCADE – die abhängigen Ausleihen werden mitgelöscht; SET NULL – der FK in den Ausleihen wird auf NULL gesetzt (hier nicht möglich, weil FahrradNr NOT NULL ist).

Bewertung: Erläuterung mit Bezug auf Ausleihe/Fahrrad 2 P, je Reaktionsmöglichkeit 1 P (zwei verlangt).

c)

Ausleihe.KundenNr – nach ihr wird gefiltert (WHERE KundenNr = …) und über sie mit Kunde verknüpft. Die Tabelle Ausleihe wächst ständig; ohne Index müsste bei jeder Abfrage die gesamte Tabelle durchsucht werden.

Bewertung: Spalte 1 P, Begründung 1 P.

Erklärung: In a) wird ein Bezug zur Situation erwartet (fehlende Rechnung, fehlender Wartungsauftrag), nicht nur die Definition. Zu c): MySQL (InnoDB) legt für Fremdschlüsselspalten automatisch einen Index an, andere DBMS nicht. Erwartet wird trotzdem die Begründung „häufig in WHERE/JOIN verwendet, Tabelle wächst ständig“.

Lösung DB-06.3 (16 Punkte)

a)

Relationale DatenbankDokumentenorientierte Datenbank
TabelleCollection (Sammlung)
Datensatz (Zeile)Document (Dokument)
Spalte (Attribut)Field (Feld bzw. Schlüssel-Wert-Paar)

Bewertung: je Zuordnung 1 P.

b)

{
  "_id": 1001,
  "vorname": "Lena",
  "nachname": "Hoffmann",
  "plz": "79098",
  "ort": "Freiburg",
  "email": "lena.hoffmann@mail.de",
  "ausleihen": [
    { "ausleiheNr": 5001, "beginn": "2025-08-02", "ende": "2025-08-03",
      "kilometer": 64, "betrag": 50.00, "modell": "Urban C5" },
    { "ausleiheNr": 5004, "beginn": "2026-03-21", "ende": "2026-03-22",
      "kilometer": 92, "betrag": 60.00, "modell": "Tour T7" },
    { "ausleiheNr": 5009, "beginn": "2026-07-18", "ende": "2026-07-18",
      "kilometer": 23, "betrag": 45.00, "modell": "Cargo L2" }
  ]
}

Datengrundlage (getestete Abfrage über Kunde, Ausleihe und Fahrrad):

KundenNrNachnameAusleiheNrBeginnEndeKilometerBetragModell
1001Hoffmann50012025-08-022025-08-036450.00Urban C5
1001Hoffmann50042026-03-212026-03-229260.00Tour T7
1001Hoffmann50092026-07-182026-07-182345.00Cargo L2

Bewertung: gültige JSON-Syntax (Klammern, doppelte Anführungszeichen, Kommas) 2 P, Kundendaten 1 P, Array für die Ausleihen 1 P, drei Ausleihen mit richtigen Werten 2 P, Modell je Ausleihe 1 P. Ebenfalls richtig: Fahrrad als eingebettetes Objekt ("fahrrad": { "fahrradNr": 101, "modell": "Urban C5" }), andere Feldnamen, KundenNr als eigenes Feld statt _id.

Erklärung: Zahlen ohne Anführungszeichen, die PLZ aber als String (führende Null bei z. B. 01067). Datumswerte gibt es in JSON nicht als eigenen Typ – sie werden als String gespeichert.

c)

Bewertung: je Vorteil 2 P, Nachteil 2 P (jeweils 1 P für die Aussage aus dem Text, 1 P für den Bezug zum VeloManager).

DB-07 · SQL: Unterabfragen

Lösung DB-07.1 (5 Punkte)

a)

SELECT FahrradNr, Modell
FROM   Fahrrad
WHERE  FahrradNr NOT IN (SELECT FahrradNr FROM Ausleihe);
FahrradNrModell
110Urban C6

Bewertung: äußere Abfrage 1 P, NOT IN 1 P, Unterabfrage 1 P.

Erklärung: NOT IN ist hier sicher, weil Ausleihe.FahrradNr NOT NULL ist. Enthielte die Unterabfrage einen NULL-Wert, wäre das Ergebnis leer – dann besser NOT EXISTS.

b)

Eine nicht korrelierte Unterabfrage ist unabhängig von der äußeren Abfrage, wird einmal ausgewertet und ihr Ergebnis weiterverwendet (wie in a). Eine korrelierte Unterabfrage verweist auf eine Spalte der äußeren Abfrage und wird deshalb für jede Zeile der äußeren Abfrage erneut ausgewertet.

Bewertung: je Begriff 1 P.

Lösung DB-07.2 (9 Punkte)

a)

SELECT AusleiheNr, KundenNr, Kilometer
FROM   Ausleihe
WHERE  Kilometer > (SELECT AVG(Kilometer) FROM Ausleihe)
ORDER BY AusleiheNr;
AusleiheNrKundenNrKilometer
5004100192
50071002118
50081006315

Bewertung: Unterabfrage mit AVG 2 P, Vergleich in WHERE 1 P, SELECT 1 P. Durchschnitt: 885 / 11 = 80,45 km (NULL der laufenden Ausleihe wird ignoriert).

b)

SELECT KundenNr, Vorname, Nachname
FROM   Kunde
WHERE  KundenNr IN (SELECT a.KundenNr
                    FROM   Ausleihe a
                    WHERE  a.FahrradNr IN (SELECT f.FahrradNr
                                           FROM   Fahrrad f
                                           WHERE  f.KategorieNr = (SELECT KategorieNr
                                                                   FROM   Kategorie
                                                                   WHERE  Bezeichnung = 'E-Lastenrad')))
ORDER BY KundenNr;
KundenNrVornameNachname
1001LenaHoffmann
1004PaulWagner
1005SofiaKeller

Gleichwertig mit EXISTS (getestet, gleiches Ergebnis):

SELECT k.KundenNr, k.Vorname, k.Nachname
FROM   Kunde k
WHERE  EXISTS (SELECT *
               FROM   Ausleihe a
                 INNER JOIN Fahrrad f     ON f.FahrradNr     = a.FahrradNr
                 INNER JOIN Kategorie kat ON kat.KategorieNr = f.KategorieNr
               WHERE  a.KundenNr = k.KundenNr
                 AND  kat.Bezeichnung = 'E-Lastenrad');

Bewertung: äußere Abfrage 1 P, Unterabfrage auf Ausleihe 1 P, Unterabfrage auf Fahrrad 1 P, Kategorie über die Bezeichnung ermittelt 2 P. Eine reine JOIN-Lösung mit DISTINCT liefert dasselbe Ergebnis, erfüllt aber die Vorgabe „mit Unterabfragen“ nicht: max. 3 P.

Erklärung: Häufiger Fehler in a): WHERE Kilometer > AVG(Kilometer) – Aggregatfunktionen sind in WHERE nicht erlaubt, deshalb die Unterabfrage. In b) liefert die mittlere Unterabfrage mehrere Fahrräder, dort ist IN nötig; die innerste liefert genau eine Kategorie, dort genügt =.

Lösung DB-07.3 (14 Punkte)

a)

SELECT f.FahrradNr, f.Modell, f.Kaufpreis
FROM   Fahrrad f
WHERE  f.Kaufpreis > (SELECT AVG(f2.Kaufpreis)
                      FROM   Fahrrad f2
                      WHERE  f2.KategorieNr = f.KategorieNr)
ORDER BY f.FahrradNr;
FahrradNrModellKaufpreis
106Trail X94490.00
108Cargo L25590.00
110Urban C62799.00

Durchschnittlicher Kaufpreis je Kategorie zur Kontrolle:

KategorieNrSchnitt
12599.00
23199.00
34390.00
45490.00
5649.00

Bewertung: zwei Aliase für dieselbe Tabelle 1 P, Unterabfrage mit AVG 2 P, Korrelation f2.KategorieNr = f.KategorieNr 2 P, SELECT 1 P. Alternativ: Join mit einer Unterabfrage in FROM (SELECT KategorieNr, AVG(Kaufpreis) AS Schnitt FROM Fahrrad GROUP BY KategorieNr).

Erklärung: Ohne Korrelation würde mit dem Gesamtdurchschnitt aller Fahrräder (3460,40 €) verglichen – das Ergebnis wären dann die Fahrräder 105 bis 108 (alle E-Mountainbikes und Lastenräder), das Fahrrad 110 fehlte. In Kategorie 2 kosten beide Räder gleich viel, Kategorie 5 hat nur ein Rad – dort liegt keins über dem Durchschnitt.

b)

DELETE FROM Kunde
WHERE  NOT EXISTS (SELECT * FROM Ausleihe a
                   WHERE a.KundenNr = Kunde.KundenNr)
  AND  NOT EXISTS (SELECT * FROM Ausleihe_Archiv aa
                   WHERE aa.KundenNr = Kunde.KundenNr);

Gelöscht werden 1008 Roth und 1010 Neumann (2 Zeilen). 1009 Krüger hat keine aktuelle Ausleihe, aber zwei archivierte (4002, 4005) und bleibt deshalb erhalten.

Bewertung: DELETE FROM Kunde 1 P, NOT EXISTS auf Ausleihe mit Korrelation 1 P, NOT EXISTS auf Ausleihe_Archiv 1 P, Verknüpfung mit AND 1 P, gelöschte Kunden 1 P.

c)

SELECT AVG(Anzahl) AS AusleihenProKunde
FROM   (SELECT KundenNr, COUNT(*) AS Anzahl
        FROM   Ausleihe
        GROUP BY KundenNr) AS tbl;
AusleihenProKunde
1.7143

Bewertung: Unterabfrage mit COUNT und GROUP BY 1 P, Alias der Unterabfrage 1 P, äußeres AVG 1 P. Ergebnis: 12 Ausleihen / 7 Kunden ≈ 1,71.

DB-08 · SQL: DDL

Lösung DB-08.1 (5 Punkte)

a)

DDLDMLDCL
CREATE TABLE, ALTER TABLEUPDATE, DELETEGRANT, REVOKE

Bewertung: alle sechs richtig 2 P, vier oder fünf richtig 1 P.

b)

SpalteDatentyp
Kunde.PLZCHAR(5) (oder VARCHAR(5))
Fahrrad.KaufpreisDECIMAL(8,2)
Ausleihe.BeginnDATE

Begründung PLZ: Postleitzahlen können mit 0 beginnen (Kunde 1008: 01067 Dresden) – als Zahl ginge die führende Null verloren. Außerdem wird mit PLZ nicht gerechnet, und Mustersuchen wie LIKE '791%' setzen Text voraus.

Bewertung: drei passende Datentypen 2 P (Kaufpreis als DOUBLE: ½ P Abzug), Begründung PLZ 1 P.

Erklärung: INSERT, SELECT, UPDATE und DELETE (die CRUD-Befehle) sind DML, die Strukturbefehle CREATE, ALTER und DROP sind DDL, GRANT und REVOKE sind DCL. Für Geldbeträge wird DECIMAL statt DOUBLE erwartet, weil Gleitkommazahlen Rundungsfehler haben.

Lösung DB-08.2 (9 Punkte)

a)

CREATE TABLE Wartung (
  WartungNr INTEGER      NOT NULL,
  FahrradNr INTEGER      NOT NULL,
  Datum     DATE         NOT NULL,
  Art       VARCHAR(30)  NOT NULL DEFAULT 'Inspektion',
  Kosten    DECIMAL(8,2) CHECK (Kosten >= 0),
  PRIMARY KEY (WartungNr),
  FOREIGN KEY (FahrradNr) REFERENCES Fahrrad (FahrradNr)
);

Getestet: Ein INSERT ohne Art erhält 'Inspektion'; negative Kosten werden mit „CHECK constraint failed“, eine nicht existierende FahrradNr mit „FOREIGN KEY constraint failed“ abgewiesen.

Bewertung: Spalten mit sinnvollen Datentypen 1 P, PRIMARY KEY 1 P, FOREIGN KEY … REFERENCES 1 P, NOT NULL für die Pflichtfelder 1 P, DEFAULT 1 P, CHECK 1 P. AUTO_INCREMENT für WartungNr ist zulässig.

b)

CREATE INDEX idx_kunde_nachname ON Kunde (Nachname);

Nachteil: zusätzlicher Speicherbedarf und langsamere Schreibzugriffe (INSERT, UPDATE, DELETE auf Kunde), weil der Index jeweils mit aktualisiert werden muss.

Bewertung: CREATE INDEX 2 P, Nachteil 1 P.

Erklärung: Die Tabelle Fahrrad muss vor Wartung existieren, sonst schlägt der FOREIGN KEY fehl. CHECK-Bedingungen prüft MySQL erst ab Version 8.0.16 (ältere Versionen ignorieren sie). Der Index auf Nachname lohnt sich, weil oft danach gesucht wird und die Kundentabelle stetig wächst.

Lösung DB-08.3 (13 Punkte)

a)

ALTER TABLE Fahrrad ADD COLUMN Hersteller CHAR(3);

Bewertung: ALTER TABLE … ADD 1 P, Spaltenname und Datentyp 1 P.

b)

UPDATE Fahrrad
SET    Hersteller = LEFT(Rahmennummer, 3);       -- oder SUBSTRING(Rahmennummer, 1, 3)
FahrradNrRahmennummerHersteller
101WAW-23-0017WAW
102WAW-23-0018WAW
103KTX-24-1120KTX
104KTX-24-1121KTX
105MTN-22-0456MTN
106MTN-25-0811MTN
107CRG-24-0033CRG
108CRG-25-0090CRG
109KID-23-0301KID
110WAW-26-0002WAW

Bewertung: UPDATE … SET 1 P, Textfunktion mit richtigen Parametern 2 P. Ein WHERE ist nicht nötig, da alle Fahrräder betroffen sind.

c)

ALTER TABLE Kunde ADD CONSTRAINT uq_kunde_email UNIQUE (Email);
-- alternativ: CREATE UNIQUE INDEX uq_kunde_email ON Kunde (Email);

Die Kunden 1003 und 1007 sind kein Problem: UNIQUE verbietet nur doppelte Werte; NULL bedeutet „kein Wert“ und darf (in MySQL) mehrfach vorkommen. Getestet: Eine doppelte E-Mail wird abgewiesen, ein weiterer Kunde ohne E-Mail ist erlaubt.

Bewertung: Constraint 1 P, Erläuterung NULL 1 P.

Dialekt-Hinweis: SQL Server lässt bei einem UNIQUE-Constraint nur einen NULL-Wert zu; MySQL, PostgreSQL und SQLite erlauben mehrere.

d)

CREATE TABLE Zubehoer (
  ZubehoerNr  INTEGER      NOT NULL,
  Bezeichnung VARCHAR(40)  NOT NULL,
  Tagespreis  DECIMAL(5,2) NOT NULL,
  PRIMARY KEY (ZubehoerNr)
);

CREATE TABLE Ausleihe_Zubehoer (
  AusleiheNr INTEGER NOT NULL,
  ZubehoerNr INTEGER NOT NULL,
  Anzahl     INTEGER NOT NULL DEFAULT 1,
  PRIMARY KEY (AusleiheNr, ZubehoerNr),
  FOREIGN KEY (AusleiheNr) REFERENCES Ausleihe (AusleiheNr),
  FOREIGN KEY (ZubehoerNr) REFERENCES Zubehoer (ZubehoerNr)
);

Bewertung: Zubehoer mit Datentypen und PK 2 P; Ausleihe_Zubehoer: Spalten inkl. DEFAULT 1 1 P, zusammengesetzter PK 1 P, je FK 1 P (2 P). Zubehoer muss vor Ausleihe_Zubehoer angelegt werden. Getestet: Ein zweiter Eintrag mit gleicher AusleiheNr und ZubehoerNr wird wegen des PK abgewiesen.

DB-09 · SQL: UNION

Lösung DB-09.1 (3 Punkte)

a)

Beide verbinden die Ergebnismengen zweier SELECT-Anweisungen untereinander. UNION entfernt dabei doppelte Zeilen (wie DISTINCT), UNION ALL behält alle Zeilen einschließlich Duplikaten und ist deshalb schneller.

Bewertung: 2 P (je Operator 1 P).

b)

Beide SELECT-Anweisungen müssen dieselbe Anzahl Spalten mit kompatiblen Datentypen in gleicher Reihenfolge liefern.

Bewertung: 1 P.

Erklärung: Merkhilfe: UNION = Menge (ohne Doppelte), UNION ALL = Liste (alles). Die Spaltennamen des Gesamtergebnisses kommen aus dem ersten SELECT.

Lösung DB-09.2 (7 Punkte)

a)

SELECT AusleiheNr, FahrradNr, Beginn, Betrag
FROM   Ausleihe
WHERE  KundenNr = 1001
UNION ALL
SELECT AusleiheNr, FahrradNr, Beginn, Betrag
FROM   Ausleihe_Archiv
WHERE  KundenNr = 1001
ORDER BY Beginn;
AusleiheNrFahrradNrBeginnBetrag
40011012024-05-1050.00
40081012025-06-0750.00
50011012025-08-0250.00
50041032026-03-2160.00
50091082026-07-1845.00

Bewertung: zwei SELECTs mit Bedingung 2 P, UNION ALL 1 P (UNION ist hier ebenfalls richtig, weil die AusleiheNr jede Zeile eindeutig macht), ORDER BY nur am Ende 1 P.

b)

Mit UNION: 8 Zeilen (die KundenNr 1001–1007 und 1009), mit UNION ALL: 20 Zeilen (12 aus Ausleihe + 8 aus Ausleihe_Archiv). UNION entfernt die doppelten KundenNr – z. B. kommt 1001 dreimal in Ausleihe und zweimal im Archiv vor –, UNION ALL hängt alle Zeilen unverändert aneinander.

Bewertung: je Anzahl 1 P, Begründung 1 P.

Erklärung: In a) steht ORDER BY nur einmal am Ende und sortiert das Gesamtergebnis; ein ORDER BY im ersten SELECT wäre ein Syntaxfehler. In b) entfernt UNION Duplikate sowohl innerhalb einer Tabelle als auch über beide Tabellen hinweg.

Lösung DB-09.3 (13 Punkte)

a)

SELECT Jahr, COUNT(*) AS Anzahl, SUM(Betrag) AS Umsatz, SUM(Kilometer) AS Kilometer
FROM   (SELECT YEAR(Beginn) AS Jahr, Betrag, Kilometer
        FROM   Ausleihe
        UNION ALL
        SELECT YEAR(Beginn), Betrag, Kilometer
        FROM   Ausleihe_Archiv) AS alle
GROUP BY Jahr
ORDER BY Jahr;
JahrAnzahlUmsatzKilometer
20246294.00309
20255334.00273
20269732.00742

Bewertung: innere SELECTs mit YEAR 2 P, UNION ALL 1 P, Unterabfrage in FROM mit Alias 2 P, COUNT/SUM 1 P, GROUP BY Jahr 1 P, ORDER BY 1 P.

Erklärung: 2026 enthält auch die laufende Ausleihe 5012 (Anzahl 9); deren NULL-Werte werden bei SUM ignoriert. Das Jahr 2025 kommt in beiden Tabellen vor (Archiv: 4007, 4008; aktuell: 5001–5003) – deshalb muss erst vereinigt und dann gruppiert werden.

b)

UNION würde Zeilen entfernen, die in allen ausgewählten Spalten gleich sind, obwohl es verschiedene Ausleihen sind. Beispiel: Archiv 4008 und Ausleihe 5001 ergeben beide (2025; 50.00; 64 km) – mit UNION bliebe nur eine Zeile übrig, 2025 hätte dann 4 statt 5 Ausleihen, 284 € statt 334 € und 209 statt 273 km (getestet):

JahrAnzahlUmsatzKilometer
20246294.00309
20254284.00209
20269732.00742

Bewertung: Begründung 1 P, konkretes Beispiel 1 P.

c)

Der Kollege gruppiert jede Tabelle getrennt und hängt die Gruppenergebnisse aneinander. Da das Jahr 2025 in beiden Tabellen vorkommt, erscheint 2025 zweimal (einmal mit 3 Ausleihen aus Ausleihe, einmal mit 2 aus dem Archiv) – die Anforderung „jedes Jahr genau einmal“ ist verletzt. Richtig ist, erst beide Tabellen zu vereinigen und dann zu gruppieren (wie in a).

JahrAnzahlUmsatzKilometer
20246294.00309
20253224.00143
20252110.00130
20269732.00742

Bewertung: Erläuterung (getrennte Gruppierung → doppelte Gruppe) 2 P, Bezug auf 2025 bzw. Lösungsweg 1 P.

Hinweis: In der Originalaufgabe S24 T2 4d funktionierte die Variante des Kollegen, weil dort alle älteren Jahre ausschließlich im Archiv lagen. Prüfe also immer, ob sich die Gruppen der beiden Tabellen überschneiden.

DB-10 · SQL: Benutzer und Rechte

Lösung DB-10.1 (3 Punkte)

a)

Jeder Benutzer bzw. jede Anwendung erhält nur die Rechte, die für die jeweilige Aufgabe unbedingt erforderlich sind. Dadurch werden Schäden durch Fehlbedienung, Missbrauch oder kompromittierte Zugangsdaten begrenzt und personenbezogene Daten nur Berechtigten zugänglich gemacht.

Bewertung: Erläuterung 1 P, Zweck/Nutzen 1 P.

b)

REVOKE (z. B. REVOKE UPDATE ON velomanager.Kunde FROM benutzer;)

Bewertung: 1 P.

Erklärung: Das Prinzip gilt auch für Anwendungen: Der Datenbankbenutzer des Kundenportals erhält nur die DML-Rechte, die das Portal braucht – keine DDL- oder Administrationsrechte.

Lösung DB-10.2 (6 Punkte)
-- a)
CREATE USER werkstatt IDENTIFIED BY 'Passwort';

-- b)
GRANT SELECT ON velomanager.Fahrrad   TO werkstatt;
GRANT SELECT ON velomanager.Kategorie TO werkstatt;
GRANT SELECT, INSERT, UPDATE ON velomanager.Wartung TO werkstatt;

-- c)
REVOKE UPDATE ON velomanager.Wartung FROM werkstatt;

Bewertung: a) 1 P; b) je GRANT-Anweisung 1 P (3 P); c) 1 P. In MySQL wird der Benutzer genauer als 'werkstatt'@'localhost' (oder @'%') angegeben – beide Schreibweisen sind richtig. Pro GRANT ist in MySQL nur ein Objekt erlaubt, daher zwei getrennte Anweisungen für Fahrrad und Kategorie.

d)

Nach dem Prinzip der minimalen Rechte: Die Werkstatt muss Wartungen erfassen, aber keine Historie löschen. Gelöschte Wartungen würden Nachweise (Sicherheit, Gewährleistung, Kostenauswertung) vernichten; versehentliches oder missbräuchliches Löschen wird so ausgeschlossen.

Bewertung: 1 P.

Erklärung: Häufige Fehler: mehrere Tabellen in einer GRANT-Anweisung (in MySQL nicht erlaubt), REVOKE mit TO statt FROM, Passwort ohne Anführungszeichen. Maßgeblich für die Syntax ist in der Prüfung der Belegsatz.

Lösung DB-10.3 (10 Punkte)

a)

RolleKundeFahrradAusleiheWartung
StationspersonalS, I, USS, I, U–
Werkstatt–S–S, I, U
ControllingSSSS

Bewertung: Stationspersonal 2 P, Werkstatt 1 P, Controlling 1 P.

b)

CREATE ROLE stationspersonal;
GRANT SELECT, INSERT, UPDATE ON velomanager.Kunde    TO stationspersonal;
GRANT SELECT, INSERT, UPDATE ON velomanager.Ausleihe TO stationspersonal;
GRANT SELECT                 ON velomanager.Fahrrad  TO stationspersonal;
GRANT stationspersonal TO mueller;

Bewertung: CREATE ROLE 1 P, Rechte-Vergabe an die Rolle 2 P, Rolle dem Benutzer zuweisen 1 P. (In MySQL 8 wird die Rolle zusätzlich mit SET DEFAULT ROLE stationspersonal TO mueller; beim Anmelden aktiviert – nicht verlangt.)

c)

Bewertung: je erläutertem Vorteil 1 P.

Erklärung: Zum Anlegen und Ändern gehört praktisch immer das Leserecht (sonst kann der Datensatz nicht gesucht werden) – deshalb steht S überall dabei. DELETE erhält keine Rolle, weil es in den Anforderungen nicht vorkommt.

DB-11 · Trigger, Stored Procedure, View

Lösung DB-11.1 (4 Punkte)

a)

Eine Stored Procedure ist eine in der Datenbank gespeicherte, benannte Folge von SQL-Anweisungen (ggf. mit Parametern, Variablen und Kontrollstrukturen), die mit CALL name(…) aufgerufen wird. Sie bündelt wiederkehrende Abläufe zentral im DBMS, verringert den Datenverkehr zwischen Anwendung und Datenbank und erlaubt, Benutzern nur das Ausführungsrecht statt Tabellenrechte zu geben.

Bewertung: Begriff 1 P, Aufruf/Zweck 1 P.

b)

Ein Trigger ist SQL-Code, der automatisch ausgeführt wird, wenn auf einer bestimmten Tabelle ein Ereignis (INSERT, UPDATE oder DELETE) eintritt – vor (BEFORE) oder nach (AFTER) der Änderung, meist für jede betroffene Zeile. Einsatz z. B. für Protokollierung, Plausibilitätsprüfungen oder das Aktualisieren abgeleiteter Werte.

Bewertung: Begriff/automatische Auslösung durch Ereignis 1 P, Zeitpunkt oder Einsatzzweck 1 P.

Erklärung: Abgrenzung: Eine Stored Procedure wird ausdrücklich aufgerufen, ein Trigger läuft automatisch beim Ereignis. Beide sind in der Datenbank gespeichert – im Unterschied zu SQL-Anweisungen im Anwendungscode.

Lösung DB-11.2 (6 Punkte)

a)

CREATE VIEW v_laufende_ausleihen AS
SELECT a.AusleiheNr, a.Beginn, k.Nachname, f.Modell, s.Name AS Station
FROM   Ausleihe a
  INNER JOIN Kunde k   ON k.KundenNr  = a.KundenNr
  INNER JOIN Fahrrad f ON f.FahrradNr = a.FahrradNr
  INNER JOIN Station s ON s.StationNr = f.StationNr
WHERE  a.Ende IS NULL;

Ergebnis von SELECT * FROM v_laufende_ausleihen;:

AusleiheNrBeginnNachnameModellStation
50122026-10-08KellerTour T7Messe

Bewertung: CREATE VIEW … AS 1 P, Joins über vier Tabellen 1 P, WHERE a.Ende IS NULL 1 P, Ergebniszeile 1 P.

b)

Vorteile (zwei genügen): komplexe Abfragen werden vereinfacht und wiederverwendet; Zugriffsschutz, weil Benutzer nur die freigegebenen Spalten/Zeilen sehen; die Anwendung bleibt stabil, wenn sich die darunterliegenden Tabellen ändern; immer aktuelle Daten, da die View keine Kopie speichert.

Bewertung: je Vorteil 1 P.

Erklärung: Die View speichert keine Daten, nur die Abfrage. Wird die Ausleihe 5012 zurückgegeben (Ende gesetzt), taucht sie beim nächsten Aufruf automatisch nicht mehr auf.

Lösung DB-11.3 (10 Punkte)

a)

TabelleEreignisZeitpunkt
AusleiheINSERT (neue Ausleihe)BEFORE – nur vor dem Einfügen kann die Änderung noch verhindert werden
BedingungAktion
Es existiert bereits eine Ausleihe mit derselben FahrradNr (NEW.FahrradNr), deren Ende NULL ist.Einfügen abbrechen und Fehlermeldung ausgeben („Fahrrad ist bereits verliehen“).

Bewertung: Tabelle und Ereignis 1 P, Zeitpunkt 1 P, Bedingung 1 P, Aktion 1 P.

Umsetzung in MySQL (nicht verlangt):

CREATE TRIGGER trg_keine_doppelausleihe
BEFORE INSERT ON Ausleihe
FOR EACH ROW
BEGIN
  IF EXISTS (SELECT * FROM Ausleihe
             WHERE FahrradNr = NEW.FahrradNr AND Ende IS NULL) THEN
    SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Fahrrad ist bereits verliehen';
  END IF;
END;

Getestet (gleichwertiger SQLite-Trigger mit RAISE(ABORT, …)): Eine neue Ausleihe für Fahrrad 103 (laufende Ausleihe 5012) wird abgewiesen, eine für Fahrrad 101 wird angenommen.

b)

Die Prozedur schließt eine laufende Ausleihe ab: Für die Ausleihe mit der übergebenen Nummer – aber nur, wenn sie noch kein Ende hat – setzt sie das Rückgabedatum auf das heutige Datum, speichert die gefahrenen Kilometer und berechnet den Betrag als Anzahl der Miettage (einschließlich Abholtag) × Tagespreis der Kategorie des Fahrrads. Bereits abgeschlossene Ausleihen bleiben unverändert.

CALL rueckgabe(5012, 84);

Ausleihe 5012: Beginn 08.10.2026, Fahrrad 103 (Trekking-E-Bike, 30,00 € je Tag). DATEDIFF('2026-10-10', '2026-10-08') + 1 = 3 Tage → 3 × 30,00 € = 90,00 €.

AusleiheNrKundenNrFahrradNrBeginnEndeKilometerBetrag
501210051032026-10-082026-10-108490.00

Bewertung: Beschreibung 2 P, Aufruf 1 P, Betrag 1 P.

c)

Vorteil: Die Regel gilt zentral für alle Anwendungen (Webportal, App, Stationsterminal) und kann nicht umgangen werden; weniger Datenübertragung. Nachteil: Herstellerspezifische Syntax (Bindung an das DBMS); Logik ist schwerer zu testen, zu debuggen und zu versionieren und für Entwickler „versteckt“.

Bewertung: je 1 P.

Erklärung: Zu a): Ein AFTER-Trigger käme zu spät – die doppelte Ausleihe wäre dann schon gespeichert. Zu b): Die Bedingung a.Ende IS NULL verhindert, dass eine bereits abgeschlossene Ausleihe durch einen zweiten Aufruf überschrieben wird. Typischer Rechenfehler: 2 statt 3 Tage, weil das „+ 1“ übersehen wird.

DB-12 · Datenqualität beim Import

Lösung DB-12.1 (5 Punkte)

a)

Vier aus: Vollständigkeit, Korrektheit (Richtigkeit), Konsistenz (Widerspruchsfreiheit), Einheitlichkeit, Eindeutigkeit (keine Dubletten), Atomarität, Gültigkeit/Plausibilität, Aktualität.

Bewertung: je Kriterium ½ P.

b)

Bewertung: je Schritt 1 P.

Erklärung: „Nennen“ heißt: Der Begriff genügt. Bei „Erläutern“ (b) gehört zu jedem Schritt, was dort passiert.

Lösung DB-12.2 (9 Punkte)

Drei der folgenden Probleme mit je einer Folge:

Problem (mit Beispiel aus Anlage 6)Folge beim Import
Uneinheitliche, unvollständige oder ungültige Datumsangaben: „03.02.2025“, „4.2.“ (ohne Jahr), „2025-02-07“, „11.02.25“, „Feb 25“ (ohne Tag), „14.13.2025“ (Monat 13 existiert nicht).Der Import in die Spalte Datum (DATE, NOT NULL) schlägt fehl oder es werden falsche Daten gespeichert (welches Jahr hat „4.2.“?); zeitliche Auswertungen werden falsch.
Fahrrad nicht eindeutig gekennzeichnet: mal FahrradNr („101“), mal Modell und Farbe („Urban C5 blau“), mal Rahmennummer, mal nur das Modell („Tour T7“ gibt es zweimal), mal zwei Räder in einem Feld („103/104“).Der Fremdschlüssel FahrradNr kann nicht oder nur falsch zugeordnet werden → FK-Verletzung oder Wartungen beim falschen Fahrrad; die Wartungshistorie ist unbrauchbar.
Kosten nicht numerisch bzw. uneinheitlich: „89,00 €“, „145,5“, „ca. 130“, „inkl.“, „2x 89 €“, „29.90 EUR“ (Währungsangaben, Text, Schätzwerte, Sammelbeträge, unterschiedliche Dezimaltrennzeichen).Der Import in DECIMAL schlägt fehl oder liefert falsche Beträge (z. B. „29.90“ als 2990 interpretiert); Kostenauswertungen je Fahrrad sind falsch.
Mehrere Werte in einem Feld / uneinheitliche Schreibweisen: Mechaniker „Weber, Sanchez“, Arbeiten „Insp. + Reifen“; „Weber“ / „T. Weber“, „Inspektion“ / „Insp.“.Keine eindeutige Zuordnung zu einer Wartungsart bzw. einem Mitarbeiter; Auswertungen je Art oder Mechaniker zersplittern.
Fehlende Werte: Mechaniker leer.NOT NULL-Verletzung bzw. unvollständige Datensätze.
Dubletten: Die Zeile „11.02.25 – 105 – Akku geprüft“ steht doppelt.Die Wartung wird doppelt gespeichert; Anzahl und Kosten der Wartungen werden zu hoch ausgewiesen.

Bewertung: je Problem 3 P (Beschreibung mit Beispiel aus der Anlage 2 P, Folge 1 P); maximal 9 P.

Erklärung: Der Operator „beschreiben“ verlangt den konkreten Bezug zur Anlage: „Datum uneinheitlich“ allein reicht nicht für die volle Punktzahl, „4.2. ohne Jahr, 14.13.2025 ungültig“ schon. Die Folge muss sich auf den Import in die Tabelle Wartung beziehen (Datentyp, Fremdschlüssel, Auswertungen).

Lösung DB-12.3 (12 Punkte)

a)

SpalteProblemTransformation
NameVor- und Nachname in einem Feld (nicht atomar), uneinheitliche Reihenfolge („Hoffmann, Lena“, „Jonas Albrecht“, „Schulze Mira“), abgekürzte Vornamen („Hoffmann, L.“).Aufteilen in Vorname und Nachname: bei Komma ist der Teil davor der Nachname; ohne Komma Abgleich mit einer Vornamensliste; nicht eindeutige Fälle (Zeilen 3, 7) in eine Klärungsliste; Abkürzungen bei der Dublettenprüfung auflösen.
AdresseStraße, PLZ und Ort in einem Feld, unterschiedliche Trennzeichen, teilweise fehlt die PLZ (Zeilen 3–6) oder nur ein Ortsteil („Wiehre“).Per Muster zerlegen (fünfstellige Zahl = PLZ, Rest danach = Ort, davor = Straße/Hausnummer); fehlende PLZ über ein PLZ-Verzeichnis ergänzen bzw. nachfragen; die Straße wird in der Zieltabelle Kunde nicht gespeichert (oder Zieltabelle erweitern).
E-MailUngültige Adressen („j.albrecht@web,de“, „s.keller(at)posteo.de“), fehlende Werte.Syntaxprüfung (genau ein @, gültige Domain); bekannte Muster korrigieren („(at)“ → „@“, „,de“ → „.de“); fehlende Adressen als NULL übernehmen (Spalte erlaubt NULL); nicht reparierbare Adressen in die Klärungsliste.

Bewertung: je Spalte 2 P (Problem 1 P, Transformation 1 P).

b)

  1. Daten vereinheitlichen (Kleinschreibung, „str.“ → „straße“, Telefonnummern nur als Ziffern, Namen zerlegt).
  2. Dublettenkandidaten über Vergleichsmerkmale finden: gleiche E-Mail-Adresse, gleiche Telefonnummer oder gleicher Nachname + gleiche Adresse bei ähnlichem Vornamen (Ähnlichkeitsvergleich, z. B. „Lena“ / „L.“). Zeilen 1 und 5 stimmen in E-Mail, Telefon und Adresse überein.
  3. Kandidaten von der Fachabteilung bestätigen lassen, dann zu einem Datensatz zusammenführen (vollständigste Angaben übernehmen, „Kunde seit“ = früheres Datum) und die Dublette verwerfen; bereits vorhandene Verweise (z. B. Ausleihen) auf den verbleibenden Datensatz umhängen.

Bewertung: Vergleichsmerkmale/Erkennung 2 P, Zusammenführen/Bereinigen 1 P.

c)

Beispiel für eine Entscheidung (Import mit Bereinigung): Die Kartei umfasst rund 950 Kunden, darunter Stammkunden. Eine Neuerfassung wäre sehr aufwendig (mehrere Personentage) und selbst fehleranfällig. Die meisten Mängel (Namensformat, Adressaufteilung, E-Mail-Tippfehler, Datumsformate) lassen sich regelbasiert per Skript beheben; nur Restfälle müssen manuell geklärt werden. Daher: Import über ETL mit Staging-Tabelle, Fehlerliste und Testimport – ggf. nur Kunden, die in den letzten Jahren aktiv waren (Datenminimierung nach DSGVO).

Ebenso vertretbar ist eine begründete Entscheidung gegen den Import, z. B.: hohe Fehlerquote, viele unvollständige Datensätze, Kunden registrieren sich für die Online-Buchung ohnehin neu, alte Daten inaktiver Kunden dürften nach DSGVO ohnehin nicht weiter gespeichert werden.

Bewertung: nachvollziehbare Entscheidung 1 P, mindestens zwei Argumente mit Bezug zur Situation 2 P.

Erklärung: Welche Transformationen nötig sind, bestimmt die Zieltabelle Kunde (Anlage 2): Sie verlangt Vor- und Nachname getrennt sowie PLZ und Ort – für Straße und Telefon gibt es dort keine Spalte. In c) gibt es Punkte für Argumente aus der Situation (rund 950 Datensätze, Art der Fehler, Nutzen der Historie), nicht für die Richtung der Entscheidung.