Что такое RETURNING в UPDATE или INSERT и как он помогает
Что проверяют
Знаете ли вы, что после записи не нужен второй запрос за результатом. В Go это особенно заметно: драйверы PostgreSQL не поддерживают LastInsertId, и без RETURNING идентификатор новой строки взять негде.
Как отвечать
RETURNING приписывается к INSERT, UPDATE или DELETE и отдаёт колонки затронутых строк тем же запросом. Значения приходят уже после срабатывания триггеров и подстановки значений по умолчанию, то есть это реальное состояние строки, а не то, что вы отправляли.
var id int64
var createdAt time.Time
err := db.QueryRowContext(ctx,
`INSERT INTO users (email) VALUES ($1) RETURNING id, created_at`,
email,
).Scan(&id, &createdAt)Что это даёт: один поход в базу вместо записи плюс отдельного SELECT и никакой гонки между ними. Для UPDATE возвращаются новые значения, для DELETE строка, которой уже нет, что удобно для аудита. Строк может быть несколько: UPDATE ... RETURNING id читается через Query с обходом rows и обязательной проверкой rows.Err().
Стоит оговорить поддержку: это расширение PostgreSQL, оно же есть в SQLite и с оговорками в MariaDB, а в MySQL аналога нет, там остаётся LAST_INSERT_ID.
Где спотыкаются
Вызывают Exec вместо QueryRow. Строка запишется, но результат RETURNING будет выброшен, а часть драйверов на таком запросе вернёт ошибку. Если запрос что-то отдаёт, читать его надо как выборку.
Пишут INSERT ... ON CONFLICT DO NOTHING RETURNING id и удивляются sql.ErrNoRows при конфликте. Строка не вставлена, значит возвращать нечего; нужен либо DO UPDATE SET, либо отдельная ветка на этот случай.
Считают RETURNING заменой транзакции. Он экономит запрос, но если следом идёт вторая операция, зависящая от полученного идентификатора, обе всё равно оборачиваются в транзакцию, иначе сбой оставит половину работы.