DuckDB med Python: SQL på pandas DataFrames og Parquet-filer (2026 guide)
DuckDB kører SQL direkte på pandas DataFrames og Parquet-filer, ofte 5-20× hurtigere end pandas alene. Guide til installation, produktionsmønstre og hvorfor det slår SQLite til analytics.
DuckDB er en indlejret analytisk SQL-database, der kører i din Python-proces og kan forespørge pandas DataFrames og Parquet-filer direkte med almindelig SQL, ofte 5-20 gange hurtigere end pandas på aggregeringer og joins, og uden at data behøver at passe i RAM. I 2026 er DuckDB blevet standardværktøjet for Python-udviklere, der vil have SQLite's enkelhed kombineret med column-store-performance, og som er trætte af at flytte data mellem processer bare for at køre en GROUP BY. Denne guide viser installation, konkrete forespørgsler mod DataFrames og Parquet, håndtering af datasæt større end hukommelsen, samt hvordan jeg selv integrerer DuckDB i FastAPI-endpoints i produktion.
DuckDB 1.4 (august 2026) er en in-process kolonneorienteret OLAP-database (tænk "SQLite for analytics") med nul netværksoverhead og zero-copy integration mod pandas, Polars og PyArrow.
Du kan forespørge en Parquet-fil på 50 GB uden at læse den ind i hukommelsen. SELECT * FROM 'data.parquet' WHERE ... pushdown'er filtre og læser kun de nødvendige kolonner.
DuckDB slår typisk pandas 5-20× på joins og aggregeringer takket være vektoriseret eksekvering, multi-threading og column pruning.
Installation er én pip install duckdb. Ingen server, ingen netværk, ingen konfiguration.
DuckDB kan skrive resultater tilbage som pandas DataFrame, Arrow Table eller Polars DataFrame uden kopiering.
Den passer perfekt bag FastAPI-endpoints som read-only analytics-lag over Parquet-filer i S3 eller lokal disk.
Hvad er DuckDB og hvorfor er det relevant i 2026?
DuckDB er en open source, in-process kolonneorienteret SQL-database designet til analytiske arbejdsbelastninger. "In-process" betyder, at den kører inde i din Python-fortolker. Der er ingen server, ingen socket, ingen daemon at holde i live. Det er den samme model som SQLite, bare med et helt andet formål: hvor SQLite er optimeret til mange små transaktioner (OLTP), er DuckDB bygget til store aggregeringer, joins og window functions (OLAP).
Efter DuckDB 1.0 blev frigivet i juni 2024, har projektet i 2026 nået version 1.4 med stabile lagerformater, incremental checkpointing og en moden Python-API. DuckDB's officielle Python-dokumentation beskriver, hvordan det integrerer med hele PyData-økosystemet via Apache Arrow, med zero-copy overførsel mellem DuckDB, pandas, Polars og PyArrow. I praksis betyder det, at en forespørgsel som duckdb.query("SELECT ... FROM df").df() ikke serialiserer eller kopierer data mellem processer. Det er blot pointer-udveksling.
Den vigtigste grund til, at DuckDB er blevet så populær, er dog ikke performance alene. Det er, at du får SQL som analysesprog uden at skulle spinne en Postgres eller Snowflake op. For mig som backend-udvikler er det en enorm gevinst. Mine kolleger kender allerede SQL, mine dashboards taler SQL, og nu kan mine ETL-scripts også bruge samme sprog uden at skulle oversætte til pandas-metodekald.
DuckDB vs pandas: hvornår giver det mening?
Hurtigt svar: brug pandas til data wrangling og transformationer på små-til-medium data (under ~1 GB i hukommelsen), og brug DuckDB til aggregeringer, joins og filtreringer over større datasæt, eller når du bare gerne vil skrive analysekoden i SQL. De to teknologier er ikke konkurrenter. De er komplementære, og de deler data uden kopiering via Arrow.
Egenskab
DuckDB 1.4
pandas 3.0
Datamodel
Kolonneorienteret (Arrow)
Kolonneorienteret siden 3.0 (Arrow backend)
Sprog
SQL
Python method chaining
Multi-threading
Ja, automatisk
Nej (uden Modin/Dask)
Datasæt større end RAM
Ja, out-of-core execution
Nej (crash / MemoryError)
Læs Parquet direkte
Ja, med predicate pushdown
Ja, men læser alt ind i RAM
Typisk hastighed for joins
5-20× hurtigere
Baseline
Læringskurve
SQL (kendt af de fleste)
pandas-idiomer
Bedst til
Analytics, aggregeringer, joins
Feature engineering, rensning
Min tommelfingerregel efter to år med DuckDB i produktion: hvis din pandas-kode indeholder groupby().agg(), merge() eller pivot_table() på mere end 100 MB data, så prøv at skrive den samme operation som en DuckDB-SQL-forespørgsel. I ni ud af ti tilfælde er den nye version både kortere at læse og hurtigere at køre. Til rensning og transformation af enkelte kolonner (fx regex på strings eller tidszone-konverteringer) er pandas stadig mit foretrukne værktøj. Se vores guide til datarensning med pandas 3.0 for de mønstre.
Installation og din første forespørgsel
DuckDB har nul systemafhængigheder. Én kommando installerer alt, inklusive C++-binaries til din platform:
pip install duckdb==1.4.0
Det virker på Windows, macOS (både Intel og Apple Silicon), Linux x86_64 og ARM64. Der er ingen server at starte, ingen bruger at oprette, ingen port at åbne. Så, din første forespørgsel kan se sådan ud:
import duckdb
# In-memory database (default). Til persistering brug duckdb.connect("min.db").
result = duckdb.sql("SELECT 42 AS svar, 'Python Data Bench' AS site")
print(result)
# ┌───────┬────────────────────┐
# │ svar │ site │
# │ int32 │ varchar │
# ├───────┼────────────────────┤
# │ 42 │ Python Data Bench │
# └───────┴────────────────────┘
# Konverter til pandas DataFrame (zero-copy via Arrow)
df = result.df()
print(type(df)) # <class 'pandas.core.frame.DataFrame'>
Til vedvarende lagring bruger du duckdb.connect("analytics.db"). Filen bruger DuckDB's eget kolonnelagerformat (kompakt og hurtigt at scanne) og kan åbnes fra ethvert sprog med en DuckDB-driver, inklusive R, Java, Node.js og Rust. Bemærk at fra og med version 1.0 er lagerformatet garanteret bagudkompatibelt, hvilket ikke var tilfældet i tidlige versioner.
Læs Parquet-filer direkte med DuckDB
Det her er efter min mening DuckDB's mest undervurderede feature. Du kan forespørge en Parquet-fil, som om den var en tabel, uden nogen import- eller kopi-fase:
import duckdb
# Enkelt Parquet-fil
q = duckdb.sql("""
SELECT country, AVG(revenue) AS snit_revenue
FROM 'sales_2026.parquet'
WHERE quarter = 'Q2'
GROUP BY country
ORDER BY snit_revenue DESC
LIMIT 10
""")
print(q.df())
# Glob-pattern over mange filer (behandles som én tabel)
q = duckdb.sql("""
SELECT DATE_TRUNC('month', event_time) AS maaned,
COUNT(*) AS events
FROM 'logs/2026/*.parquet'
GROUP BY maaned
ORDER BY maaned
""")
Under motorhjelmen sker der to smarte ting. Første er column pruning: DuckDB læser kun de kolonner, du refererer til i din SELECT. Har din Parquet-fil 80 kolonner, men du bruger kun tre, læses kun de tre fra disk. Anden er predicate pushdown: filteret quarter = 'Q2' anvendes så tidligt som muligt (ofte allerede på Parquet-filens row-group-statistik), så DuckDB springer hele blokke over uden at læse dem. På et 20 GB dataset kan det reducere I/O med 90%.
Vil du læse filer direkte fra S3, GCS eller HTTPS, kan du bruge httpfs-extension:
duckdb.sql("INSTALL httpfs; LOAD httpfs;")
duckdb.sql("""
SELECT * FROM 's3://my-bucket/events/*.parquet'
WHERE user_id = 12345
""")
DuckDB henter kun de row groups, filteret matcher, ved hjælp af HTTP range requests. Det gør det til et fænomenalt værktøj til ad hoc-analyse mod data lake-buckets, uden at skulle sætte en dedikeret query engine som Athena eller Presto op.
Query pandas DataFrames med SQL
En af DuckDB's mest bekvemme features er, at du kan referere til en pandas DataFrame direkte ved variabelnavn i din SQL-forespørgsel. Det virker, fordi DuckDB inspicerer det kaldende Python-scope og genkender navnet som en Arrow-kompatibel tabel:
import pandas as pd
import duckdb
customers = pd.read_csv("customers.csv")
orders = pd.read_csv("orders.csv")
# Referer 'customers' og 'orders' direkte i SQL (ingen registrering nødvendig)
resultat = duckdb.sql("""
SELECT c.country,
COUNT(DISTINCT o.order_id) AS antal_ordrer,
SUM(o.total) AS omsaetning
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id
WHERE o.order_date >= '2026-01-01'
GROUP BY c.country
HAVING SUM(o.total) > 10000
ORDER BY omsaetning DESC
""").df()
Data kopieres ikke. DuckDB's Arrow-integration bruger de underliggende kolonne-buffere direkte. Det betyder, at selv for DataFrames på flere millioner rækker starter forespørgslen med det samme, og der er ingen serialiseringsomkostning. Sammenlign det med at bruge SQLite: du skal først skrive DataFrame'en til en tabel med to_sql(), hvilket typisk tager mange sekunder for store DataFrames.
Den samme mekanik virker for Polars DataFrames og PyArrow Tables. Er du ny i Polars-verdenen, har vi en detaljeret sammenligning i Polars vs Pandas 2026, som forklarer, hvornår du bør vælge hvad.
Kan DuckDB håndtere filer større end RAM?
Ja, og det er en af de mest efterspurgte features fra pandas-brugere. DuckDB har out-of-core execution, hvilket betyder at operatører som sort, join og aggregering automatisk spiller til disk (spill-to-disk), når hukommelsen slipper op. På en laptop med 16 GB RAM kan du realistisk køre aggregeringer på Parquet-filer op til 200-300 GB, hvis du har diskplads.
Kontrol af hukommelsesforbrug sker via PRAGMA-indstillinger:
import duckdb
con = duckdb.connect()
con.sql("PRAGMA memory_limit='8GB'")
con.sql("PRAGMA temp_directory='/tmp/duckdb_spill'")
con.sql("PRAGMA threads=8")
# Denne query kan behandle 100 GB Parquet på en 16 GB-maskine
con.sql("""
COPY (
SELECT user_id,
COUNT(*) AS events,
MAX(event_time) AS sidste_event
FROM 'events/*.parquet'
GROUP BY user_id
) TO 'user_summary.parquet' (FORMAT PARQUET, COMPRESSION ZSTD)
""")
Den samme operation i pandas ville kaste en MemoryError, så snart read_parquet() forsøgte at samle det hele i én DataFrame. Du kan omgå det med chunking eller Dask, men det kræver meget mere kode og forståelse for partitionering. Med DuckDB skriver du bare SQL'en.
DuckDB vs SQLite: den vigtigste forskel
Både DuckDB og SQLite er in-process, filbaserede databaser uden server. De ligner hinanden nok til at give forvirring, men de er optimeret til modsatrettede workloads. SQLite er en row-store designet til OLTP: mange små transaktioner, hyppige punktopslag på primærnøgler, samtidige skrivninger fra flere klienter. DuckDB er en column-store designet til OLAP: få men store forespørgsler, aggregeringer over hele tabellen, vektoriseret eksekvering på tværs af CPU-kerner.
Konkret: hvis din applikation er en webapp, der gemmer brugerkonti og henter ordrer efter ID, brug SQLite. Hvis din applikation genererer daglige rapporter over millioner af log-events, brug DuckDB. Prøv aldrig at bruge DuckDB som backing store for en applikation med samtidige writes fra flere processer. Den understøtter det formelt, men det er ikke, hvad den er bygget til.
DuckDB's egen sammenligning forklarer det som "SQLite for analytics", og det er en fair karakteristik. De to teknologier lever fint sammen i samme projekt: SQLite til metadata og transaktioner, DuckDB til analyse over Parquet-filer.
Integrer DuckDB i et FastAPI-endpoint
Her er et mønster, jeg har brugt i tre forskellige produktionsprojekter. Vi har rå event-data liggende som Parquet i S3 og vil eksponere aggregerede rapporter via HTTP. Ingen ETL-pipeline, ingen data warehouse. Bare FastAPI, DuckDB og et par gode indekser i Parquet-formatets partitionering.
from contextlib import asynccontextmanager
from fastapi import FastAPI, Query
import duckdb
DUCKDB_CON: duckdb.DuckDBPyConnection | None = None
@asynccontextmanager
async def lifespan(app: FastAPI):
global DUCKDB_CON
DUCKDB_CON = duckdb.connect(":memory:")
DUCKDB_CON.sql("INSTALL httpfs; LOAD httpfs;")
DUCKDB_CON.sql("PRAGMA threads=4")
DUCKDB_CON.sql("PRAGMA memory_limit='2GB'")
yield
DUCKDB_CON.close()
app = FastAPI(lifespan=lifespan)
@app.get("/api/revenue")
def revenue_by_country(quarter: str = Query(..., regex=r"^\d{4}-Q[1-4]$")):
# Parametriseret query (DuckDB escaper argumenter korrekt)
rows = DUCKDB_CON.execute("""
SELECT country, SUM(total) AS revenue
FROM 's3://analytics/sales/*.parquet'
WHERE quarter = ?
GROUP BY country
ORDER BY revenue DESC
""", [quarter]).fetchall()
return [{"country": c, "revenue": float(r)} for c, r in rows]
To ting er værd at bemærke. Først: DuckDB-forbindelsen er global og genbruges. Det er billigt i in-memory mode, og at oprette en ny forbindelse pr. request ville øde både RAM og cache-varme. Anden: jeg bruger den synkrone execute()-metode, ikke async. DuckDB frigiver GIL under query-eksekvering, så FastAPI's threadpool (via def ikke async def) håndterer parallelisme fint. Havde jeg brugt async def, ville jeg blokere event-loopet. Jeg har set præcis det ske i et projekt, hvor et dashboard timede ud, fordi en enkelt tung query holdt alle andre requests i kø.
Det er en klassisk async-faldgrube: alt CPU-tungt eller synkron I/O hører hjemme i en sync-route eller i en run_in_threadpool. Vil du gå videre og bruge ML-modeller bag samme mønster, viser vores guide til deploy scikit-learn med FastAPI, hvordan man kombinerer inference og analytics i samme service.
Produktions-tips og faldgruber
Efter to år med DuckDB i produktion har jeg samlet en liste af ting, jeg ville ønske, nogen havde fortalt mig fra starten:
Sæt eksplicit threads og memory_limit. Defaults tager al RAM og alle kerner, hvilket saboterer andre processer på samme maskine. I containere med CPU-limits er det katastrofalt.
Brug ZSTD-kompression til Parquet-output. Filerne bliver 20-30% mindre end Snappy, og læsehastigheden er den samme. COMPRESSION ZSTD, ROW_GROUP_SIZE 100_000 er min standard.
Partitionér store datasæt efter dato.COPY ... TO 'events/' (FORMAT PARQUET, PARTITION_BY (year, month)) gør, at forespørgsler med et dato-filter kun rører de relevante mapper.
Foretræk fetchnumpy() eller arrow() til store resultater.fetchall() laver Python-liste-tupler, hvilket er langsomt for millioner af rækker.
Test dine queries med EXPLAIN ANALYZE. Det viser den faktiske eksekveringsplan med tider, uvurderligt for at spotte manglende predicate pushdown.
Håndter concurrency korrekt. Én forbindelse pr. tråd. Del ikke en DuckDBPyConnection mellem flere tråde uden serialisering, brug .cursor() for hver tråd.
Aktivér enable_progress_bar under interaktiv brug. Lange forespørgsler viser en fremdriftsindikator, hvilket er guld ved 100 GB scans.
Ærligt talt, den sidste bullet lyder trivielt, men jeg har mistet fornuften mere end én gang, mens jeg stirrede på en tavs terminal og undrede mig over, om queryen var død eller bare arbejdede.
Til sidst en kilde til opdateringer: DuckDB's GitHub Releases-side er den bedste måde at følge nye features og breaking changes. Projektet frigiver ca. hver anden måned, og deres release notes er præcise nok til, at man kan tage stilling til opgraderinger uden at læse hele changeloggen.
Ofte stillede spørgsmål
Er DuckDB hurtigere end pandas?
Til aggregeringer, joins og filtrering på datasæt over 100 MB er DuckDB typisk 5-20 gange hurtigere end pandas på samme maskine, primært takket være vektoriseret eksekvering og multi-threading. For små DataFrames (under 10 MB) er forskellen ubetydelig, og pandas er stadig bedst til feature engineering og row-wise transformationer.
Kan DuckDB læse Parquet-filer uden at loade dem i hukommelsen?
Ja. DuckDB bruger predicate pushdown og column pruning på Parquet-filer og læser kun de relevante row groups og kolonner fra disk. Du kan forespørge en 100 GB Parquet-fil på en 16 GB laptop, så længe resultatet eller mellemtilstanden passer i RAM (eller kan spilles til disk).
Hvad er forskellen på DuckDB og SQLite?
SQLite er en row-store optimeret til OLTP (mange små transaktioner, hyppige punktopslag). DuckDB er en column-store optimeret til OLAP (store aggregeringer, analytics-queries). Begge er in-process og filbaserede, men de er bygget til modsatrettede workloads. Brug SQLite til applikationsdata og DuckDB til analyse.
Understøtter DuckDB samtidige writes?
DuckDB understøtter én writer og mange readers pr. databasefil. Flere processer kan ikke skrive samtidigt til samme fil. Til analytiske workloads er det sjældent et problem, da du normalt loader data én gang og forespørger mange gange, men det udelukker DuckDB som backing store for applikationer med samtidige writes.
Hvordan installerer man DuckDB i Python?
Kør pip install duckdb. Pakken er selvstændig og indeholder alle native binaries til Windows, macOS (Intel og ARM) og Linux. Der er ingen server at starte, ingen konfiguration og ingen systemafhængigheder ud over Python 3.9 eller nyere.
Lær at visualisere data med matplotlib og seaborn i Python. Komplet dansk guide med pandas DataFrames, linjediagrammer, boxplots, heatmaps, pairplots og et færdigt salgsdashboard du kan bygge videre på.