SQL

БЛОК 6. CRUD — 17. INSERT

📚 22 вопросовПройти тест →
Лекция

БЛОК 6. CRUD — 17. INSERT

SQL

17. INSERT

🧭 Введение: почему вставка данных — это больше, чем одна команда

INSERT выглядит простой командой, но именно на вставке чаще всего появляются
первые прод-ошибки: дубли, «битые» ссылки, неверные типы и грязные данные.
Если вставлять данные без дисциплины, база быстро теряет целостность,
а исправление потом обходится дорого: миграции, ручная чистка, rollback.
В этой лекции вы разберёте базовые и самые практичные паттерны:
  • INSERT ... VALUES;
  • множественную вставку;
  • INSERT ... SELECT;
  • типичные ошибки и способы их избежать.
💡 Совет: Перед любым INSERT задавайте себе 3 вопроса:
  1. какие колонки заполняем, 2) какие ограничения могут сработать, 3) как проверим результат.
Вывод: Хороший INSERT — это не просто запись строки, а безопасное добавление корректных данных.

⚠️ Проблема -> решение

Типичная проблема:
  1. вставляют данные без списка колонок;
  2. не учитывают NOT NULL, UNIQUE, FK;
  3. грузят данные пачкой без проверки источника.
Последствия:
  1. «тихие» ошибки качества данных;
  2. падение вставок в проде;
  3. дубли и мусор, которые ломают отчёты.
Решение:
  1. всегда задавать явный список колонок;
  2. проверять ограничения до массовой вставки;
  3. использовать предсказуемые шаблоны вставки (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 (важное правило): Один 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 логически корректен, но под нагрузкой возможна гонка:
  1. два процесса одновременно видят, что строки ещё нет;
  2. оба пытаются вставить;
  3. один падает по 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,
и понимаете ограничения схемы, ваши данные остаются чистыми и устойчивыми.
Вывод: Надёжная вставка данных = явные колонки + корректный источник + контроль ограничений.
🎯

Проверьте знания

Закрепите материал — пройдите тест по теме «БЛОК 6. CRUD — 17. INSERT»

Пройти тест →