Перейти к содержанию

INSERT и RETURNING

В прошлом уроке база выдавала идентификатор черновика. Теперь рассмотрим создание записи как законченную операцию клиента. Приложению недостаточно узнать, что команда прошла: ему нужен конкретный созданный объект с ключом и принятыми значениями. INSERT ... RETURNING позволяет получить эти данные из самой операции, сохраняя связь между отправленным запросом и ответом.

Работаем с PostgreSQL 17.11 и самостоятельным снимком catalog-db/lesson-07 из архива каталога. Он заново задаёт шесть исходных курсов и не продолжает расходование identity из предыдущей папки. SQL не запускался. Учебные вставки находятся внутри BEGIN и ROLLBACK, поэтому строки не должны оставаться после завершения примера, хотя значения генератора могут быть потрачены.

Явные колонки вставки

Новый курс относится к фронтенду и пока является черновиком. Укажем только значения, которые действительно задаёт операция:

BEGIN;
INSERT INTO catalog.courses
(topic_id, slug, title, planned_lessons, price)
VALUES (1, 'css-motion', 'Анимация CSS', 8, 0)
RETURNING id, slug, title, status, published_at;

После свежего reset ожидается ключ семь. Статус равен draft, а время публикации отсутствует. Эти два значения не переданы явно, но итоговая строка всё равно получила согласованное состояние. Ключ создан identity; статус взят из значения по умолчанию. Такой ответ полезнее предположения клиента о том, что получилось в таблице.

Список колонок важен даже в небольшом примере. Если написать VALUES без указания целевых полей, значения будут зависеть от порядка всех колонок таблицы. Добавление нового поля в схему может сделать старый запрос непонятным или неверным. Явная запись говорит, какую часть объекта создаёт операция, и отделяет её от внутренних полей базы.

При этом значение по умолчанию используется только для непереданной колонки или явного DEFAULT. Если клиент передаст NULL в обязательное поле, сервер не заменит его автоматически хорошим значением. Поэтому преобразование формы нужно обсуждать отдельно: пустая строка, отсутствие ключа в JSON и отсутствующее значение SQL могут иметь разные значения для предметной модели.

Здесь вставка получила topic_id = 1. Это число корректно только относительно выбранного снимка, где первая тема — фронтенд. В настоящем клиенте тему обычно выбирают по существующему объекту, а не предполагают глобальное значение одного номера. Внешний ключ помогает отклонить ссылку на отсутствующую тему, но не угадывает, какую из существующих тем хотел выбрать пользователь.

Ответ именно изменённой строки

RETURNING формирует результат из строк, затронутых командой. Механизм описан в документации возвращаемых данных PostgreSQL. Мы запрашиваем нужные поля явно, вместо RETURNING *, чтобы учебный ответ оставался понятным и не расширялся незаметно при добавлении новой колонки.

Представьте альтернативу: вставить курс, затем выполнить SELECT max(id). Такой запрос получает максимальный ключ всей таблицы, а не обязательно ключ вашей операции. Другой клиент мог успеть добавить объект. Поиск по уникальному slug лучше максимума, но всё равно требует дополнительного чтения и обсуждения изменений между обменами. Возвращение созданной строки естественно привязано к исходной команде.

Полученный ответ ещё не означает подтверждённую фиксацию транзакции. В учебном примере мы специально читаем объект до отката. Клиент видит созданную строку в своей операции, а затем выбирает ROLLBACK. Если приложение передало читателю ссылку до успешного завершения изменения, оно может показать объект, который в итоге не сохранился.

В lesson.sql после вставки выполняется чтение того же slug и откат:

SELECT id, slug
FROM catalog.courses
WHERE slug = 'css-motion';
ROLLBACK;
SELECT id, slug
FROM catalog.courses
WHERE slug = 'css-motion';

Первое чтение внутри транзакции должно найти курс. Второе чтение после отката должно вернуть пустой набор. Разница объясняется границей изменения, а не разным условием: текст выборки одинаков. Полную работу транзакций серия разберёт позднее; здесь достаточно отличать ответ изменённой строки от её окончательного сохранения.

Несколько создаваемых объектов

Одна вставка может передать несколько наборов значений. Создадим ещё два временных черновика в новой транзакции:

BEGIN;
INSERT INTO catalog.courses
(topic_id, slug, title, planned_lessons, price)
VALUES
 (1, 'css-colors', 'Цвет в CSS', 6, 0),
 (4, 'design-basics', 'Основы дизайна', 10, 0)
RETURNING id, slug, topic_id;
ROLLBACK;

Ожидаются две возвращённые строки. Не связывайте их с исходными значениями исключительно по позиции в ответе: порядок результата без отдельного договора не является удобным универсальным ключом сопоставления. У каждой строки есть уникальный slug, поэтому клиент может соотнести созданные объекты по нему. Числовые ключи также нельзя вычислять как «первый плюс один» вместо чтения ответа.

В этом конкретном последовательном снимке после первой потраченной identity ожидаются следующие свободные значения. Однако статья не требует от приложения вычислять их заранее. Если вы вручную добавили ещё один объект или повторили пример без reset, генератор продолжит свою историю. Смысл RETURNING как раз состоит в том, чтобы принять реальные выданные значения.

Многорядная вставка имеет общие правила команды. Если одна строка нарушает обязательность или уникальность, нельзя считать остальные подтверждённо сохранёнными только потому, что они расположены выше в листинге. При будущей ручной работе клиент должен остановиться на ошибке, а состояние транзакции потребует корректного завершения. Ошибочные варианты намеренно не помещены в автоматически выполняемый снимок.

Создание и публикация разделены

У всех новых объектов примера статус черновика. Это помогает не смешивать создание редакторской карточки с публикацией курса. Для опубликованной записи понадобятся статус и момент публикации одновременно, поскольку схема закрепляет их соответствие. Если форму создания сразу сделать формой публикации, её договор станет шире и должен включать дополнительные проверки содержимого.

Поле created_at заполняется временем текущей операции по умолчанию. Мы не приводим выдуманный точный ответ часов: он зависит от будущего выполнения. У исходных строк время зафиксировано в reset, у новой строки оно будет фактическим для выбранной среды. Различать фиксированное учебное значение и результат работы сервера важно даже в простом примере.

Что именно возвращать клиенту

Веб-приложение может потребовать разные ответы для редактора и посетителя. Редактору полезен ключ созданного черновика, чтобы перейти к его форме. Посетителю нельзя показывать неподготовленную карточку только потому, что INSERT уже вернул её название. Выбор полей результата не задаёт автоматически разрешённую аудиторию.

Точный список RETURNING помогает читать этот договор. Например, отдавая status, клиент может показать подпись «Черновик» по принятому состоянию. Отдавая лишь код, он должен получить остальные сведения другим способом или честно не показывать их. Случайное использование * удобно для короткого эксперимента, но делает ответ зависимым от всех будущих полей таблицы.

Если приложение отправляет созданную запись в очередь последующей публикации, нужно согласовать такое сообщение с фактическим сохранением. Учебный откат хорошо показывает причину: ответ строки появился, а объект в итоге отсутствует. Подробный механизм согласования внешних действий выходит за границы данной вставки, однако сама граница уже видна и не позволяет считать любой возвращённый ключ подтверждением законченной работы.

Измените при будущем изучении возвращаемый список: оставьте только id и slug, затем подумайте, какой информации клиенту теперь не хватает. Если интерфейс показывает итоговый статус, его придётся прочитать или обоснованно получить из договора операции. Именно явный ответ помогает избежать скрытых предположений. Теперь создание записи имеет понятные входные значения, согласованную строку и результат, который можно использовать после успешного завершения изменения.

Оглавление курса.