Верните только что вставленную строку в Postgres с помощью sqlx

Я использую https://github.com/jmoiron/sqlx для отправки запросов в Postgres.

Можно ли вернуть данные всей строки при вставке новой строки?

Вот запрос, который я запускаю:

result, err := Db.Exec("INSERT INTO users (name) VALUES ($1)", user.Name)

Или я должен просто использовать свой существующий user структура как источник правды о новой записи в базе данных?

3 ответа

Вот документы о транзакции sqlx:

Результат имеет два возможных фрагмента данных: LastInsertId() или RowsAffected(), доступность которых зависит от драйвера. В MySQL, например, LastInsertId() будет доступен для вставок с ключом автоинкремента, но в PostgreSQL эту информацию можно получить только из обычного курсора строки с помощью предложения RETURNING.

Итак, я сделал полную демонстрацию того, как выполнить транзакцию, используя sqlx, демонстрация создаст адресную строку в addresses table, а затем создайте пользователя в users стол с использованием нового address_id PK как user_address_id ФК пользователя.

package transaction

import (
    "database/sql"
    "github.com/jmoiron/sqlx"
    "log"
    "github.com/pkg/errors"
)
import (
    "github.com/icrowley/fake"
)

type User struct {
    UserID int `db:"user_id"`
    UserNme string `db:"user_nme"`
    UserEmail string `db:"user_email"`
    UserAddressId sql.NullInt64 `db:"user_address_id"`
}

type ITransactionSamples interface {
    CreateUserTransaction() (*User, error)
}

type TransactionSamples struct {
    Db *sqlx.DB
}

func NewTransactionSamples(Db *sqlx.DB) ITransactionSamples {
    return &TransactionSamples{Db}
}

func (ts *TransactionSamples) CreateUserTransaction() (*User, error) {
    tx := ts.Db.MustBegin()
    var lastInsertId int
    err := tx.QueryRowx(`INSERT INTO addresses (address_id, address_city, address_country, address_state) VALUES ($1, $2, $3, $4) RETURNING address_id`, 3, fake.City(), fake.Country(), fake.State()).Scan(&lastInsertId)
    if err != nil {
        tx.Rollback()
        return nil, errors.Wrap(err, "insert address error")
    }
    log.Println("lastInsertId: ", lastInsertId)

    var user User
    err = tx.QueryRowx(`INSERT INTO users (user_id, user_nme, user_email, user_address_id) VALUES ($1, $2, $3, $4) RETURNING *;`, 6, fake.UserName(), fake.EmailAddress(), lastInsertId).StructScan(&user)
    if err != nil {
        tx.Rollback()
        return nil, errors.Wrap(err, "insert user error")
    }
    err = tx.Commit()
    if err != nil {
        return nil, errors.Wrap(err, "tx.Commit()")
    }
    return &user, nil
}

Вот результат теста:

☁  transaction [master] ⚡  go test -v -count 1 ./...
=== RUN   TestCreateUserTransaction
2019/06/27 16:38:50 lastInsertId:  3
--- PASS: TestCreateUserTransaction (0.01s)
    transaction_test.go:28: &transaction.User{UserID:6, UserNme:"corrupti", UserEmail:"reiciendis_quam@Thoughtstorm.mil", UserAddressId:sql.NullInt64{Int64:3, Valid:true}}
PASS
ok      sqlx-samples/transaction        3.254s

PostgreSQL поддерживает RETURNING синтаксис для INSERT заявления.

Пример:

INSERT INTO users(...) VALUES(...) RETURNING id, name, foo, bar

Документация: https://www.postgresql.org/docs/9.6/static/sql-insert.html

Необязательное предложение RETURNING заставляет INSERT вычислять и возвращать значения, основанные на каждой фактически вставленной строке (или обновленной, если использовалось предложение ON CONFLICT DO UPDATE). Это в первую очередь полезно для получения значений, которые были предоставлены по умолчанию, таких как порядковый номер. Однако любое выражение, использующее столбцы таблицы, разрешено. Синтаксис списка RETURNING идентичен синтаксису списка вывода SELECT. Только строки, которые были успешно вставлены или обновлены, будут возвращены.

Это пример кода, который работает с именованными запросами и структурами строгого типа для вставленных данных и идентификаторов.

Запрос и структура включены для покрытия используемого синтаксиса.

      const query = `INSERT INTO checks (
        start, status) VALUES (
        :start, :status)
        returning id;`

type Row struct {
    Status string `db:"status"`
    Start time.Time `db:"start"`
}

func InsertCheck(ctx context.Context, row Row, tx *sqlx.Tx) (int64, error) {
    return insert(ctx, row, insertCheck, "checks", tx)
}


// insert inserts row into table using query SQL command
// table used only for loging, actual table name defined in query
// should not be used from services directly - implement strong type wrappers
// function expects query with named parameters
func insert(ctx context.Context, row interface{}, query string, table string, tx *sqlx.Tx) (int64, error) {
    // convert named query to native parameters format
    query, args, err := tx.BindNamed(query, row)
    if err != nil {
        return 0, fmt.Errorf("cannot bind parameters for insert into %q: %w", table, err)
    }

    var id struct {
        Val int64 `db:"id"`
    }

    err = sqlx.GetContext(ctx, tx, &id, query, args...)
    if err != nil {
        return 0, fmt.Errorf("cannot insert into %q: %w", table, err)
    }

    return id.Val, nil
}
Другие вопросы по тегам