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

Я использую https://github.com/jmoiron/sqlx, чтобы делать запросы в Postgres.

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

Вот запрос, который я выполняю:

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

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


person Sergei Basharov    schedule 18.11.2016    source источник
comment
PostgreSQL поддерживает синтаксис RETURNING для операторов INSERT. Пример: INSERT INTO users(...) VALUES(...) RETURNING id, name, foo, bar   -  person AlexM    schedule 18.11.2016
comment
Не могли бы вы сформулировать это как ответ, и я отмечу его как правильный?   -  person Sergei Basharov    schedule 18.11.2016


Ответы (3)


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. Будут возвращены только те строки, которые были успешно вставлены или обновлены.

person AlexM    schedule 22.11.2016

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

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

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

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:"[email protected]", UserAddressId:sql.NullInt64{Int64:3, Valid:true}}
PASS
ok      sqlx-samples/transaction        3.254s

person slideshowp2    schedule 27.06.2019

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

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

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
}
person Dmitry Harnitski    schedule 04.02.2021