Get back newly inserted row in Postgres with sqlx

huangapple go评论78阅读模式
英文:

Get back newly inserted row in Postgres with sqlx

问题

我使用https://github.com/jmoiron/sqlx来查询Postgres数据库。

在插入新行时,是否可能获取整行数据?

这是我运行的查询:

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

或者我应该只使用现有的user结构作为数据库中新条目的真实来源?

英文:

I use https://github.com/jmoiron/sqlx to make queries to Postgres.

Is it possible to get back the whole row data when inserting a new row?

Here is the query I run:

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

Or should I just use my existing user struct as the source of truth about the new entry in the database?

答案1

得分: 3

这是关于sqlx事务的文档:

> 结果有两个可能的数据:LastInsertId()或RowsAffected(),其可用性取决于驱动程序。例如,在MySQL中,对于具有自增键的插入操作,LastInsertId()将可用,但在PostgreSQL中,只能通过使用RETURNING子句从普通行游标中检索此信息。

所以我制作了一个完整的演示,演示如何使用sqlx执行事务。该演示将在addresses表中创建一个地址行,然后使用新的address_id作为user_address_id的外键,在users表中创建一个用户。

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

英文:

Here is docs about transaction of sqlx:

> The result has two possible pieces of data: LastInsertId() or RowsAffected(), the availability of which is driver dependent. In MySQL, for instance, LastInsertId() will be available on inserts with an auto-increment key, but in PostgreSQL, this information can only be retrieved from a normal row cursor by using the RETURNING clause.

So I made a complete demo for how to execute transaction using sqlx, the demo will create an address row in addresses table and then create a user in users table using the new address_id PK as user_address_id FK of the user.

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
}

Here is test result:

☁  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

答案2

得分: 2

这是一个使用命名查询和强类型结构与插入的数据和ID一起工作的示例代码。

查询和结构包含了使用的语法。

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
}

希望这对你有帮助!

英文:

This is a sample code that works with named queries and strong type structures for inserted data and ID.

Query and struct included to cover used syntax.

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
}

答案3

得分: 1

PostgreSQL支持在INSERT语句中使用RETURNING语法。

示例:

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的输出列表完全相同。只有成功插入或更新的行将被返回。

英文:

PostgreSQL supports RETURNING syntax for INSERT statements.

Example:

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

Documentaion: https://www.postgresql.org/docs/9.6/static/sql-insert.html
> The optional RETURNING clause causes INSERT to compute and return value(s) based on each row actually inserted (or updated, if an ON CONFLICT DO UPDATE clause was used). This is primarily useful for obtaining values that were supplied by defaults, such as a serial sequence number. However, any expression using the table's columns is allowed. The syntax of the RETURNING list is identical to that of the output list of SELECT. Only rows that were successfully inserted or updated will be returned.

huangapple
  • 本文由 发表于 2016年11月18日 18:59:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/40675365.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定