Zum Hauptinhalt springen
S-EDV news
← Alle Anleitungen
📘 Anleitung Datenbanken 22.10.2026 · 13 min Lesezeit

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

Titelbild: DuckDB 2.0 LTS, Analysen ohne Server mit CSV, Parquet und PostgreSQL

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/time meldete 120376 KB). Programm, Python-Umgebung, Daten und Sicherungen belegten zusammen rund 390 MB (du -sh: 88M Daten mit Sicherung, 190M für ~/.duckdb mit 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.
EckdatenWert
VersionDuckDB 2.0.0 LTS vom 21.10.2026, MIT-Lizenz
Containerduckdb/duckdb:2.0.0 (amd64, arm64)
SpeicherformatVersion 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

SymptomUrsache 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.0Python 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

DuckDBDatenanalyseParquetPostgreSQLPython