inserting into postgresql decimal field fails with error "cannot convert {125.00} to Int2"

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

inserting into postgresql decimal field fails with error "cannot convert {125.00} to Int2"

问题

我是你的中文翻译助手,以下是翻译好的内容:

我对golang还比较新手。我正在尝试使用gin-gonic的web应用程序向一个具有数字字段的postgresql表中插入数据。

postgres=# \d user_txns;
                       Table "public.user_txns"
   Column    |         Type          | Collation | Nullable | Default
-------------+-----------------------+-----------+----------+---------
 user_id     | character varying(15) |           | not null |
 txn_code    | smallint              |           | not null |
 description | character varying(64) |           | not null |
 txn_amount  | numeric(15,4)         |           | not null |
 txn_type    | smallint              |           | not null |
 voucher     | character varying(16) |           |          |

我正在使用jackc pgxpool将数据插入到表中,代码如下:

109  sql := `INSERT INTO user_txns VALUES ($1,$2, $3, $4, $5)`
 110  _, err = tx.Exec(context.Background(), sql,
 111       claims["phone"],
 112       recharge,
 113       "User recharge",
 114       recharge.Amount,
 115       credit,
 116  )
 117  if err != nil {
 118   c.JSON(http.StatusInternalServerError, gin.H{"msg": err.Error()})
 119   return
 120  },

请求的有效载荷是一个具有以下结构的JSON请求:

{
  "amount": 125.00 
}

我将请求解析为以下定义的结构体:

type Recharge struct {
  Amount string `json:"amount" binding:"required"`
}

插入操作失败,报错信息为:

"msg": "cannot convert {125} to Int2"

在向十进制字段插入数据时,应该使用哪种正确的golang数据类型?

谢谢。

英文:

I am fairly new to golang. I am trying to insert into a postgresql table which has a numeric field, using a web app gin-gonic.

postgres=# \d user_txns;
                       Table "public.user_txns"
   Column    |         Type          | Collation | Nullable | Default
-------------+-----------------------+-----------+----------+---------
 user_id     | character varying(15) |           | not null |
 txn_code    | smallint              |           | not null |
 description | character varying(64) |           | not null |
 txn_amount  | numeric(15,4)         |           | not null |
 txn_type    | smallint              |           | not null |
 voucher     | character varying(16) |           |          |

I am using jackc pgxpool to insert into the table as below.

109 ▏ sql := `INSERT INTO user_txns VALUES ($1,$2, $3, $4, $5)`
▎ 110 ▏ _, err = tx.Exec(context.Background(), sql,
▎ 111 ▏ ▏ ▏ ▏ ▏ ▏ claims["phone"],
▎ 112 ▏ ▏ ▏ ▏ ▏ ▏ recharge,
▎ 113 ▏ ▏ ▏ ▏ ▏ ▏ "User recharge",
▎ 114 ▏ ▏ ▏ ▏ ▏ ▏ recharge.Amount,
▎ 115 ▏ ▏ ▏ ▏ ▏ ▏ credit,
▎ 116 ▏ )
▎ 117 ▏ if err != nil {
▎ 118 ▏ ▏ c.JSON(http.StatusInternalServerError, gin.H{"msg": err.Error()})
▎ 119 ▏ ▏ return
▎ 120 ▏ },

The payload is a json request that has the following structure:

{
  "amount": 125.00 
}

I unmarshal the request into a struct defined as below.

type Recharge struct {
  Amount string `json:"amount" binding:"required"`
}

The insert fails with error
> "msg": "cannot convert {125} to Int2"

What is the right golang data type to use for inserting into a decimal field?

thanx

答案1

得分: 1

将值125.00插入到PostgreSQL的numeric类型列中的最简单方法是在Go中使用float类型。这可以直接使用,因此无需实现任何自定义接口。

例如:

CREATE TABLE t (
    id serial PRIMARY KEY
    , amount numeric(15,4) NOT NULL
    -- ...
);
data := []byte(`{"amount": 125.00}`)
var obj struct {
	Amount float64 `json:"amount"`
}
if err := json.Unmarshal(data, &obj); err != nil {
	panic(err)
}

_, err := db.Exec(`INSERT INTO t (amount) VALUES ($1)`, obj.Amount)

然而,浮点类型存在舍入误差的问题,因此存储货币金额的常见做法是使用表示以分为单位的整数。例如,125.00变为12500。这种方法也可以直接使用。

例如:

CREATE TABLE t (
    id serial PRIMARY KEY
    , amount int8 NOT NULL
    -- ...
);
data := []byte(`{"amount": 12500}`)
var obj struct {
	Amount int64 `json:"amount"`
}
if err := json.Unmarshal(data, &obj); err != nil {
	panic(err)
}

_, err := db.Exec(`INSERT INTO t (amount) VALUES ($1)`, obj.Amount)

如果你想要使用pgtype.Numeric来存储和检索数据库中的金额,则需要进行一些额外的工作,因为pgtype.Numeric不知道如何编码/解码JSON中的125.00/"125.00"值。

你可以声明一个自定义的结构类型,将其嵌入pgtype.Numeric类型,然后让自定义的结构类型实现json.Marshalerjson.Unmarshaler接口。

例如:

CREATE TABLE t (
    id serial PRIMARY KEY
    , amount numeric(15,4) NOT NULL
    -- ...
);
type MyNumeric struct {
    pgtype.Numeric
}

func (n *MyNumeric) UnmarshalJSON(data []byte) error {
	var s json.Number
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}
	return n.Numeric.Set(s.String())
}

func (n MyNumeric) MarshalJSON() ([]byte, error) {
	var f float64
	if err := n.Numeric.AssignTo(&f); err != nil {
		return nil, err
	}
	return []byte(strconv.FormatFloat(f, 'f', -1, 64)), nil
}
data := []byte(`{"amount": 125.00}`)
var obj struct {
	Amount MyNumeric `json:"amount"`
}
if err := json.Unmarshal(data, &obj); err != nil {
	panic(err)
}

_, err := db.Exec(`INSERT INTO t (amount) VALUES ($1)`, obj.Amount)
英文:

The easiest way to insert a value like 125.00 into a postgres column of type numeric would be to use a float type in Go. This works out of the box and so there's no need to implement any kind of custom interface.

For example:

CREATE TABLE t (
    id serial PRIMARY KEY
    , amount numeric(15,4) NOT NULL
    -- ...
);
data := []byte(`{"amount": 125.00}`)
var obj struct {
	Amount float64 `json:"amount"`
}
if err := json.Unmarshal(data, &obj); err != nil {
	panic(err)
}

_, err := db.Exec(`INSERT INTO t (amount) VALUES ($1)`, obj.Amount)

Float types are subject to rounding errors however, and because of that a common practice for storing monetary amounts is to use integers that represent the value in cents. E.g. 125.00 becomes 12500. This also works out of the box.

For example:

CREATE TABLE t (
    id serial PRIMARY KEY
    , amount int8 NOT NULL
    -- ...
);
data := []byte(`{"amount": 12500}`)
var obj struct {
	Amount int64 `json:"amount"`
}
if err := json.Unmarshal(data, &obj); err != nil {
	panic(err)
}

_, err := db.Exec(`INSERT INTO t (amount) VALUES ($1)`, obj.Amount)

If you want to use the pgtype.Numeric for storing and retrieving the amount to and from the database then you'll have to do some extra work because pgtype.Numeric doesn't know how to encode/decode JSON 125.00/"125.00" values.

One thing you can do is to declare a custom struct type, have it embed the pgtype.Numeric type, and then have the custom struct type implement the json.Marshaler and json.Unmarshaler interfaces.

For example:

CREATE TABLE t (
    id serial PRIMARY KEY
    , amount numeric(15,4) NOT NULL
    -- ...
);
type MyNumeric struct {
    pgtype.Numeric
}

func (n *MyNumeric) UnmarshalJSON(data []byte) error {
	var s json.Number
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}
	return n.Numeric.Set(s.String())
}

func (n MyNumeric) MarshalJSON() ([]byte, error) {
	var f float64
	if err := n.Numeric.AssignTo(&f); err != nil {
		return nil, err
	}
	return []byte(strconv.FormatFloat(f, 'f', -1, 64)), nil
}
data := []byte(`{"amount": 125.00}`)
var obj struct {
	Amount MyNumeric `json:"amount"`
}
if err := json.Unmarshal(data, &obj); err != nil {
	panic(err)
}

_, err := db.Exec(`INSERT INTO t (amount) VALUES ($1)`, obj.Amount)

huangapple
  • 本文由 发表于 2022年7月16日 17:33:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/73003096.html
匿名

发表评论

匿名网友

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

确定