Обзор DuckDB: SQL для файлов и локальной аналитики показывает встроенную аналитическую базу, которая читает CSV и Parquet прямо в запросе. Документация проекта называет её реляционной СУБД, а блог MotherDuck описывает как OLAP-базу внутри процесса приложения. Ниже — рабочий пример, типы, CLI, Python и границы многопользовательской работы.
Встроенная аналитическая база и отличие OLAP от OLTP
DuckDB устроена как встроенная база: движок работает внутри того же процесса, что и ваша программа. В блоге MotherDuck для начинающих она описана как in-process SQL OLAP database, оптимизированная для аналитики и часто организованная по схеме «звезда». Слово «встроенная» здесь означает способ запуска: отдельный сервер не поднимается, библиотека подключается к приложению.
Документация проекта на странице SQL Introduction называет DuckDB реляционной системой управления базами данных (RDBMS). Это важная деталь: несмотря на файловый сценарий, перед вами полноценная реляционная модель с таблицами, схемами и типами. Там же сказано, что каждая строка таблицы имеет один и тот же набор именованных колонок, а каждая колонка относится к конкретному типу данных. Таблицы хранятся внутри схем, а набор схем составляет базу, к которой вы обращаетесь.
Различие OLAP и OLTP удобно разбирать по характеру нагрузки. Транзакционная система обслуживает много коротких операций: вставка заказа, обновление статуса, списание остатка. Аналитическая система отвечает на вопрос по массиву строк: сколько, в разрезе чего, за какой период. DuckDB относят к аналитическому классу. В обзоре MotherDuck прямо указано, что это OLAP-база для аналитики.
Практический вывод для читателя: выбирайте DuckDB, когда задача состоит в чтении файлов и агрегатах. Транзакционный многопользовательский сервер решает другой класс задач, и об этом ниже будет отдельный разбор. Встроенный режим даёт короткий путь от файла до результата: вы не разворачиваете инфраструктуру, а сразу пишете SQL.
Ещё одно следствие встроенности — жизненный цикл. Блог MotherDuck отмечает, что по умолчанию DuckDB работает как процесс в памяти и не сохраняет данные. Это значит, что после закрытия сессии созданные таблицы исчезают, если вы не открыли файл базы. Такой режим удобен для разовых проверок и экспериментов с выгрузками.
Для локальной аналитики это свойство часто оказывается преимуществом: вы загружаете CSV, считаете агрегаты, закрываете процесс, и на диске не остаётся ничего лишнего. Когда результат нужно сохранить, вы открываете файл базы, и тогда таблицы записываются на диск. Разницу между памятью процесса и персистентной базой мы разберём в отдельном разделе с командами.
Отдельно стоит сказать про масштаб. Встроенная модель рассчитана на аналитику на одной машине, и обещать произвольный объём данных в оперативной памяти нельзя. Если файл не помещается в память целиком, планируйте работу с диском и проверяйте поведение на своих данных. Это ограничение честнее принять заранее, чем столкнуться с ним в середине отчёта.
SQL непосредственно по CSV и Parquet
Ключевая возможность DuckDB — читать файлы прямо в запросе. В документации проекта на странице Quickstart указано, что DuckDB автоматически определяет заголовок CSV и типы колонок. Это снимает подготовительный этап: вы не описываете схему вручную, а сразу обращаетесь к файлу как к таблице.
Функция read_csv принимает путь к файлу и возвращает набор строк, с которым работает обычный SQL. Синтаксис запроса остаётся привычным: SELECT, WHERE, GROUP BY, ORDER BY. Такой подход называют запросом по файлу: данные читаются в момент выполнения, а таблица в базе при этом не создаётся. Блог MotherDuck для начинающих отдельно поясняет, что при таком чтении набор данных считывается, а фактическая таблица в базе не создаётся.
Для Parquet действует та же логика. Документация библиотеки Ibis на странице бэкенда DuckDB описывает две операции: регистрацию CSV-файла как таблицы в текущей базе и регистрацию parquet-файла как таблицы. Там же описана запись результата выполнения выражения в CSV-файл. Эти формулировки показывают типовой цикл: зарегистрировать источник, выполнить выражение, выгрузить результат.
Почему это удобно на практике. Аналитик получает выгрузку из другой системы, кладёт её рядом со скриптом и сразу считает метрики. Инженер проверяет лог, сохранённый в CSV, не поднимая хранилище. Разработчик сверяет две выгрузки между собой одним запросом. Во всех случаях файл остаётся файлом, а SQL даёт привычный язык выборки.
Сравнение двух форматов по назначению приведём в таблице ниже. Оно опирается на общее описание форматов и на операции, названные в документации Ibis.
| Критерий | CSV | Parquet |
|---|---|---|
| Что хранит | Текст с разделителями | Колоночный бинарный формат |
| Типы колонок | Определяются при чтении | Хранятся вместе с данными |
| Когда удобен | Выгрузки, логи, ручные правки | Обмен между системами, повторные чтения |
| Операция в Ibis | Регистрация CSV как таблицы | Регистрация parquet как таблицы |
| Выгрузка результата | Запись выражения в CSV | Запись выражения в CSV |
Из таблицы видно разделение ролей. CSV удобен там, где данные приходят текстом и их правят руками. Parquet удобен там, где важен обмен между инструментами и повторное чтение. DuckDB работает с обоими вариантами в одном запросе, поэтому формат перестаёт быть барьером.
Практический порядок действий для файла выглядит так. Сначала вы кладёте файл в рабочую папку. Затем пишете запрос с read_csv и указываете путь. После этого проверяете, как определились типы колонок, и при необходимости задаёте параметры чтения явно. В конце считаете агрегат и сверяете результат с ожидаемым числом строк.
Проверка типов — обязательный шаг. Автоопределение удобно, но на нестандартных данных оно может дать неожиданный результат. Если колонка с числами содержит разделитель, отличный от ожидаемого, тип может оказаться текстовым. Тогда агрегат либо не выполнится, либо посчитает не то, что вы имели в виду. Явные параметры чтения снимают эту неопределённость.
Типы данных и параметры чтения файлов
Типы данных в DuckDB описывает документация проекта. На странице SQL Introduction сказано, что каждая колонка таблицы относится к конкретному типу, а строки имеют одинаковый набор именованных колонок. Это базовая модель реляционной базы, и она же определяет, как ведёт себя агрегат.
Для числовых колонок важны два семейства: целые и дробные. Сумма целых даёт целое, сумма дробных — дробное. Если колонка распознана как текст, SUM по ней не даст ожидаемого числа. Поэтому первый контроль при чтении файла — посмотреть, какие типы присвоены колонкам.
Параметры чтения управляют тем, как файл интерпретируется. К ним относятся разделитель колонок, наличие заголовка, десятичный разделитель и правила разбора значений. Документация проекта на странице Quickstart подтверждает автоматическое определение заголовка и типов. Когда автоматика ошибается, параметры задаются явно.
Десятичный разделитель — самая частая ловушка в русскоязычных выгрузках. Значение вида 1,20 в одном диалекте означает дробное число, в другом — две колонки или текст. Молчаливое приведение здесь опасно: одна и та же строка может быть прочитана как единица, как сто двадцать или как текст. Явное указание диалекта и десятичного разделителя убирает двусмысленность.
Приведём учебный пример, который показывает связь между числом строк, типом колонки и итоговой суммой. Этот учебный вход служит для ручной проверки логики.
dept,amount,status
A,12,paid
A,5,cancelled
B,7,paid
A,3,paid
В этом файле четыре строки данных. Колонка amount содержит целые числа, колонка status содержит текст. Если десятичный разделитель настроен неверно, строка с дробным значением в реальной выгрузке может быть разобрана неправильно. Поэтому перед агрегатом полезно посмотреть первые строки и типы.
Таблица ниже связывает контрольные величины с тем, что они проверяют. Она поможет поймать ошибку чтения до того, как она попадёт в отчёт.
| Контроль | Значение в примере | Что проверяет |
|---|---|---|
| Входных строк | 4 | Файл прочитан целиком |
| Оплаченных строк | 3 | Фильтр по статусу сработал |
| Итоговых групп | 2 | Группировка по подразделению |
| Сумма по A | 15 | Агрегат по отфильтрованным строкам |
| Сумма по B | 7 | Агрегат по второй группе |
| Общий итог | 22 | Сумма всех оплаченных строк |
Такой набор контролей даёт быструю диагностику. Если входных строк меньше четырёх, файл прочитан частично. Если оплаченных строк не три, фильтр захватил лишнее. Если групп не две, группировка идёт по другой колонке. Если сумма расходится, проблема в типе или в десятичном разделителе.
Параметры чтения стоит фиксировать в самом запросе, чтобы результат воспроизводился. Когда настройки лежат в коде, коллега получит тот же ответ на том же файле. Когда они подразумеваются, результат зависит от версии и окружения. Явность здесь экономит время на разборе расхождений.
Полный CSV, запрос группировки и эталон A15/B7
Соберём полный пример от файла до результата. Файл orders.csv содержит четыре строки: две оплаченные по подразделению A, одну отменённую по A и одну оплаченную по B. Задача — посчитать сумму оплаченных заказов по каждому подразделению.
Запрос для этой задачи выглядит так:
SELECT dept, SUM(amount) AS total
FROM read_csv('orders.csv')
WHERE status = 'paid'
GROUP BY dept
ORDER BY dept;
Разберём его по частям. FROM read_csv('orders.csv') читает файл как таблицу. WHERE status = 'paid' оставляет только оплаченные строки. GROUP BY dept собирает строки в группы по подразделению. SUM(amount) складывает суммы внутри группы. ORDER BY dept упорядочивает результат по названию подразделения.
Ожидаемый результат — две строки: для A сумма 15, для B сумма 7. Общий итог по обеим группам равен 22. Отменённая строка с суммой 5 в результат не попадает, потому что фильтр по статусу её исключает. Это ручной эталон: он получен арифметикой по четырём строкам файла, и запрос в примере не запускался.
Проверим эталон вручную. По подразделению A оплаченных строк две: 12 и 3. Их сумма равна 15. По подразделению B оплаченная строка одна: 7. Сумма равна 7. Общий итог: 15 плюс 7 равно 22. Отменённая строка по A имеет сумму 5 и в итог не входит.
Теперь свяжем эталон с контролями из предыдущего раздела. Входных строк четыре. После фильтра остаётся три. После группировки получается две группы. Суммы по группам — 15 и 7. Общий итог — 22. Любое расхождение с этими числами указывает на конкретный шаг: чтение, фильтр, группировку или агрегат.
Практическая ценность эталона в том, что он даёт точку сверки. Вы запускаете запрос на своём файле и сравниваете числа. Если они совпали, логика чтения и агрегата работает. Если разошлись, вы знаете, какой контроль проверить первым. Такой подход экономит время при переходе на новые выгрузки.
Отдельно отметим порядок строк в результате. ORDER BY dept даёт предсказуемую последовательность: сначала A, затем B. Без сортировки порядок групп может отличаться между запусками. Для сверки это неважно, для отчёта — важно, поэтому сортировку лучше указывать явно.
CLI, Python и персистентная база против памяти процесса
В этом обзоре рассматриваются запуск из командной строки и использование из Python. Документация проекта на странице Quickstart описывает создание файла базы: команда создаёт файл quickstart.duckdb в текущем каталоге либо открывает его, если он уже существует. Там же сказано, что таблицы, созданные в этой базе, сохраняются в файле.
Это ключевое различие режимов. По умолчанию, как отмечает блог MotherDuck, DuckDB работает как процесс в памяти и не сохраняет данные. Когда вы открываете файл базы, таблицы записываются на диск. Выбор между режимами определяет, что произойдёт после закрытия сессии.
Сравним режимы по явным критериям.
| Критерий | Память процесса | Файл базы |
|---|---|---|
| Сохранение после закрытия | Нет | Да |
| Создание таблиц | Временное | Записывается в файл |
| Повторное открытие | Данные недоступны | Данные на месте |
| Типичная задача | Разовая проверка | Повторяемый отчёт |
| Команда запуска | Открытие без файла | Открытие файла базы |
Из таблицы видно разделение сценариев. Разовая проверка выгрузки не требует сохранения. Повторяемый отчёт удобнее держать в файле базы, чтобы не пересобирать таблицы каждый раз.
Для Python доступен тот же движок через библиотеку. Документация Ibis на странице бэкенда DuckDB описывает создание клиента Ibis, подключённого к базе DuckDB. Это показывает, что DuckDB встраивается в аналитический стек Python и работает как бэкенд для выражений.
Порядок работы из Python выглядит так. Вы подключаетесь к базе, открывая файл или создавая соединение в памяти. Затем выполняете запрос и получаете результат в привычной структуре. При необходимости записываете результат в CSV. Документация Ibis отдельно называет операцию записи результата выражения в CSV-файл.
Режим только для чтения полезен, когда база нужна как источник. Блог MotherDuck упоминает запуск DuckDB с базой в режиме read-only, чтобы избежать изменения базы. Это уместно, когда вы анализируете чужой файл и хотите исключить случайную запись.
Практический выбор между CLI и Python зависит от задачи. CLI удобен для быстрой проверки файла и разовых запросов. Python удобен, когда запрос встроен в скрипт, повторяется по расписанию или связан с обработкой результата. Оба варианта используют один движок, поэтому логика запроса переносится между ними.
Отдельно про персистентность. Файл базы удобно версионировать вместе с проектом, если он небольшой. Для больших наборов данных файл базы может занимать много места, и тогда чтение исходных файлов в запросе остаётся более лёгким путём. Выбор зависит от того, как часто вы обращаетесь к одним и тем же данным.
Число входных, отфильтрованных и итоговых строк как контроль
Контроль количества строк — самый дешёвый способ поймать ошибку чтения. Идея проста: вы знаете, сколько строк в файле, сколько должно остаться после фильтра и сколько групп должно получиться. Каждое из этих чисел проверяется отдельным запросом.
Порядок проверки для нашего примера такой. Сначала считаете все строки файла: их должно быть четыре. Затем считаете строки со статусом оплачено: их должно быть три. Затем считаете число групп по подразделению: их должно быть две. В конце сверяете суммы по группам с эталоном.
Запросы для контроля выглядят так:
SELECT COUNT(*) FROM read_csv('orders.csv');
SELECT COUNT(*) FROM read_csv('orders.csv') WHERE status = 'paid';
SELECT COUNT(DISTINCT dept) FROM read_csv('orders.csv') WHERE status = 'paid';
Первый запрос даёт число входных строк. Второй — число строк после фильтра. Третий — число итоговых групп. Вместе они образуют цепочку, по которой видно, на каком шаге возникло расхождение.
Почему это работает. Каждый шаг обработки меняет число строк предсказуемым образом. Чтение даёт все строки файла. Фильтр уменьшает их до подходящих по условию. Группировка сворачивает строки в группы. Если число на любом шаге отличается от ожидаемого, ошибка локализована.
Частая причина расхождения на первом шаге — неверный разделитель. Если файл использует точку с запятой, а чтение ожидает запятую, строки могут склеиться или разбиться иначе. Тогда число входных строк изменится, и это сразу станет видно.
Частая причина расхождения на втором шаге — регистр или лишние пробелы в значениях статуса. Строка Paid и строка paid для фильтра различаются. Если в выгрузке встречаются оба варианта, фильтр захватит только один. Проверка числа отфильтрованных строк это покажет.
Частая причина расхождения на третьем шаге — группировка по колонке с высокой кардинальностью. Если группировать по идентификатору заказа, групп будет столько же, сколько строк. Группировка по подразделению даёт небольшое число групп. Выбор колонки для группировки определяет результат.
Соберём контрольные величины в одну таблицу для быстрой сверки.
| Шаг | Ожидаемое число | Что означает расхождение |
|---|---|---|
| Чтение файла | 4 | Проблема с разделителем или заголовком |
| Фильтр по статусу | 3 | Проблема со значениями статуса |
| Группировка | 2 | Проблема с колонкой группировки |
| Сумма по A | 15 | Проблема с типом или агрегатом |
| Сумма по B | 7 | Проблема с типом или агрегатом |
Такой контроль помогает обнаружить ошибки чтения. Его стоит включать в каждый новый сценарий с незнакомым файлом. Когда числа совпадают, дальше можно строить более сложные запросы с уверенностью в исходных данных.
Ограничения многопользовательской работы и ошибочный десятичный разделитель
Встроенная модель определяет границы применения. DuckDB работает внутри процесса приложения, и это отличает её от серверной СУБД, которая обслуживает множество одновременных подключений. Для локальной аналитики встроенный режим удобен, для многопользовательской транзакционной нагрузки нужен другой инструмент.
Для совместной работы определите, какие процессы читают файл и какие изменяют его. До выбора локальной базы проверьте, как используемая конфигурация координирует запись. Для командной работы с общей базой применяют серверные решения или облачные сервисы. Документация MotherDuck описывает облачное хранилище, построенное на DuckDB, с изолированным вычислительным экземпляром для каждого пользователя или агента.
Это показывает направление масштабирования. Локальный режим остаётся удобным для индивидуальной аналитики. Когда требуется совместный доступ, переходят к серверной или облачной модели. Выбор определяется числом одновременных пользователей и требованиями к транзакциям.
Второе ограничение связано с разбором чисел. Десятичный разделитель в разных выгрузках отличается. Значение 1,20 в одном диалекте означает дробное число, в другом — текст или две колонки. Молчаливое приведение здесь недопустимо: результат будет непредсказуемым.
Разберём ошибку подробно. Пусть в файле есть строка с суммой 1,20. Если чтение настроено на запятую как разделитель колонок, значение распадётся на две части. Если запятая считается десятичным разделителем, получится дробное число. Если ни то ни другое не задано явно, поведение зависит от автоматики, и результат может оказаться неверным.
Правильный порядок действий такой. Сначала посмотрите на образец файла и определите, какой символ разделяет колонки, а какой отделяет дробную часть. Затем задайте оба параметра явно в запросе. После этого проверьте типы колонок и число строк. Только потом считайте агрегат.
Проверка типа колонки — быстрый способ поймать проблему. Если колонка с суммами определилась как текст, агрегат не даст ожидаемого числа. Явное указание типа при чтении снимает неопределённость. Это особенно важно для выгрузок из систем, где формат чисел зависит от локали.
Отдельно про значения, которые нельзя молча считать ни единицей, ни ста двадцатью. Строка с неоднозначным разделителем требует явного решения. Вы либо задаёте диалект, либо приводите значение к нужному типу, либо исключаете строку из расчёта. Оставлять выбор автоматике в таком случае рискованно.
Соберём ограничения в таблицу для наглядности.
| Ограничение | Проявление | Что делать |
|---|---|---|
| Встроенный режим | Движок работает внутри приложения | Для совместной работы проверить координацию доступа |
| Память процесса | Данные не сохраняются по умолчанию | Открыть файл базы для персистентности |
| Десятичный разделитель | 1,20 читается неоднозначно |
Задать диалект и разделитель явно |
| Тип колонки | Числа распознаны как текст | Проверить типы и задать явно |
| Объём в памяти | Большой файл не помещается целиком | Планировать работу с диском |
Из таблицы видно, что ограничения касаются режима работы и разбора данных. Оба класса проблем решаются настройкой, если знать, где искать. Контроль строк и проверка типов дают раннее предупреждение.
Для локальной аналитики этих границ достаточно. Вы читаете файлы, считаете агрегаты, сохраняете результат. Когда задача выходит за рамки одного процесса или требует совместного доступа, выбирается другой инструмент. Понимание границ помогает принять решение до начала работы.
Частые вопросы
Что такое DuckDB простыми словами?
DuckDB — встроенная аналитическая база данных, которая работает внутри процесса приложения. Документация проекта называет её реляционной СУБД, а блог MotherDuck описывает как OLAP-базу для аналитики. Она читает CSV и Parquet прямо в запросе и выполняет обычный SQL. Отдельный сервер для начала работы не требуется.
Чем DuckDB отличается от SQLite?
Обе базы встроенные, но ориентированы на разные нагрузки. SQLite относится к транзакционному классу и обслуживает короткие операции. DuckDB относится к аналитическому классу и оптимизирована под агрегаты по массивам строк. Выбор зависит от задачи: транзакции или аналитика.
Как читать CSV в DuckDB?
Используйте функцию read_csv с путём к файлу в запросе. Документация проекта на странице Quickstart указывает, что DuckDB автоматически определяет заголовок CSV и типы колонок. При нестандартном формате задайте разделитель и десятичный разделитель явно. После чтения проверьте типы и число строк.
DuckDB хранит данные между запусками?
По умолчанию DuckDB работает как процесс в памяти и не сохраняет данные. Чтобы таблицы остались, откройте файл базы: документация проекта описывает создание файла quickstart.duckdb, и таблицы в нём сохраняются на диск. Выбор режима определяет, что произойдёт после закрытия сессии.
Можно ли использовать DuckDB для нескольких пользователей?
DuckDB работает внутри процесса приложения. Для совместного доступа отдельно выбирают способ координации чтения и записи либо серверное или облачное решение. Документация MotherDuck описывает облачное хранилище на DuckDB с изолированным вычислительным экземпляром для каждого пользователя. Локальный режим остаётся удобным для индивидуальной аналитики.
