Learning Platform
Глоссарий Troubleshooting
Урок 15.02 · 22 мин
Средний
copyparquetcsvexport

COPY: экспорт в Parquet, CSV и JSON

Аналитика не заканчивается запросом — результат нужно куда-то отдать: в файл для коллеги, в data lake для следующего пайплайна, в формат, который поймёт другой инструмент. Команда COPY — основной механизм DuckDB для выгрузки данных в файлы. Этот урок разбирает COPY для экспорта в три главных формата — Parquet, CSV и JSON — и опции, которыми управляют выводом.

COPY симметрична: ею можно и читать файлы в таблицу, и писать таблицы в файлы.

Parquet: Pages — минимальные единицы кодирования Этот урок — про запись (COPY ... TO); чтение внешних файлов разбиралось в модуле про внешние данные.


Базовый синтаксис COPY … TO

Общая форма: COPY <источник> TO '<путь>' (опции). Источник — таблица или результат запроса. Путь — файл назначения. Опции в скобках управляют форматом.

-- экспорт таблицы целиком в Parquet
COPY sales TO 'sales.parquet' (FORMAT parquet);

-- экспорт результата запроса, а не всей таблицы
COPY (
  SELECT region, sum(amount) AS total
  FROM sales
  GROUP BY region
) TO 'region_totals.parquet' (FORMAT parquet);

-- в CSV
COPY sales TO 'sales.csv' (FORMAT csv, HEADER true);

-- в JSON
COPY sales TO 'sales.json' (FORMAT json);

Возможность экспортировать результат произвольного запроса — ключевая. Не нужно создавать промежуточную таблицу: фильтрация, агрегация, join делаются прямо в COPY (...), и на диск ложится уже готовый результат.

COPY ... TO: запрос на диск без промежуточной таблицы
ЗапросПроизвольный SELECT с фильтрами, агрегацией, join — что угодно
COPY (...) TO
Файл выбранного форматаParquet, CSV или JSON — результат сразу на диске

DuckDB также понимает сокращённую запись без явного FORMAT — формат выводится из расширения файла:

-- формат определён по расширению .parquet
COPY sales TO 'sales.parquet';

Выбор формата: Parquet, CSV или JSON

Три формата решают три разные задачи. Выбор не вкусовой — у каждого своя ниша.

ФорматСильные стороныКогда выбирать
Parquetколоночный, сжатый, со схемой и статистикой; компактный и быстрый для повторного чтенияdata lake, аналитические пайплайны, промежуточные данные, обмен между движками
CSVтекстовый, читается человеком и любым инструментомвыгрузка для не-технических пользователей, импорт в системы, понимающие только CSV
JSONтекстовый, представляет вложенные структурыAPI-обмен, данные с вложенностью, интеграция с JSON-ориентированными системами

Parquet — формат по умолчанию для аналитики. Он колоночный, как и хранилище DuckDB; данные сжаты; в файл встроены схема (типы колонок) и статистика (min/max по row group). При повторном чтении такого файла работают projection pushdown и filter pushdown — читаются только нужные колонки и пропускаются row group по статистике. Для промежуточных данных между шагами пайплайна и для data lake Parquet — правильный выбор почти всегда.

CSV — для совместимости, не для производительности. Текстовый формат: больше по объёму (нет сжатия по умолчанию, числа хранятся как текст), медленнее парсится, не несёт типов. Зато его открывает что угодно — Excel, любой язык, любая БД. Выбирайте CSV, когда на другом конце система или человек, понимающие только CSV.

JSON — для вложенных данных и интеграции. Текстовый, но в отличие от плоского CSV умеет представлять STRUCT, LIST, вложенность. Нужен, когда данные иерархичны или принимающая сторона ждёт JSON.

TIP

Практическое правило: между шагами своего пайплайна и в data lake — всегда Parquet (компактно, быстро, типизированно, поддерживает pushdown при чтении). CSV и JSON — только на границе с внешним миром, где требуется текстовый формат. Не используйте CSV как формат промежуточного хранения внутри пайплайна — это потеря и в скорости, и в объёме.


Опции формата

Каждый формат принимает свои опции в скобках. Разберём самые употребимые.

Опции CSV:

COPY sales TO 'sales.csv' (
  FORMAT csv,
  HEADER true,            -- писать строку заголовка с именами колонок
  DELIMITER ';',          -- разделитель полей (по умолчанию запятая)
  QUOTE '"',              -- символ кавычки для значений с разделителем
  NULL 'NULL'             -- чем представлять NULL в тексте
);

HEADER true — почти всегда нужно: без заголовка получатель не знает имён колонок. DELIMITER меняют под локальные соглашения (в некоторых регионах принята точка с запятой). NULL задаёт текстовое представление пустых значений.

Опции Parquet:

COPY sales TO 'sales.parquet' (
  FORMAT parquet,
  COMPRESSION zstd,       -- кодек сжатия: snappy, gzip, zstd, lz4
  ROW_GROUP_SIZE 122880   -- число строк в одной row group
);

COMPRESSION выбирает кодек: snappy — быстрый, умеренное сжатие; zstd — лучше сжимает, чуть медленнее; обычно zstd — хороший баланс. ROW_GROUP_SIZE задаёт размер row group — единицы, по которой при чтении работает пропуск по статистике; значение по умолчанию разумно, менять его стоит осознанно.

Опции JSON:

COPY sales TO 'sales.json' (
  FORMAT json,
  ARRAY true              -- весь файл как один JSON-массив; иначе newline-delimited JSON
);

ARRAY true пишет файл как единый JSON-массив объектов; по умолчанию — newline-delimited JSON (ndjson), где каждая строка — отдельный JSON-объект. ndjson удобнее для потоковой обработки, единый массив — для инструментов, ждущих цельный JSON-документ.


COPY против EXPORT DATABASE

Стоит сразу разграничить два механизма, чтобы не путать.

COPY ... TO выгружает одну таблицу или один результат запроса в один файл. Это инструмент для выборочного экспорта: «отдать вот этот результат вот в этот файл».

EXPORT DATABASE (отдельный урок модуля) выгружает всю базу целиком — все таблицы плюс SQL-схему — в каталог. Это инструмент бэкапа и миграции всей базы.

COPY и EXPORT DATABASE: разный масштаб
COPY ... TOОдна таблица или один результат запроса в один файл — выборочный экспорт
Один файлParquet, CSV или JSON по выбору
EXPORT DATABASEВсе таблицы и схема целиком — бэкап и миграция всей базы
Каталог с файлами + схемаНабор файлов данных плюс SQL-скрипт схемы

Под капотом EXPORT DATABASE использует COPY для каждой таблицы — но добавляет выгрузку схемы и организует всё как единый дамп.


COPY … FROM: симметрия чтения

COPY симметрична. У неё есть и обратная форма — COPY ... FROM, которая читает файл в существующую таблицу:

-- загрузить CSV в уже созданную таблицу
COPY sales FROM 'sales.csv' (FORMAT csv, HEADER true);

-- загрузить Parquet
COPY sales FROM 'sales.parquet' (FORMAT parquet);

Разница с INSERT INTO ... SELECT FROM 'file' из прошлого урока в основном стилистическая: COPY ... FROM — это исторический SQL-синтаксис массовой загрузки (он пришёл ещё из PostgreSQL), а INSERT ... SELECT — более гибкий, потому что между файлом и таблицей можно вставить любые преобразования. Для прямой загрузки «файл как есть в таблицу» обе формы равноценны; когда нужна фильтрация или трансформация на лету — выбирают INSERT ... SELECT. Важно, что COPY целиком — это один инструмент с двумя направлениями: TO пишет, FROM читает, и опции формата у обоих одинаковые.


COPY и параллелизм

COPY ... TO для одного файла DuckDB выполняет эффективно: данные читаются из источника векторизованно и пишутся потоково. Запись в один Parquet-файл хорошо оптимизирована, и для умеренных объёмов один файл — нормальный выбор.

Когда нужно записать очень большой объём, есть смысл писать не в один файл, а в несколько файлов с партиционированием по колонке. У этого две выгоды. Первая — запись: разные партиции пишутся параллельно, что лучше утилизирует и CPU, и диск. Вторая — чтение: партиционированный набор поддерживает partition pruning, и будущие запросы с фильтром по колонке партиционирования прочитают лишь часть данных. То есть выбор «один файл против многих партиций» — это не только про скорость записи сейчас, но и про скорость чтения потом.

Партиционированной записи (COPY ... PARTITION_BY) посвящён следующий урок — он развивает тему COPY именно в эту сторону.


Попробуй сам

  1. Создай таблицу t AS SELECT range AS id, range % 7 AS grp, random() AS val FROM range(500000);
  2. Выгрузи её в три формата: COPY t TO 'out.parquet' (FORMAT parquet);, COPY t TO 'out.csv' (FORMAT csv, HEADER true);, COPY t TO 'out.json' (FORMAT json); Сравни размеры файлов на диске — какой компактнее и почему?
  3. Выгрузи не всю таблицу, а результат запроса: COPY (SELECT grp, count(*) AS cnt, avg(val) AS a FROM t GROUP BY grp) TO 'summary.parquet'; Прочитай его обратно: SELECT * FROM 'summary.parquet';
  4. Поэкспериментируй с опциями CSV: выгрузи t с DELIMITER ';' и с HEADER false. Открой файлы текстовым редактором — как изменился вывод?
  5. Выгрузи t в Parquet с COMPRESSION zstd и с COMPRESSION snappy в два разных файла. Сравни их размеры. Затем подумай, почему для промежуточных данных пайплайна Parquet предпочтительнее CSV сразу по двум причинам.

Сжатие данных: zstd, Snappy, LZ4 — trade-off скорость против степени
Проверка знанийKnowledge check
Почему для промежуточных данных внутри аналитического пайплайна и для data lake формат Parquet предпочтительнее CSV, и для чего тогда вообще нужен экспорт в CSV?
ОтветAnswer
Parquet предпочтительнее CSV для пайплайнов и data lake по нескольким причинам. Parquet колоночный — как и хранилище DuckDB; данные в нём сжаты (CSV по умолчанию без сжатия, а числа хранятся как текст, поэтому CSV объёмнее); в Parquet-файл встроены схема (типы колонок) и статистика (min/max по row group). При повторном чтении Parquet работают projection pushdown (читаются только нужные колонки) и filter pushdown (пропуск row group по статистике), поэтому Parquet и компактнее, и быстрее для повторного чтения, и сохраняет типы. CSV же объёмнее, медленнее парсится и не несёт типов — каждый раз при чтении типы приходится выводить заново. Поэтому внутри пайплайна и в data lake правильный выбор почти всегда Parquet. Экспорт в CSV нужен для совместимости с внешним миром: CSV — текстовый формат, который открывает что угодно — Excel, любой язык, любая БД, и его понимают даже системы, не знающие Parquet. CSV выбирают, когда на принимающем конце человек или система, понимающие только текстовый CSV; то есть CSV — формат границы с внешним миром, а не формат внутреннего хранения.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. Что можно указать в качестве источника в команде COPY ... TO?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 6