Ниже — нормальный практический гайд без воды. Цель простая:
- у тебя есть очищенный CSV
- ты хочешь загрузить его в PostgreSQL
- ты хочешь понимать не только “куда нажать в DBeaver”, а что реально происходит на уровне SQL и CLI
Общая логика процесса
Импорт CSV в PostgreSQL — это не “магия DBeaver”. Это почти всегда одна и та же цепочка:
- Изучить CSV
- Определить схему таблицы
- Создать таблицу
- Загрузить данные
- Проверить результат
- При необходимости — добавить индексы / ограничения / очистку
Это важно: главная работа не в кнопке Import, а в правильной схеме таблицы.
Если схема плохая — импорт пройдет, но база будет мусорной. Если схема хорошая — потом SQL-анализ будет нормальным.
Что нужно решить до импорта
Перед загрузкой CSV в Postgres нужно понять 6 вещей:
1. Есть ли заголовок
Пример:
id,name,age,city
1,Alex,25,Berlin
2,John,31,ParisЗдесь первая строка — header.
2. Какой разделитель
Чаще всего:
,;\t(tab)
3. Как кодируются пропуски
Например:
- пустая строка
NULLNaNN/Aunknown
4. Какие типы данных у столбцов
Например:
integernumerictextdatetimestampboolean
5. Нужен ли surrogate key
Часто CSV не имеет хорошего первичного ключа. Тогда стоит добавить:
id bigserial primary key6. Нужна ли staging-таблица
Очень полезный подход:
- staging table — грузим “как есть”
- final table — потом нормализуем и кастуем типы
Это особенно полезно, если CSV грязный.
Подходы импорта
Есть 3 нормальных практических сценария:
Вариант A — быстро и просто
Создать таблицу и импортировать CSV напрямую через DBeaver.
Подходит, если:
- CSV небольшой/средний
- данные уже очищены
- ты хочешь быстро потренироваться
Вариант B — через psql
Использовать:
- SQL
COPY - meta-command
\copy
Это уже ближе к реальной работе.
Вариант C — через staging table
Сначала грузишь всё в строки/text, потом отдельным SQL приводишь типы.
Это самый устойчивый подход для грязных данных.
Сначала — как проектировать таблицу
Допустим, CSV такой:
customer_id,full_name,age,signup_date,spent_amount,is_active
101,Alex Smith,29,2024-01-15,120.50,true
102,John Brown,34,2024-02-10,89.99,falseТогда таблица может быть такой:
create table customers (
customer_id integer primary key,
full_name text not null,
age integer,
signup_date date,
spent_amount numeric(12,2),
is_active boolean
);Как выбирать типы
text
Используй по умолчанию для строк.
Не надо избыточно лепить varchar(255), если нет бизнес-причины.
integer / bigint
Для целых чисел.
bigint — если значений очень много.
numeric(12,2)
Для денег и точных чисел.
Не используй float для финансов.
date
Если только дата.
timestamp
Если дата + время.
boolean
Для true/false.
Практический совет по EDA перед импортом
Перед загрузкой через Pandas проверь:
import pandas as pd
df = pd.read_csv("data.csv")
print(df.head())
print(df.info())
print(df.isna().sum())
print(df.nunique())Полезно еще посмотреть:
print(df.dtypes)Если хочешь подготовить CSV под PostgreSQL, часто полезно:
df.columns = (
df.columns
.str.strip()
.str.lower()
.str.replace(" ", "_")
.str.replace(r"[^\w]+", "_", regex=True)
)То есть привести имена столбцов к виду:
Customer ID→customer_idSpent Amount ($)→spent_amount
Это сильно упрощает жизнь в SQL.
Импорт через DBeaver
Способ 1 — создать таблицу и импортировать CSV
Шаг 1
Подключаешься к базе в DBeaver.
Шаг 2
Создаешь таблицу через SQL Editor:
create table customers (
customer_id integer primary key,
full_name text,
age integer,
signup_date date,
spent_amount numeric(12,2),
is_active boolean
);Шаг 3
Правый клик по таблице → Import Data
Шаг 4
Выбираешь CSV-файл.
Шаг 5
Настраиваешь:
- delimiter
- header
- encoding
- mapping columns
Шаг 6
Запускаешь импорт.
Способ 2 — DBeaver сам создаст таблицу
Иногда DBeaver умеет на основе CSV сам предложить схему. Это удобно, но не идеально.
Проблема: он может неверно определить типы:
- дату засунуть в text
- числа в varchar
- boolean не распознать
- длины строк оценить криво
Для обучения это нормально, но для хорошей практики лучше:
- сначала самому определить схему
- потом импортировать
Проверка после импорта
После любого импорта сразу выполняй:
select * from customers limit 10;select count(*) from customers;select count(*) filter (where customer_id is null) as null_customer_id,
count(*) filter (where full_name is null) as null_full_name
from customers;Если нужно проверить дубликаты:
select customer_id, count(*)
from customers
group by customer_id
having count(*) > 1;Импорт через psql
Теперь к важному. В PostgreSQL есть две похожие, но принципиально разные вещи:
1. COPY
SQL-команда PostgreSQL-сервера.
Пример:
COPY customers(customer_id, full_name, age, signup_date, spent_amount, is_active)
FROM '/absolute/path/to/customers.csv'
WITH (
FORMAT csv,
HEADER true
);2. \copy
Это команда клиента psql, которая читает файл с машины клиента, а не с машины сервера.
Пример:
\copy customers(customer_id, full_name, age, signup_date, spent_amount, is_active) \
from '/Users/you/data/customers.csv' \
with (format csv, header true)Ключевое различие: COPY vs \copy
COPY
- выполняется сервером Postgres
- файл должен быть доступен серверу
- часто требует повышенных прав
- очень быстрый
\copy
- выполняется через клиент
psql - файл читается с твоего локального компьютера
- обычно удобнее в локальной практике
- в реальной жизни используется очень часто
Для твоего случая на локальном Mac почти всегда удобнее именно:
\copy ...Полный сценарий через psql
Шаг 1. Подключиться
psql -U your_user -d your_database -h localhost -p 5432Например:
psql -U postgres -d kaggle_practice -h localhost -p 5432Если хочешь сразу через connection string:
psql "host=localhost port=5432 dbname=kaggle_practice user=postgres"Шаг 2. Создать таблицу
create table customers (
customer_id integer primary key,
full_name text,
age integer,
signup_date date,
spent_amount numeric(12,2),
is_active boolean
);Шаг 3. Импортировать через \copy
\copy customers(customer_id, full_name, age, signup_date, spent_amount, is_active)
from '/Users/yourname/Desktop/customers.csv'
with (format csv, header true);Если разделитель ;, то:
\copy customers
from '/Users/yourname/Desktop/customers.csv'
with (format csv, header true, delimiter ';');Если null представлен пустой строкой:
\copy customers
from '/Users/yourname/Desktop/customers.csv'
with (format csv, header true, null '');Шаг 4. Проверить
select count(*) from customers;
select * from customers limit 10;Если хочется через SQL COPY
Это делается так:
COPY customers
FROM '/absolute/path/to/customers.csv'
WITH (FORMAT csv, HEADER true);Но на локальной машине часто упираешься в:
- права доступа
- доступность файла для сервера
- ограничения Postgres
Поэтому для практики лучше держать в голове правило:
локальный импорт руками → \copy
серверный controlled import → COPY
Импорт через pgcli
pgcli — это по сути более приятный клиент к Postgres.
Он поддерживает SQL и psql-подобные штуки, но в реальной практике надо помнить:
- SQL-команда
COPY— работает как SQL \copyзависит от клиентской поддержки meta-команд
Часто безопаснее мыслить так:
В pgcli используешь:
- подключение
- создание таблицы
- SQL-запросы
- если
\copyведет себя нестабильно — идешь в обычныйpsql
Подключение:
pgcli postgresql://postgres:password@localhost:5432/kaggle_practiceИли:
pgcli -h localhost -p 5432 -U postgres kaggle_practiceПосле входа:
create table customers (
customer_id integer primary key,
full_name text,
age integer,
signup_date date,
spent_amount numeric(12,2),
is_active boolean
);Дальше либо пробуешь:
\copy customers
from '/Users/yourname/Desktop/customers.csv'
with (format csv, header true);либо используешь psql для самого импорта, а pgcli оставляешь для работы с SQL.
Мой практический совет:
для загрузки CSV держи основным инструментом psql, а не pgcli.
pgcli хорош как shell для запросов, но не как основной ingestion-инструмент.
Самый надежный подход: staging table
Если CSV грязный, не пытайся сразу идеально кастовать всё в финальные типы. Сначала грузишь в сырой слой.
Шаг 1. Создать staging table
create table staging_customers (
customer_id text,
full_name text,
age text,
signup_date text,
spent_amount text,
is_active text
);Шаг 2. Загрузить CSV
\copy staging_customers
from '/Users/yourname/Desktop/customers.csv'
with (format csv, header true);Шаг 3. Создать финальную таблицу
create table customers (
customer_id integer primary key,
full_name text,
age integer,
signup_date date,
spent_amount numeric(12,2),
is_active boolean
);Шаг 4. Перелить с преобразованием
insert into customers (
customer_id,
full_name,
age,
signup_date,
spent_amount,
is_active
)
select
customer_id::integer,
full_name,
nullif(age, '')::integer,
nullif(signup_date, '')::date,
nullif(spent_amount, '')::numeric(12,2),
nullif(is_active, '')::boolean
from staging_customers;Это уже хороший инженерный подход.
Почему staging table сильнее прямого импорта
Потому что реальный CSV часто содержит:
- пустые строки
- смешанные типы
- “N/A”
- мусор в числовом поле
- кривые даты
- пробелы
- дубликаты
Если ты грузишь сразу в final table, импорт ломается на первой проблемной строке.
Если грузишь в staging:
- данные заходят
- потом ты диагностируешь мусор SQL-запросами
- потом исправляешь
Полезные SQL-проверки после staging
Найти строки, где age не число
select *
from staging_customers
where age is not null
and age <> ''
and age !~ '^\d+$';Найти кривые даты
Напрямую проверить дату regex-ом можно грубо:
select *
from staging_customers
where signup_date is not null
and signup_date <> ''
and signup_date !~ '^\d{4}-\d{2}-\d{2}$';Найти странные boolean
select distinct is_active
from staging_customers;Если CSV уже очищен в Pandas
Хороший вариант — подготовить CSV так, чтобы Postgres его съел без борьбы.
Например:
import pandas as pd
df = pd.read_csv("raw.csv")
df.columns = (
df.columns
.str.strip()
.str.lower()
.str.replace(" ", "_")
.str.replace(r"[^\w]+", "_", regex=True)
)
df = df.drop_duplicates()
df.to_csv("clean.csv", index=False)Если есть даты:
df["signup_date"] = pd.to_datetime(df["signup_date"], errors="coerce").dt.dateЕсли есть булевы:
df["is_active"] = df["is_active"].astype("boolean")Если есть числовые:
df["spent_amount"] = pd.to_numeric(df["spent_amount"], errors="coerce")Потом сохраняешь:
df.to_csv("clean.csv", index=False)Как автоматически получить SQL schema из Pandas
Pandas dtype не равен PostgreSQL dtype один в один, но можно использовать как черновик.
Пример грубой логики:
print(df.dtypes)Допустим:
customer_id int64
full_name object
age int64
signup_date object
spent_amount float64
is_active boolИз этого ты можешь сделать:
create table customers (
customer_id bigint,
full_name text,
age integer,
signup_date date,
spent_amount numeric(12,2),
is_active boolean
);Но слепо автоматике доверять не надо.
Если хочешь импортировать без ручного create table
Есть внешние способы, но для обучения лучше делать руками. Почему:
- ты учишься мыслить как аналитик/инженер
- ты понимаешь типы
- ты начинаешь видеть data quality issues
- ты перестаешь зависеть от GUI
Кнопка “auto-import” развивает слабое понимание.
Полезные команды psql
Список таблиц
\dtОписание таблицы
\d customersВыход
\qВыполнить SQL из файла
psql -U postgres -d kaggle_practice -f schema.sqlВыполнить одну команду из shell
psql -U postgres -d kaggle_practice -c "select count(*) from customers;"Хорошая практика: держать schema.sql и load.sql
Вместо ручной возни полезно делать два файла.
schema.sql
drop table if exists customers;
create table customers (
customer_id integer primary key,
full_name text,
age integer,
signup_date date,
spent_amount numeric(12,2),
is_active boolean
);load.sql
\copy customers(customer_id, full_name, age, signup_date, spent_amount, is_active)
from '/Users/yourname/Desktop/customers.csv'
with (format csv, header true);Тогда workflow такой:
psql -U postgres -d kaggle_practice -f schema.sql
psql -U postgres -d kaggle_practice -f load.sqlЭто уже похоже на взрослый reproducible process.
Как разметить таблицу “правильно”
Вот где суть.
Минимальный уровень
- адекватные имена столбцов
- правильные типы данных
primary key, если естьnot null, если поле обязательно
Пример:
create table sales (
sale_id bigint primary key,
customer_id bigint not null,
product_name text not null,
quantity integer not null,
unit_price numeric(12,2) not null,
sale_date date not null
);Следующий уровень
check constraints- индексы
- foreign keys
- unique
Пример:
create table sales (
sale_id bigint primary key,
customer_id bigint not null,
product_name text not null,
quantity integer not null check (quantity > 0),
unit_price numeric(12,2) not null check (unit_price >= 0),
sale_date date not null
);Когда добавлять индексы
Индексы нужны не “потому что так красиво”, а под частые запросы.
Например, если часто фильтруешь по sale_date:
create index idx_sales_sale_date on sales(sale_date);Если часто делаешь join по customer_id:
create index idx_sales_customer_id on sales(customer_id);Но для маленького Kaggle-датасета индексы часто не критичны.
Типичные ошибки при импорте CSV в Postgres
1. Неверный delimiter
CSV может быть не comma, а semicolon.
Ошибка: столбцы “съезжают”.
Решение:
with (format csv, header true, delimiter ';')2. Проблемы с кодировкой
Русский текст может ломаться из-за encoding.
Иногда надо указать:
with (format csv, header true, encoding 'UTF8')3. Пустые строки не кастуются в integer/date
Например ''::integer упадет.
Решение:
- staging table
nullif(col, '')
4. Даты в неожиданном формате
Например 03/15/2024 вместо 2024-03-15.
Решение:
- преобразовать в Pandas заранее
- или использовать staging и потом
to_date()
Пример:
to_date(signup_date, 'MM/DD/YYYY')5. Дубликаты по primary key
Если есть повторяющиеся ID, вставка сломается.
Проверка:
select customer_id, count(*)
from staging_customers
group by customer_id
having count(*) > 1;6. Зарезервированные слова в названиях столбцов
Например:
ordergroupuser
Лучше избегать.
Плохой вариант:
create table test ("order" text);Лучше переименовать:
order_nameuser_name
Как бы я рекомендовал тебе тренироваться
Вот реальный полезный пайплайн.
Сценарий для практики
Этап 1 — EDA в Pandas
- открыть CSV
- посмотреть shape, columns, types, missing values
- убрать мусор
- нормализовать названия столбцов
Этап 2 — определить SQL-схему
- выбрать типы
- выбрать PK
- выбрать nullable/not null
Этап 3 — создать таблицу в Postgres
- через DBeaver SQL Editor
- или через
psql
Этап 4 — загрузить данные
- сначала через DBeaver
- потом тот же датасет через
psql \copy
Этап 5 — верификация
count(*)limit 10- null checks
- duplicate checks
Этап 6 — mini-analysis in SQL
group byorder by- агрегаты
- joins, если несколько таблиц
Пример полного мини-цикла
1. Создать таблицу
drop table if exists employees;
create table employees (
employee_id integer primary key,
full_name text not null,
department text,
salary numeric(12,2),
hire_date date
);2. Загрузить CSV через psql
\copy employees(employee_id, full_name, department, salary, hire_date)
from '/Users/yourname/Desktop/employees.csv'
with (format csv, header true);3. Проверить
select count(*) from employees;
select * from employees limit 5;4. Мини-анализ
select department, avg(salary) as avg_salary
from employees
group by department
order by avg_salary desc;Когда использовать DBeaver, а когда CLI
DBeaver
Используй, когда:
- быстро посмотреть данные
- сделать импорт через GUI
- визуально проверить mapping
- удобно писать SQL
psql
Используй, когда:
- хочешь понимать PostgreSQL глубже
- автоматизируешь импорт
- работаешь через shell/script
- хочешь reproducible workflow
pgcli
Используй, когда:
- хочешь более приятный CLI
- автодополнение
- удобную интерактивную работу
Но для самого импорта CSV я бы ставил приоритет так:
DBeaver → для простого визуального импорта psql → для серьезного и повторяемого импорта pgcli → как удобная SQL-консоль, но не основной ingestion tool
Что стоит запомнить как базовое правило
Если кратко:
Для чистого CSV
- вручную создаешь таблицу
- грузишь через
\copy
Для грязного CSV
- staging table из
text \copyв staginginsert into final select ... cast ...
Это и есть правильное мышление.
Минимальный набор команд, который тебе реально нужен
Подключение
psql -U postgres -d your_db -h localhost -p 5432Создание таблицы
create table your_table (...);Импорт
\copy your_table from '/absolute/path/file.csv' with (format csv, header true);Проверка
select count(*) from your_table;
select * from your_table limit 10;Описание таблицы
\d your_tableМой прямой вывод
Суть не в том, чтобы “научиться импортировать CSV”. Это слишком низкий уровень фрейма.
Суть в другом:
ты строишь пайплайн перехода из сырого файла в аналитически пригодную таблицу.
Вот это уже правильное мышление:
- raw data
- schema design
- ingestion
- validation
- analysis
И именно это отличает человека, который просто жмет кнопки в DBeaver, от человека, который реально понимает, что делает.
Если хочешь, следующим сообщением я могу дать тебе:
1) готовый шаблон schema.sql + load.sql + checks.sql,
или 2) разобрать твой конкретный CSV и показать, как именно под него спроектировать таблицу.
Reference:
- ChatGPT