英文:
Unable to INSERT/UPDATE data with custom type in postgresql using golang
问题
我正在尝试使用jackc/pgx在PostgreSQL中插入/更新数据,其中包含自定义类型的列。这是一个以golan结构体形式编写的表类型:
// 将此结构体作为PSQL中的类型添加
type DayPriceModel struct {
Date time.Time `json:"date"`
High float32 `json:"high"`
Low float32 `json:"low"`
Open float32 `json:"open"`
Close float32 `json:"close"`
}
// 我的表中的两列
type SecuritiesPriceHistoryModel struct {
Symbol string `json:"symbol"`
History []DayPriceModel `json:"history"`
}
我编写了以下代码来插入数据:
func insertToDB(data SecuritiesPriceHistoryModel) {
DBConnection := config.DBConnection
_, err := DBConnection.Exec(context.Background(), "INSERT INTO equity.securities_price_history (symbol) VALUES ($1)", data.Symbol, data.History)
}
但是我无法插入自定义数据类型(DayPriceModel
)。
我遇到了一个错误:
Failed to encode args[1]: unable to encode
错误非常长,大部分显示了我的数据,所以我只选择了主要部分。
如何使用这样的自定义数据类型将数据插入PSQL?
PS:最好使用jackc/pgx进行实现,但数据库/SQL也可以。
英文:
I am trying to insert/update data in PostgreSQL using jackc/pgx into a table that has column of custom type. This is the table type written as a golan struct:
// Added this struct as a Types in PSQL
type DayPriceModel struct {
Date time.Time `json:"date"`
High float32 `json:"high"`
Low float32 `json:"low"`
Open float32 `json:"open"`
Close float32 `json:"close"`
}
// The 2 columns in my table
type SecuritiesPriceHistoryModel struct {
Symbol string `json:"symbol"`
History []DayPriceModel `json:"history"`
}
I have written this code for inserting data:
func insertToDB(data SecuritiesPriceHistoryModel) {
DBConnection := config.DBConnection
_, err := DBConnection.Exec(context.Background(), "INSERT INTO equity.securities_price_history (symbol) VALUES ($1)", data.Symbol, data.History)
}
But I am unable to insert the custom data type (DayPriceModel
).
I am getting an error
> Failed to encode args[1]: unable to encode
The error is very long and mostly shows my data so I have picked out the main part.
How do I INSERT data into PSQL with such custom data types?
PS: An implementation using jackc/pgx is preferred but database/SQL would just do fine
答案1
得分: 1
我对pgx不够熟悉,不知道如何设置支持复合类型的数组。但是,正如评论中已经提到的,你可以实现driver.Valuer
接口,并让该实现生成一个有效的字面量。如果你存储的是结构体的切片,也适用这种方法,你只需要声明一个命名切片,并让它实现valuer
接口,然后在使用时替换匿名切片。
// 命名切片类型
type DayPriceModelList []DayPriceModel
// 数组复合类型的字面量语法如下:'{("(foo,123)", "(bar,987)")}'。
// 因此,下面的实现必须以该格式返回切片内容。
func (l DayPriceModelList) Value() (driver.Value, error) {
// 空切片?返回 NULL
if l == nil {
return nil, nil
}
// 空切片?返回空数组
if len(l) == 0 {
return []byte{'{', '}'}, nil
}
out := []byte{'{'}
for _, v := range l {
// 这里假设 pg 复合类型中的日期字段接受默认的 time.Time 格式。
// 如果不是这种情况,你可以提供以复合类型字段理解的格式,例如:
// v.Date.Format("<pg 复合类型理解的格式>")
x := fmt.Sprintf(`"(Date:%s,High:%f,Low:%f,Open:%f,Close:%f)",`,
v.Date,
v.High,
v.Low,
v.Open,
v.Close)
out = append(out, x...)
}
out[len(out)-1] = '}' // 将最后一个","替换为"}"
return out, nil
}
在编写插入查询时,请确保在占位符之后添加显式转换,例如:
type SecuritiesPriceHistoryModel struct {
Symbol string `json:"symbol"`
History DayPriceModelList `json:"history"` // 使用命名切片类型
}
// ...
_, err := db.Exec(ctx, `INSERT INTO equity.securities_price_history (
symbol
, history
) VALUES (
$1
, $2::my_composite_type[])
`, data.Symbol, data.History)
// 将 my_composite_type 替换为数据库中复合类型的名称
注意1:根据你在 PostgreSQL 中定义复合类型的确切方式,上述示例可能有效,也可能无效。如果无效,只需调整代码使其生效。
注意2:上述示例的一般方法是有效的,但可能不够高效。如果你需要代码具有高性能,请不要直接使用示例代码。
英文:
I'm not familiar enough with pgx to know how to setup support for arrays of composite types. But, as already mentioned in the comment, you can implement the driver.Valuer
interface and have that implementation produce a valid literal, this also applies if you are storing slices of structs, you just need to declare a named slice and have that implement the valuer, and then use it instead of the unnamed slice.
// named slice type
type DayPriceModelList []DayPriceModel
// the syntax for array of composites literal looks like
// this: '{"(foo,123)", "(bar,987)"}'. So the implementation
// below must return the slice contents in that format.
func (l DayPriceModelList) Value() (driver.Value, error) {
// nil slice? produce NULL
if l == nil {
return nil, nil
}
// empty slice? produce empty array
if len(l) == 0 {
return []byte{'{', '}'}, nil
}
out := []byte{'{'}
for _, v := range l {
// This assumes that the date field in the pg composite
// type accepts the default time.Time format. If that is
// not the case then you simply provide v.Date in such a
// format which the composite's field understand, e.g.,
// v.Date.Format("<layout that the pg composite understands>")
x := fmt.Sprintf(`"(%s,%f,%f,%f,%f)",`,
v.Date,
v.High,
v.Low,
v.Open,
v.Close)
out = append(out, x...)
}
out[len(out)-1] = '}' // replace last "," with "}"
return out, nil
}
And when you are writing the insert query, make sure to add an explicit cast right after the placeholder, e.g.
type SecuritiesPriceHistoryModel struct {
Symbol string `json:"symbol"`
History DayPriceModelList `json:"history"` // use the named slice type
}
// ...
_, err := db.Exec(ctx, `INSERT INTO equity.securities_price_history (
symbol
, history
) VALUES (
$1
, $2::my_composite_type[])
`, data.Symbol, data.History)
// replace my_composite_type with the name of the composite type in the database
NOTE#1: Depending on the exact definition of your composite type in postgres the above example may or may not work, if it doesn't, simply adjust the code to make it work.
NOTE#2: The general approach in the example above is valid, however it is likely not very efficient. If you need the code to be performant do not use the example verbatim.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论