You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Golang Gin PostgreSQL中UUID字段NULL值扫描错误求助

解决PostgreSQL UUID字段NULL值扫描问题(Go + Gin + libpq)

问题核心

数据库中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)
    }
}

之前错误原因分析

  1. 第一个错误: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,导致扫描失败。

  2. 第二个错误:sql: expected 16 destination arguments in Scan, not 15
    SELECT *, COALESCE(id_dashboard::text, '') AS id_dashboard会返回原表15列 + 新生成的id_dashboard列,共16列,但Scan只传入15个参数,数量不匹配导致报错。

内容的提问来源于stack exchange,提问作者Guilherme Rodrigues

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 09:45:08