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 Python: SQL na DataFrame 2026

Aktualizacja: 14 czerwca 2026

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):

Operacjapandas 3.0Polars 1.7DuckDB 1.1
groupby + sum (5 kolumn)42.1 s0.9 s0.8 s
join 5 GB × 50 MB78.4 s2.1 s1.9 s
Średnia krocząca (okno 30)61.0 s3.4 s2.7 s
Wczytanie 5 GB Parquet18.2 s1.4 s0.0 s (leniwe)
Zużycie RAM (peak)14.8 GB5.9 GB2.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.

Editorial Team
O Autorze Editorial Team

Our team of expert writers and editors.