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_csvist von der Datenbank getrennt und liefert fertige Parameter-Tupel. Den Preis lassen wir als Text ('1.50'): Postgres wandelt ihn fürNUMERICexakt um und vermeidet Rundungsfehler durchfloat. - Alle Einfügungen laufen in einer einzigen Transaktion. Bricht etwas mittendrin ab, wird nichts gespeichert: Es gibt keine halb importierte Datei.
executemanyschickt 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 einDecimal. Das Format>10.2ffunktioniert 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).