【问题标题】:Get back newly inserted row in Postgres with sqlx使用 sqlx 在 Postgres 中取回新插入的行
【发布时间】:2021-02-05 00:18:55
【问题描述】:

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

插入新行时是否可以取回整行数据?

这是我运行的查询:

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

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

【问题讨论】:

  • PostgreSQL 支持 RETURNING 语法用于 INSERT 语句。示例:INSERT INTO users(...) VALUES(...) RETURNING id, name, foo, bar
  • 你能把它作为答案,我将它标记为正确的吗?

标签: sql postgresql go sqlx


【解决方案1】:

这里是关于sqlx事务的文档:

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

所以我做了一个完整的demo来说明如何使用sqlx执行事务,demo将在addresses表中创建一个地址行,然后在users表中使用新的address_id创建一个用户@PK as @ 987654326@用户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:"reiciendis_quam@Thoughtstorm.mil", UserAddressId:sql.NullInt64{Int64:3, Valid:true}}
PASS
ok      sqlx-samples/transaction        3.254s

【讨论】:

    【解决方案2】:

    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 的输出列表的语法相同。只会返回成功插入或更新的行。

    【讨论】:

      【解决方案3】:

      这是一个示例代码,适用于插入数据和 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
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-08-21
        • 1970-01-01
        • 2020-05-05
        • 2021-09-18
        • 2021-09-06
        • 2019-05-28
        • 1970-01-01
        • 2011-12-16
        相关资源
        最近更新 更多