DuckDB 2.0 LTS: CSV, Parquet und PostgreSQL ohne Datenbankserver analysieren
DuckDB 2.0.0 LTS wertet CSV- und Parquet-Dateien direkt mit SQL aus und liest PostgreSQL-Tabellen ohne Export mit. So installieren Sie CLI und Python-Paket, sichern die Analysedatenbank, stellen sie wieder her und aktualisieren von 1.5 auf 2.0.
Geprüft am 22.10.2026 · für DuckDB 2.0.0
Mit KI erstellt – redaktionelle Prüfung ausstehend
WerbelinksMit * markierte Links sind Werbelinks: Bei einem Kauf erhalten wir eine Provision, der Preis bleibt gleich. Als Amazon-Partner verdiene ich an qualifizierten Verkäufen. Mehr dazu

Für Monatsauswertungen aus CSV-Exporten oder einen Abgleich mit der Kundentabelle im PostgreSQL der Warenwirtschaft brauchen Sie keinen Datenbankserver. DuckDB läuft als einzelnes Programm oder Python-Paket, liest CSV und Parquet direkt per SQL und bindet PostgreSQL ohne Export ein. Version 2.0.0 erschien am 21. Oktober 2026 als LTS-Version mit Unterstützung bis mindestens Oktober 2027. Die Grenzen: Eine Datei hat immer nur einen schreibenden Prozess, und Dateien im neuen 2.0-Format öffnet DuckDB 1.x nicht.
Voraussetzungen
- Linux mit x86-64- oder ARM64-CPU (CLI-Pakete gibt es auch für macOS und Windows). Getestet unter Debian 12 auf einem Intel Core i7-12700T mit 14 zugewiesenen Kernen.
- 2 CPU-Kerne und 4 GB RAM reichen für die Beispiele: Im Test liefen die Abfragen auch in einem Container mit genau diesen Grenzen, die Auswertung über eine Million Zeilen belegte höchstens rund 120 MB (
/usr/bin/timemeldete 120376 KB). Programm, Python-Umgebung, Daten und Sicherungen belegten zusammen rund 390 MB (du -sh: 88M Daten mit Sicherung, 190M für~/.duckdbmit zwei CLI-Versionen und Erweiterungen, 92M Python-Umgebung). - Python 3.11 oder neuer für das Python-Paket: Debian 12 bringt 3.11 mit, Ubuntu 24.04 3.12. Unter Ubuntu 22.04 (Python 3.10) installiert pip nur DuckDB 1.x.
- Docker Engine mit Docker Compose Plugin für das PostgreSQL-Beispiel und den Container-Weg, siehe Docker und Docker Compose auf Linux installieren.
| Eckdaten | Wert |
|---|---|
| Version | DuckDB 2.0.0 LTS vom 21.10.2026, MIT-Lizenz |
| Container | duckdb/duckdb:2.0.0 (amd64, arm64) |
| Speicherformat | Version 69 für neue Dateien |
Schritt 1: DuckDB-CLI installieren
Das offizielle Installationsskript legt jede Version in einem eigenen Ordner unter ~/.duckdb/cli/ ab und setzt den Verweis latest auf die neueste stabile Version. Ältere Versionen bleiben so parallel nutzbar. Root-Rechte braucht das Skript nicht.
sudo apt install -y curl ca-certificates
curl https://install.duckdb.org | bash
echo 'export PATH="$HOME/.duckdb/cli/latest:$PATH"' >> ~/.bashrc
source ~/.bashrc
duckdb -version
Ohne Skript aus dem Netz laden Sie das Archiv vom GitHub-Release und prüfen die SHA-256-Summe von der Installationsseite des Projekts:
curl -sSLO https://github.com/duckdb/duckdb/releases/download/v2.0.0/duckdb-cli-linux-amd64.tar.gz
echo 'c6507206baf7793c6013dbd82368469a45f20dc0e17a6f92f204602f652ed2bc duckdb-cli-linux-amd64.tar.gz' | sha256sum -c
sudo tar -xzf duckdb-cli-linux-amd64.tar.gz -C /usr/local/bin duckdb
Verifizieren: duckdb -version meldet v2.0.0 (Cyanoptera) d54cbbe3df, sha256sum -c antwortet mit OK.
Schritt 2: Projektordner und Beispieldaten anlegen
Alle Beispiele arbeiten in ~/duckdb-labor, die Analysedateien liegen in daten/. DuckDB erzeugt selbst eine Million Bestellungen als CSV:
mkdir -p ~/duckdb-labor/daten ~/duckdb-labor/pg-init
cd ~/duckdb-labor/daten
cat > beispieldaten.sql <<'SQL'
SELECT setseed(0.42);
COPY (
SELECT i AS bestell_id,
DATE '2026-01-01' + CAST(floor(random() * 273) AS INTEGER) AS datum,
'K' || lpad(CAST(1 + floor(random() * 500) AS INTEGER)::VARCHAR, 4, '0') AS kunden_nr,
['Dresden', 'Leipzig', 'Berlin', 'Hamburg', 'München', 'Köln'][1 + CAST(floor(random() * 6) AS INTEGER)] AS filiale,
['Notebook', 'Monitor', 'Dockingstation', 'Headset', 'Tastatur', 'Maus'][1 + CAST(floor(random() * 6) AS INTEGER)] AS artikel,
1 + CAST(floor(random() * 5) AS INTEGER) AS menge,
round(9.90 + random() * 990, 2) AS betrag
FROM range(1, 1000001) AS t(i)
) TO 'bestellungen.csv' (HEADER);
SQL
duckdb -c '.read beispieldaten.sql'
ls -lh bestellungen.csv; head -3 bestellungen.csv
-rw-r--r-- 1 root root 48M Oct 22 17:32 bestellungen.csv
bestell_id,datum,kunden_nr,filiale,artikel,menge,betrag
1,2026-07-23,K0221,Leipzig,Tastatur,3,406.5
2,2026-07-03,K0165,Leipzig,Notebook,4,468.21
Verifizieren: wc -l bestellungen.csv zählt 1000001 Zeilen (Kopfzeile plus eine Million Datensätze).
Schritt 3: CSV abfragen und in Parquet umwandeln
DuckDB liest die CSV-Datei ohne Tabelle und erkennt Spalten und Datentypen selbst (DESCRIBE FROM 'bestellungen.csv' zeigt sie). Für wiederkehrende Auswertungen wandeln Sie in Parquet um: Das Format speichert spaltenweise und komprimiert.
duckdb -c "SELECT filiale, count(*) AS bestellungen, round(sum(menge * betrag)) AS umsatz FROM 'bestellungen.csv' GROUP BY filiale ORDER BY umsatz DESC"
duckdb -c "COPY (FROM 'bestellungen.csv') TO 'bestellungen.parquet' (FORMAT parquet, COMPRESSION zstd)"
ls -lh bestellungen.csv bestellungen.parquet
time duckdb -c "SELECT filiale, count(*) AS bestellungen, round(sum(menge * betrag)) AS umsatz FROM 'bestellungen.parquet' GROUP BY filiale ORDER BY umsatz DESC"
-rw-r--r-- 1 root root 48M Oct 22 17:32 bestellungen.csv
-rw-r--r-- 1 root root 7.5M Oct 22 17:32 bestellungen.parquet
┌─────────┬──────────────┬─────────────┐
│ filiale │ bestellungen │ umsatz │
│ varchar │ int64 │ double │
├─────────┼──────────────┼─────────────┤
│ München │ 166921 │ 253497883.0 │
│ Berlin │ 166655 │ 252892130.0 │
│ Dresden │ 166645 │ 252538096.0 │
│ Köln │ 166721 │ 252452915.0 │
│ Hamburg │ 166893 │ 252380564.0 │
│ Leipzig │ 166165 │ 251712764.0 │
└─────────┴──────────────┴─────────────┘
real 0m0.048s
user 0m0.073s
sys 0m0.037s
Verifizieren: Beide Abfragen liefern dieselben sechs Filialen mit denselben Summen. Im Test brauchte die Auswertung samt Programmstart auf CSV 0,12 s, auf Parquet 0,05 s.
Schritt 4: PostgreSQL ohne Export mitlesen
Die Erweiterung postgres bindet eine PostgreSQL-Datenbank wie ein weiteres Schema ein, DuckDB lädt sie beim ersten Gebrauch selbst nach. Als Gegenstück startet diese ~/duckdb-labor/compose.yaml ein PostgreSQL mit 500 Beispielkunden. Der Dienst duckdb ist die CLI im Container, ui gehört zu Schritt 8.
services:
postgres:
image: postgres:18.6-bookworm
environment:
POSTGRES_DB: shop
POSTGRES_USER: shop
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD fehlt in .env}
AUSWERTUNG_PASSWORD: ${AUSWERTUNG_PASSWORD:?AUSWERTUNG_PASSWORD fehlt in .env}
ports:
- "127.0.0.1:5432:5432"
volumes:
- ./pg-daten:/var/lib/postgresql
- ./pg-init:/docker-entrypoint-initdb.d:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U shop -d shop"]
interval: 10s
timeout: 5s
retries: 6
restart: unless-stopped
duckdb:
image: duckdb/duckdb:2.0.0
profiles: ["werkzeug"]
working_dir: /workspace
volumes:
- ./daten:/workspace
- duckdb-erweiterungen:/root/.duckdb
ui:
image: duckdb/duckdb:2.0.0
profiles: ["ui"]
network_mode: host
stdin_open: true
working_dir: /workspace
volumes:
- ./daten:/workspace
- duckdb-erweiterungen:/root/.duckdb
command: ["duckdb", "ui.duckdb", "-cmd", "CALL start_ui_server();"]
volumes:
duckdb-erweiterungen:
Das Skript ~/duckdb-labor/pg-init/01-shop.sh läuft beim ersten Start auf leerem Datenordner und legt die Rolle auswertung an, die nur lesen darf:
#!/bin/bash
# 500 Beispielkunden und die Leserolle "auswertung" (nur SELECT)
set -e
psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v pw="$AUSWERTUNG_PASSWORD" <<'SQL'
CREATE TABLE kunden (kunden_nr text PRIMARY KEY, name text NOT NULL, region text NOT NULL);
INSERT INTO kunden
SELECT 'K' || lpad(i::text, 4, '0'), 'Kunde ' || i,
(ARRAY['Sachsen','Bayern','Berlin','Hamburg','Nordrhein-Westfalen'])[1 + i % 5]
FROM generate_series(1, 500) AS i;
CREATE ROLE auswertung LOGIN PASSWORD :'pw';
GRANT SELECT ON ALL TABLES IN SCHEMA public TO auswertung;
SQL
Passwörter erzeugen, PostgreSQL starten und das Passwort der Leserolle in ~/.pgpass ablegen. So steht es weder im SQL noch in der Shell-History:
cd ~/duckdb-labor
printf 'POSTGRES_PASSWORD=%s\nAUSWERTUNG_PASSWORD=%s\n' "$(openssl rand -hex 16)" "$(openssl rand -hex 16)" > .env
chmod 600 .env
docker compose up -d --wait --pull always postgres
echo "127.0.0.1:5432:shop:auswertung:$(grep ^AUSWERTUNG_PASSWORD ~/duckdb-labor/.env | cut -d= -f2)" > ~/.pgpass
chmod 600 ~/.pgpass
Die Abfrage verbindet die Parquet-Datei mit der Kundentabelle. READ_ONLY verhindert zusätzlich zur Rolle jeden Schreibzugriff:
cd ~/duckdb-labor/daten
duckdb <<'SQL'
ATTACH 'host=127.0.0.1 dbname=shop user=auswertung' AS pg (TYPE postgres, READ_ONLY);
SELECT k.region, count(*) AS bestellungen, round(sum(b.menge * b.betrag)) AS umsatz
FROM 'bestellungen.parquet' AS b
JOIN pg.public.kunden AS k USING (kunden_nr)
GROUP BY k.region
ORDER BY umsatz DESC;
SQL
┌─────────────────────┬──────────────┬─────────────┐
│ region │ bestellungen │ umsatz │
│ varchar │ int64 │ double │
├─────────────────────┼──────────────┼─────────────┤
│ Sachsen │ 199642 │ 303605115.0 │
│ Berlin │ 200168 │ 303574955.0 │
│ Hamburg │ 200279 │ 303094703.0 │
│ Nordrhein-Westfalen │ 199867 │ 302858578.0 │
│ Bayern │ 200044 │ 302341001.0 │
└─────────────────────┴──────────────┴─────────────┘
Verifizieren: docker compose ps zeigt PostgreSQL als healthy, die Abfrage liefert fünf Regionen. Ohne lokale Installation läuft dieselbe CLI im Container: docker compose run --rm -T duckdb duckdb -version meldet v2.0.0 (Cyanoptera) d54cbbe3df. Healthchecks und weitere Härtung erklärt Docker Compose absichern.
Schritt 5: Auswertungen mit Python automatisieren
Für Skripte und Cronjobs kommt das Python-Paket in eine eigene virtuelle Umgebung:
sudo apt install -y python3-venv
python3 -m venv ~/duckdb-venv
~/duckdb-venv/bin/pip install duckdb==2.0.0
Das Skript ~/duckdb-labor/daten/auswertung.py schreibt Monatssummen in die Datenbankdatei analyse.duckdb und als CSV für die Tabellenkalkulation:
import duckdb
print("DuckDB", duckdb.__version__)
con = duckdb.connect("analyse.duckdb")
con.execute("""
CREATE OR REPLACE TABLE umsatz_monat AS
SELECT date_trunc('month', datum) AS monat, filiale,
round(sum(menge * betrag), 2) AS umsatz
FROM 'bestellungen.parquet'
GROUP BY ALL
""")
monate = con.sql("""
SELECT strftime(monat, '%Y-%m') AS monat, round(sum(umsatz)) AS umsatz
FROM umsatz_monat GROUP BY ALL ORDER BY monat
""")
monate.show()
monate.write_csv("umsatz_monat.csv")
con.close()
cd ~/duckdb-labor/daten && ~/duckdb-venv/bin/python auswertung.py
DuckDB 2.0.0
┌─────────┬─────────────┐
│ monat │ umsatz │
│ varchar │ double │
├─────────┼─────────────┤
│ 2026-01 │ 171745042.0 │
│ 2026-02 │ 155870591.0 │
│ 2026-03 │ 171758429.0 │
│ 2026-04 │ 167168586.0 │
│ 2026-05 │ 171771942.0 │
│ 2026-06 │ 166737602.0 │
│ 2026-07 │ 172132857.0 │
│ 2026-08 │ 171939772.0 │
│ 2026-09 │ 166349531.0 │
└─────────┴─────────────┘
Verifizieren: Die erste Zeile meldet DuckDB 2.0.0, umsatz_monat.csv enthält neun Monate. Ältere Skripte prüfen Sie gegen die Python-Migrationsseite: In 2.0 fehlt zum Beispiel fetch_arrow_table() an execute()-Ergebnissen.
Schritt 6: Analysedatenbank sichern und wiederherstellen
Für den Test bekommt analyse.duckdb eine Kopie der Kunden, die Bestellungen und eine Prüfmarke:
cd ~/duckdb-labor/daten
duckdb analyse.duckdb <<'SQL'
ATTACH 'host=127.0.0.1 dbname=shop user=auswertung' AS pg (TYPE postgres, READ_ONLY);
CREATE TABLE kunden AS FROM pg.public.kunden;
CREATE TABLE bestellungen AS FROM 'bestellungen.parquet';
CREATE TABLE pruefmarke AS SELECT 'restore-test ' || strftime(now(), '%Y-%m-%d %H:%M:%S') AS marke;
DETACH pg;
SQL
mkdir -p backup
duckdb analyse.duckdb -c "EXPORT DATABASE 'backup/$(date +%F)' (FORMAT parquet)"
ls -l backup/*/
total 12004
-rw-r--r-- 1 root root 12264729 Oct 22 17:33 bestellungen.parquet
-rw-r--r-- 1 root root 5096 Oct 22 17:33 kunden.parquet
-rw-r--r-- 1 root root 320 Oct 22 17:33 load.sql
-rw-r--r-- 1 root root 347 Oct 22 17:33 pruefmarke.parquet
-rw-r--r-- 1 root root 329 Oct 22 17:33 schema.sql
-rw-r--r-- 1 root root 1235 Oct 22 17:33 umsatz_monat.parquet
EXPORT DATABASE schreibt Schema (schema.sql), Ladebefehle (load.sql) und je Tabelle eine Parquet-Datei. Trigger, neu in 2.0, enthält der Export laut Dokumentation nicht. Alternativ kopieren Sie die Datei nach duckdb analyse.duckdb -c 'CHECKPOINT', solange kein anderer Prozess sie geöffnet hat.
Zur Probe des Ernstfalls löschen Sie die Datei und importieren in eine neue, leere Datei. rm löscht ohne Rückfrage, also nur mit vorhandener Sicherung:
duckdb analyse.duckdb -csv -noheader -c 'FROM pruefmarke'
rm analyse.duckdb
duckdb analyse.duckdb -c "IMPORT DATABASE 'backup/$(date +%F)'"
duckdb analyse.duckdb -csv -noheader -c 'FROM pruefmarke'
Verifizieren: Die Prüfmarke ist vor und nach dem Import identisch, im Test restore-test 2026-10-22 17:33:23. bestellungen hat wieder 1000000, kunden 500 Zeilen.
Schritt 7: Von DuckDB 1.x auf 2.0 aktualisieren
Das Installationsskript ist zugleich der Update-Weg: curl https://install.duckdb.org | bash legt 2.0.0 neben eine vorhandene 1.5.6, latest zeigt danach auf 2.0.0. Im Container tauschen Sie den Tag duckdb/duckdb:1.5.6 gegen duckdb/duckdb:2.0.0 und laden mit docker compose --profile werkzeug --profile ui pull neu. Im Test liefen beide Wege von 1.5.6 auf 2.0.0.
Entscheidend ist das Speicherformat. Neue Dateien legt 2.0 im Format 69 an, das Versionen vor 2.0 nicht lesen. Vorhandene Dateien behalten ihr Format: Im Test schrieb 2.0.0 in eine mit 1.5.6 angelegte Datei, und 1.5.6 las sie danach weiter. Teilen sich Prozesse unterschiedlicher Versionen Dateien, schreiben Sie diese im alten Format, oder Sie ziehen alte Dateien per Export und Import ins neue Format um:
duckdb kompatibel.duckdb -storage-version v1.5.0 -c 'CREATE TABLE t AS SELECT 42 AS x'
~/.duckdb/cli/1.5.6/duckdb bestand.duckdb -c "EXPORT DATABASE 'umzug'"
duckdb bestand-v2.duckdb -c "IMPORT DATABASE 'umzug'"
duckdb bestand-v2.duckdb -c "SELECT database_name, tags FROM duckdb_databases()"
In Abfragen fallen zwei Änderungen am ehesten auf: Die Lambda-Schreibweise x -> x + 1 ist abgeschaltet (neu lambda x: x + 1), und Ganzzahldivision durch null mit // oder % bricht ab, statt NULL zu liefern; TRY(7 // 0) ergibt weiterhin NULL. Für geschäftskritische Systeme empfiehlt das Projekt einen Parallelbetrieb und das erste Patch-Release, angekündigt für November.
Verifizieren: ls ~/.duckdb/cli/ zeigt 1.5.6, 2.0.0 und latest. Die letzte Abfrage meldet für bestand-v2 {storage_version=v2.0.0+}, dieselbe Abfrage auf kompatibel.duckdb {storage_version=v1.5.0+}.
Schritt 8: Weboberfläche für Ad-hoc-Abfragen
Die Erweiterung ui bringt eine Notebook-Oberfläche im Browser. Abfragen laufen laut Dokumentation lokal, die Programmdateien der Oberfläche lädt der eingebaute Webserver von ui.duckdb.org.
cd ~/duckdb-labor
docker compose run --rm -T duckdb duckdb ui.duckdb -c "CREATE TABLE bestellungen AS FROM 'bestellungen.parquet'"
docker compose up -d --pull always ui
Das Image läuft als root, Dateien wie ui.duckdb, die der Container in daten/ anlegt, gehören deshalb root.
Der Server lauscht nur auf localhost:4213. Vom Arbeitsplatz erreichen Sie ihn per SSH-Tunnel (ssh -L 4213:localhost:4213 benutzer@server.example.com, dann http://localhost:4213). Geben Sie den Port nicht frei: Die Oberfläche führt SQL mit den Rechten des DuckDB-Prozesses aus. Hinter einem Reverse Proxy mit eigener Domain startet sie nicht: Im Test beantwortete der Server Abfragen mit dem Origin-Header einer fremden Domain mit HTTP 401, mit Origin: http://localhost:4213 mit 200.
Verifizieren: curl -s -o /dev/null -w '%{http_code}' http://localhost:4213/ meldet 200. Solange die Oberfläche läuft, sperrt sie ui.duckdb für andere Prozesse; docker compose stop ui beendet sie.
Troubleshooting
| Symptom | Ursache und Lösung |
|---|---|
IO Error: Trying to read a database file with version number 69, but we can only read versions between 64 and 68. | DuckDB vor 2.0 öffnet eine 2.0-Datei. Prozess aktualisieren oder Datei mit -storage-version v1.5.0 schreiben. |
IO Error: Could not set lock on file "…/daten/analyse.duckdb": Conflicting lock is held in … | Ein anderer Prozess hat die Datei schreibend geöffnet. Diesen Prozess beenden. |
Invalid Input Error: Cannot execute statement of type "INSERT" on database "pg" which is attached in read-only mode! | Schreibversuch über die mit READ_ONLY eingebundene PostgreSQL-Datenbank. Gewollt. |
IO Error: Unable to connect to Postgres … FATAL: password authentication failed for user "auswertung" | Falsches Passwort oder unpassende Zeile in ~/.pgpass: Host, Port, Datenbank und Benutzer müssen exakt zur ATTACH-Zeile passen. |
Catalog Error: Table with name "bestellungen" already exists! | IMPORT DATABASE in eine Datei mit vorhandenen Tabellen. Immer in eine leere Datei importieren. |
Binder Error: Deprecated lambda arrow (->) detected. | Alte Lambda-Schreibweise. Auf lambda x: … umstellen. |
Invalid Input Error: Division by zero in expression (7 // 0). | Ganzzahldivision oder Modulo durch null. Ausdruck in TRY(…) setzen. |
ERROR: No matching distribution found for duckdb==2.0.0 | Python 3.10 oder älter. Ohne Versionsangabe installiert pip dort 1.5.6. |
Häufige Fragen
Wie lange wird DuckDB 2.0 unterstützt?
Laut Ankündigung bis mindestens Oktober 2027. Die bisherige LTS-Version 1.4 endet am 17. November 2026, 1.5 wird bis März 2027 unterstützt.
Können mehrere Programme dieselbe Datei nutzen?
Schreiben darf immer nur ein Prozess. Im Test scheiterte ein zweiter Prozess an der Dateisperre; auch duckdb -readonly bekam keinen Zugriff, solange der erste Prozess lief. Für gemeinsame Auswertungen eignen sich Parquet-Dateien. Für Client-Server-Betrieb gibt es das Quack-Protokoll, in 2.0 überarbeitet.
Warum zeigt die CLI plötzlich Markdown-Tabellen?
Das ist der Agentenmodus von 2.0. Er greift, wenn die Umgebungsvariable eines KI-Agenten gesetzt ist, die Ausgabe nicht im Terminal landet und kein Ausgabeformat gewählt ist. -no-agent oder DUCKDB_AGENT_MODE=0 schalten ihn ab.
Fazit
DuckDB 2.0.0 erledigt Auswertungen, für die sonst eine Datenbank aufgesetzt und gepflegt wird: CSV und Parquet direkt abfragen, PostgreSQL schreibgeschützt einbinden, Ergebnisse per Python weitergeben. Vor dem Update klären Sie, welche Programme dieselben Dateien öffnen, und schreiben gemeinsame Dateien im alten Format, solange noch 1.x im Einsatz ist.
Weiterführende Anleitungen und Quellen
- Docker und Docker Compose auf Linux installieren
- Docker Compose absichern
- PostgreSQL nativ installieren und absichern
- PostgreSQL pg_dump und pg_restore
- GitHub: Release DuckDB v2.0.0
- DuckDB: Ankündigung 2.0.0 LTS
- DuckDB: Migration auf 2.0
- DuckDB: Speicherversionen
- DuckDB: Python-Code auf 2.0 umstellen
- DuckDB: PostgreSQL-Erweiterung
- DuckDB: Installation und Prüfsummen
- Docker Hub: duckdb/duckdb


