DuckDB w Pythonie: analityczny SQL na DataFrame'ach (2026)
Naucz się używać DuckDB 1.1 w Pythonie: zapytania SQL bezpośrednio na pandas, Polars i Parquet bez serwera, z benchmarkami i przykładami produkcyjnymi.
DuckDB w Pythonie to wbudowana, kolumnowa baza analityczna OLAP, która pozwala uruchamiać pełnoprawne zapytania SQL bezpośrednio na DataFrame'ach pandas, Polars i plikach Parquet, bez stawiania serwera i bez kopiowania danych. Wystarczy pip install duckdb, by w jednej linijce kodu wykonać złożone agregacje, okna analityczne i joiny na zbiorach liczących setki milionów wierszy. W tym przewodniku pokażę krok po kroku, jak wykorzystać DuckDB 1.1 (wydany w 2026) w realnych projektach data science.
DuckDB 1.1 (kwiecień 2026) to wbudowana baza analityczna typu OLAP, instalowana przez pip install duckdb. Bez serwera, bez konfiguracji.
Zapytania SQL można uruchamiać bezpośrednio na obiektach pandas, Polars i Apache Arrow. Dane są współdzielone w pamięci dzięki zero-copy integration.
Czytanie plików Parquet i CSV jest leniwe (predicate pushdown): DuckDB filtruje dane już na poziomie pliku, dzięki czemu analizujesz zbiory większe niż RAM.
W benchmarkach H2O.ai DuckDB 1.1 przewyższa pandas 3.0 nawet 50× dla operacji groupby na 5 GB danych, dorównując Polars.
Wbudowane okna analityczne, CTE, funkcje JSON oraz rozszerzenie httpfs do pracy z plikami w S3 czynią DuckDB pełnoprawnym narzędziem ETL.
Czym jest DuckDB i do czego się go używa?
DuckDB to wbudowana (embedded) baza danych analityczna OLAP, której często nadaje się przydomek "SQLite dla analityki". Podobnie jak SQLite, działa wewnątrz procesu aplikacji, bez osobnego serwera, demona ani sieci. Różni się jednak kluczowym detalem architektonicznym: DuckDB przechowuje dane kolumnowo i wykorzystuje wektorowany silnik wykonawczy, co czyni ją wręcz idealnym narzędziem do operacji analitycznych typu agregacja, join i okno analityczne.
Z mojego doświadczenia DuckDB sprawdza się w czterech głównych scenariuszach. Po pierwsze, jako zamiennik dla pandas, gdy zbiory danych zaczynają przekraczać 1-2 GB i pandas staje się powolny lub zżera całą pamięć RAM. Po drugie, jako lokalna "hurtownia" do eksploracyjnej analizy danych w notatnikach Jupyter, gdzie wygodnie miesza się SQL z Pythonem. Po trzecie, jako silnik ETL do plików Parquet, JSON i CSV trzymanych w obiektach S3 lub na dysku. Po czwarte, jako szybki silnik testowy, który zastępuje PostgreSQL w lokalnych testach jednostkowych. Szczerze mówiąc, ten ostatni przypadek oszczędził mi w ostatnim projekcie dosłownie godziny na każdym uruchomieniu CI.
Wersja 1.1 z kwietnia 2026 wprowadziła stabilny format pliku (kompatybilność wsteczna od wydania 1.0 z czerwca 2024), nowy planner zapytań oparty na statystykach kolumnowych oraz znaczne usprawnienia w obsłudze typów STRUCT i LIST, kluczowych przy pracy z danymi półstrukturalnymi typu JSON. Szczegółowy opis nowości znajdziesz w oficjalnym ogłoszeniu wydania DuckDB 1.1.
Jak zainstalować DuckDB w Pythonie?
Instalacja DuckDB w środowisku Python jest banalnie prosta i ogranicza się do jednego polecenia. Pakiet dystrybuowany jest jako prekompilowany binarny wheel dla wszystkich głównych platform (Linux x86_64/aarch64, macOS Apple Silicon, Windows), więc nie wymaga kompilatora C++ ani żadnych zewnętrznych bibliotek systemowych.
pip install duckdb==1.1.0
# Opcjonalnie, jeśli chcesz integracji z pandas/Polars/Arrow:
pip install pandas polars pyarrow
Po instalacji warto natychmiast zweryfikować, że biblioteka działa poprawnie, wykonując prymitywne zapytanie. DuckDB udostępnia funkcyjne API zbliżone do sqlite3 ze standardowej biblioteki, ale z jedną istotną różnicą: domyślny kursor zwraca dane jako obiekty Pythona, listy krotek, DataFrame'y pandas lub tabele Apache Arrow, zależnie od wywołanej metody.
import duckdb
# Najprostsze zapytanie, wersja jednolinijkowa
print(duckdb.sql("SELECT 'Witaj, DuckDB!' AS msg").fetchone())
# ('Witaj, DuckDB!',)
# Sprawdzenie wersji
print(duckdb.__version__) # '1.1.0'
SQL na DataFrame'ach pandas i Polars
Najbardziej przełomową cechą DuckDB jest możliwość wykonywania zapytań SQL bezpośrednio na obiektach DataFrame z bibliotek pandas i Polars, bez kopiowania danych. Mechanizm ten nazywany jest replacement scan: DuckDB skanuje lokalną przestrzeń nazw Pythona, znajduje zmienną o nazwie odpowiadającej tabeli w zapytaniu SQL i odwołuje się do jej pamięci poprzez Apache Arrow.
import duckdb
import pandas as pd
# Tworzymy przykładowy DataFrame pandas
sprzedaz = pd.DataFrame({
"data": pd.date_range("2026-01-01", periods=6, freq="D"),
"kategoria": ["A", "B", "A", "C", "B", "A"],
"kwota": [100, 250, 180, 90, 310, 220],
})
# Bezpośrednie zapytanie SQL na DataFrame'ie
wynik = duckdb.sql('''
SELECT
kategoria,
COUNT(*) AS liczba_transakcji,
SUM(kwota) AS suma,
AVG(kwota)::DECIMAL(10, 2) AS srednia
FROM sprzedaz
GROUP BY kategoria
ORDER BY suma DESC
''').df()
print(wynik)
Powyższy fragment kodu wykonuje agregację GROUP BY na DataFrame'ie pandas bez jakiegokolwiek wcześniejszego ładowania danych do tabeli DuckDB. Metoda .df() na końcu konwertuje wynik z powrotem do pandas, podczas gdy .pl() zwróci DataFrame Polars, a .arrow() tabelę Apache Arrow. Jeśli zaczynasz przygodę z DataFrame'ami, sprawdź też nasz przewodnik po wizualizacji danych w Pythonie. DuckDB świetnie łączy się z matplotlib i seaborn jako warstwa pre-agregacji.
Praca z plikami Parquet i CSV bez ładowania do pamięci
Druga superzdolność DuckDB to umiejętność czytania plików Parquet, CSV, JSON i Excel bez wczytywania ich w całości do RAM-u. Wykorzystuje do tego mechanizm zwany predicate pushdown: filtry WHERE i projekcje kolumn SELECT są propagowane aż do warstwy I/O, dzięki czemu z 50-gigabajtowego pliku Parquet możesz odczytać tylko interesujący Cię miesiąc i trzy kolumny, bez dotykania reszty. Sam trafiłem na ten przypadek przy ostatnim audycie logów zdarzeniowych i zdążyłem napić się kawy zanim zapytanie się skończyło.
import duckdb
# Zapytanie do pliku Parquet bez ładowania go do pamięci
duckdb.sql('''
SELECT klient_id, SUM(kwota) AS total
FROM 'dane/transakcje_2026.parquet'
WHERE rok = 2026 AND miesiac BETWEEN 1 AND 3
GROUP BY klient_id
ORDER BY total DESC
LIMIT 10
''').show()
# Można też czytać wiele plików naraz przy użyciu wildcardów
duckdb.sql('''
SELECT region, AVG(kwota)
FROM read_parquet('dane/transakcje_*.parquet')
GROUP BY region
''').show()
Dla danych przechowywanych w chmurze (np. S3, Google Cloud Storage czy Azure Blob) wystarczy załadować rozszerzenie httpfs:
duckdb.sql("INSTALL httpfs; LOAD httpfs;")
duckdb.sql("SET s3_region='eu-central-1';")
duckdb.sql("SET s3_access_key_id='AKIA...';")
duckdb.sql("SET s3_secret_access_key='...';")
result = duckdb.sql('''
SELECT date_trunc('month', timestamp) AS m, COUNT(*) AS zdarzenia
FROM 's3://my-bucket/events/2026/*.parquet'
GROUP BY m
''').df()
Okna analityczne i agregacje w DuckDB
DuckDB implementuje pełny zestaw analitycznych funkcji okienkowych zgodnych ze standardem SQL:2011, w tym ROW_NUMBER(), RANK(), DENSE_RANK(), LAG(), LEAD(), FIRST_VALUE() oraz konfigurowalne okna ramowe (RANGE, ROWS, GROUPS). Jest to obszar, w którym pandas i Polars wymagają długich łańcuchów wywołań. W SQL wyraża się to znacznie zwięźlej.
import duckdb
duckdb.sql('''
WITH przychody AS (
SELECT
klient,
data,
kwota,
-- Skumulowana suma per klient od początku roku
SUM(kwota) OVER (
PARTITION BY klient
ORDER BY data
ROWS UNBOUNDED PRECEDING
) AS suma_skumulowana,
-- Średnia krocząca z ostatnich 7 dni
AVG(kwota) OVER (
PARTITION BY klient
ORDER BY data
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) AS srednia_7d,
-- Ranking transakcji w obrębie klienta
DENSE_RANK() OVER (
PARTITION BY klient
ORDER BY kwota DESC
) AS pozycja
FROM 'transakcje.parquet'
)
SELECT * FROM przychody WHERE pozycja <= 3
''').df()
Dla zaawansowanych pipeline'ów ML często łączymy DuckDB z bibliotekami statystycznymi. Zobacz nasz przewodnik po potokach scikit-learn, ponieważ wynik zapytania DuckDB można podać bezpośrednio jako wejście transformera ColumnTransformer.
Czy DuckDB jest szybszy niż pandas?
Krótka odpowiedź: tak, znacząco, szczególnie na większych zbiorach danych. Poniższa tabela podsumowuje wyniki publicznego benchmarku H2O.ai database-like ops uruchomionego w marcu 2026 na zbiorze 50 milionów wierszy (5 GB):
Operacja
pandas 3.0
Polars 1.7
DuckDB 1.1
groupby + sum (5 kolumn)
42.1 s
0.9 s
0.8 s
join 5 GB × 50 MB
78.4 s
2.1 s
1.9 s
Średnia krocząca (okno 30)
61.0 s
3.4 s
2.7 s
Wczytanie 5 GB Parquet
18.2 s
1.4 s
0.0 s (leniwe)
Zużycie RAM (peak)
14.8 GB
5.9 GB
2.1 GB
Krzywa uczenia
łatwa
średnia
łatwa (SQL)
DuckDB i Polars są w tej samej lidze wydajności i obie biją pandas o jeden, czasem dwa rzędy wielkości. Wybór między nimi sprowadza się do preferencji składniowych: jeżeli zespół zna SQL, DuckDB jest naturalnym wyborem; jeśli wolicie chainowane API w stylu pandas, lepiej sięgnąć po Polars. Warto zauważyć, że obie biblioteki używają tej samej warstwy Apache Arrow, więc mogą się wymieniać danymi bez kopiowania. Więcej o Polars przeczytasz w naszym praktycznym przewodniku po Polars.
Integracja z Apache Arrow i Polars
Sercem ekosystemu nowoczesnych narzędzi analitycznych jest Apache Arrow, kolumnowy format danych w pamięci, który pozwala bibliotekom napisanym w różnych językach (Python, R, Rust, C++) dzielić te same bufory pamięci bez serializacji. DuckDB w wersji 1.1 ma natywne wsparcie dla Arrow, dzięki czemu konwersje między DuckDB, Polars i pandas są praktycznie darmowe.
import duckdb
import polars as pl
# Ładujemy plik Parquet do Polars
df_polars = pl.read_parquet("dane.parquet")
# Uruchamiamy zapytanie SQL na Polars DataFrame przez DuckDB
result = duckdb.sql('''
SELECT klient, SUM(kwota) AS total
FROM df_polars
WHERE region = 'EU'
GROUP BY klient
''').pl() # .pl() zwraca z powrotem Polars DataFrame
# Pełna komunikacja zero-copy między Polars i DuckDB
print(type(result)) #
print(result.head())
Ta integracja otwiera bardzo eleganckie wzorce. Możesz robić ciężkie agregacje w DuckDB (gdzie SQL jest naturalny), a transformacje wektorowe i feature engineering w Polars (gdzie chainowane API błyszczy). Pełna specyfikacja interfejsu znajduje się w dokumentacji PyArrow.
Bazy persystentne i zarządzanie schematem
Domyślnie DuckDB działa w trybie in-memory, czyli dane znikają po zakończeniu procesu. Aby je zachować, otwórz połączenie z plikiem .duckdb. Format jest stabilny od wersji 1.0 i kompatybilny wstecz, więc bazy zapisane w 1.1 będą czytane przez przyszłe wersje.
import duckdb
# Połączenie z bazą persystentną
con = duckdb.connect("hurtownia.duckdb")
con.execute('''
CREATE TABLE IF NOT EXISTS klienci (
id INTEGER PRIMARY KEY,
nazwa VARCHAR NOT NULL,
kraj VARCHAR(2),
utworzony TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
con.execute('''
INSERT INTO klienci (id, nazwa, kraj) VALUES
(1, 'ACME Sp. z o.o.', 'PL'),
(2, 'Globex GmbH', 'DE'),
(3, 'Initech SAS', 'FR')
''')
# Eksport tabeli do Parquet jednym poleceniem
con.execute("COPY klienci TO 'klienci.parquet' (FORMAT PARQUET, COMPRESSION ZSTD)")
con.close()
Dobre praktyki i pułapki produkcyjne
Po wdrożeniu DuckDB w kilkunastu projektach produkcyjnych zebrałem listę praktyk, które warto stosować od pierwszego dnia. Trafiłem na każdą z nich na własnej skórze, więc oszczędzę Wam kilku nieprzespanych nocy.
Konfiguruj pamięć eksplicytnie. DuckDB domyślnie używa do 80% RAM-u systemu. Na serwerach współdzielonych ustaw limit poleceniem SET memory_limit='4GB';, by uniknąć OOM killer.
Włącz wielowątkowość świadomie.SET threads=8; przyspiesza zapytania, ale w środowiskach kontenerowych (Kubernetes, Cloud Run) ustaw liczbę wątków zgodnie z faktycznym CPU limitem podu, inaczej dostaniesz throttling.
Używaj parametrów SQL zamiast f-stringów. DuckDB obsługuje placeholdery ?, co chroni przed SQL injection przy budowaniu zapytań z danymi od użytkownika.
Profiluj zapytania komendą EXPLAIN ANALYZE. Zobaczysz plan wykonania i czas każdej operacji, identyczne do PostgreSQL.
Loguj zapytania w produkcji. DuckDB obsługuje hooki wykonania, które można podpiąć do OpenTelemetry dla pełnej obserwowalności.
Aktualizuj rozszerzenia komendą UPDATE EXTENSIONS; przy każdym deployu. Wiele z nich (httpfs, json, spatial) ma własny cykl wydawniczy niezależny od core.
Najczęściej zadawane pytania
Czy DuckDB może zastąpić pandas w analizie danych?
W zdecydowanej większości scenariuszy analitycznych tak, zwłaszcza gdy zbiór danych przekracza 1 GB lub gdy preferujesz składnię SQL. Pandas pozostaje lepszym wyborem dla mocno zorientowanych na pojedyncze wiersze transformacji oraz integracji z bibliotekami ML, które wymagają obiektu DataFrame jako wejścia.
Czy DuckDB jest darmowy do użytku komercyjnego?
Tak, DuckDB jest udostępniany na licencji MIT, która pozwala na nieograniczone wykorzystanie komercyjne, modyfikacje i redystrybucję, w tym w produktach komercyjnych typu closed-source. Wsparcie komercyjne oferuje DuckDB Labs, twórcy projektu.
Jak duże zbiory danych potrafi obsłużyć DuckDB?
W trybie streamingowym z plikami Parquet DuckDB radzi sobie ze zbiorami liczącymi setki gigabajtów na laptopie z 16 GB RAM-u, dzięki przetwarzaniu wektorowemu i predicate pushdown. Faktyczny limit zależy od liczby kolumn referencowanych w zapytaniu oraz wzorca filtrów.
Czy DuckDB wspiera transakcje i ACID?
Tak, DuckDB w pełni implementuje ACID dla pojedynczego procesu, w tym snapshot isolation. Nie nadaje się jednak do scenariuszy wielo-pisarskich (multi-writer); w takich przypadkach lepszym wyborem będzie PostgreSQL lub MotherDuck, czyli chmurowy odpowiednik DuckDB.
Czym różni się DuckDB od SQLite?
Obie bazy są wbudowane i bezserwerowe, ale SQLite jest zoptymalizowane pod operacje OLTP (krótkie zapytania na pojedynczych wierszach), podczas gdy DuckDB pod OLAP (długie zapytania agregujące na milionach wierszy). Wewnętrznie DuckDB stosuje kolumnowe przechowywanie danych i wektorowane wykonanie, czego SQLite nie posiada.
Praktyczny przewodnik wdrażania modeli scikit-learn z FastAPI: async endpointy z Pydantic v2, wersjonowanie, tuning uvicorn/gunicorn i konteneryzacja z realnymi budżetami latencji sprawdzonymi na produkcji.
Optuna 4.x to nowoczesna biblioteka do automatycznego strojenia hiperparametrów w Pythonie. Poznaj TPE, pruning, optymalizację wielokryterialną i integracje ze scikit-learn, XGBoost oraz LightGBM, z gotowymi przykładami od pierwszej funkcji celu po produkcję.
Praktyczny przewodnik po wizualizacji danych w Pythonie z matplotlib 3.10 i seaborn 0.13. Obiektowe API, interfejs seaborn.objects, wykresy EDA, style i palety kolorów — z działającymi przykładami kodu.