17. INSERT
🧭 Введение: почему вставка данных — это больше, чем одна команда
INSERT выглядит простой командой, но именно на вставке чаще всего появляютсяпервые прод-ошибки: дубли, «битые» ссылки, неверные типы и грязные данные.
Если вставлять данные без дисциплины, база быстро теряет целостность,
а исправление потом обходится дорого: миграции, ручная чистка, rollback.
а исправление потом обходится дорого: миграции, ручная чистка, rollback.
В этой лекции вы разберёте базовые и самые практичные паттерны:
INSERT ... VALUES;- множественную вставку;
INSERT ... SELECT;- типичные ошибки и способы их избежать.
💡 Совет:
Перед любым
INSERT задавайте себе 3 вопроса:- какие колонки заполняем, 2) какие ограничения могут сработать, 3) как проверим результат.
✅ Вывод:
Хороший
INSERT — это не просто запись строки, а безопасное добавление корректных данных.⚠️ Проблема -> решение
Типичная проблема:
- вставляют данные без списка колонок;
- не учитывают
NOT NULL,UNIQUE,FK; - грузят данные пачкой без проверки источника.
Последствия:
- «тихие» ошибки качества данных;
- падение вставок в проде;
- дубли и мусор, которые ломают отчёты.
Решение:
- всегда задавать явный список колонок;
- проверять ограничения до массовой вставки;
- использовать предсказуемые шаблоны вставки (single, batch, insert-select).
🟢 Если совсем просто:
Сначала проверяем схему и ограничения, потом вставляем данные.
🎯 Как понять, что этап прошёл успешно:
Каждая вставка либо проходит корректно, либо падает с понятной причиной.
🛠️ Чем помогает и как работает
INSERT нужен почти в любом CRUD-сценарии:- регистрация пользователей;
- создание заказов;
- загрузка справочников;
- перенос данных из staging-таблиц;
- наполнение отчётных таблиц.
🟢 Если совсем просто:
INSERT добавляет новые строки в таблицу по правилам схемы.🎯 Как понять, что этап прошёл успешно:
Вы умеете выбрать правильный способ вставки под задачу и объём данных.
Чем помогает:
INSERT ... VALUES— точечная вставка;- множественный
VALUES— быстрее для небольших пакетов; INSERT ... SELECT— перенос/преобразование данных между таблицами;- ограничения (
NOT NULL,UNIQUE,FK) защищают целостность.
Как это работает:
- Шаг 1: выбираем таблицу-приёмник и список колонок;
- Шаг 2: готовим значения или источник (
SELECT); - Шаг 3: выполняем вставку;
- Шаг 4: база валидирует ограничения;
- Шаг 5: проверяем, что вставлено именно то, что ожидали.
✅ Вывод:
Эффективная вставка данных опирается на явный контракт между SQL и схемой таблицы.
📚 Ключевые термины (простыми словами)
Перед практикой синхронизируем словарь.
🟢 Если совсем просто:
Нужно понимать, откуда берутся значения и какие ограничения их проверяют.
🎯 Как понять, что этап прошёл успешно:
Вы уверенно различаете single insert, batch insert и insert-select.
- Single-row insert — вставка одной строки.
- Batch insert — вставка нескольких строк одним запросом.
- Insert-select — вставка результата
SELECTв другую таблицу. - Target columns — колонки таблицы, которые вы заполняете.
- Constraint violation — ошибка ограничения (
NOT NULL,UNIQUE,FK,CHECK). - Default value — значение по умолчанию, если колонка не передана.
- Idempotent insert — вставка, которая не создаёт дубли при повторном запуске.
✅ Вывод:
Эти термины покрывают основу для безопасной работы с
INSERT.➕ 1. INSERT ... VALUES: базовая вставка одной строки
Это основной шаблон для CRUD-операций: создать пользователя, заказ, комментарий.
🟢 Если совсем просто:
INSERT ... VALUES добавляет одну новую строку с конкретными значениями.🎯 Как понять, что этап прошёл успешно:
Строка вставлена, ограничения не нарушены, структура данных корректна.
Назначение:
Точечно добавить запись в таблицу.
Простыми словами:
Вы явно перечисляете колонки и соответствующие значения.
Для новичка:
Всегда указывайте список колонок, даже если кажется, что «и так работает».
Аналогия:
Это как заполнять форму с фиксированными полями.
Пример:
INSERT INTO users ( email, full_name, is_active)VALUES ( 'alice@example.com', 'Alice Johnson', true);Production-бонус:
INSERT ... RETURNING (PostgreSQL):
После вставки часто нужен id, created_at или дефолтные вычисленные поля.RETURNING позволяет получить их сразу, без отдельного SELECT.INSERT INTO users ( email, full_name, is_active)VALUES ( 'alice@example.com', 'Alice Johnson', true)RETURNING id, email, created_at;✅ Вывод:
RETURNING убирает лишний round-trip и снижает риск гонок «вставил -> потом ищу».🔎 Как это происходит на практике:
- Контекст: регистрация нового пользователя.
- Действия: backend отправляет
INSERTв таблицуusers. - Результат: новая строка появляется в базе.
Характеристики:
- просто и предсказуемо;
- удобно для API-ручек создания;
- сразу валидируется ограничениями таблицы.
Когда использовать:
Когда нужно создать одну новую сущность.
✅ Вывод:
Одиночный
INSERT ... VALUES — фундамент любой операции create.📦 2. Множественная вставка: несколько строк одним запросом
Когда нужно вставить пачку данных (например, seed или импорт небольшого списка),
выгоднее сделать один
выгоднее сделать один
INSERT с несколькими наборами значений.🟢 Если совсем просто:
Один запрос может вставить сразу много строк.
🎯 Как понять, что этап прошёл успешно:
Все строки вставлены одним запросом, структура и типы соблюдены.
Назначение:
Сократить накладные расходы на множество отдельных запросов.
Простыми словами:
VALUES (...), (...), (...) — это пакетная вставка.Для новичка:
Следите, чтобы порядок и число значений совпадали во всех наборах.
Аналогия:
Это как загрузить коробку документов сразу, а не по одному листу.
Пример:
INSERT INTO products ( sku, title, price)VALUES ('SKU-1001', 'Notebook', 1290.00), ('SKU-1002', 'Mouse', 890.00), ('SKU-1003', 'Keyboard', 2190.00);🔎 Как это происходит на практике:
- Контекст: первичное наполнение каталога.
- Действия: DevOps/Backend вставляет стартовый набор товаров.
- Результат: таблица получает несколько строк за один round-trip.
Характеристики:
- быстрее, чем множество одиночных insert;
- удобна для небольших batch-операций;
- для очень больших объёмов (100k+ строк) обычно используют bulk-load (
COPYв PostgreSQL), а не гигантскийVALUES; - любая ошибка ограничения может прервать весь запрос.
Атомарность statement (важное правило):
Один
Если хотя бы одна строка нарушит constraint, весь statement откатится (ничего не вставится), если не используется
INSERT ... VALUES (...), (...), ... — это один statement.Если хотя бы одна строка нарушит constraint, весь statement откатится (ничего не вставится), если не используется
ON CONFLICT.Мини-пример:
INSERT INTO users ( email)VALUES ('a@a.com'), ('a@a.com');✅ Вывод:
Batch-вставка удобна, но работает по принципу «всё или ничего».
Когда использовать:
Когда вставляете небольшой или средний фиксированный набор данных.
✅ Вывод:
Множественная вставка — базовый способ ускорить загрузку без усложнения SQL.
🔁 3. INSERT ... SELECT: вставка из другой таблицы
Этот паттерн нужен для ETL-задач, миграций и наполнения витрин,
когда данные берутся не «руками», а из результата запроса.
когда данные берутся не «руками», а из результата запроса.
🟢 Если совсем просто:
INSERT ... SELECT копирует или трансформирует данные из источника в приёмник.🎯 Как понять, что этап прошёл успешно:
В целевой таблице оказываются только нужные и корректно преобразованные строки.
Назначение:
Массово переносить или агрегировать данные внутри БД.
Простыми словами:
SELECT строит набор строк, INSERT сохраняет его в другую таблицу.Для новичка:
Сначала выполните только
SELECT, убедитесь в данных, потом добавляйте INSERT.Аналогия:
Это как переливать воду через фильтр: сначала фильтруем, потом наливаем в новый сосуд.
Пример:
INSERT INTO active_users ( user_id, email, loaded_at)SELECT u.id AS user_id, u.email, NOW() AS loaded_atFROM users uWHERE u.is_active = true;Production-приём: дедупликация источника перед вставкой:
Если в
staging уже есть дубли, NOT EXISTS относительно целевой таблицы может быть недостаточно:UNIQUE может «взорваться» из-за дублей внутри самого источника.Вариант A:
SELECT DISTINCT:INSERT INTO users ( email, full_name)SELECT DISTINCT s.email, s.full_nameFROM staging_users sWHERE s.email IS NOT NULL;Вариант B:
DISTINCT ON (PostgreSQL), выбрать «лучшую» строку:INSERT INTO users ( email, full_name)SELECT DISTINCT ON (s.email) s.email, s.full_nameFROM staging_users sWHERE s.email IS NOT NULLORDER BY s.email, s.loaded_at DESC;✅ Вывод:
Перед
INSERT ... SELECT часто нужно дедуплицировать сам источник, иначе UNIQUE может падать даже при anti-join.🔎 Как это происходит на практике:
- Контекст: обновление служебной таблицы сегментов.
- Действия: из
usersвыбирают активных и вставляют вactive_users. - Результат: целевая таблица обновляется без выгрузки в приложение.
Характеристики:
- мощно для массовых операций;
- позволяет вставлять результат вычислений;
- требует особенно внимательной проверки условий
SELECT.
Когда использовать:
Когда источник данных уже находится в БД.
✅ Вывод:
INSERT ... SELECT — главный инструмент для серверной загрузки и трансформации данных.♻️ 4. Идемпотентность: ON CONFLICT, NOT EXISTS и гонки
В проде вставки часто выполняются повторно: ретраи, cron, параллельные воркеры.
Поэтому логика должна быть идемпотентной.
Поэтому логика должна быть идемпотентной.
🟢 Если совсем просто:
Запрос должен корректно переживать повторный запуск без дублей.
🎯 Как понять, что этап прошёл успешно:
Повторный запуск не ломает данные и не создаёт лишние строки.
Назначение:
Безопасно вставлять данные в условиях конкуренции и повторных запусков.
Простыми словами:
В PostgreSQL основной production-шаблон —
ON CONFLICT (UPSERT).Для новичка:
ON CONFLICT работает только если есть UNIQUE индекс/constraint по ключу конфликта.Аналогия:
Это как вход по ключу: если такой ключ уже есть, решаем, пропустить или обновить запись.
Пример 1: вставь, если ещё нет (
DO NOTHING):INSERT INTO users ( email, full_name)VALUES ( 'a@a.com', 'Alice')ON CONFLICT (email) DO NOTHING;Пример 2: вставь или обнови (
DO UPDATE):INSERT INTO users ( email, full_name, updated_at)VALUES ( 'a@a.com', 'Alice', NOW())ON CONFLICT (email) DO UPDATESET full_name = EXCLUDED.full_name, updated_at = EXCLUDED.updated_at;Пример 3: upsert + возврат актуальной записи (
ON CONFLICT + RETURNING):INSERT INTO users ( email, full_name)VALUES ( 'a@a.com', 'Alice')ON CONFLICT (email) DO UPDATESET full_name = EXCLUDED.full_nameRETURNING id, email, full_name;✅ Вывод:
ON CONFLICT + RETURNING часто закрывает сразу две задачи: идемпотентность и возврат актуальной строки.Важное уточнение про конкуренцию:
NOT EXISTS логически корректен, но под нагрузкой возможна гонка:- два процесса одновременно видят, что строки ещё нет;
- оба пытаются вставить;
- один падает по
UNIQUE.
Для высокой конкуренции
В других СУБД аналогичный паттерн реализуют через
ON CONFLICT обычно надёжнее.В других СУБД аналогичный паттерн реализуют через
MERGE/UPSERT-механику.🔎 Как это происходит на практике:
- Контекст: синхронизация пользователей из внешней CRM.
- Действия: повторные загрузки идут параллельно.
- Результат:
ON CONFLICTстабилизирует поведение и убирает лишние аварии.
Характеристики:
- хорошо работает при ретраях и параллельных воркерах;
- снижает шум ошибок по
UNIQUE; - требует правильно настроенного уникального ключа.
Когда использовать:
Когда вставка может запускаться повторно или конкурентно.
✅ Вывод:
Для production-идемпотентности под нагрузкой
ON CONFLICT обычно предпочтительнее NOT EXISTS.🔐 5. Транзакции и порядок FK-вставок
Если несколько
INSERT логически связаны, они должны выполняться атомарно.🟢 Если совсем просто:
Связанные вставки делаем в одной транзакции.
🎯 Как понять, что этап прошёл успешно:
Нет «полузаписей» (например, заказ без позиций).
Назначение:
Гарантировать целостность при множественных связанных вставках.
Простыми словами:
Либо фиксируем все шаги, либо откатываем все.
Для новичка:
Для
FK действует правило порядка: сначала родитель, потом ребёнок.Аналогия:
Нельзя добавить пункт меню к ресторану, которого ещё не существует.
Пример:
BEGIN; INSERT INTO orders ( user_id, status, created_at)VALUES ( 123, 'new', NOW())RETURNING id; INSERT INTO order_items ( order_id, sku, qty)VALUES ( :order_id, 'SKU-1', 2); COMMIT;Правило порядка FK-вставок:
- сначала
users, потомorders; - сначала
orders, потомorder_items.
🔎 Как это происходит на практике:
- Контекст: checkout в интернет-магазине.
- Действия: создают заказ и позиции в одной транзакции.
- Результат: система не получает частично сохранённые данные.
Характеристики:
- атомарность для связанных сущностей;
- меньше data-corruption сценариев;
- требует явного управления транзакцией.
Когда использовать:
Когда бизнес-операция состоит из нескольких зависимых вставок.
✅ Вывод:
Если данные связаны, транзакция и правильный порядок вставок обязательны.
🚨 6. Типичные ошибки при вставке данных
Большинство падений
INSERT связаны не с синтаксисом, а с нарушением ограничений.🟢 Если совсем просто:
Чаще всего падает из-за
NOT NULL, UNIQUE, FK или несовпадения типов.🎯 Как понять, что этап прошёл успешно:
Вы заранее понимаете, какие ограничения могут сработать, и проверяете их до вставки.
Назначение:
Уметь быстро диагностировать и предотвращать ошибки вставки.
Простыми словами:
БД защищает целостность и не пропускает некорректные строки.
Для новичка:
Смотрите текст ошибки: там обычно прямо указано, какое ограничение нарушено.
Аналогия:
Это как контроль на входе: без пропуска (валидных данных) внутрь не пустят.
Мини-демо ошибок от СУБД:
INSERT INTO orders (user_id, status)VALUES (999999, 'new'); INSERT INTO users (email, full_name)VALUES ('a@a.com', 'A'); INSERT INTO users (email, full_name)VALUES ('a@a.com', 'B'); 🔎 Как это происходит на практике:
- Контекст: production API создаёт заказ.
- Действия: приходит payload с неверным
user_idили дубликатом email. - Результат: база отклоняет запись и возвращает понятную ошибку.
Характеристики:
- ограничения защищают данные от мусора;
- ошибки помогают локализовать проблему;
- при пакетной вставке часто падает весь запрос.
Когда использовать:
Всегда, как контрольный чек-лист перед запуском insert-потока.
✅ Вывод:
Понимание ошибок вставки — это ключ к устойчивому CRUD и чистым данным.
⚡ 7. Комбинируем паттерны в боевом сценарии
В проде часто нужен pipeline:
временная загрузка -> очистка/фильтр ->
временная загрузка -> очистка/фильтр ->
INSERT ... SELECT в целевую таблицу.🟢 Если совсем просто:
Сначала кладём сырые данные, потом переносим только валидные.
🎯 Как понять, что этап прошёл успешно:
В целевой таблице нет дублей и нарушений ограничений.
Назначение:
Сделать вставку предсказуемой и безопасной при массовых загрузках.
Простыми словами:
Не пишем «грязь» сразу в production-таблицу.
Для новичка:
Для читабельного ETL подойдёт
NOT EXISTS, но для конкуренции под нагрузкой чаще надёжнее ON CONFLICT.Аналогия:
Это как сортировочная линия: сначала проверка, потом отправка в основной склад.
Пример:
INSERT INTO users ( email, full_name, is_active)SELECT s.email, s.full_name, trueFROM staging_users sWHERE s.email IS NOT NULL AND s.full_name IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM users u WHERE u.email = s.email );🔎 Как это происходит на практике:
- Контекст: регулярный импорт пользователей из внешней системы.
- Действия: фильтруют некорректные строки и исключают существующие email.
- Результат: в
usersпопадают только новые и валидные записи.
Характеристики:
- снижает риск дубликатов;
- повышает качество данных;
- хорошо масштабируется для ETL.
Когда использовать:
Когда вставляете данные из внешних источников или staging.
✅ Вывод:
Комбинация
INSERT ... SELECT + фильтрация + anti-duplicate — базовый прод-паттерн загрузки.🆚 Сравнение способов вставки
| Подход | Что делает | Когда подходит | Риск |
|---|---|---|---|
INSERT ... VALUES | Добавляет одну строку | CRUD create, точечные операции | Ошибка в данных сразу ломает запрос |
INSERT ... VALUES (...), (...) | Добавляет несколько строк | Небольшие пакетные вставки | Любая ошибка может уронить весь batch |
INSERT ... SELECT | Вставляет результат запроса | ETL, миграции, синхронизация | Неверный SELECT создаст мусор в масштабе |
✅ Вывод:
Способ вставки выбирается по источнику данных и объёму операции.
🧠 Must-Know (запомнить)
- Всегда указывайте список целевых колонок в
INSERT. - После create-операций в PostgreSQL часто сразу используйте
RETURNING. - Проверяйте
NOT NULL,UNIQUE,FKдо массовой вставки. - Для пакетов используйте множественный
VALUES, чтобы снизить накладные расходы. - Batch
INSERT ... VALUES (...), (...)атомарен: безON CONFLICTошибка одной строки откатывает весь statement. - Для очень больших загрузок используйте bulk-load (
COPY), а не гигантскийVALUES. - Перед
INSERT ... SELECTсначала отдельно запускайтеSELECTи валидируйте результат. - Перед
INSERT ... SELECTчасто нужно дедуплицировать сам источник (DISTINCT/DISTINCT ON). - Для идемпотентности под конкуренцией чаще используйте
ON CONFLICT, а не толькоNOT EXISTS. ON CONFLICTтребует уникальный индекс/constraint по ключу конфликта.- В PostgreSQL часто комбинируйте
ON CONFLICTсRETURNING, чтобы сразу вернуть актуальную запись. - Для связанных вставок (
order + items) используйте транзакцию. - Соблюдайте порядок
FK: сначала parent, потом child. - Ограничения БД — это защита данных, а не «помеха разработке».
- Для повторных загрузок продумывайте защиту от дублей.
- Ошибки вставки нужно читать по имени constraint — это ускоряет диагностику.
✅ Вывод:
Эти правила закрывают основной безопасный минимум для работы с
INSERT.❌ Частые мифы
❌ Миф: В
INSERT можно не писать список колонок, БД сама разберётся.
✅ Как правильно: список колонок лучше указывать всегда.
📎 Почему это важно: изменения схемы не сломают вставку неожиданно.❌ Миф: Если запрос синтаксически верный, данные точно корректные.
✅ Как правильно: корректность гарантируется ограничениями и проверкой входа.
📎 Почему это важно: иначе мусор попадёт в базу и всплывёт позже в отчётах.
❌ Миф: Массовую загрузку проще делать только через приложение.
✅ Как правильно:
INSERT ... SELECT часто быстрее и надёжнее внутри БД.
📎 Почему это важно: меньше round-trip и меньше рисков рассинхронизации.❌ Миф: Ошибки constraint — это проблема базы, а не запроса.
✅ Как правильно: constraint ошибки показывают, что insert нарушает контракт схемы.
📎 Почему это важно: если исправить причину, качество данных резко растёт.
🎤 Часто спрашивают на собеседованиях
❓ Вопрос: Почему важно указывать колонки в
INSERT?
✅ Ответ: это защищает запрос от изменений порядка колонок и делает вставку предсказуемой.❓ Вопрос: Когда использовать множественный
VALUES?
✅ Ответ: когда нужно вставить небольшой пакет строк быстрее, чем отдельными запросами.❓ Вопрос: Чем
INSERT ... SELECT отличается от обычного INSERT?
✅ Ответ: он вставляет набор строк из результата запроса, а не фиксированные литералы.❓ Вопрос: Зачем нужен
INSERT ... RETURNING?
✅ Ответ: чтобы сразу получить id/дефолтные поля после вставки без отдельного запроса.❓ Вопрос: Почему
INSERT может падать даже при правильном синтаксисе?
✅ Ответ: из-за ограничений таблицы (NOT NULL, UNIQUE, FK, CHECK).❓ Вопрос: Как снизить риск дублей при повторной загрузке?
✅ Ответ: использовать anti-duplicate логику (
NOT EXISTS, ON CONFLICT в PostgreSQL).❓ Вопрос: Чем
ON CONFLICT лучше NOT EXISTS под нагрузкой?
✅ Ответ: ON CONFLICT устойчивее к гонкам конкурентных вставок и обычно даёт меньше шумных падений по UNIQUE.❓ Вопрос: Когда несколько insert-запросов нужно оборачивать в транзакцию?
✅ Ответ: когда операции логически связаны (например,
orders и order_items) и должны фиксироваться атомарно.❓ Вопрос: Что делать перед запуском
INSERT ... SELECT в проде?
✅ Ответ: сначала прогнать только SELECT, проверить объём и качество набора, потом вставлять.🚨 Типичные ошибки
- Вставлять без списка колонок.
- Путать порядок значений относительно колонок.
- Делать
INSERT, потом отдельныйSELECTзаid, вместоRETURNING. - Игнорировать
NOT NULLи вставлятьNULLв обязательные поля. - Ловить дубликаты только на уровне приложения, без
UNIQUEв БД. - Ожидать, что при batch-вставке сохранится «хотя бы часть» строк после ошибки constraint.
- Использовать только
NOT EXISTSв высококонкурентной вставке и не учитывать гонки. - Загружать
INSERT ... SELECTбез предварительной проверки выборки. - Не дедуплицировать источник (
staging) и получать падение поUNIQUEвнутриINSERT ... SELECT. - Не учитывать внешние ключи и получать ошибки на дочерних таблицах.
- Вставлять child-запись раньше parent-строки и ловить
FK-ошибки. - Повторно запускать загрузку без защиты от дублей.
✅ Вывод:
Почти все ошибки вставки предсказуемы, если работать от схемы и ограничений.
✅ Best Practices
- Всегда пишите
INSERT INTO table (col1, col2, ...). - Для CRUD-create в PostgreSQL добавляйте
RETURNING id, ..., если данные нужны сразу. - Для API-вставок валидируйте входные данные до SQL.
- Для batch-вставок держите разумный размер пакета.
- Для конкурентной идемпотентности предпочитайте
ON CONFLICT. - Для upsert в PostgreSQL часто используйте
ON CONFLICT ... RETURNING. - Для
INSERT ... SELECTприменяйте явные фильтры и защиту от дублей. - Для
INSERT ... SELECTзаранее дедуплицируйте источник, если ключ может повторяться. - Критичные массовые вставки выполняйте в транзакции.
- Для связанных сущностей соблюдайте порядок
parent -> child. - Для очень больших объёмов выбирайте bulk-load (
COPY/loader). - После вставки проверяйте результат (
COUNT, выборка последних строк, бизнес-проверки). - Логируйте и анализируйте constraint-ошибки как часть observability.
✅ Вывод:
Сильная практика
INSERT — это баланс скорости вставки и строгого качества данных.🏁 Заключение
INSERT — базовая CRUD-команда, но именно на ней строится качество всей базы.Если вы уверенно работаете с
VALUES, batch и INSERT ... SELECT,и понимаете ограничения схемы, ваши данные остаются чистыми и устойчивыми.
✅ Вывод:
Надёжная вставка данных = явные колонки + корректный источник + контроль ограничений.