Einführung: Warum DuckDB das Schweizer Taschenmesser der Python-Datenanalyse ist
Hand aufs Herz — wer mit Python und Daten arbeitet, kennt das Problem: pandas ist fantastisch für kleinere Datensätze, aber irgendwann wird es einfach quälend langsam. Die Aggregation dauert eine Ewigkeit, der Arbeitsspeicher läuft über, und man fragt sich, ob es nicht einen besseren Weg geben muss.
Spoiler: Den gibt es. DuckDB.
DuckDB ist eine eingebettete, spaltenorientierte SQL-Datenbank, die oft als „das SQLite der Analytik" bezeichnet wird — und ehrlich gesagt, dieser Vergleich trifft es ziemlich gut. Entwickelt wurde sie 2018 von den Datenbankforschern Mark Raasveldt und Hannes Mühleisen am Centrum Wiskunde & Informatica (CWI) in Amsterdam. Die Grundidee war simpel, aber clever: eine analytische Datenbank bauen, die so unkompliziert wie SQLite zu verwenden ist, aber speziell für OLAP-Workloads (Online Analytical Processing) optimiert wurde. Kein separater Server nötig, kein Docker-Container, keine Cloud — DuckDB läuft direkt in Ihrem Python-Prozess.
Mit über 6 Millionen monatlichen Downloads auf PyPI und dem dritten Platz als „meistgewünschte Datenbank" in der StackOverflow-Umfrage 2024 hat sich DuckDB fest im modernen Data-Science-Stack etabliert. In diesem Leitfaden gehen wir alles durch, was Sie für den produktiven Einsatz brauchen — von der Installation über die pandas-Integration bis hin zu ETL-Pipelines. Also, los geht's.
Installation und erste Schritte
DuckDB installieren
Die Installation ist denkbar einfach. Sie brauchen Python 3.9 oder neuer — dann reicht ein einziger pip-Befehl:
pip install duckdb
Wer lieber conda nutzt, geht diesen Weg:
conda install python-duckdb -c conda-forge
Die aktuelle stabile Version ist 1.4.4. Um zu prüfen, ob alles geklappt hat:
import duckdb
print(duckdb.__version__)
# Ausgabe: 1.4.4
Die erste Abfrage: In-Memory-Datenbank
Der einfachste Einstieg ist die In-Memory-Datenbank. Damit führen Sie SQL-Abfragen aus, ohne überhaupt eine Datei anlegen zu müssen. Das ist vor allem beim Experimentieren super praktisch:
import duckdb
# Einfache SQL-Abfrage ausführen
result = duckdb.sql("SELECT 42 AS antwort, 'Hallo DuckDB' AS begruessung")
result.show()
# Ausgabe:
# ┌─────────┬──────────────┐
# │ antwort │ begruessung │
# │ int32 │ varchar │
# ├─────────┼──────────────┤
# │ 42 │ Hallo DuckDB │
# └─────────┴──────────────┘
DuckDB nutzt standardmäßig eine In-Memory-Datenbank, wenn Sie duckdb.sql() oder duckdb.connect() ohne Argumente aufrufen. Alle Tabellen und Daten leben nur während der aktuellen Python-Sitzung — danach sind sie weg.
Persistente Datenbanken
Wenn Ihre Daten die Sitzung überleben sollen, verbinden Sie sich einfach mit einer Datei:
import duckdb
# Persistente Datenbank erstellen/öffnen
con = duckdb.connect("meine_analyse.duckdb")
# Tabelle erstellen und Daten einfügen
con.execute("""
CREATE TABLE IF NOT EXISTS verkaufsdaten (
produkt VARCHAR,
region VARCHAR,
umsatz DECIMAL(10, 2),
datum DATE
)
""")
con.execute("""
INSERT INTO verkaufsdaten VALUES
('Laptop', 'Nord', 1299.99, '2025-01-15'),
('Tablet', 'Süd', 599.50, '2025-01-16'),
('Monitor', 'West', 449.00, '2025-02-01'),
('Tastatur', 'Nord', 89.99, '2025-02-03')
""")
# Daten abfragen
result = con.execute("SELECT * FROM verkaufsdaten WHERE umsatz > 500").fetchdf()
print(result)
con.close()
Beim nächsten Verbindungsaufbau sind alle Daten noch da. Praktisch: Sie können die Datenbank auch im schreibgeschützten Modus öffnen mit duckdb.connect("meine_analyse.duckdb", read_only=True) — zum Beispiel, wenn mehrere Prozesse parallel lesen sollen.
DuckDB und pandas: Eine perfekte Symbiose
DataFrames direkt mit SQL abfragen
Okay, jetzt wird es richtig spannend. Eines der absolut besten Features von DuckDB ist die nahtlose pandas-Integration. Sie können SQL-Abfragen direkt auf DataFrames ausführen — ohne Daten kopieren oder konvertieren zu müssen. DuckDB erkennt Python-Variablen automatisch, wenn deren Namen mit den Tabellennamen in Ihrer Abfrage übereinstimmen:
import duckdb
import pandas as pd
# pandas DataFrame erstellen
mitarbeiter = pd.DataFrame({
'name': ['Anna', 'Boris', 'Clara', 'David', 'Eva'],
'abteilung': ['IT', 'Marketing', 'IT', 'Vertrieb', 'Marketing'],
'gehalt': [65000, 55000, 72000, 58000, 61000],
'eintrittsjahr': [2019, 2021, 2018, 2022, 2020]
})
# SQL direkt auf dem DataFrame ausführen
result = duckdb.sql("""
SELECT
abteilung,
COUNT(*) AS anzahl,
ROUND(AVG(gehalt), 2) AS durchschnittsgehalt,
MAX(gehalt) AS hoechstgehalt
FROM mitarbeiter
GROUP BY abteilung
ORDER BY durchschnittsgehalt DESC
""")
result.show()
# Ausgabe:
# ┌───────────┬────────┬─────────────────────┬──────────────┐
# │ abteilung │ anzahl │ durchschnittsgehalt │ hoechstgehalt│
# │ varchar │ int64 │ double │ int64 │
# ├───────────┼────────┼─────────────────────┼──────────────┤
# │ IT │ 2 │ 68500.00 │ 72000 │
# │ Marketing │ 2 │ 58000.00 │ 61000 │
# │ Vertrieb │ 1 │ 58000.00 │ 58000 │
# └───────────┴────────┴─────────────────────┴──────────────┘
Ergebnisse zurück in pandas konvertieren
DuckDB-Ergebnisse lassen sich mit .df() oder .fetchdf() mühelos in DataFrames umwandeln. So nutzen Sie das Beste aus beiden Welten — DuckDB für die schwere Rechenarbeit, pandas für alles Weitere:
import duckdb
import pandas as pd
# Große Berechnung in DuckDB durchführen
verkauf_df = pd.DataFrame({
'produkt': ['A', 'B', 'A', 'C', 'B', 'A'] * 1000,
'menge': [10, 20, 15, 5, 25, 30] * 1000,
'preis': [9.99, 19.99, 9.99, 49.99, 19.99, 9.99] * 1000
})
# Aggregation in DuckDB, Ergebnis als pandas DataFrame
ergebnis = duckdb.sql("""
SELECT
produkt,
SUM(menge) AS gesamtmenge,
ROUND(SUM(menge * preis), 2) AS gesamtumsatz
FROM verkauf_df
GROUP BY produkt
ORDER BY gesamtumsatz DESC
""").df()
print(type(ergebnis)) # <class 'pandas.core.frame.DataFrame'>
print(ergebnis)
Dieser hybride Ansatz hat sich in meinen Projekten immer wieder bewährt: DuckDB übernimmt die schweren Aggregationen, und danach geht's mit pandas weiter — sei es für Visualisierung, Export oder Machine-Learning-Pipelines.
Dateien direkt abfragen: CSV, Parquet und JSON
CSV-Dateien lesen und abfragen
Was DuckDB wirklich herausstechen lässt: Sie können Dateien direkt mit SQL abfragen, ohne sie vorher irgendwo hinladen zu müssen. DuckDB liest dabei intelligent nur die Spalten und Zeilen, die tatsächlich gebraucht werden:
import duckdb
# CSV-Datei direkt mit SQL abfragen
result = duckdb.sql("""
SELECT
stadt,
COUNT(*) AS bestellungen,
SUM(betrag) AS gesamtbetrag
FROM 'bestellungen.csv'
WHERE datum >= '2025-01-01'
GROUP BY stadt
ORDER BY gesamtbetrag DESC
LIMIT 10
""")
result.show()
Parquet-Dateien: Der Goldstandard
Parquet ist das bevorzugte Format für analytische Workloads — und DuckDB spielt hier seine Stärken voll aus. Im Vergleich zu CSV kann DuckDB Parquet-Dateien bis zu 600-mal schneller lesen (ja, Sie haben richtig gelesen). Das liegt daran, dass Parquet spaltenorientiert aufgebaut ist und perfekt zur Engine von DuckDB passt:
import duckdb
# Einzelne Parquet-Datei abfragen
result = duckdb.sql("""
SELECT *
FROM 'verkaufsdaten_2025.parquet'
WHERE region = 'DACH'
LIMIT 100
""")
# Mehrere Parquet-Dateien mit Glob-Pattern
umsatz = duckdb.sql("""
SELECT
EXTRACT(MONTH FROM datum) AS monat,
SUM(umsatz) AS monatsumsatz
FROM 'daten/verkauf_*.parquet'
GROUP BY monat
ORDER BY monat
""").df()
# Parquet-Dateien aus S3 lesen (mit httpfs-Extension)
duckdb.sql("INSTALL httpfs; LOAD httpfs;")
remote_data = duckdb.sql("""
SELECT *
FROM 's3://mein-bucket/daten/report.parquet'
LIMIT 50
""")
print(remote_data.df())
JSON-Dateien verarbeiten
Auch JSON geht — besonders praktisch bei API-Antworten oder Log-Daten:
import duckdb
# JSON-Datei abfragen
result = duckdb.sql("""
SELECT
event_type,
COUNT(*) AS anzahl,
AVG(response_time_ms) AS avg_antwortzeit
FROM 'server_logs.json'
WHERE timestamp >= '2025-06-01'
GROUP BY event_type
ORDER BY anzahl DESC
""")
result.show()
Daten exportieren
Natürlich können Sie Ergebnisse auch direkt in verschiedene Formate exportieren:
import duckdb
# Ergebnis als Parquet speichern
duckdb.sql("""
COPY (
SELECT * FROM 'rohdaten.csv'
WHERE status = 'aktiv'
) TO 'gefilterte_daten.parquet' (FORMAT PARQUET, COMPRESSION ZSTD)
""")
# Als CSV exportieren
duckdb.sql("""
COPY (
SELECT * FROM 'analyse_ergebnis.parquet'
) TO 'bericht.csv' (HEADER, DELIMITER ';')
""")
Fortgeschrittene SQL-Features: Window Functions und CTEs
Window Functions für analytische Berechnungen
Window Functions gehören zu den mächtigsten Werkzeugen in der SQL-Welt — und DuckDB beherrscht sie hervorragend. In Version 1.4 wurde sogar die Sortierung komplett neu implementiert (k-Way-Merge-Sort), was die Performance bei Window Functions spürbar verbessert hat. Hier ein etwas umfangreicheres Beispiel:
import duckdb
import pandas as pd
# Beispieldaten erstellen
umsatzdaten = pd.DataFrame({
'monat': pd.date_range('2024-01-01', periods=12, freq='MS'),
'region': ['Nord', 'Süd', 'Nord', 'Süd'] * 3,
'umsatz': [12000, 15000, 13500, 14200, 16000, 15800,
17200, 16500, 18000, 17800, 19500, 18200]
})
result = duckdb.sql("""
SELECT
monat,
region,
umsatz,
-- Laufende Summe pro Region
SUM(umsatz) OVER (
PARTITION BY region
ORDER BY monat
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS laufende_summe,
-- Gleitender 3-Monats-Durchschnitt
ROUND(AVG(umsatz) OVER (
ORDER BY monat
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
), 2) AS gleitender_durchschnitt,
-- Rang innerhalb der Region
RANK() OVER (
PARTITION BY region
ORDER BY umsatz DESC
) AS umsatz_rang,
-- Veränderung zum Vormonat
umsatz - LAG(umsatz, 1) OVER (
PARTITION BY region
ORDER BY monat
) AS veraenderung_vormonat
FROM umsatzdaten
ORDER BY monat
""")
result.show()
Common Table Expressions (CTEs)
Seit DuckDB 1.4 werden CTEs standardmäßig materialisiert statt inline erweitert. Klingt technisch, heißt aber im Klartext: bessere Performance und vorhersagbarere Ergebnisse bei komplexen Abfragen.
import duckdb
import pandas as pd
bestellungen = pd.DataFrame({
'kunde_id': [1, 2, 1, 3, 2, 1, 3, 2],
'produkt': ['A', 'B', 'C', 'A', 'A', 'B', 'C', 'C'],
'betrag': [100, 200, 150, 300, 120, 90, 250, 180],
'datum': pd.to_datetime([
'2025-01-05', '2025-01-10', '2025-01-15', '2025-02-01',
'2025-02-10', '2025-02-20', '2025-03-01', '2025-03-15'
])
})
result = duckdb.sql("""
WITH kundenumsatz AS (
SELECT
kunde_id,
SUM(betrag) AS gesamtbetrag,
COUNT(*) AS bestellanzahl
FROM bestellungen
GROUP BY kunde_id
),
kundensegment AS (
SELECT
*,
CASE
WHEN gesamtbetrag >= 400 THEN 'Premium'
WHEN gesamtbetrag >= 200 THEN 'Standard'
ELSE 'Basis'
END AS segment
FROM kundenumsatz
)
SELECT
segment,
COUNT(*) AS kunden_anzahl,
ROUND(AVG(gesamtbetrag), 2) AS avg_umsatz,
SUM(bestellanzahl) AS total_bestellungen
FROM kundensegment
GROUP BY segment
ORDER BY avg_umsatz DESC
""")
result.show()
Performance-Vergleich: DuckDB vs. pandas
Warum ist DuckDB schneller?
Diese Frage höre ich oft, und die Antwort liegt in der Architektur. DuckDB übertrifft pandas bei analytischen Workloads aus mehreren Gründen:
- Spaltenorientierte Speicherung: DuckDB speichert Daten spaltenweise. Bei Aggregationen werden nur die benötigten Spalten gelesen — das allein macht schon einen enormen Unterschied.
- Vektorisierte Ausführung: Statt Zeile für Zeile arbeitet DuckDB mit Vektoren von typischerweise 2048 Werten auf einmal. Das optimiert die CPU-Cache-Nutzung enorm.
- Automatische Parallelisierung: DuckDB nutzt automatisch alle CPU-Kerne, während pandas standardmäßig nur einen Thread verwendet. Ja, wirklich nur einen.
- Query-Optimierung: Ein vollwertiger Optimizer mit Predicate Pushdown, smarten Join-Algorithmen und intelligenter Speicherverwaltung tut sein Übriges.
- Lazy Evaluation bei Dateien: Beim Abfragen von Parquet-Dateien liest DuckDB nur die Spalten und Zeilengruppen, die tatsächlich gebraucht werden.
Benchmark-Ergebnisse in der Praxis
Zahlen sagen mehr als Worte. Bei einem Test mit einem E-Commerce-Datensatz brauchte pandas 1,99 Sekunden für eine Kundenanalyse — DuckDB erledigte dieselbe Aufgabe in 0,117 Sekunden. Das ist ein Speedup von Faktor 17.
Bei größeren Datensätzen (100+ Millionen Zeilen Clickstream-Daten im Parquet-Format) sieht es so aus:
- Group-By-Aggregation (100M Zeilen): pandas ~115 Sek., DuckDB ~31 Sek.
- Großer Join mit Aggregation: pandas ~162 Sek., DuckDB ~35 Sek.
- Zeitreihen-Fensterberechnung: pandas ~129 Sek., DuckDB ~33 Sek.
Und hier ein Beispiel zum Selbst-Ausprobieren — probieren Sie es mal auf Ihrem Rechner:
import duckdb
import pandas as pd
import numpy as np
import time
# Großen DataFrame erstellen (5 Millionen Zeilen)
n = 5_000_000
df = pd.DataFrame({
'kategorie': np.random.choice(['Elektronik', 'Kleidung', 'Lebensmittel',
'Bücher', 'Sport'], n),
'region': np.random.choice(['Nord', 'Süd', 'Ost', 'West'], n),
'umsatz': np.random.uniform(10, 1000, n),
'menge': np.random.randint(1, 50, n)
})
# pandas-Methode
start = time.time()
pandas_result = (
df.groupby(['kategorie', 'region'])
.agg({'umsatz': ['sum', 'mean', 'count'], 'menge': 'sum'})
.sort_values(('umsatz', 'sum'), ascending=False)
)
pandas_zeit = time.time() - start
# DuckDB-Methode
start = time.time()
duckdb_result = duckdb.sql("""
SELECT
kategorie,
region,
ROUND(SUM(umsatz), 2) AS gesamt_umsatz,
ROUND(AVG(umsatz), 2) AS durchschnitt_umsatz,
COUNT(*) AS anzahl,
SUM(menge) AS gesamt_menge
FROM df
GROUP BY kategorie, region
ORDER BY gesamt_umsatz DESC
""").df()
duckdb_zeit = time.time() - start
print(f"pandas: {pandas_zeit:.3f} Sekunden")
print(f"DuckDB: {duckdb_zeit:.3f} Sekunden")
print(f"Speedup: {pandas_zeit / duckdb_zeit:.1f}x")
Benutzerdefinierte Funktionen (UDFs)
Manchmal reichen die eingebauten SQL-Funktionen nicht aus — und genau dafür gibt es UDFs (User-Defined Functions). DuckDB lässt Sie Python-Funktionen registrieren und direkt in SQL nutzen. Es gibt zwei Varianten: native UDFs und die deutlich schnelleren Arrow-basierten UDFs.
import duckdb
con = duckdb.connect()
# Native UDF: einfach, aber langsamer
def berechne_rabatt(preis, menge):
if menge >= 100:
return preis * 0.85 # 15% Rabatt
elif menge >= 50:
return preis * 0.90 # 10% Rabatt
elif menge >= 10:
return preis * 0.95 # 5% Rabatt
return preis
con.create_function(
'rabatt_preis',
berechne_rabatt,
[duckdb.typing.DOUBLE, duckdb.typing.INTEGER],
duckdb.typing.DOUBLE
)
# UDF in SQL-Abfrage verwenden
result = con.sql("""
SELECT
produkt,
preis,
menge,
rabatt_preis(preis, menge) AS endpreis
FROM (VALUES
('Laptop', 999.0, 120),
('Maus', 29.99, 5),
('Monitor', 449.0, 55)
) AS t(produkt, preis, menge)
""")
result.show()
Für bessere Performance lohnt sich der Blick auf Arrow-UDFs, die ganze Batches statt einzelner Zeilen verarbeiten:
import duckdb
import pyarrow as pa
con = duckdb.connect()
# Arrow-basierte UDF (batch-weise, deutlich schneller)
def normalize_batch(values):
arr = values.to_pylist()
min_val = min(arr)
max_val = max(arr)
rang = max_val - min_val if max_val != min_val else 1
normalized = [(v - min_val) / rang for v in arr]
return pa.array(normalized)
con.create_function(
'normalisiere',
normalize_batch,
[duckdb.typing.DOUBLE],
duckdb.typing.DOUBLE,
type='arrow'
)
result = con.sql("""
SELECT
wert,
ROUND(normalisiere(wert), 4) AS normalisiert
FROM (VALUES (10.0), (20.0), (30.0), (50.0), (100.0)) AS t(wert)
""")
result.show()
ETL-Pipelines mit DuckDB aufbauen
Eine vollständige ETL-Pipeline
DuckDB ist ein hervorragender Motor für ETL-Pipelines (Extract, Transform, Load). Im Vergleich zu verteilten Systemen wie Apache Spark brauchen Sie hier keine Cloud-Infrastruktur, keine komplizierten Configs — einfach Python und DuckDB. Für Datensätze bis etwa 100 GB reicht ein einzelner Rechner völlig aus (und Sie sparen sich nebenbei eine Menge Geld):
import duckdb
import os
from datetime import datetime
def etl_pipeline(quell_verzeichnis: str, ziel_datei: str):
"""
ETL-Pipeline: Rohdaten aus CSV-Dateien lesen,
transformieren und als optimierte Parquet-Datei speichern.
"""
con = duckdb.connect()
# === EXTRACT ===
# Alle CSV-Dateien aus dem Quellverzeichnis lesen
con.execute(f"""
CREATE TABLE rohdaten AS
SELECT *
FROM read_csv_auto('{quell_verzeichnis}/*.csv',
header=true,
dateformat='%Y-%m-%d')
""")
zeilen_roh = con.execute("SELECT COUNT(*) FROM rohdaten").fetchone()[0]
print(f"Extract: {zeilen_roh:,} Zeilen geladen")
# === TRANSFORM ===
# Datenbereinigung und Transformation
con.execute("""
CREATE TABLE transformiert AS
SELECT
-- Bereinigung: Leerzeichen entfernen, Großschreibung
TRIM(UPPER(kunde_name)) AS kunde_name,
LOWER(TRIM(email)) AS email,
-- Datumstransformationen
bestelldatum,
EXTRACT(YEAR FROM bestelldatum) AS jahr,
EXTRACT(QUARTER FROM bestelldatum) AS quartal,
EXTRACT(MONTH FROM bestelldatum) AS monat,
DAYNAME(bestelldatum) AS wochentag,
-- Berechnete Felder
menge,
einzelpreis,
ROUND(menge * einzelpreis, 2) AS gesamtpreis,
ROUND(menge * einzelpreis * 0.19, 2) AS mwst_betrag,
-- Kategorisierung
CASE
WHEN menge * einzelpreis >= 1000 THEN 'Großbestellung'
WHEN menge * einzelpreis >= 100 THEN 'Mittel'
ELSE 'Klein'
END AS bestellkategorie,
-- Duplikate und NULL-Werte behandeln
COALESCE(region, 'Unbekannt') AS region
FROM rohdaten
WHERE bestelldatum IS NOT NULL
AND menge > 0
AND einzelpreis > 0
""")
zeilen_transform = con.execute(
"SELECT COUNT(*) FROM transformiert"
).fetchone()[0]
entfernt = zeilen_roh - zeilen_transform
print(f"Transform: {zeilen_transform:,} gültige Zeilen "
f"({entfernt:,} ungültige entfernt)")
# === LOAD ===
# Als optimierte Parquet-Datei speichern
con.execute(f"""
COPY transformiert
TO '{ziel_datei}'
(FORMAT PARQUET, COMPRESSION ZSTD, ROW_GROUP_SIZE 100000)
""")
dateigroesse = os.path.getsize(ziel_datei) / (1024 * 1024)
print(f"Load: Gespeichert als {ziel_datei} ({dateigroesse:.1f} MB)")
con.close()
# Pipeline ausführen
etl_pipeline('rohdaten/', 'warehouse/verkauf_bereinigt.parquet')
Inkrementelle Verarbeitung
Bei Pipelines, die regelmäßig laufen, wollen Sie natürlich nicht jedes Mal den gesamten Datenbestand neu verarbeiten. Hier kommt inkrementelle Verarbeitung ins Spiel:
import duckdb
def inkrementelles_update(db_pfad: str, neue_daten_pfad: str):
"""Nur neue Daten verarbeiten und zur bestehenden Datenbank hinzufügen."""
con = duckdb.connect(db_pfad)
# Letzten verarbeiteten Zeitstempel ermitteln
result = con.execute("""
SELECT COALESCE(MAX(bestelldatum), DATE '1900-01-01')
FROM verkaufsdaten
""").fetchone()
letztes_datum = result[0]
# Nur neue Daten laden
neue_zeilen = con.execute(f"""
INSERT INTO verkaufsdaten
SELECT *
FROM read_parquet('{neue_daten_pfad}')
WHERE bestelldatum > '{letztes_datum}'
""").fetchone()
print(f"Inkrementell: Neue Daten ab {letztes_datum} eingefügt")
con.close()
inkrementelles_update('warehouse.duckdb', 'neue_bestellungen.parquet')
Fortgeschrittene Tipps und Best Practices
1. Parquet statt CSV — immer
Wenn Sie die Wahl haben, nehmen Sie Parquet. Immer. DuckDB liest Parquet-Dateien dramatisch schneller als CSVs. Das spaltenorientierte Format erlaubt Predicate Pushdown, und DuckDB muss nur die Spalten lesen, die Ihre Abfrage tatsächlich braucht.
# CSV zu Parquet konvertieren — ein Einzeiler
import duckdb
duckdb.sql("""
COPY (SELECT * FROM 'grosse_datei.csv')
TO 'grosse_datei.parquet' (FORMAT PARQUET, COMPRESSION ZSTD)
""")
2. Extensions für erweiterte Funktionalität
DuckDB hat ein umfangreiches Extension-System, das die Grundfunktionalität erheblich erweitert:
import duckdb
con = duckdb.connect()
# httpfs: Zugriff auf Remote-Dateien (S3, HTTP)
con.execute("INSTALL httpfs; LOAD httpfs;")
# spatial: Geospatiale Abfragen
con.execute("INSTALL spatial; LOAD spatial;")
# iceberg: Apache-Iceberg-Tabellen lesen und schreiben
con.execute("INSTALL iceberg; LOAD iceberg;")
# Verfügbare Extensions anzeigen
result = con.execute("""
SELECT extension_name, installed, loaded, description
FROM duckdb_extensions()
WHERE installed = true
""").fetchdf()
print(result)
3. Performance richtig konfigurieren
import duckdb
con = duckdb.connect()
# Anzahl der Threads einstellen (Standard: alle Kerne)
con.execute("SET threads TO 8;")
# Speicherlimit setzen (verhindert übermäßigen RAM-Verbrauch)
con.execute("SET memory_limit = '8GB';")
# Temporäres Verzeichnis für Out-of-Core-Verarbeitung
con.execute("SET temp_directory = '/tmp/duckdb_temp';")
# Fortschrittsanzeige für lange Abfragen aktivieren
con.execute("SET enable_progress_bar = true;")
# Aktuelle Konfiguration anzeigen
result = con.execute("""
SELECT name, value, description
FROM duckdb_settings()
WHERE name IN ('threads', 'memory_limit', 'temp_directory')
""").fetchdf()
print(result)
4. DuckDB in Jupyter Notebooks
DuckDB und Jupyter vertragen sich bestens. Mit der Magic-Extension schreiben Sie SQL direkt in Notebook-Zellen:
# In der ersten Zelle installieren
# pip install duckdb jupysql
import duckdb
%load_ext sql
%sql duckdb:///:memory:
# Ab jetzt können Sie SQL-Magic verwenden:
# %%sql
# SELECT * FROM 'meine_daten.parquet' LIMIT 10
5. Gemeinsam genutzte In-Memory-Datenbanken
Brauchen Sie mehrere Verbindungen zur selben In-Memory-Datenbank (zum Beispiel in verschiedenen Threads)? Benannte In-Memory-Datenbanken machen das möglich:
import duckdb
# Beide Verbindungen teilen sich dieselbe Datenbank
con1 = duckdb.connect(':memory:shared_db')
con2 = duckdb.connect(':memory:shared_db')
# Tabelle über con1 erstellen
con1.execute("CREATE TABLE test (id INTEGER, wert VARCHAR)")
con1.execute("INSERT INTO test VALUES (1, 'Hallo'), (2, 'Welt')")
# Über con2 abfragen — sieht dieselben Daten
result = con2.execute("SELECT * FROM test").fetchdf()
print(result)
Wann Sie DuckDB verwenden sollten — und wann nicht
Ideale Einsatzszenarien
- Analytische Abfragen auf großen Datensätzen: GROUP BY, JOINs, Window Functions auf Millionen bis Milliarden von Zeilen — hier fühlt sich DuckDB zuhause.
- Lokale ETL-Pipelines: Daten aus CSV/Parquet/JSON lesen, transformieren und speichern — ganz ohne Cloud.
- Explorative Datenanalyse: Schnelles Abfragen großer Parquet-Dateien in Jupyter Notebooks.
- Prototyping: SQL-basierte Analysen entwickeln, bevor sie ins produktive Data Warehouse wandern.
- Eingebettete Analytik: Analytische Features direkt in Python-Anwendungen integrieren.
- Hybride Workflows: pandas für Datenmanipulation, DuckDB für schwere Aggregationen — das Beste aus beiden Welten.
Weniger geeignete Szenarien
- Transaktionale Workloads (OLTP): Für häufige einzelne INSERT/UPDATE/DELETE-Operationen bleiben PostgreSQL oder SQLite die bessere Wahl.
- Hochkonkurrente Systeme: DuckDB ist für wenige gleichzeitige Verbindungen optimiert — nicht für Hunderte.
- Datensätze über 100 GB: Ab hier lohnt sich der Blick auf verteilte Systeme wie Spark, Snowflake oder BigQuery.
- Produktive Webanwendungen: DuckDB ersetzt keine Backend-Datenbank mit vielen gleichzeitigen Nutzern.
DuckDB 1.4 LTS: Die wichtigsten Neuerungen
Die im September 2025 veröffentlichte Version 1.4.0 ist die erste Long-Term-Support-Version — ein echtes Zeichen der Reife. Hier die Highlights:
- AES-256-Verschlüsselung: Datenbanken lassen sich jetzt verschlüsseln — endlich eine Lösung für sensible Daten.
- MERGE-Statements: Vollständige Unterstützung für MERGE-Anweisungen (Upsert-Logik).
- DuckLake: Ein neues ACID-konformes Lakehouse-Format mit Zeitreise-Abfragen (Time Travel).
- Iceberg-Schreibunterstützung: Apache Iceberg-Tabellen können jetzt gelesen und beschrieben werden.
- Verbesserter Sortierer: Der k-Way-Merge-Sort-Algorithmus bringt spürbar mehr Performance bei Window Functions.
- Materialisierte CTEs: Common Table Expressions werden standardmäßig materialisiert.
- Spark-Kompatibilitätsschicht: Eine API-Kompatibilitätsschicht für bestehenden Spark-Code — praktisch für die Migration.
- Über 3.500 Commits von mehr als 90 Mitwirkenden seit Version 1.3.
Fazit und nächste Schritte
DuckDB hat sich als unverzichtbares Werkzeug im Python-Data-Stack etabliert, und das vollkommen zurecht. Es schließt die Lücke zwischen der Einfachheit von pandas und der Leistungsfähigkeit großer analytischer Datenbanken — ohne Server, ohne Cloud, ohne Kopfschmerzen. Die Integration mit pandas, Polars und Apache Arrow macht es zum idealen Begleiter für hybride Workflows.
Mein Tipp für den Einstieg:
- Installieren Sie DuckDB und spielen Sie mit den Codebeispielen aus diesem Artikel herum
- Konvertieren Sie eine große CSV-Datei in Parquet und staunen Sie über den Geschwindigkeitsunterschied
- Ersetzen Sie eine langsame pandas-Aggregation in Ihrem Projekt durch eine DuckDB-SQL-Abfrage
- Bauen Sie eine einfache ETL-Pipeline, die Rohdaten bereinigt und als Parquet speichert
- Erkunden Sie die Extensions — insbesondere httpfs für Remote-Dateien und spatial für Geodaten
Die Python-Datenwelt entwickelt sich rasant weiter, und DuckDB steht dabei ganz vorne mit. Ob Datenwissenschaftler, Dateningenieur oder Python-Entwickler — DuckDB gehört in Ihren Werkzeugkasten. Probieren Sie es aus, Sie werden nicht enttäuscht sein.