1. Was ist eine relationale Datenbank? (Die Aktenschrank-Analogie)
In vorherigen Kapiteln haben wir Daten in einfachen JSON- oder CSV-Dateien gespeichert. Bei größeren Anwendungen stoßen Textdateien jedoch schnell an ihre Grenzen: Man kann nicht blitzschnell nach einzelnen Datensätzen suchen, ohne die gesamte Datei einzulesen, und gleichzeitige Schreibzugriffe führen zu Datenverlust. Hier kommen relationale Datenbanksysteme (RDBMS) ins Spiel!
- Die Datenbankdatei (
.db): Der gesamte Aktenschrank im Büro. - Die Tabelle (
Table): Eine einzelne ausziehbare Schublade (z.B. Schublade „Kunden“). - Die Spalte (
Column / Field): Ein vorgedrucktes Formularfeld auf den Karteikarten (z.B. „Name“, „E-Mail“, „Guthaben“). - Die Zeile (
Row / Record): Eine einzelne, ausgefüllte Karteikarte für genau einen Kunden. - Der Primärschlüssel (
PRIMARY KEY): Die eindeutige Kundennummer oben rechts auf jeder Karteikarte.
Der Lebenszyklus einer SQLite-Verbindung in Python
Python bringt mit sqlite3 eine vollständige, vollwertige SQL-Datenbank direkt in der Standardbibliothek mit – ohne Server-Installation!
import sqlite3 # 1. Verbindung herstellen (erstellt die Datei automatisch, falls nicht vorhanden) conn = sqlite3.connect("kunden.db") # 2. row_factory aktivieren: Erlaubt Dict-artigen Spaltenzugriff wie row["name"] conn.row_factory = sqlite3.Row # 3. Cursor erzeugen (der 'Stift', mit dem wir Befehle an die DB schicken) cursor = conn.cursor() # 4. SQL-Befehl ausführen cursor.execute(""" CREATE TABLE IF NOT EXISTS kunden ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, email TEXT NOT NULL UNIQUE, guthaben REAL DEFAULT 0.0 ) """) # 5. Transaktion dauerhaft speichern (Commit) conn.commit() # 6. Verbindung sauber schließen conn.close()
2. Die 4 Säulen von SQL: CRUD im Überblick
In relationalen Datenbanken dreht sich alles um die vier elementaren CRUD-Operationen:
| Operation | SQL-Befehl | Python-Beispiel mit Platzhaltern (?) |
|---|---|---|
| Create (Erstellen) | INSERT INTO ... |
cursor.execute("INSERT INTO kunden (name, email, guthaben) VALUES (?, ?, ?)", (name, email, 50.0)) |
| Read (Lesen / Suchen) | SELECT ... WHERE |
cursor.execute("SELECT * FROM kunden WHERE id = ?", (kunden_id,))row = cursor.fetchone() |
| Update (Aktualisieren) | UPDATE ... SET |
cursor.execute("UPDATE kunden SET guthaben = guthaben + ? WHERE id = ?", (betrag, kunden_id)) |
| Delete (Löschen) | DELETE FROM ... |
cursor.execute("DELETE FROM kunden WHERE id = ?", (kunden_id,)) |
cursor.fetchone(): Liefert die nächste einzelne Ergebniszeile (oderNone).cursor.fetchall(): Liefert eine Liste aller passenden Ergebniszeilen (oder[]).cursor.lastrowid: Die automatisch generierte ID des zuletzt eingefügten Datensatzes.cursor.rowcount: Die Anzahl der Zeilen, die durch das letzteUPDATEoderDELETEverändert wurden.
3. 🚨 Sicherheit: Schutz vor SQL-Injections mit ?-Parametern
Eine der häufigsten und gefährlichsten Sicherheitslücken in der Software-Entwicklung ist die SQL-Injection. Sie entsteht, wenn Benutzereingaben unbedacht per String-Formatierung (z.B. f-Strings) direkt in den SQL-Befehl eingebettet werden!
❌ GEFÄHRLICH: String-Formatierung
# Angreifer gibt als Name ein:
# Robert'); DROP TABLE kunden;--
query = f"INSERT INTO kunden (name) VALUES ('{user_name}')"
cursor.execute(query)
# BUMM! Die Tabelle kunden wird gelöscht!
✅ SICHER: Parametrisierte Abfrage
# Das sqlite3-Modul maskiert alle Zeichen:
query = "INSERT INTO kunden (name) VALUES (?)"
cursor.execute(query, (user_name,))
# Der Name wird harmlos als Text gespeichert!
4. In-Memory Datenbanken mit :memory:
Für automatisierte Unit Tests oder temporäre Berechnungen bietet SQLite ein geniales Feature: die In-Memory-Datenbank.
conn = sqlite3.connect(":memory:")
Die Datenbank existiert ausschließlich im flüchtigen RAM. Sobald conn.close() aufgerufen wird oder der Test endet, ist alles rückstandslos bereinigt –
ideal für blitzschnelle, voneinander isolierte Tests ohne lästige temporäre Testdateien auf der Festplatte!
5. 🎯 Deine Aufgaben in aufgabe.py
Öffne die Datei aufgabe.py und implementiere die Klasse KundenDatenbank:
💡 Lösungshinweise (Aufklappen bei Bedarf)
🔍 Tipp zur Tabellenerstellung
self.cursor.execute(""" CREATE TABLE IF NOT EXISTS kunden ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, email TEXT NOT NULL UNIQUE, guthaben REAL DEFAULT 0.0 ) """) self.conn.commit()
🔍 Tipp zu row_factory & Dict-Rückgabe
self.cursor.execute("SELECT id, name, email, guthaben FROM kunden WHERE id = ?", (kunden_id,)) row = self.cursor.fetchone() if row is None: return None return { "id": row["id"], "name": row["name"], "email": row["email"], "guthaben": float(row["guthaben"]) }
6. 🧪 Lösung überprüfen
Führe im Terminal im Ordner 21_datenbanken_und_sqlite folgenden Befehl aus:
python3 test_aufgabe.py