5. Unterrichtsblock

Python und Datenbanken

In Block 4 hast du Noten in einer Textdatei gespeichert. Das klappt für kleine Listen. Sobald viele Daten zusammenkommen, die man durchsuchen, sortieren und ändern will, nimmt man eine Datenbank. Die Daten stehen dort in Tabellen, und man arbeitet mit ihnen über die Sprache SQL (Structured Query Language).

BegriffBedeutungBeispiel
Datenbankeine Sammlung zusammengehöriger Tabellen, bei SQLite eine einzige Dateischule.db
TabelleDaten zu einer Sache, aufgebaut aus Spalten und Zeilenschueler
Spalteeine Eigenschaft mit festem Namen und Datentypvorname (Text)
Zeile / Datensatzein einzelner EintragLina Berger, 2008, 10a
PrimärschlüsselSpalte, deren Wert jede Zeile eindeutig machtid = 1, 2, 3 …
SQLdie Sprache, in der man mit der Datenbank sprichtCREATE TABLE …

SQLite oder MySQL?

Wir arbeiten mit SQLite. Das Modul sqlite3 gehört zu Python, du musst nichts installieren und brauchst kein Internet. Viele Firmen und Webseiten verwenden Datenbank-Server wie MySQL. Die SQL-Befehle sind dort fast genauso, du kannst dein Wissen also mitnehmen.

SQLiteMySQL
Wo liegen die Daten?in einer Datei neben deinem Programmauf einem Datenbank-Server
Installationkeine, sqlite3 ist in Python enthaltenServer und Python-Paket nötig
AnmeldungkeineBenutzer und Passwort
Platzhalter in Befehlen?%s
EinsatzApps, Lernen, kleine bis mittlere ProgrammeWebseiten und Firmen mit vielen Nutzern gleichzeitig

Verbindung und Cursor

Jede Arbeit mit einer Datenbank läuft in vier Schritten ab: verbinden, Befehle über den Cursor ausführen, Änderungen speichern, Verbindung schließen. sqlite3.connect() öffnet die Datenbankdatei und legt sie an, wenn es sie noch nicht gibt. Den Pfad baust du wie in Block 4 vom Skript-Ordner aus:

import sqlite3
from pathlib import Path

ORDNER = Path(__file__).resolve().parent
datenbank = ORDNER / "schule.db"

verbindung = sqlite3.connect(datenbank)    # 1. verbinden
cursor = verbindung.cursor()               # 2. Cursor holen
# cursor.execute("SQL-Befehl")            #    Befehle ausführen
verbindung.commit()                        # 3. speichern
verbindung.close()                         # 4. schließen

print("Datei vorhanden:", datenbank.exists())
Ausgabe im Terminal
Datei vorhanden: True

Der Cursor ist wie ein Zeiger in der Datenbank: Über ihn schickst du mit execute() SQL-Befehle und holst dir Ergebnisse ab.

Merke: Änderungen an der Datenbank sind erst nach verbindung.commit() dauerhaft gespeichert. Mit verbindung.close() gibst du die Datei wieder frei. Solange sie offen ist, sperrt Windows sie für andere Programme.

Aufgabe 1 – Die erste Datenbank

Zeit: ca. 8 Minuten

Eine SQLite-Datenbank ist eine ganz normale Datei. Das kannst du nachmessen. Lege eine neue Datei block_05_sql_grundlagen.py an, die so beginnt:

import sqlite3
from pathlib import Path

ORDNER = Path(__file__).resolve().parent
  1. Verbinde dich mit ORDNER / "test.db" und schließe die Verbindung gleich wieder. Sage vorher voraus: Gibt es die Datei danach? Wie groß ist sie (stat().st_size aus Block 4)?
  2. Lege mit CREATE TABLE IF NOT EXISTS notiz (id INTEGER PRIMARY KEY, text TEXT) eine Tabelle an, dann commit() und close(). Wie groß ist die Datei jetzt?
  3. Öffne test.db im Editor von VS Code. Warum kannst du den Inhalt nicht wie eine Textdatei lesen? Antworte als Kommentar.

Kontrolle: Erst 0 Bytes, nach dem Anlegen der Tabelle 8192 Bytes.

Tabellen erstellen

Eine Tabelle legst du mit CREATE TABLE an. Für jede Spalte gibst du Name und Datentyp an. SQLite kennt nur wenige Datentypen:

SQLite-Typspeichertin MySQL etwa
INTEGERganze ZahlenINT
REALKommazahlenFLOAT, DOUBLE
TEXTText beliebiger LängeVARCHAR(255), TEXT
BLOBBinärdaten, z. B. BilderBLOB
NULL„kein Wert“ (leeres Feld)NULL

Hinter dem Datentyp kannst du Regeln für die Spalte festlegen:

ZusatzWirkung
PRIMARY KEYPrimärschlüssel: eindeutig und nie leer
AUTOINCREMENTvergibt die Nummer automatisch: 1, 2, 3 … (MySQL: AUTO_INCREMENT)
NOT NULLPflichtfeld, darf nicht leer bleiben
UNIQUEjeder Wert darf nur einmal vorkommen
DEFAULT wertStandardwert, wenn nichts angegeben wird

Längere SQL-Befehle schreibst du in einen String mit drei Anführungszeichen, dann bleiben sie übersichtlich. IF NOT EXISTS legt die Tabelle nur an, wenn es sie noch nicht gibt:

verbindung = sqlite3.connect(datenbank)
cursor = verbindung.cursor()
cursor.execute("""
    CREATE TABLE IF NOT EXISTS schueler (
        id          INTEGER PRIMARY KEY AUTOINCREMENT,
        vorname     TEXT NOT NULL,
        nachname    TEXT NOT NULL,
        geburtsjahr INTEGER,
        klasse      TEXT DEFAULT '10a'
    )
""")
verbindung.commit()
verbindung.close()
print("Tabelle schueler angelegt")
Ausgabe im Terminal
Tabelle schueler angelegt
Merke: SQL-Schlüsselwörter wie CREATE TABLE schreibt man üblicherweise groß, Tabellen- und Spaltennamen klein, ohne Leerzeichen und ohne Umlaute (schueler, nicht Schüler). Texte innerhalb von SQL stehen in einfachen Anführungszeichen: '10a'.

Aufgabe 2 – Der Bauplan

Zeit: ca. 10 Minuten

Die Schulbibliothek braucht eine Tabelle buch in bibliothek.db. Jedes Buch hat eine automatisch vergebene Nummer, einen Titel und einen Autor (beide Pflicht), ein Erscheinungsjahr, eine ISBN, die es nur einmal geben darf, und einen Status ausgeliehen, der am Anfang 0 ist.

  1. Schreibe den CREATE TABLE-Befehl und führe ihn aus.
  2. Gib mit PRAGMA table_info(buch) alle Spalten aus. Kontrolle: 6 Spalten.
  3. Lass IF NOT EXISTS weg und starte das Programm zweimal. Sage vorher voraus, was beim zweiten Start passiert, und notiere die Fehlermeldung als Kommentar.

Tabellen anzeigen und prüfen

Welche Tabellen in einer Datenbank stecken, steht in der internen Tabelle sqlite_master. Die Spalten einer Tabelle liefert PRAGMA table_info(name). Das Ergebnis holst du mit fetchall() ab, eine Liste mit einem Tupel pro Zeile (mehr dazu in Block 6):

verbindung = sqlite3.connect(datenbank)
cursor = verbindung.cursor()

cursor.execute("SELECT name FROM sqlite_master WHERE type = 'table'")
print(cursor.fetchall())

cursor.execute("PRAGMA table_info(schueler)")
for spalte in cursor.fetchall():
    print(spalte)
verbindung.close()
Ausgabe im Terminal
[('schueler',), ('sqlite_sequence',)]
(0, 'id', 'INTEGER', 0, None, 1)
(1, 'vorname', 'TEXT', 1, None, 0)
(2, 'nachname', 'TEXT', 1, None, 0)
(3, 'geburtsjahr', 'INTEGER', 0, None, 0)
(4, 'klasse', 'TEXT', 0, "'10a'", 0)

Neben schueler taucht sqlite_sequence auf: Dort merkt sich SQLite, welche Nummer AUTOINCREMENT als Nächstes vergibt. Jede Spaltenzeile enthält Nummer, Name, Datentyp, Pflicht (1 = NOT NULL), Standardwert und Primärschlüssel (1 = ja).

Ob es die Datenbank selbst gibt, prüfst du wie jede Datei mit datenbank.exists() aus Block 4. Bei MySQL gibt es dafür eigene Befehle wie SHOW DATABASES und SHOW TABLES.

Merke: Plane eine Tabelle gleich beim Anlegen vollständig. SQLite kann mit ALTER TABLE nachträglich nur Spalten hinzufügen oder umbenennen, einen Primärschlüssel aber nicht mehr ergänzen.

Übungsaufgaben

Die Aufgaben 3 bis 7 sind die Kernaufgaben, wer schneller ist, macht mit den Bonusaufgaben weiter. Schreibe alle Aufgaben in deine Datei block_05_sql_grundlagen.py aus Aufgabe 1. Sie beginnt immer mit diesem Kopf:

import sqlite3
from pathlib import Path

ORDNER = Path(__file__).resolve().parent

Aufgabe 3 – Die Schuldatenbank

Zeit: ca. 10 Minuten

Für die Schulverwaltung brauchst du eine Datenbank schule.db mit zwei Tabellen.

  1. Tabelle klasse: Nummer (Primärschlüssel), Name wie „10a“ (Pflicht, eindeutig), Raum und Klassenlehrkraft (beides Text).
  2. Tabelle schueler: Nummer (Primärschlüssel, automatisch), Vorname und Nachname (Pflicht), Geburtsjahr (ganze Zahl) und die Nummer der Klasse (ganze Zahl).
  3. Gib alle Tabellen der Datenbank aus, ohne die interne Tabelle sqlite_sequence. Kontrolle: klasse, schueler.
  4. Gib für beide Tabellen die Spalten in der Form schueler.vorname (TEXT, Pflicht) aus.

Zusatz: Warum speichert schueler nur die Nummer der Klasse und nicht den Namen „10a“? Antworte als Kommentar. Stichwort: doppelte Daten.

Aufgabe 4 – Fehlersuche im Bauplan

Zeit: ca. 8 Minuten

Dieser Bauplan für eine Tabelle lehrkraft enthält drei Fehler. SQLite meldet immer nur den ersten.

bauplan = """
    CREATE TABLE IF NOT EXISTS lehrkraft (
        id    INTEGER PRIMARY KEY AUTOINCREMENT
        name  TEXT NOT NULL,
        fach  TEXT,
        order INTEGER,
    )
"""
  1. Führe den Befehl in schule.db aus und lies die Fehlermeldung genau.
  2. Behebe die Fehler einzeln und starte nach jeder Korrektur neu. Schreibe zu jedem Fehler einen Kommentar, was falsch war.
  3. Kontrolle: Die Tabelle wird ohne Fehler angelegt und hat 4 Spalten.

Tipp: order ist ein SQL-Schlüsselwort (es kommt in Block 6 bei ORDER BY vor) und darf nicht als Spaltenname verwendet werden.

Aufgabe 5 – Gibt es die Tabelle schon?

Zeit: ca. 12 Minuten

Programme prüfen oft zuerst, was in einer Datenbank steht. Schreibe dafür zwei Funktionen für deine schule.db aus Aufgabe 3.

  1. Schreibe tabelle_existiert(cursor, name), die mit einer Abfrage auf sqlite_master True oder False zurückgibt. Setze den Tabellennamen mit dem Platzhalter ? ein, nicht mit einem f-String.
  2. Teste: schueler ergibt True, lehrer ergibt False.
  3. Schreibe spalten(cursor, name), die die Spaltennamen einer Tabelle als Liste zurückgibt. Kontrolle für klasse: ['id', 'name', 'raum', 'lehrkraft'].
  4. Frage mit input() nach einem Tabellennamen und gib aus, ob es sie gibt und welche Spalten sie hat.

Zusatz: Lagere beide Funktionen in ein eigenes Modul datenbank.py aus und importiere sie (Block 2).

Aufgabe 6 – Planen für den Sportverein

Zeit: ca. 12 Minuten

Ein Sportverein will Mitglieder, Mannschaften und Trainingszeiten verwalten. Diesmal planst du die Tabellen selbst.

  1. Überlege, welche Tabellen und Spalten nötig sind. Schreibe den Plan zuerst als Kommentar auf (Tabelle: Spalten mit Typ).
  2. Jede Tabelle bekommt einen Primärschlüssel. Pflichtangaben bekommen NOT NULL, Werte, die es nur einmal geben darf (z. B. die E-Mail-Adresse), bekommen UNIQUE.
  3. Lege die Tabellen in verein.db an und gib zur Kontrolle alle Tabellen mit ihren Spalten aus.

Zusatz: Ein Mitglied kann in mehreren Mannschaften spielen und eine Mannschaft hat viele Mitglieder. Wie bildest du das ab? Tipp: eine dritte Tabelle, die nur zwei Nummern speichert.

Aufgabe 7 – Neu anfangen

Zeit: ca. 8 Minuten

Beim Testen will man oft mit einer leeren Datenbank neu starten. Verbinde dafür Block 4 und Block 5.

  1. Frage: „Datenbank schule.db neu anlegen? (ja/nein)“.
  2. Bei „ja“ löschst du die Datei mit unlink() (Block 4), aber nur, wenn es sie gibt. Danach legst du die beiden Tabellen aus Aufgabe 3 neu an.
  3. Gib zum Schluss die Tabellen und die Dateigröße aus.
  4. Denkfrage als Kommentar: Was passiert unter Windows, wenn du die Datei löschen willst, während noch eine Verbindung zu ihr offen ist?

Bonusaufgabe 1 – Der Datenbank-Inspektor

Ein kleines Werkzeug soll zeigen, was in allen Datenbanken im Skript-Ordner steckt.

  1. Gehe mit ORDNER.glob("*.db") alle Datenbankdateien durch und gib Name und Größe aus.
  2. Gib für jede Datei ihre Tabellen aus (ohne interne sqlite_-Tabellen).
  3. Gib für jede Tabelle die Spalten als ausgerichtete Tabelle aus: Name, Typ und eine Markierung PK für den Primärschlüssel und Pflicht für NOT NULL.

Bonusaufgabe 2 – Nachträglich ändern

Manchmal fällt erst später auf, dass eine Spalte fehlt. SQLite kann eine bestehende Tabelle mit ALTER TABLE nur eingeschränkt ändern.

  1. Füge der Tabelle schueler mit ALTER TABLE schueler ADD COLUMN email TEXT eine Spalte hinzu und zeige die Spalten an.
  2. Führe denselben Befehl noch einmal aus. Fange den Fehler ab und gib ihn aus.
  3. Versuche, mit ADD COLUMN eine zweite Spalte mit PRIMARY KEY anzulegen. Was meldet SQLite? Erkläre als Kommentar, warum man den Primärschlüssel gleich beim Anlegen planen muss.
Kurz zusammengefasst:
  • SQLite speichert eine ganze Datenbank in einer Datei, sqlite3 ist in Python schon enthalten.
  • Ablauf: connect() → cursor() → execute() → commit() → close().
  • CREATE TABLE IF NOT EXISTS name (spalte TYP REGELN, …) legt eine Tabelle an. Datentypen: INTEGER, REAL, TEXT.
  • Jede Tabelle braucht einen Primärschlüssel, meist id INTEGER PRIMARY KEY AUTOINCREMENT. Weitere Regeln: NOT NULL, UNIQUE, DEFAULT.
  • Tabellen anzeigen: sqlite_master, Spalten anzeigen: PRAGMA table_info(name), Ergebnis mit fetchall() abholen.