Abschnitt 16 · Lektion 3
Abfragen
Richtig lohnt sich eine Datenbank erst, wenn du nicht alle Zeilen haben willst, sondern nur die, nach denen du fragst. Bisher kamen Zeilen nur in die Tabelle hinein, jetzt kommen sie wieder heraus.
Gefragt wird in Mias Laden. laden.py legt dafür bei jedem Start laden.db neu an, mit acht
Artikeln samt Preis in Cent, Bestand und Mindesthaltbarkeitsdatum (mhd).
Alles holen
# Legt laden.db bei jedem Start neu an, mit den Artikeln aus Mias Laden.
import sqlite3
from pathlib import Path
ARTIKEL = [
("Brot", 249, 12, "2026-10-09"),
("Milch", 119, 30, "2026-10-14"),
("Butter", 229, 3, "2026-11-20"),
("Käse", 399, 5, "2026-10-28"),
("Joghurt", 89, 4, "2026-10-12"),
("Eier", 329, 2, "2026-10-21"),
("Kaffee", 699, 9, "2027-03-01"),
("Apfelsaft", 189, 14, "2027-01-15"),
]
def oeffnen():
"""Legt laden.db neu an und gibt eine Verbindung dorthin zurück."""
Path("laden.db").unlink(missing_ok=True)
verbindung = sqlite3.connect("laden.db")
verbindung.execute("CREATE TABLE artikel (name TEXT, preis INTEGER, bestand INTEGER, mhd TEXT)")
verbindung.executemany("INSERT INTO artikel VALUES (?, ?, ?, ?)", ARTIKEL)
verbindung.commit()
return verbindung from laden import oeffnen
verbindung = oeffnen()
zeilen = verbindung.execute("SELECT name, preis FROM artikel").fetchall()
print(zeilen)
for name, preis in zeilen:
print(f"{name}: {preis / 100:.2f} Euro")
verbindung.close() Eine Frage an die Datenbank heißt Abfrage (query) und beginnt mit SELECT (auswählen). Dahinter
stehen die Spalten, die man haben will, und nach FROM die Tabelle, also
SELECT name, preis FROM artikel. Für alle Spalten schreibt man SELECT *.
execute schickt die Abfrage, und fetchall() holt die Antwort ab. Das ist eine Liste mit einem
Tupel je Zeile. In jedem Tupel stehen die Werte in derselben Reihenfolge wie die Spalten hinter
SELECT.
Die Liste geht wie jede andere durch eine Schleife, und die Tupel lassen sich gleich im Kopf der
Schleife auspacken: for name, preis in zeilen:. Der Preis kommt in Cent aus der Tabelle und wird
erst beim Ausgeben zu Euro.
In welcher Reihenfolge die Zeilen kommen, legt ohne weitere Angabe die Datenbank fest. Hier ist es die, in der sie eingetragen wurden. Versprochen ist das aber nicht.
Filtern und sortieren
# Legt laden.db bei jedem Start neu an, mit den Artikeln aus Mias Laden.
import sqlite3
from pathlib import Path
ARTIKEL = [
("Brot", 249, 12, "2026-10-09"),
("Milch", 119, 30, "2026-10-14"),
("Butter", 229, 3, "2026-11-20"),
("Käse", 399, 5, "2026-10-28"),
("Joghurt", 89, 4, "2026-10-12"),
("Eier", 329, 2, "2026-10-21"),
("Kaffee", 699, 9, "2027-03-01"),
("Apfelsaft", 189, 14, "2027-01-15"),
]
def oeffnen():
"""Legt laden.db neu an und gibt eine Verbindung dorthin zurück."""
Path("laden.db").unlink(missing_ok=True)
verbindung = sqlite3.connect("laden.db")
verbindung.execute("CREATE TABLE artikel (name TEXT, preis INTEGER, bestand INTEGER, mhd TEXT)")
verbindung.executemany("INSERT INTO artikel VALUES (?, ?, ?, ?)", ARTIKEL)
verbindung.commit()
return verbindung from laden import oeffnen
verbindung = oeffnen()
zeilen = verbindung.execute("""
SELECT name, bestand FROM artikel
WHERE bestand < 5
ORDER BY bestand
""").fetchall()
for name, bestand in zeilen:
print(f"{name}: noch {bestand}")
verbindung.close() Mia will wissen, was nachbestellt werden muss, also alles unter fünf Stück. WHERE (wo) hängt eine
Bedingung an die Abfrage, und zurück kommen nur die Zeilen, auf die sie passt.
Verglichen wird wie in Python mit <, >, <= und >=. Gleich ist in SQL aber ein einfaches =,
und ungleich ist !=. Mehrere Bedingungen verbindet man mit AND oder OR, wie and und or in
Python. ORDER BY (sortieren nach) bestimmt die Reihenfolge, hier mit dem kleinsten Bestand zuerst.
Abfragen werden schnell länger als eine Zeile. Diese steht deshalb in drei Anführungszeichen, wie ein Docstring. So darf der Text über mehrere Zeilen gehen, und Zeilenumbrüche und Leerzeichen am Anfang der Zeilen stören SQL nicht.
Es ist dieselbe Inventur wie in Lektion 7.2. Dort hat eine Schleife jeden Artikel angesehen, hier sucht die Datenbank, und zurück kommen genau die drei Zeilen, um die es geht. Bei acht Artikeln ist das nur kürzer. Bei einem Haushaltsbuch über zehn Jahre heißt es, dass Python nicht mehr jede Buchung einlesen muss, um drei davon zu finden.
Ein Datum vergleichen
# Legt laden.db bei jedem Start neu an, mit den Artikeln aus Mias Laden.
import sqlite3
from pathlib import Path
ARTIKEL = [
("Brot", 249, 12, "2026-10-09"),
("Milch", 119, 30, "2026-10-14"),
("Butter", 229, 3, "2026-11-20"),
("Käse", 399, 5, "2026-10-28"),
("Joghurt", 89, 4, "2026-10-12"),
("Eier", 329, 2, "2026-10-21"),
("Kaffee", 699, 9, "2027-03-01"),
("Apfelsaft", 189, 14, "2027-01-15"),
]
def oeffnen():
"""Legt laden.db neu an und gibt eine Verbindung dorthin zurück."""
Path("laden.db").unlink(missing_ok=True)
verbindung = sqlite3.connect("laden.db")
verbindung.execute("CREATE TABLE artikel (name TEXT, preis INTEGER, bestand INTEGER, mhd TEXT)")
verbindung.executemany("INSERT INTO artikel VALUES (?, ?, ?, ?)", ARTIKEL)
verbindung.commit()
return verbindung from laden import oeffnen
verbindung = oeffnen()
zeilen = verbindung.execute("""
SELECT name, mhd FROM artikel
WHERE mhd < '2026-10-15' AND bestand > 5
ORDER BY mhd
""").fetchall()
print(zeilen)
verbindung.close() Mia setzt den Preis für alles herunter, was vor dem 15. Oktober abläuft und wovon noch mehr als fünf
Stück da sind. Wann ein Artikel abläuft, steht in der Spalte mhd, und jetzt zahlt sich die Form
vom Anfang des Abschnitts aus.
Weil ein Datum als Text in der Form 2026-10-09 dasteht, vergleicht < es richtig, Zeichen für
Zeichen, mit dem Jahr zuerst. mhd < '2026-10-15' heißt also „vor dem 15. Oktober“. Text steht in
SQL immer in einfachen Anführungszeichen, und ein Datum ist hier Text.
Die zweite Bedingung kommt mit AND dazu. Deshalb fehlt der Joghurt. Er läuft zwar am 12. ab, aber
es sind nur noch vier. ORDER BY mhd stellt das früheste Datum nach vorn, und auch das klappt aus
demselben Grund.
Ein Datum ohne Anführungszeichen
# Legt laden.db bei jedem Start neu an, mit den Artikeln aus Mias Laden.
import sqlite3
from pathlib import Path
ARTIKEL = [
("Brot", 249, 12, "2026-10-09"),
("Milch", 119, 30, "2026-10-14"),
("Butter", 229, 3, "2026-11-20"),
("Käse", 399, 5, "2026-10-28"),
("Joghurt", 89, 4, "2026-10-12"),
("Eier", 329, 2, "2026-10-21"),
("Kaffee", 699, 9, "2027-03-01"),
("Apfelsaft", 189, 14, "2027-01-15"),
]
def oeffnen():
"""Legt laden.db neu an und gibt eine Verbindung dorthin zurück."""
Path("laden.db").unlink(missing_ok=True)
verbindung = sqlite3.connect("laden.db")
verbindung.execute("CREATE TABLE artikel (name TEXT, preis INTEGER, bestand INTEGER, mhd TEXT)")
verbindung.executemany("INSERT INTO artikel VALUES (?, ?, ?, ?)", ARTIKEL)
verbindung.commit()
return verbindung from laden import oeffnen
verbindung = oeffnen()
zeilen = verbindung.execute("""
SELECT name, mhd FROM artikel
WHERE mhd < 2026-10-15 AND bestand > 5
ORDER BY mhd
""").fetchall()
print(zeilen)
print(verbindung.execute("SELECT 2026-10-15").fetchone())
verbindung.close() Der Fehler dieser Lektion ist dieselbe Abfrage, nur ohne die Anführungszeichen um das Datum. Die Liste ist leer, und eine Meldung gibt es nicht.
Für SQLite ist 2026-10-15 kein Datum, sondern eine Rechnung, 2026 minus 10 minus 15. Das Ergebnis
steht in der zweiten Zeile der Ausgabe: (2001,). Verglichen wird dann jedes Haltbarkeitsdatum mit
2001. Die Spalte ist als TEXT angelegt, also macht SQLite für den Vergleich auch aus der 2001
Text. Und als Text kommt '2026-10-09' nach '2001', denn an der dritten Stelle steht eine 2 und
keine 0. So ist kein einziges Datum kleiner.
Ein Name ohne Anführungszeichen geht ebenfalls schief, aber laut. WHERE name = Butter endet mit
sqlite3.OperationalError: no such column: Butter. Ohne Anführungszeichen hält SQLite das Wort für
den Namen einer Spalte. Von beiden ist das stille Datum das gemeinere.
Zeilen mit Namen
# Legt laden.db bei jedem Start neu an, mit den Artikeln aus Mias Laden.
import sqlite3
from pathlib import Path
ARTIKEL = [
("Brot", 249, 12, "2026-10-09"),
("Milch", 119, 30, "2026-10-14"),
("Butter", 229, 3, "2026-11-20"),
("Käse", 399, 5, "2026-10-28"),
("Joghurt", 89, 4, "2026-10-12"),
("Eier", 329, 2, "2026-10-21"),
("Kaffee", 699, 9, "2027-03-01"),
("Apfelsaft", 189, 14, "2027-01-15"),
]
def oeffnen():
"""Legt laden.db neu an und gibt eine Verbindung dorthin zurück."""
Path("laden.db").unlink(missing_ok=True)
verbindung = sqlite3.connect("laden.db")
verbindung.execute("CREATE TABLE artikel (name TEXT, preis INTEGER, bestand INTEGER, mhd TEXT)")
verbindung.executemany("INSERT INTO artikel VALUES (?, ?, ?, ?)", ARTIKEL)
verbindung.commit()
return verbindung import sqlite3
from laden import oeffnen
verbindung = oeffnen()
verbindung.row_factory = sqlite3.Row
zeilen = verbindung.execute("SELECT * FROM artikel WHERE preis > 300 ORDER BY preis DESC").fetchall()
for zeile in zeilen:
print(zeile["name"], zeile["preis"])
kaese = verbindung.execute("SELECT * FROM artikel WHERE name = 'Käse'").fetchone()
print(kaese)
print(dict(kaese))
print(verbindung.execute("SELECT * FROM artikel WHERE name = 'Quark'").fetchone())
verbindung.close() Ein Tupel wie ('Käse', 399, 5, '2026-10-28') ist schnell abgezählt, solange es vier Werte sind.
Wenn du lieber zeile["preis"] schreibst als zeile[1], stellst du die Verbindung um. Mit
verbindung.row_factory = sqlite3.Row kommt jede Zeile als sqlite3.Row zurück, und die nimmt den
Namen der Spalte so, wie ein Dictionary einen Schlüssel nimmt.
Im Beispiel werden damit die Artikel über 3 Euro geholt, mit ORDER BY preis DESC (descending,
absteigend) der teuerste zuerst.
Gibst du so eine Zeile mit print aus, siehst du allerdings nur <sqlite3.Row object at 0x…>. Das
ist eine Speicheradresse wie bei den Objekten in Lektion 14.3. dict(zeile) macht ein echtes
Dictionary daraus, und das zeigt print mit allen Werten.
fetchone() holt nur die erste Zeile der Antwort. Das ist praktisch, wenn man genau eine erwartet.
Passt keine Zeile, kommt None zurück, das ist die letzte Zeile der Ausgabe. Wer das nicht prüft,
landet beim nächsten kaese["preis"] bei einem TypeError, wie bei jeder Funktion, die None
zurückgeben kann.
Was die Aufgabe will
Im Haushaltsbuch stehen jetzt Ausgaben aus mehreren Monaten, und gefragt ist nur ein Teil davon. Suchen und sortieren soll dabei die Datenbank.
Zum Mitnehmen
SELECT holt Spalten aus einer Tabelle, WHERE lässt nur passende Zeilen durch, und ORDER BY sortiert sie, mit DESC von groß nach klein. fetchall() liefert eine Liste von Tupeln, fetchone() eine einzelne Zeile oder None, und mit sqlite3.Row sind die Werte über die Namen ihrer Spalten erreichbar. Text und Datum brauchen in SQL einfache Anführungszeichen, ohne sie rechnet SQLite 2026-10-15 als Subtraktion aus.
Jetzt du
Basis Konto, kostenlosZu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.
Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.
In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.
Was in dieser Lektion steckt
-
Artikel mit 5 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.
-
Aufgabe, dein Code läuft auf einem Server
Öffnet sich mit dem Basis Konto.