RETURNING
Тема дорожной карты · PostgreSQL
Предложение RETURNING в PostgreSQL расширяет операторы INSERT, UPDATE и DELETE возможностью возвращать строки, затронутые модификацией данных, устраняя необходимость в отдельном запросе SELECT для получения сгенерированных значений или подтверждения изменений. Чаще всего RETURNING используется для получения автоматически сгенерированных первичных ключей после INSERT — например, INSERT INTO orders (...) VALUES (...) RETURNING id — что необходимо в скриптах администрирования баз данных и в коде приложения, которому нужен идентификатор новой строки. Предложение может возвращать любые выражения над столбцами, включая вычисляемые значения, что делает его полезным для захвата временны́х меток updated_at, значений последовательностей из столбцов SERIAL или IDENTITY, или полной обновлённой строки. RETURNING в PostgreSQL мощнее, чем LAST_INSERT_ID() в MySQL, поскольку работает с UPDATE и DELETE и может возвращать несколько строк при затрагивании нескольких строк. Использование RETURNING сокращает число обращений к серверу PostgreSQL и упрощает логику приложения — это одновременно выигрыш в производительности и улучшение качества кода.
Как это работает
RETURNING использует SQL: SELECT, INSERT, UPDATE, DELETE. Расширения Postgres: INSERT ... ON CONFLICT (col) DO UPDATE (upsert), INSERT ... RETURNING (получить строку без второго SELECT), UPDATE ... WHERE ... RETURNING, DELETE ... RETURNING, CTE (WITH ... AS), включая writable CTE (WITH inserted AS (INSERT ...) SELECT ...). Параметризованные запросы ($1, $2 в libpq / ? в драйверах) обязательны для безопасности.
Когда применять
INSERT ... ON CONFLICT — для идемпотентных записей (без read-then-write race). RETURNING — когда нужна строка сразу после записи. CTE — для читаемости сложных запросов; оптимизатор инлайнит простые non-recursive CTE с 12. Всегда параметризуйте — конкатенация строк это #1 путь SQL-инъекции. Многошаговые операции — в транзакцию.
Типичные ошибки
Ловушки RETURNING: DELETE FROM tbl без WHERE (пуф — данных нет); UPDATE без WHERE с тем, что ожидали (используйте BEGIN; + SELECT count(*) WHERE ...; потом UPDATE); огромные IN-списки WHERE id IN (1,2,...,10000) вместо = ANY(array) или join (парсер взрывается); нет транзакций для multi-row бизнес-операций (partial writes на крэше).