mitmario.dev

Abfragen

Python Sandbox 3 Min Lesezeit 5 BeispieleLektion 3 von 6

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

Alles holen
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

Was nachbestellt werden muss
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

Was bald abläuft
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

Ein Datum ohne Anführungszeichen
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

Zeilen mit Namen
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, kostenlos

Zu 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.