SQLINSERTDMLtutorial

Что такое INSERT в SQL? Добавление строк для начинающих

INSERT — это команда «добавить новую строку в таблицу». Простыми словами: базовый синтаксис, batch-вставка нескольких строк за раз, INSERT FROM SELECT, RETURNING для возврата id, и UPSERT через ON CONFLICT для идемпотентных вставок. С таблицами before/after и частыми ошибками.

4 мин чтенияСправочникSQL · INSERT · DML · tutorial

INSERT — это команда «добавить новую строку в таблицу». Самая базовая операция: пока в таблицу никто ничего не вставил, она пустая, и в SELECT выбирать нечего.

Если возвращаться к аналогии с блокнотом контактов: INSERT — это «новая страница, новый человек: имя такое, телефон такой, email такой».

Зачем нужен INSERT

Любые данные в БД когда-то появились через INSERT. Регистрация пользователя, оформление заказа, новый комментарий — всё это под капотом INSERT INTO .... Без него таблицы остаются пустыми.

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

INSERT INTO users (email, display_name, country)
VALUES ('alice@example.com', 'Аня', 'RU');

По частям:

  • INSERT INTO users — в какую таблицу вставляем.
  • (email, display_name, country) — список колонок, которые заполняем.
  • VALUES (...) — значения в том же порядке, что и колонки.

Колонки указывать явно — это правильная привычка. Даже если ты сейчас вставляешь все поля, через месяц в таблицу добавят новую колонку, и INSERT INTO users VALUES (...) без списка колонок упадёт неожиданно.

Пример с таблицей

Пустая таблица users:

id email display_name country
нет строк

Делаем INSERT:

INSERT INTO users (email, display_name, country)
VALUES ('anya@example.com', 'Аня', 'RU');

Результат:

id email display_name country
1 anya@example.com Аня RU

id мы не передавали — он автоматически стал 1, потому что объявлен как SERIAL (автоинкремент). Следующий INSERT получит id = 2.

Колонки с DEFAULT (created_at TIMESTAMPTZ DEFAULT NOW() и им подобные) тоже можно не передавать — Postgres сам подставит дефолт.

Batch-вставка — несколько строк за раз

Если нужно вставить много строк, не делай N отдельных INSERT — это медленно. Используй batch:

INSERT INTO users (email, display_name, country)
VALUES
  ('anya@example.com', 'Аня', 'RU'),
  ('bob@example.com',  'Боб', 'US'),
  ('vera@example.com', 'Вера', 'BY');

Один запрос, одна транзакция, один проход по индексам — резко быстрее, чем три отдельных INSERT'а. Если льёшь данные тысячами — обязательно batch'ом. Сетевая задержка часто доминирует над временем самой вставки.

На практике батчами по 1000-10000 строк работают комфортно.

INSERT FROM SELECT — копирование из другой таблицы

Иногда нужно скопировать данные из одной таблицы в другую (или преобразовать на лету):

-- Перенести «архивные» заказы в отдельную таблицу
INSERT INTO orders_archive (id, customer_id, amount, archived_at)
SELECT id, customer_id, amount, NOW()
FROM orders
WHERE status = 'completed' AND created_at < '2024-01-01';

Это транзакционно — либо все строки скопируются, либо ни одной (если хоть одна нарушит constraint). Удобно для миграций данных и backfill.

RETURNING — получить вставленные значения

PostgreSQL умеет возвращать вставленные строки прямо из INSERT:

INSERT INTO orders (customer_id, amount)
VALUES (5, 100)
RETURNING id, created_at;
-- → возвращает строку с авто-сгенерированным id и created_at

Без RETURNING пришлось бы делать второй запрос (SELECT MAX(id) или currval('orders_id_seq')), и между ними возможна гонка с другой транзакцией. RETURNING решает атомарно.

RETURNING есть в PostgreSQL, SQLite (3.35+), Oracle. В MySQL — нет (там LAST_INSERT_ID()).

UPSERT через ON CONFLICT

«Вставь, а если уже есть — обнови» — типичная задача для миграций, импортов, идемпотентных операций. PostgreSQL решает через ON CONFLICT:

INSERT INTO users (email, display_name, last_login_at)
VALUES ('anya@example.com', 'Аня', NOW())
ON CONFLICT (email) DO UPDATE
  SET last_login_at = EXCLUDED.last_login_at,
      display_name  = EXCLUDED.display_name;

Логика:

  1. Пытаемся вставить.
  2. Если возникает конфликт по уникальному ограничению на email — выполняется UPDATE с указанными полями.
  3. Псевдо-таблица EXCLUDED содержит значения, которые мы пытались вставить (из VALUES).

Альтернативные формы:

  • ON CONFLICT (email) DO NOTHING — игнорировать дубликаты, не считать ошибкой.
  • ON CONFLICT ON CONSTRAINT users_email_key DO UPDATE ... — указать конкретный constraint по имени.
  • WHERE в UPDATE — условный апдейт: DO UPDATE SET ... WHERE users.last_login_at < EXCLUDED.last_login_at (обновляем только если новая дата свежее).

ON CONFLICT требует, чтобы существовало уникальное ограничение (UNIQUE, PRIMARY KEY или EXCLUDE) на колонке. Без него Postgres не знает, что считать «конфликтом».

В MySQL аналог — INSERT ... ON DUPLICATE KEY UPDATE. В стандарте SQL есть MERGE, и Postgres поддерживает его с 15-й версии — но ON CONFLICT пока выразительнее.

Частые ошибки новичков

1. Пропущенный список колонок. INSERT INTO users VALUES ('alice@...', 'Аня', 'RU') без списка зависит от порядка колонок в таблице. Кто-то добавит ALTER TABLE ... ADD COLUMN status — и старый код сломается. Всегда пиши список колонок.

2. Несоответствие количества значений. INSERT INTO t (a, b, c) VALUES (1, 2) — три колонки, два значения, ошибка. Иногда видно сразу, иногда нет (если values динамические из приложения).

3. Кавычки в строках. Строки и даты — одинарные кавычки. Двойные кавычки в PostgreSQL — для имён колонок:

-- Правильно
INSERT INTO users (name) VALUES ('Аня');

-- Неправильно — Postgres решит, что Аня это имя колонки
INSERT INTO users (name) VALUES ("Аня");

4. UPSERT без ON CONFLICT. Сценарий «проверь существование → если нет, INSERT» в коде приложения не атомарен — между проверкой и вставкой другая транзакция может вставить ту же строку. Уникальное ограничение + ON CONFLICT DO NOTHING — единственный надёжный способ.

5. id передан вручную, потом ломается автоинкремент. Если ты явно вставил id = 1000, а sequence стоит на 5 — следующий INSERT без id попробует поставить id = 6. И всё хорошо. Но если был insert id = 1, и кто-то уже до тебя вставил такой — конфликт. Лучше вообще не передавать id, пусть БД сама.

6. INSERT миллиарда строк одним запросом. PostgreSQL примет, но WAL-лог распухнет, репликация затормозит, всё. Bulk-импорт — батчами, либо через COPY FROM (специальная команда для bulk-load — в десятки раз быстрее любого INSERT).

Мини-резюме

  • INSERT INTO table (cols) VALUES (vals) — добавляет новую строку.
  • Список колонок указывай явно. Меньше боли при ALTER TABLE.
  • Несколько строк за раз — VALUES (..), (..), (..) (batch).
  • INSERT FROM SELECT — для копирования и backfill.
  • RETURNING — забрать сгенерированный id и другие значения сразу.
  • ON CONFLICT — UPSERT (вставь или обнови). Требует UNIQUE или PRIMARY KEY на колонке.

Закрепи на практике

Решай задачи в SQL-тренажёре с мгновенной проверкой и подсказками.

Открыть тренажёр