Как вернуть только что вставленную строку в базе данных
Что проверяют
Знаете ли вы возможности своей СУБД и драйвера, а не только общий database/sql. Правильный ответ укладывается в один запрос, без второго SELECT. Следом спрашивают про LastInsertId и про то, что вернётся при ON CONFLICT DO NOTHING.
Как отвечать
Для PostgreSQL основной способ это RETURNING:
database/sql:db.QueryRowContext(ctx, "INSERT INTO users (email) VALUES ($1) RETURNING id, email, created_at", email).Scan(&u.ID, &u.Email, &u.CreatedAt). Вставка и чтение идут за один round trip, в ответе уже значения по умолчанию и правки изBEFORE-триггеров;- pgx v5: тот же запрос через
pool.Query, а в структуру удобно собратьpgx.CollectOneRow(rows, pgx.RowToStructByName[User]). Для нового кода на PostgreSQL обычно берут pgx, он лучше поддерживает типы PostgreSQL и активнее развивается, чемlib/pq; - sqlx:
db.GetContext(ctx, &u, query, args...)сканируетRETURNING *в структуру по тегамdb.
В других СУБД по-своему. MySQL RETURNING не поддерживает: Exec возвращает sql.Result, из него берут LastInsertId() и дальше делают SELECT по этому id. MariaDB умеет INSERT ... RETURNING с версии 10.5, SQLite с 3.35, в SQL Server аналог OUTPUT INSERTED.*. В драйверах PostgreSQL LastInsertId() не работает и возвращает ошибку.
Граничный случай: при ON CONFLICT DO NOTHING RETURNING id конфликтная строка ничего не вернёт, и Scan получит sql.ErrNoRows. Проверяйте его через errors.Is и решайте, ошибка это или «уже существует». Если строка нужна в любом случае, используют ON CONFLICT ... DO UPDATE или отдельный SELECT.
Где спотыкаются
Делают INSERT, а потом SELECT max(id). При параллельной вставке запрос вернёт чужую строку.
Переносят привычку из MySQL и вызывают LastInsertId() на PostgreSQL, не зная про RETURNING.
Используют db.Query вместо QueryRow и не закрывают rows, соединение не возвращается в пул. Ещё одна мелочь, которую замечают на ревью: ошибку оборачивают через %v, а не %w, и вызывающий код уже не может проверить её через errors.Is.