Golang Gin PostgreSQL中UUID字段NULL值扫描错误求助
问题核心
数据库中id_dashboard字段允许NULL,但模型中使用的uuid.UUID类型无法接收NULL值,同时后续使用COALESCE的方式错误导致列数不匹配。
解决方案一:修改模型使用uuid.NullUUID(推荐)
gofrs/uuid库提供了NullUUID类型,它实现了sql.Scanner和driver.Valuer接口,可以直接处理数据库中的NULL UUID值,无需额外扫描逻辑。
修改后的模型代码
package models import ( "time" "github.com/gofrs/uuid" ) type Programa struct { IDPrograma uuid.UUID `json:"id_programa"` Denominacao string `json:"denominacao"` Descricao string `json:"descricao"` TipoPrograma int `json:"tipo_programa"` IDTabela uuid.UUID `json:"id_tabela"` FormularioNavegacao string `json:"formulario_navegacao"` FormularioEdicao string `json:"formulario_edicao"` LinkAjuda string `json:"link_ajuda"` IDDashboard uuid.NullUUID `json:"id_dashboard"` // 替换为NullUUID类型 RotaBackend string `json:"rota_backend"` Customizado bool `json:"customizado"` DataInclusao time.Time `json:"data_inclusao"` IDUsuarioInclusao int64 `json:"id_usuario_inclusao"` DataAlteracao time.Time `json:"data_alteracao"` IDUsuarioAlteracao int64 `json:"id_usuario_alteracao"` }
修改后的控制器代码
func GetPrograma() gin.HandlerFunc { return func(c *gin.Context) { var GetAllPrograma []models.Programa rows, err := DB.Query(`SELECT * FROM sis_programa`) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "Erro ao realizar select na tabela sis_programa ": err, }) log.Println("Erro ao realizar select na tabela sis_programa ", err) return } defer rows.Close() for rows.Next() { var getAll models.Programa // 直接扫描到模型字段,无需额外临时变量 err := rows.Scan(&getAll.IDPrograma, &getAll.Denominacao, &getAll.Descricao, &getAll.TipoPrograma, &getAll.IDTabela, &getAll.FormularioNavegacao, &getAll.FormularioEdicao, &getAll.LinkAjuda, &getAll.IDDashboard, &getAll.RotaBackend, &getAll.Customizado, &getAll.DataInclusao, &getAll.IDUsuarioInclusao, &getAll.DataAlteracao, &getAll.IDUsuarioAlteracao) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "Erro ao realizar scan na tabela sis_programa ": err, }) log.Println("Erro ao realizar scan na tabela sis_programa ", err.Error()) return } GetAllPrograma = append(GetAllPrograma, getAll) } if err := rows.Err(); err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "Erro ao ler as linhas da tabela sis_programa ": err, }) log.Println("Erro ao ler as linhas da tabela sis_programa ", err.Error()) return } c.JSON(http.StatusOK, GetAllPrograma) } }
说明
uuid.NullUUID包含两个字段:UUID uuid.UUID和Valid bool。当数据库字段为NULL时,Valid设为false,UUID为零值;当字段有有效值时,Valid为true,UUID存储实际值。序列化JSON时,若Valid为false,该字段会输出null,符合JSON规范。
解决方案二:不修改模型,使用临时变量处理(适合无法修改模型的场景)
如果不能修改模型,可以在扫描时使用uuid.NullUUID作为临时变量,再根据Valid状态赋值给模型的IDDashboard:
修改后的控制器代码
func GetPrograma() gin.HandlerFunc { return func(c *gin.Context) { var GetAllPrograma []models.Programa rows, err := DB.Query(`SELECT * FROM sis_programa`) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "Erro ao realizar select na tabela sis_programa ": err, }) log.Println("Erro ao realizar select na tabela sis_programa ", err) return } defer rows.Close() for rows.Next() { var getAll models.Programa var tempIDDashboard uuid.NullUUID // 临时变量接收NULL UUID err := rows.Scan(&getAll.IDPrograma, &getAll.Denominacao, &getAll.Descricao, &getAll.TipoPrograma, &getAll.IDTabela, &getAll.FormularioNavegacao, &getAll.FormularioEdicao, &getAll.LinkAjuda, &tempIDDashboard, &getAll.RotaBackend, &getAll.Customizado, &getAll.DataInclusao, &getAll.IDUsuarioInclusao, &getAll.DataAlteracao, &getAll.IDUsuarioAlteracao) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "Erro ao realizar scan na tabela sis_programa ": err, }) log.Println("Erro ao realizar scan na tabela sis_programa ", err.Error()) return } // 根据Valid状态赋值,NULL时保持模型的零值UUID if tempIDDashboard.Valid { getAll.IDDashboard = tempIDDashboard.UUID } GetAllPrograma = append(GetAllPrograma, getAll) } if err := rows.Err(); err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "Erro ao ler as linhas da tabela sis_programa ": err, }) log.Println("Erro ao ler as linhas da tabela sis_programa ", err.Error()) return } c.JSON(http.StatusOK, GetAllPrograma) } }
之前错误原因分析
第一个错误:
sql: Scan error on column index 7, name "id_dashboard": converting NULL to string is unsupported
使用sql.NullString接收PostgreSQL的NULL UUID时,libpq驱动无法将二进制类型的UUID(NULL状态)直接转换为string,导致扫描失败。第二个错误:
sql: expected 16 destination arguments in Scan, not 15SELECT *, COALESCE(id_dashboard::text, '') AS id_dashboard会返回原表15列 + 新生成的id_dashboard列,共16列,但Scan只传入15个参数,数量不匹配导致报错。
内容的提问来源于stack exchange,提问作者Guilherme Rodrigues

