Как импортировать CSV и Excel в PostgreSQL
Практика3 минРедакция Innovialabs
Коротко
Загружайте файл сначала во временную staging-таблицу с текстовыми колонками через COPY, \copy, DBeaver или Python, затем приводите типы, убирайте дубли и переносите проверенные данные в рабочую таблицу.
Короткий ответ
Надёжная схема импорта одна для любого инструмента:
- Загрузить файл как есть в staging-таблицу, где все колонки —
text. - Преобразовать типы, даты и форматы SQL-запросом.
- Перенести данные в рабочую таблицу, отсеяв дубли.
- Проверить результат: количество строк, пустые значения, суммы.
Так ошибка в одной строке файла не обрывает весь импорт, а плохие данные не попадают в рабочие таблицы.
Какой инструмент выбрать
| Способ | Когда подходит |
|---|---|
COPY | Файл уже лежит на сервере базы данных, нужны скорость и большие объёмы |
\copy в psql | Файл на вашем компьютере, а база удалённая |
| DBeaver | Разовая загрузка без командной строки, с визуальным сопоставлением колонок |
| Python (pandas) | Excel с несколькими листами, сложная очистка, регулярный автоматический импорт |
Подготовка staging-таблицы
CREATE TABLE staging_clients (
name text,
phone text,
city text,
created text
);
Текстовые колонки принимают что угодно: «12.03.2026», «n/a», пробелы. Разбираться с этим удобнее уже в SQL.
COPY и \copy
COPY читает файл на сервере PostgreSQL. Для этого нужны права суперпользователя или роль pg_read_server_files.
COPY staging_clients (name, phone, city, created)
FROM '/var/lib/postgresql/import/clients.csv'
WITH (FORMAT csv, HEADER true, DELIMITER ';', ENCODING 'WIN1251');
\copy — команда клиента psql: файл читается на вашей машине и передаётся на сервер. Команда пишется в одну строку:
\copy staging_clients (name, phone, city, created) FROM 'clients.csv' WITH (FORMAT csv, HEADER true, DELIMITER ';', ENCODING 'WIN1251')
Ключевые параметры:
- DELIMITER — Excel с русской или узбекской локалью часто сохраняет CSV с точкой с запятой, а не запятой.
- ENCODING — старые выгрузки из Excel и 1С бывают в
WIN1251. Если вместо кириллицы видите «кракозябры», проблема в кодировке. Лучше сразу сохранять как «CSV UTF-8». - HEADER — пропустить строку с заголовками.
DBeaver
Подходит, когда нужно загрузить файл один раз и без консоли:
- Правый клик по staging-таблице → Import Data → CSV.
- Укажите кодировку, разделитель и формат даты в настройках импорта.
- Проверьте сопоставление колонок файла и таблицы.
- Запустите импорт и посмотрите лог ошибок.
Файл Excel перед этим сохраните как CSV или используйте Python.
Python и Excel
Для Excel удобнее всего pandas: он читает листы .xlsx напрямую (нужен пакет openpyxl).
import pandas as pd
from sqlalchemy import create_engine
df = pd.read_excel("clients.xlsx", sheet_name="Клиенты", dtype=str)
df.columns = ["name", "phone", "city", "created"]
engine = create_engine("postgresql+psycopg://user:password@localhost:5432/shop")
df.to_sql("staging_clients", engine, if_exists="append", index=False, chunksize=1000)
dtype=str важен: без него Excel и pandas превращают телефоны в числа, теряют ведущие нули и показывают длинные номера в экспоненциальном виде. Пароль к базе храните в переменных окружения, а не в коде.
Даты, форматы и дубли
Преобразуйте и перенесите данные одним запросом:
INSERT INTO clients (name, phone, city, created_at)
SELECT DISTINCT ON (phone_clean)
trim(name),
phone_clean,
nullif(trim(city), ''),
to_date(created, 'DD.MM.YYYY')
FROM (
SELECT *, regexp_replace(phone, '\D', '', 'g') AS phone_clean
FROM staging_clients
) s
WHERE phone_clean <> ''
ORDER BY phone_clean
ON CONFLICT (phone) DO NOTHING;
Что здесь происходит:
to_dateс явным форматом не даёт перепутать день и месяц;- телефоны приводятся к цифрам, чтобы «+998 90 123-45-67» и «998901234567» считались одним номером;
DISTINCT ONубирает дубли внутри файла,ON CONFLICT— пересечения с уже существующими данными (нужен уникальный индекс поphone).
Проверка после импорта
- Сравните число строк в файле, staging и рабочей таблице. Разница должна объясняться дублями и отброшенными строками.
- Посчитайте NULL в обязательных полях.
- Сверьте суммы и диапазоны: даты не из будущего, суммы без отрицательных значений.
- Посмотрите несколько случайных строк глазами.
После успешной проверки очистите staging через TRUNCATE.