GORM Postgres Raw SQL UPDATE操作decimal类型相加报错如何解决
问题1:是否需要将decimal类型转为float处理?
绝对不要。float为非精确数值类型,转换后会出现精度丢失问题,完全不适合积分、金额这类对精度要求高的业务场景。
问题2:可以通过修改SQL或类型适配解决
报错的根本原因是Go的decimal.Decimal类型默认被GORM序列化为字符串传入SQL,PostgreSQL执行coins + ?时会识别为「text类型 + 未知类型」,触发类型不匹配错误。你可以选择以下两种方案解决:
方案1:直接修改Raw SQL语句(快速修复)
在占位符后显式指定转换为PostgreSQL原生支持的numeric类型即可,同时将decimal变量转为字符串传入,不会损失精度:
convertedCurr = points.Div(config.BaseFactor).Round(2) // 给数值类型的参数占位符加::numeric显式类型转换 sqlCoins := "UPDATE coins SET coins = coins + ?::numeric, points = points + ?::numeric WHERE tenant = ? AND user_id = ?" errUpdateCoins = database.GetDbWriteClient().Raw(sqlCoins, convertedCurr.String(), points.String(), tenant, userId).Scan(&coin).Count(&updatedCount).Error
方案2:自定义类型适配(长期通用方案)
给decimal.Decimal实现GORM要求的driver.Valuer和sql.Scanner接口,让GORM自动完成Go类型与PostgreSQL numeric类型的映射,后续所有SQL操作都不需要额外加类型转换:
import ( "database/sql/driver" "fmt" "github.com/shopspring/decimal" ) // 自定义Decimal类型适配GORM和PostgreSQL type Decimal decimal.Decimal // Value 实现driver.Valuer接口,写入数据库时自动转为字符串 func (d Decimal) Value() (driver.Value, error) { return decimal.Decimal(d).String(), nil } // Scan 实现sql.Scanner接口,读取数据库时自动解析为Decimal类型 func (d *Decimal) Scan(value interface{}) error { if value == nil { *d = Decimal(decimal.Zero) return nil } var str string switch v := value.(type) { case string: str = v case []byte: str = string(v) default: return fmt.Errorf("unsupported type for decimal scan: %T", value) } dec, err := decimal.NewFromString(str) if err != nil { return err } *d = Decimal(dec) return nil }
之后将你的模型中coins、points字段的类型替换为上面自定义的Decimal类型,原SQL不需要任何修改即可正常执行。
补充优化建议
UPDATE类写操作建议用GORM的Exec方法替代Raw+Scan,写法更简洁:
res := database.GetDbWriteClient().Exec(sqlCoins, convertedCurr, points, tenant, userId) errUpdateCoins = res.Error updatedCount = res.RowsAffected
内容的提问来源于stack exchange,提问作者HalfWebDev
相关产品推荐
相关产品推荐

