DuckDB в Python 2026: SQL-аналитика поверх pandas и Parquet

DuckDB 1.3 даёт встраиваемый колоночный движок для SQL поверх pandas, Polars и Parquet без сервера. Установка, оконные функции, QUALIFY, бенчмарки и продакшен-практики на Python.

DuckDB Python: SQL поверх pandas (2026)

Обновлено: 4 июня 2026

DuckDB, это встраиваемая колоночная OLAP-СУБД, которая позволяет выполнять полноценные SQL-запросы напрямую над pandas DataFrame, Polars LazyFrame и Parquet-файлами без отдельного сервера, заменяя ручные пайплайны groupby и merge компактным SQL. В Python она устанавливается одной командой pip install duckdb, работает в одном процессе и в 2026 году стала фактическим стандартом локальной аналитики для дата-сайентистов и ETL-инженеров. Ниже практическое руководство по DuckDB 1.x: интеграция с pandas, чтение Parquet, оконные функции, бенчмарки и продакшен-практики (со ссылками на официальные источники).

  • DuckDB 1.x это «SQLite для аналитики»: колоночный движок в одном .dll, без сервера и зависимостей.
  • DuckDB читает CSV, Parquet и Arrow напрямую и выполняет SQL поверх pandas DataFrame без копирования через нулевую интеграцию Arrow.
  • На типичных аналитических запросах (groupby + join по миллионам строк) DuckDB в 5–30 раз быстрее pandas и сопоставим с Polars.
  • В отличие от SQLite, DuckDB поддерживает QUALIFY, PIVOT, оконные функции, ASOF-джойны и массивы. Это полноценный аналитический диалект SQL.
  • DuckDB-Python поддерживает потоковое чтение, read_parquet('s3://...') и расширения для PostgreSQL, MySQL и Iceberg.
  • Главные подводные камни: однопроцессная запись, ограничение по объёму ОЗУ при сортировках и блокировка файла в WAL-режиме.

Что такое DuckDB и зачем он Python-разработчику

Если коротко, DuckDB это in-process аналитическая СУБД с открытым исходным кодом, разработанная в CWI (Амстердам) и впервые выпущенная в 2019 году. Архитектурно она ближе всего к ClickHouse и Vertica: колоночное хранение, векторизованный исполнитель и оптимизатор на основе статистики. Но в отличие от них DuckDB не требует сервера, библиотека встраивается в Python-процесс ровно так же, как sqlite3 из стандартной библиотеки.

В мае 2026 года вышла стабильная ветка DuckDB 1.3. Она закрепила обратно совместимый формат файлов и добавила поддержку Apache Iceberg, ASOF-джойнов и потоковой записи Parquet. По данным официальной документации DuckDB, движок поддерживает запросы над файлами, превышающими объём оперативной памяти, за счёт out-of-core сортировки и хэш-агрегации.

Для дата-сайентиста DuckDB решает три практические задачи. Во-первых, заменяет цепочки df.groupby().agg().merge() на читаемый SQL. Во-вторых, читает Parquet и CSV в десятки раз быстрее, чем pd.read_csv. В-третьих, даёт колоночный движок без необходимости разворачивать Spark или ClickHouse, что особенно ценно в Jupyter и CI-пайплайнах. Если вы уже знакомы с современными DataFrame-библиотеками, посмотрите наше практическое руководство по Polars для пользователей pandas: DuckDB прекрасно дополняет Polars в гибридных пайплайнах.

Установка и первый запрос в Jupyter

Поехали. DuckDB поставляется как обычный wheel и не требует системных зависимостей. Достаточно одной команды:

pip install "duckdb>=1.3,<2" pandas pyarrow

Минимальный запрос в Jupyter или скрипте выглядит так:

import duckdb

# Соединение в памяти, данные не сохраняются между сессиями
con = duckdb.connect(":memory:")

result = con.sql("SELECT 42 AS answer, 'hello' AS greeting").df()
print(result)
#    answer greeting
# 0      42    hello

Метод .df() возвращает pandas DataFrame, .pl() — Polars, а .arrow() отдаёт таблицу Apache Arrow без копирования. Если вы работаете в JupyterLab, обязательно установите расширение jupysql и магию %sql: она превращает ячейки в полноценную SQL-консоль над DuckDB. Подробнее о настройке окружения см. наше руководство по JupyterLab 4.5 для дата-сайентиста.

Как выполнять SQL поверх pandas и Polars

Главная «магия» DuckDB-Python в том, что объекты DataFrame видны как обычные таблицы. Это называется replacement scan: при разборе запроса DuckDB ищет имя в локальной области видимости Python и автоматически подставляет указатель на Arrow-буфер pandas.

import duckdb
import pandas as pd

orders = pd.DataFrame({
    "order_id":    [1, 2, 3, 4, 5],
    "customer_id": [101, 102, 101, 103, 102],
    "amount":      [120.5, 75.0, 200.0, 50.0, 300.0],
})

customers = pd.DataFrame({
    "customer_id": [101, 102, 103],
    "country":     ["RU", "DE", "RU"],
})

# Никаких CREATE TABLE: pandas-объекты доступны как таблицы по имени переменной
revenue_by_country = duckdb.sql("""
    SELECT c.country,
           COUNT(*)          AS order_count,
           SUM(o.amount)     AS revenue,
           AVG(o.amount)     AS avg_ticket
    FROM   orders   o
    JOIN   customers c USING (customer_id)
    GROUP  BY c.country
    ORDER  BY revenue DESC
""").df()

print(revenue_by_country)

Аналогично работает Polars: duckdb.sql("SELECT * FROM polars_df WHERE x > 0").pl(). Никакой сериализации не происходит, DuckDB читает Arrow-буферы напрямую. Это делает гибридные пайплайны (pandas + Polars + DuckDB) предсказуемо быстрыми.

А если вы хотите наоборот, записать результат запроса в pandas-таблицу, используйте con.execute(...).fetchdf() или универсальный con.sql(...).df(). Оба варианта возвращают полноценный DataFrame с типами, выведенными из схемы DuckDB, включая Decimal, дата-время с таймзоной и nullable-integer.

Чтение Parquet и CSV без загрузки в память

Файловые форматы, это то, ради чего многие переходят на DuckDB. Движок умеет читать Parquet с предикатным push-down и проекцией колонок, поэтому запрос вида «сумма по дате за один день из терабайта Parquet» считывает с диска лишь несколько мегабайт. Честно говоря, на одном из проектов это сэкономило мне часы ожидания в Jupyter.

# Запрос напрямую к каталогу с Parquet-файлами, без явного создания таблицы
duckdb.sql("""
    SELECT date_trunc('month', event_time) AS month,
           COUNT(*)                        AS events
    FROM   read_parquet('data/events/*.parquet')
    WHERE  event_time >= DATE '2026-01-01'
    GROUP  BY 1
    ORDER  BY 1
""").df()

Для CSV есть отдельная функция read_csv_auto, которая определяет разделитель, типы колонок и кодировку. На больших CSV (более 1 ГБ) она в 5–10 раз быстрее pandas.read_csv за счёт параллельной декомпрессии и потоковой обработки. Если CSV «грязный», передайте ignore_errors=true и store_rejects=true, и DuckDB сохранит проблемные строки в служебной таблице.

Оконные функции, QUALIFY и PIVOT

DuckDB реализует современный диалект SQL уровня PostgreSQL 16 с собственными расширениями. Это важная причина выбрать его поверх SQLite, где оконные функции появились лишь недавно, а PIVOT отсутствует. Рассмотрим типичную задачу: для каждого клиента оставить только заказы на сумму выше его средней.

duckdb.sql("""
    SELECT order_id, customer_id, amount
    FROM   orders
    QUALIFY amount > AVG(amount) OVER (PARTITION BY customer_id)
""").df()

Конструкция QUALIFY позволяет фильтровать результат оконной функции без подзапроса. Этого нет ни в pandas, ни в SQLite. Аналогично, PIVOT разворачивает длинные таблицы в широкие одним выражением:

duckdb.sql("""
    PIVOT orders
    ON     customer_id
    USING  SUM(amount)
""").df()

Для временных рядов важна поддержка ASOF JOIN: он находит ближайшее по времени значение из второй таблицы (типичная задача при матчинге биржевых котировок и заявок). Подробное описание синтаксиса есть в разделе ASOF JOIN документации DuckDB. В сочетании с scikit-learn это удобно для генерации фичей. Посмотрите наше руководство по Pipeline и ColumnTransformer, чтобы корректно встроить SQL-фичи в обучающий пайплайн.

DuckDB vs pandas vs SQLite: что выбрать

Эти три инструмента часто рассматриваются как взаимозаменяемые, но решают разные задачи. Таблица ниже суммирует ключевые различия в типичных сценариях аналитики 2026 года.

ХарактеристикаDuckDB 1.3pandas 2.xSQLite 3.46
ХранениеКолоночноеВ памяти, NumPy/ArrowСтроковое
SQLПолный аналитический диалектНет (только API)Базовый, ограничены оконные функции
Чтение ParquetНативно, с push-downЧерез PyArrow, в памятьНет
Запросы больше ОЗУДа, out-of-coreНетДа, но медленно
ПараллелизмМногопоточныйОднопоточныйОднопоточный
Размер данныхДо терабайта на ноутбукеДо ~10 ГБДо терабайтов, OLTP
ЗаписьОдин писатель за разОдин процессМногопроцессная, WAL
Лучший сценарийЛокальная аналитика, ETL, отчётыГибкие трансформации, ML-фичиOLTP, конфиги, мобильные приложения

Если коротко: SQLite для оперативных записей, pandas для гибкой алгоритмики и интеграции с ML, DuckDB для SQL-аналитики и быстрого чтения файловых форматов. На практике их часто комбинируют: DuckDB готовит агрегаты, pandas строит фичи, scikit-learn обучает модель.

Производительность и работа с памятью

В независимых бенчмарках H2O.ai groupby/join (50 ГБ синтетических данных, 5 миллиардов строк) DuckDB 1.3 показывает время выполнения в пределах 10–30% от Polars 1.0 и стабильно опережает pandas в 10–50 раз на агрегациях. На запросах TPCH-SF100 движок укладывается в 60 секунд на стандартном ноутбуке с 16 ГБ ОЗУ. Это сопоставимо с кластером Spark из четырёх воркеров.

Ключевые факторы производительности:

  • Векторизованный исполнитель. Все операторы работают над блоками из 2048 значений, что максимизирует кеш CPU.
  • Многопоточность по умолчанию. DuckDB использует все доступные ядра без явной настройки; ограничить число потоков можно через PRAGMA threads=4.
  • Внешнюю сортировку и хэш-агрегацию. Если данные не помещаются в ОЗУ, DuckDB сбрасывает промежуточные результаты на диск. Лимит памяти настраивается параметром PRAGMA memory_limit='8GB'.
  • Zero-copy Arrow. Передача данных в pandas/Polars не сериализуется, что критично для интерактивной работы.

Продакшен-практики: персистентность, S3 и расширения

Для постоянного хранения создайте файл базы данных:

con = duckdb.connect("warehouse.duckdb")

con.sql("""
    CREATE TABLE IF NOT EXISTS events AS
    SELECT * FROM read_parquet('s3://bucket/events/*.parquet')
""")

con.sql("CHECKPOINT")  # принудительная запись на диск
con.close()

Файл warehouse.duckdb можно копировать, версионировать в DVC и монтировать в Docker. Для S3 нужно сначала установить расширение httpfs и задать креды:

con.sql("INSTALL httpfs; LOAD httpfs;")
con.sql("""
    CREATE SECRET (
        TYPE S3,
        KEY_ID    'AKIA...',
        SECRET    '...',
        REGION    'eu-central-1'
    )
""")

В дата-инженерных пайплайнах DuckDB обычно играет роль staging-слоя: читает «сырые» Parquet из data lake, выполняет SQL-трансформации (включая dbt-модели через адаптер dbt-duckdb) и записывает агрегаты обратно в Parquet или PostgreSQL. Расширение postgres_scanner позволяет джойнить таблицы DuckDB напрямую с PostgreSQL без репликации, что закрывает классический «второй слой» аналитики. Полный список расширений и поведение оптимизатора описаны в документации по расширениям DuckDB, а исходники доступны в основном репозитории DuckDB на GitHub.

Частые ошибки и как их избежать

За три года работы с DuckDB в продакшене я выделил четыре повторяющиеся проблемы (с ними приходится бороться почти на каждом новом проекте). Перечислю их с готовыми решениями.

1. Один писатель на файл. В отличие от SQLite, DuckDB не допускает параллельных писателей в одну базу. Если у вас несколько процессов записывают данные, разнесите их по отдельным файлам и периодически выполняйте ATTACH для объединения. Для много-читательских сценариев используйте режим read_only=True.

В реальных пайплайнах я обычно изолирую запись в один Airflow-таск, а все остальные задачи открывают базу через duckdb.connect('warehouse.duckdb', read_only=True). Это снимает гонки и одновременно позволяет десяткам читателей работать с одним файлом.

2. Игнорирование memory_limit. По умолчанию DuckDB занимает до 80% системной памяти. На разделяемом сервере это приводит к OOM. Явно задавайте PRAGMA memory_limit='4GB' и PRAGMA threads=4 для предсказуемого поведения в CI.

3. Неправильные типы при чтении CSV. Авто-детектор может определить числовой столбец как VARCHAR, если в нём встречаются пустые строки. Передавайте явный параметр columns={'amount': 'DOUBLE'} или используйте SAMPLE_SIZE=-1 для полного сканирования.

4. Перенос pandas-привычек на SQL. Конструкция SELECT * FROM df с фильтром в Python-коде, это антипаттерн: данные сначала материализуются в pandas, потом снова парсятся DuckDB. Пишите фильтр прямо в SQL, чтобы движок применил push-down и не вытягивал лишние строки. Аналогичные принципы мы разбирали в контексте миграции данных в руководстве по переходу на pandas 3.0: там же есть подробности про Copy-on-Write, который влияет на скорость zero-copy обмена с DuckDB.

Часто задаваемые вопросы

Что такое DuckDB простыми словами?

DuckDB, это встраиваемая аналитическая база данных, которая работает внутри вашего Python-процесса, как SQLite, но оптимизирована под SQL-запросы над большими таблицами. Она читает Parquet и CSV напрямую и в десятки раз быстрее pandas на агрегациях.

В чём разница между DuckDB и SQLite?

SQLite, это OLTP-СУБД со строковым хранением, оптимизированная под транзакции. DuckDB, это OLAP-движок с колоночным хранением и многопоточным исполнителем. SQLite быстрее на коротких записях по одной строке, DuckDB выигрывает на агрегациях и джойнах миллионов строк.

DuckDB быстрее pandas?

На типичных аналитических запросах (groupby, join, агрегации) DuckDB быстрее pandas в 5–50 раз благодаря векторизованному колоночному исполнителю и параллелизму. На построчных трансформациях и работе с маленькими таблицами разница нивелируется или pandas выигрывает.

Можно ли использовать DuckDB в продакшене?

Да. С версии 1.0 (июнь 2024) формат файла стабилен, есть LTS-релизы и адаптер для dbt. DuckDB используют Mode Analytics, Hex, MotherDuck и тысячи дата-команд для интерактивной аналитики и батч-ETL. Для high-concurrency OLTP-нагрузок он не предназначен.

Как DuckDB читает pandas DataFrame без копирования?

Через механизм replacement scan и общий Arrow-буфер. При разборе запроса DuckDB находит имя переменной Python и читает данные напрямую из памяти pandas/Polars. Сериализация и копирование не выполняются, поэтому передача DataFrame фактически бесплатна.

Editorial Team
Об авторе Editorial Team

Our team of expert writers and editors.