Python Lernhandbuch

Kapitel 12: Postgres einbinden

Musterlösung mit Erklärung

Deine Lösung hat die Prüfung noch nicht bestanden. Lies die Erklärung, dann versuche es erneut – möglichst ohne abzuschreiben.
Aufgabe noch einmal lesen

Schreibe kunden_import.py, das:

  1. mit der Tabelle verkaeufe (id SERIAL PRIMARY KEY, kunde TEXT, artikel TEXT, menge INT, preis NUMERIC(10,2)) arbeitet und sie anlegt, falls sie fehlt,
  2. die Datei verkaeufe.csv aus Kapitel 11 einliest und alle Zeilen in einer Transaktion einfügt,
  3. danach per SQL den Umsatz pro Kunde berechnet (SUM(menge * preis), GROUP BY kunde, absteigend) und als Tabelle ausgibt,
  4. die Verbindungsdaten aus der Umgebungsvariable DATABASE_URL liest.
python
import csv
import os

import psycopg
from psycopg.rows import dict_row

SCHEMA = '''
CREATE TABLE IF NOT EXISTS verkaeufe (
    id SERIAL PRIMARY KEY,
    kunde TEXT NOT NULL,
    artikel TEXT NOT NULL,
    menge INT NOT NULL,
    preis NUMERIC(10, 2) NOT NULL
)
'''


def lese_csv(pfad):
    with open(pfad, newline='', encoding='utf-8-sig') as f:
        return [
            (z['kunde'], z['artikel'], int(z['menge']), z['preis'])
            for z in csv.DictReader(f, delimiter=';')
        ]


def main():
    zeilen = lese_csv('verkaeufe.csv')

    with psycopg.connect(os.environ['DATABASE_URL'], row_factory=dict_row) as conn:
        conn.execute(SCHEMA)
        with conn.cursor() as cur:
            cur.executemany(
                'INSERT INTO verkaeufe (kunde, artikel, menge, preis) VALUES (%s, %s, %s, %s)',
                zeilen,
            )
        auswertung = conn.execute('''
            SELECT kunde, SUM(menge * preis) AS umsatz
            FROM verkaeufe
            GROUP BY kunde
            ORDER BY umsatz DESC
        ''').fetchall()

    print(f'{len(zeilen)} Zeilen importiert')
    for z in auswertung:
        print(f'{z["kunde"]:<10}{z["umsatz"]:>10.2f}')


if __name__ == '__main__':
    main()

Aufruf: DATABASE_URL=postgresql://lernen:geheim@localhost:5432/lerndb uv run kunden_import.py

Was passiert:

  • Die Funktion lese_csv ist von der Datenbank getrennt und liefert fertige Parameter-Tupel. Den Preis lassen wir als Text ('1.50'): Postgres wandelt ihn für NUMERIC exakt um und vermeidet Rundungsfehler durch float.
  • Alle Einfügungen laufen in einer einzigen Transaktion. Bricht etwas mittendrin ab, wird nichts gespeichert: Es gibt keine halb importierte Datei.
  • executemany schickt die Zeilen effizient gebündelt.
  • Die Aggregation macht die Datenbank mit GROUP BY. Das ist schneller und kürzer als in Python zu summieren, weil die Datenbank dafür gebaut ist.
  • z['umsatz'] ist ein Decimal. Das Format >10.2f funktioniert auch damit.

Warum so: Die Daten werden erst vollständig gelesen und geprüft, bevor die Datenbank angefasst wird. So bleibt die Transaktion kurz. Wird die Übung ein zweites Mal ausgeführt, entstehen doppelte Zeilen. In der Praxis würdest du dafür einen eindeutigen Schlüssel und ON CONFLICT verwenden (siehe Muster oben).