Как загрузить CSV в SQL

Зачем аналитику уметь это

Загрузка CSV в SQL — классическая задача. Вам присылают файл с данными, а нужно работать с ним в базе: джоинить с другими таблицами, считать агрегаты, строить витрины. Копировать через Excel в 100-миллионная таблица невозможно — нужен SQL подход.

Ниже разберём, как это делается в разных СУБД и какие подводные камни ждут.

PostgreSQL: команда COPY

Самый быстрый способ. Работает как системная операция, быстрее всех альтернатив.

Сначала создаём таблицу с правильной структурой:

CREATE TABLE orders (
    order_id INTEGER,
    user_id INTEGER,
    amount NUMERIC(10, 2),
    created_at TIMESTAMP,
    status VARCHAR(20)
);

Потом загружаем CSV:

COPY orders FROM '/path/to/orders.csv'
WITH (FORMAT CSV, HEADER TRUE, DELIMITER ',', ENCODING 'UTF8');

Параметры:

  • FORMAT CSV — CSV format.
  • HEADER TRUE — первая строка — заголовки, пропустить.
  • DELIMITER — разделитель (запятая, точка с запятой, табуляция).
  • ENCODING — обычно UTF8, иногда WIN1251 для старых файлов.

Важно: путь к файлу указывается с точки зрения сервера, не клиента. Если файл на вашем лэптопе, а база на сервере, COPY не найдёт его.

PostgreSQL: \copy

Клиентский вариант COPY. Запускается из psql и работает с файлами на клиенте.

psql -d mydb -c "\copy orders FROM '/path/on/your/laptop/orders.csv' CSV HEADER"

Путь теперь локальный. Медленнее, чем серверный COPY, но работает без прав на сервер.

MySQL: LOAD DATA INFILE

Аналог COPY в MySQL. Похожий синтаксис.

LOAD DATA INFILE '/path/to/orders.csv'
INTO TABLE orders
FIELDS TERMINATED BY ','
OPTIONALLY ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 LINES;

Параметры:

  • FIELDS TERMINATED BY — разделитель.
  • OPTIONALLY ENCLOSED BY — кавычки вокруг значений.
  • LINES TERMINATED BY — конец строки. На Windows часто \r\n.
  • IGNORE 1 LINES — пропустить заголовок.

По умолчанию MySQL требует, чтобы файл был на сервере. Для клиентского файла добавьте LOCAL после LOAD DATA. Но это может быть отключено настройками security.

Прокачать тему на реальных задачах удобно в боте @kariernik_bot — база вопросов собрана с собеседований в Яндексе, Авито, Ozon, Тинькофф.

ClickHouse: INSERT из файла

ClickHouse имеет несколько способов. Самый простой — через clickhouse-client:

clickhouse-client --query "INSERT INTO orders FORMAT CSVWithNames" < orders.csv

CSVWithNames означает, что первая строка — заголовки. Если заголовков нет, используйте просто CSV.

Для больших файлов лучше разбивать на части:

split -l 100000 big_file.csv part_
for f in part_*; do
    clickhouse-client --query "INSERT INTO orders FORMAT CSV" < $f
done

Это параллельная загрузка, которая не падает при проблеме с одним файлом.

Через Python и pandas

Самый гибкий способ. Работает в Jupyter notebook, хорошо для разовых задач.

import pandas as pd
from sqlalchemy import create_engine

df = pd.read_csv('orders.csv', parse_dates=['created_at'])

engine = create_engine('postgresql://user:password@host:5432/dbname')
df.to_sql('orders', engine, if_exists='append', index=False)

Параметры:

  • if_exists = 'append' — добавить в существующую таблицу.
  • if_exists = 'replace' — удалить и создать заново.
  • if_exists = 'fail' — бросить ошибку, если таблица есть.
  • index = False — не писать индекс pandas как колонку.

Для больших данных используйте chunksize:

df.to_sql('orders', engine, if_exists='append', index=False, chunksize=10000)

pandas будет загружать по 10к строк за раз. Для миллионов строк — обязательно.

Проблемы с форматом CSV

CSV — простой формат, но с подводными камнями.

Разделитель. В России часто ; вместо , из-за Excel и запятой как десятичного разделителя. Проверяйте.

Кавычки. Значения с запятыми внутри должны быть в кавычках: "Moscow, Russia". Иначе распарсится как два поля.

Encoding. Русские файлы часто в Windows-1251 вместо UTF-8. Результат — нечитаемые иероглифы. Конвертируйте в UTF-8 сначала или указывайте encoding при загрузке.

Newlines. Windows использует \r\n, Unix \n. Иногда это вызывает проблемы.

Дата. '01/02/2026' — это 1 февраля или 2 января? Зависит от locale. Явно указывайте format при парсинге.

Проверка после загрузки

После загрузки сравните количество строк и базовые метрики:

SELECT COUNT(*) FROM orders;
SELECT MIN(created_at), MAX(created_at) FROM orders;
SELECT COUNT(*) FILTER (WHERE amount IS NULL) AS null_amounts FROM orders;

Убедитесь, что число строк совпадает с ожидаемым. Min/max по дате — в нужном диапазоне. NULL-ы — в ожидаемой доле.

На собесе такие штуки часто спрашивают. Быстрый способ довести до автоматизма — тренажёр в Telegram с задачами из реальных интервью.

Большие файлы

Для CSV на гигабайты прямая загрузка может упасть по timeout или memory. Варианты:

Разбить файл на части через split и загружать по очереди.

Использовать специализированные инструменты: pgfutter для PostgreSQL, mysqlimport для MySQL, streaming for ClickHouse.

Конвертировать в parquet через pandas, потом загружать — parquet быстрее и компактнее CSV.

Автоматизация

Для ежедневной загрузки CSV из внешнего источника используют Airflow. DAG делает download, validate, load:

from airflow import DAG
from airflow.operators.python import PythonOperator

def load_daily_csv():
    df = pd.read_csv('/tmp/daily.csv')
    df.to_sql('orders', engine, if_exists='append', index=False)

with DAG('daily_load', schedule='@daily', start_date=...) as dag:
    load_task = PythonOperator(
        task_id='load',
        python_callable=load_daily_csv
    )

Такой подход надёжнее, чем ручная загрузка — есть логи, retry, alerts.

Читайте также

FAQ

Какой способ быстрее?

Для одноразовой загрузки — COPY / LOAD DATA INFILE нативных СУБД. Pandas медленнее, но удобнее.

Как обработать кривой CSV?

Сначала посмотрите в tekстовом редакторе. Проверьте encoding, разделитель, наличие кавычек.

CSV или Parquet?

Для архива и больших данных — Parquet. Для обмена между системами — CSV (всё ещё стандарт).

Можно ли загружать Excel напрямую?

В PostgreSQL — нет (через extension можно). В pandas — да: pd.read_excel.