MV Abitur Computer Science study materials
MV Abitur Computer Science study resources

Fahrradverleih: eine flache Tabelle, zwei Preise und eine stille Lücke

MV Abitur Computer Science · Put your knowledge to work in context.

All case studies

Fahrradverleih: eine flache Tabelle, zwei Preise und eine stille Lücke

Die Fahrradverleih-Initiative einer Kleinstadt verwaltet ihre Ausleihen in einer einzigen Tabelle ausleihe mit den Spalten ausleih_id, kunde_nr, kunde_name, kunde_email, rad_nr, rad_typ, preis_pro_stunde, start_zeit und ende_zeit. Bei 4.800 Ausleihen und 795 Kundinnen und Kunden funktioniert das erstaunlich lange. Als der Stundensatz für E-Lastenfahrräder von 4,50 € auf 5,20 € steigt, müssen 340 Zeilen geändert werden; drei werden übersehen, sodass derselbe Radtyp nun zu zwei Preisen abgerechnet wird und eine Rechnung um 12,60 € zu hoch ausfällt. Die Auswertung SELECT kunde_name, COUNT(*) FROM ausleihe GROUP BY kunde_name liefert 812 Zeilen statt 795 – es gibt Schreibvarianten („müller“, „Müller “) und zugleich zwei verschiedene Personen namens „Schmidt“, die zu einer Zeile verschmelzen. Die Umsatzabfrage meldet 4.100 €, obwohl 17 Ausleihen noch laufen und ihr ende_zeit NULL ist; sie tauchen in der Summe stillschweigend nicht auf. Der Vorstand will bis zur nächsten Sitzung einen belastbaren Vorschlag.

1. Überführen Sie das Schema in die dritte Normalform. Begründen Sie die Zerlegung anhand der beobachteten Anomalien.

Read the worked answer

Funktionale Abhängigkeiten in der Ausgangstabelle:

  • ausleih_id → kunde_nr, rad_nr, start_zeit, ende_zeit
  • kunde_nr → kunde_name, kunde_email
  • rad_nr → rad_typ
  • rad_typ → preis_pro_stunde

Damit hängen kunde_name, kunde_email, rad_typ und preis_pro_stunde nur transitiv über kunde_nr bzw. rad_nr vom Schlüssel ausleih_id ab – die Tabelle ist höchstens in 1NF/2NF.

Zerlegung:

  • kunde(kunde_nr PK, name, email)
  • radtyp(rad_typ PK, preis_pro_stunde)
  • rad(rad_nr PK, rad_typ FK → radtyp)
  • ausleihe(ausleih_id PK, kunde_nr FK, rad_nr FK, start_zeit, ende_zeit)

Begründung über die Anomalien:

  • Änderungsanomalie: Der Preis eines Radtyps steht in jeder der 340 Ausleihzeilen. Genau daraus entstand der Fehler mit den drei übersehenen Zeilen und der um 12,60 € zu hohen Rechnung. Nach der Zerlegung ist es genau eine Zeile in radtyp.
  • Einfügeanomalie: Ein neuer Radtyp ohne bisherige Ausleihe kann nicht erfasst werden, weil rad_typ nur zusammen mit einer Ausleihe existiert. In radtyp geht das sofort.
  • Löschanomalie: Wird die letzte Ausleihe einer Kundin gelöscht, verschwinden mit ihr Name und E-Mail. In der zerlegten Variante bleibt der Kundendatensatz erhalten.

Hinweis: kunde_name ist kein Schlüsselkandidat – die Schreibvarianten zeigen das. Deshalb ist kunde_nr als künstlicher Schlüssel richtig; die Namensbereinigung ist eine separate Aufgabe (Dublettenprüfung, Trimmen, einheitliche Schreibweise).

2. Erklären Sie, warum die Umsatzabfrage zu niedrig ist, und entwerfen Sie eine korrigierte Auswertung. Gehen Sie darauf ein, ob NULL hier durch 0 ersetzt werden sollte.

Read the worked answer

Ursache: ende_zeit IS NULL bedeutet „Ausleihe noch nicht beendet“, also einen unbekannten Wert – nicht 0 und nicht „keine Dauer“. Die Dauer wird aus start_zeit und ende_zeit berechnet und ist damit ebenfalls NULL. SUM ignoriert NULL-Werte vollständig (im Gegensatz zu COUNT(*)), addiert also stillschweigend nur die abgeschlossenen Ausleihen. Es gibt keine Fehlermeldung, weil das Verhalten SQL-konform ist – das macht den Fehler gefährlich: Die Zahl 4.100 € sieht plausibel aus.

Korrektur – zwei getrennte Kennzahlen statt einer Scheingenauigkeit:

-- abgeschlossene Ausleihen
SELECT SUM(dauer * preis_pro_stunde) AS umsatz_abgeschlossen
FROM ... WHERE ende_zeit IS NOT NULL;

-- laufende Ausleihen: vorläufiger Wert zum Stichtag
SELECT COUNT(*) AS laufend,
       SUM((CURRENT_TIMESTAMP - start_zeit) * preis_pro_stunde) AS umsatz_vorlaeufig
FROM ... WHERE ende_zeit IS NULL;

Beide Werte werden getrennt ausgewiesen und in der Monatsauswertung addiert, wobei der vorläufige Anteil als solcher gekennzeichnet wird.

NULL durch 0 ersetzen? Nein. COALESCE(ende_zeit, start_zeit) würde laufende Ausleihen mit Dauer 0 einrechnen – der Umsatz wäre zu niedrig und die Ursache unsichtbar. Semantisch falsch wäre auch ein CHECK-Constraint, der NULL verbietet. Richtig ist, die Bedeutung von NULL im Datenmodell zu dokumentieren und die Auswertung explizit zu trennen. Wer die Unterscheidung nicht braucht, kann alternativ ein Statusattribut (offen/beendet) führen – dann aber mit Konsistenzregel, dass ende_zeit genau bei beendet gesetzt ist.

3. Die Namensauswertung liefert 812 statt 795 Zeilen. Analysieren Sie beide Fehlerrichtungen und begründen Sie, warum die Gruppierung über kunde_nr erfolgen muss.

Read the worked answer

Zwei gegenläufige Effekte überdecken sich:

  1. Aufblähung: 20 Schreibvarianten bestehender Kundinnen (Groß-/Kleinschreibung, Leerzeichen am Ende, „Müller“/„Mueller“) erzeugen zusätzliche Gruppen. Eine Person erscheint mehrfach mit jeweils kleinerem COUNT.
  2. Verschmelzung: Mindestens zwei verschiedene Personen namens „Schmidt“ landen in derselben Gruppe; ihre Ausleihen werden addiert. Wer nur diese Zeile sieht, hält eine Person für besonders aktiv.

Rechnerisch: 795 Personen + 20 Varianten − 3 Verschmelzungen = 812 Zeilen. Dass die Zahl größer ist als die Kundenzahl, verdeckt den zweiten Fehler – man sieht ihn erst, wenn man die Summe der COUNTS mit der Zahl der Ausleihen vergleicht.

Warum kunde_nr: Namen sind keine Schlüssel. Sie sind nicht eindeutig (Personen), nicht stabil (Heirat, Korrektur) und nicht normalisiert (Schreibweisen). kunde_nr ist künstlich, eindeutig und änderungsfest; die Gruppierung über den Fremdschlüssel ist damit verlustfrei.

Korrigierte Abfrage:

SELECT k.kunde_nr, k.name, COUNT(a.ausleih_id) AS ausleihen
FROM kunde k LEFT JOIN ausleihe a ON a.kunde_nr = k.kunde_nr
GROUP BY k.kunde_nr, k.name
ORDER BY ausleihen DESC;

Der LEFT JOIN stellt zusätzlich sicher, dass auch Kundinnen ohne Ausleihe mit 0 erscheinen – der INNER JOIN der alten Abfrage hätte sie verschluckt.

Flankierende Maßnahme: Beim Erfassen TRIM und eine einheitliche Schreibweise erzwingen, Dubletten über E-Mail oder Geburtsdatum prüfen. Die Normalform löst das Problem nicht automatisch – sie erzwingt nur, dass der Name an genau einer Stelle steht.