DuckDB für Python: Der komplette Leitfaden zur analytischen In-Process-Datenbank

DuckDB wird oft als „SQLite der Analytik" bezeichnet — und das aus gutem Grund. Dieser Leitfaden zeigt, wie Sie DuckDB in Python für Datenanalyse, pandas-Integration, Parquet-Abfragen und ETL-Pipelines einsetzen. Inklusive Benchmarks und Best Practices.

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:

  1. Installieren Sie DuckDB und spielen Sie mit den Codebeispielen aus diesem Artikel herum
  2. Konvertieren Sie eine große CSV-Datei in Parquet und staunen Sie über den Geschwindigkeitsunterschied
  3. Ersetzen Sie eine langsame pandas-Aggregation in Ihrem Projekt durch eine DuckDB-SQL-Abfrage
  4. Bauen Sie eine einfache ETL-Pipeline, die Rohdaten bereinigt und als Parquet speichert
  5. 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.

Über den Autor Editorial Team

Our team of expert writers and editors.