公司动态

gorm sqlnull有哪些类型

📅 2026/7/24 21:10:09
gorm sqlnull有哪些类型
GORM 的sql.Null*类型主要对应 Go 标准库database/sql中的可空类型用于处理数据库中的 NULL 值。以下是常见的类型标准库database/sql中的 Null 类型表格类型说明字段结构sql.NullString可空字符串{String string, Valid bool}sql.NullInt64可空整数{Int64 int64, Valid bool}sql.NullFloat64可空浮点数{Float64 float64, Valid bool}sql.NullBool可空布尔值{Bool bool, Valid bool}sql.NullTime可空时间Go 1.13{Time time.Time, Valid bool}GORM 扩展的 Null 类型GORM 自身也提供了一些扩展类型在gorm.io/gorm或github.com/jinzhu/gorm中表格类型说明gorm.DeletedAt软删除时间戳基于sql.NullTime或gorm.io/datatypesdatatypes.JSONJSON 类型GORM v2datatypes.Date日期类型GORM v2datatypes.Time时间类型GORM v2使用示例goimport ( database/sql time ) type User struct { ID uint Name sql.NullString // 可空字符串 Age sql.NullInt64 // 可空整数 Score sql.NullFloat64 // 可空浮点数 IsActive sql.NullBool // 可空布尔 Birthday sql.NullTime // 可空时间 DeletedAt gorm.DeletedAt // 软删除 }替代方案guregu/null或lib/pq如果你需要更友好的 JSON 序列化支持可以使用第三方库goimport github.com/guregu/null type User struct { Name null.String // 序列化为字符串或 null而不是 {String:, Valid:false} Age null.Int Score null.Float Time null.Time }null包中的类型与sql.Null*类似但在 JSON 序列化时更直观直接输出值或null而不是对象形式。总结GORM 本身没有定义独立的sqlnull包而是直接使用 Go 标准库的sql.Null*类型GORM v2 的datatypes包提供了一些额外的数据类型支持。null.Bool来自github.com/guregu/null包的传值方式如下创建 null.Bool 值goimport github.com/guregu/null // 1. 有效值非 NULL b1 : null.BoolFrom(true) // Validtrue, Booltrue b2 : null.BoolFrom(false) // Validtrue, Boolfalse // 2. 空值NULL b3 : null.NewBool(false, false) // Validfalse数据库存 NULL b4 : null.Bool{} // 零值等同于 Validfalse // 3. 从 sql.NullBool 转换 var sqlBool sql.NullBool nb : null.BoolFromPtr(sqlBool.Bool) // 需要自行处理 Valid在结构体中使用gotype User struct { ID uint IsAdmin null.Bool gorm:column:is_admin IsVip null.Bool gorm:column:is_vip }传值到数据库Create/Updatego// 插入is_admin true, is_vip NULL user : User{ IsAdmin: null.BoolFrom(true), IsVip: null.Bool{}, // 或 null.NewBool(false, false) } db.Create(user) // 更新将 is_admin 设为 NULL db.Model(user).Update(is_admin, null.Bool{}) // 更新将 is_admin 设为 false db.Model(user).Update(is_admin, null.BoolFrom(false))读取和判断govar user User db.First(user) if user.IsAdmin.Valid { // 数据库值不是 NULL fmt.Println(IsAdmin:, user.IsAdmin.Bool) // true 或 false } else { // 数据库值是 NULL fmt.Println(IsAdmin is NULL) }作为函数参数传递gofunc UpdateUserStatus(userID uint, isAdmin null.Bool) error { return db.Model(User{}).Where(id ?, userID).Update(is_admin, isAdmin).Error } // 调用 UpdateUserStatus(1, null.BoolFrom(true)) // 设为 true UpdateUserStatus(2, null.BoolFrom(false)) // 设为 false UpdateUserStatus(3, null.Bool{}) // 设为 NULL从 HTTP 请求解析常见场景gotype UpdateRequest struct { IsAdmin *bool json:is_admin // 指针区分 undefined 和 null } func Handler(c *gin.Context) { var req UpdateRequest c.BindJSON(req) var nb null.Bool if req.IsAdmin nil { nb null.Bool{} // 不更新或设为 NULL } else { nb null.BoolFrom(*req.IsAdmin) } db.Model(User{}).Where(id ?, id).Update(is_admin, nb) }核心要点表格通过 JSON 请求传值时null.Bool字段应该如何传。JSON 传值方式1. 传有效布尔值JSON{ is_admin: true, is_vip: false }2. 传 NULL数据库会存 NULLJSON{ is_admin: null, is_vip: null }3. 不传该字段使用零值也是 NULLJSON{ is_admin: true // is_vip 不传解析后为 null.Bool{}Validfalse }完整示例结构体定义goimport github.com/guregu/null type User struct { ID uint json:id Name string json:name IsAdmin null.Bool json:is_admin IsVip null.Bool json:is_vip }接收 JSON 并处理gofunc CreateUser(c *gin.Context) { var user User if err : c.ShouldBindJSON(user); err ! nil { c.JSON(400, gin.H{error: err.Error()}) return } // 入库 db.Create(user) c.JSON(200, user) }各种 JSON 输入的效果表格JSON 输入IsAdmin结果数据库存储{is_admin: true}null.Bool{Bool:true, Valid:true}true{is_admin: false}null.Bool{Bool:false, Valid:true}false{is_admin: null}null.Bool{Bool:false, Valid:false}NULL{}不传null.Bool{Bool:false, Valid:false}NULL注意事项guregu/null的 JSON 反序列化null.Bool已经实现了json.Unmarshaler接口所以ShouldBindJSON可以直接解析go// 内部逻辑大致是 // - JSON true/false → Validtrue, Bool对应值 // - JSON null → Validfalse // - 字段缺失 → 零值Validfalse如果你用的是指针*bool方式有时候为了区分不传和传 null会用指针gotype Request struct { IsAdmin *bool json:is_admin // 指针 } func Handler(c *gin.Context) { var req Request c.BindJSON(req) var nb null.Bool if req.IsAdmin nil { // 字段未传保持原值或不做更新 } else { nb null.BoolFrom(*req.IsAdmin) } }表格JSON 输入req.IsAdmin含义{is_admin: true}*bool(true)设为 true{is_admin: false}*bool(false)设为 false{is_admin: null}nil设为 NULL{}nil不更新总结直接用null.Bool接收 JSON 时传true/false/null即可无需特殊处理guregu/null.Bool比sql.NullBool更实用主要体现在以下几个方面对比表格特性sql.NullBoolguregu/null.BoolJSON 序列化输出对象{Bool:true,Valid:true}输出原生值true/false/nullJSON 反序列化不支持直接反序列化true/false/null完美支持字符串输出需手动处理.String()返回true/false/null零值含义Validfalse表示 NULL相同但更简洁方法丰富度基础From(),FromPtr(),Ptr(),ValueOrZero()等JSON 序列化对比关键差异goimport ( database/sql encoding/json github.com/guregu/null ) type SqlUser struct { IsAdmin sql.NullBool json:is_admin } type NullUser struct { IsAdmin null.Bool json:is_admin } // 数据Validtrue, Booltrue sqlUser : SqlUser{IsAdmin: sql.NullBool{Bool: true, Valid: true}} nullUser : NullUser{IsAdmin: null.BoolFrom(true)} // 序列化结果 sqlJSON, _ : json.Marshal(sqlUser) // {is_admin:{Bool:true,Valid:true}} nullJSON, _ : json.Marshal(nullUser) // {is_admin:true}sql.NullBool的 JSON 输出对前端极不友好而null.Bool直接输出true/false/null前端无需额外处理。反序列化对比go// 前端传{is_admin: true} json.Unmarshal([]byte({is_admin:true}), sqlUser) // ❌ 报错 json.Unmarshal([]byte({is_admin:true}), nullUser) // ✅ 成功 // 前端传{is_admin: null} json.Unmarshal([]byte({is_admin:null}), nullUser) // ✅ Validfalse实用方法gob : null.BoolFrom(true) b.Valid // true/false是否为有效值 b.Bool // 实际的 bool 值 b.Ptr() // *bool 指针 b.ValueOrZero() // 无效时返回 false有效时返回 Bool 值 b.String() // true / false / null // 从指针创建 var p *bool null.BoolFromPtr(p) // Validfalse指针为 nil总结表格场景推荐纯数据库操作不涉及 JSONsql.NullBool无需额外依赖Web API、前后端交互、JSON 序列化null.Bool需要链式操作、更丰富的工具方法null.Bool结论在 GORM Web 开发场景中guregu/null包是更优选择它解决了sql.Null*在 JSON 处理上的痛点代码更简洁、前后端交互更顺畅。null.Bool字段的查询条件写法与普通 bool 字段类似GORM 会自动处理Valid和Bool字段。基本查询goimport github.com/guregu/null type User struct { ID uint Name string IsAdmin null.Bool } // 查询 is_admin true 且 Valid true 的记录 var users []User db.Where(is_admin ?, null.BoolFrom(true)).Find(users) // 查询 is_admin false db.Where(is_admin ?, null.BoolFrom(false)).Find(users) // 查询 is_admin IS NULL db.Where(is_admin IS NULL).Find(users)常见查询场景1. 查询有效值非 NULLgo// 查询所有设置了 is_admin不管 true/false的用户 db.Where(is_admin IS NOT NULL).Find(users)2. 查询特定值go// 查询管理员 db.Where(is_admin ?, true).Find(users) // 或者使用 null.Bool效果相同 db.Where(is_admin ?, null.BoolFrom(true)).Find(users)3. 查询 NULL 值go// 查询未设置 is_admin 的用户 db.Where(is_admin IS NULL).Find(users) // 或者用 GORM 的 map 写法 db.Where(map[string]interface{}{is_admin: nil}).Find(users)复杂条件组合go// 查询是管理员 或 is_admin 为 NULL 的用户 db.Where(is_admin ? OR is_admin IS NULL, true).Find(users) // 使用结构体查询GORM 会忽略零值字段但 null.Bool 的零值 Validfalse // 注意这种方式不会匹配 NULL只会匹配 Validfalse 且 Boolfalse db.Where(User{IsAdmin: null.BoolFrom(false)}).Find(users)动态条件根据请求参数gofunc GetUsers(isAdmin *bool) ([]User, error) { var users []User query : db.Model(User{}) if isAdmin nil { // 不传参数查询所有 } else if *isAdmin { query query.Where(is_admin ?, true) } else { query query.Where(is_admin ? OR is_admin IS NULL, false) // 或者只查 falsequery.Where(is_admin ?, false) } err : query.Find(users).Error return users, err }原生 SQL 方式go// 直接写 SQL db.Raw(SELECT * FROM users WHERE is_admin ?, null.BoolFrom(true)).Scan(users) // 或者 Exec db.Exec(UPDATE users SET is_admin ? WHERE id ?, null.BoolFrom(true), 1)注意事项表格情况说明null.Bool{}零值Validfalse, Boolfalse查询时匹配NULL还是false取决于数据库GORM 结构体查询db.Where(User{IsAdmin: null.Bool{}})可能生成is_admin IS NULL或忽略建议显式写 Where最佳实践查询条件建议直接用原生 SQL 或 map避免结构体零值陷阱推荐写法go// ✅ 明确、可读、无歧义 db.Where(is_admin ?, true) // true db.Where(is_admin ?, false) // false db.Where(is_admin IS NULL) // NULL db.Where(is_admin IS NOT NULL) // 非 NULLnull.Bool为 NULL即Validfalse时不添加该条件到查询中可以这样处理动态条件判断gofunc GetUsers(isAdmin null.Bool, isVip null.Bool) ([]User, error) { var users []User query : db.Model(User{}) // 只有 Validtrue 时才添加条件 if isAdmin.Valid { query query.Where(is_admin ?, isAdmin.Bool) } if isVip.Valid { query query.Where(is_vip ?, isVip.Bool) } err : query.Find(users).Error return users, err }封装通用方法go// 添加 null.Bool 条件Valid 时才添加 func WhereNullBool(db *gorm.DB, field string, val null.Bool) *gorm.DB { if val.Valid { return db.Where(field ?, val.Bool) } return db } // 使用 query : db.Model(User{}) query WhereNullBool(query, is_admin, req.IsAdmin) query WhereNullBool(query, is_vip, req.IsVip) query.Find(users)结合 HTTP 请求gotype UserQuery struct { Name string form:name IsAdmin null.Bool form:is_admin // 不传时 Validfalse IsVip null.Bool form:is_vip } func ListUsers(c *gin.Context) { var q UserQuery c.ShouldBindQuery(q) query : db.Model(User{}) if q.Name ! { query query.Where(name LIKE ?, %q.Name%) } // is_admin 不传Validfalse→ 不添加条件查全部 // is_admintrue/false → 添加对应条件 if q.IsAdmin.Valid { query query.Where(is_admin ?, q.IsAdmin.Bool) } if q.IsVip.Valid { query query.Where(is_vip ?, q.IsVip.Bool) } var users []User query.Find(users) c.JSON(200, users) }请求示例表格请求IsAdmin.Valid查询条件GET /usersfalse无 is_admin 条件GET /users?is_admintruetrueis_admin trueGET /users?is_adminfalsetrueis_admin falseGET /users?is_adminnullfalse无 is_admin 条件核心逻辑判断val.Valid为true才加条件为false则跳过。ull.String的使用与null.Bool类似以下是常见条件查询写法基本查询goimport github.com/guregu/null type User struct { ID uint Name null.String Email null.String Phone null.String } // 精确匹配Validtrue 且非空 db.Where(name ?, null.StringFrom(张三)).Find(users) // 模糊查询Validtrue 时 db.Where(name LIKE ?, %null.StringFrom(张).String%).Find(users)动态条件Valid 时才添加gofunc GetUsers(name null.String, email null.String) ([]User, error) { query : db.Model(User{}) // 有值才加条件 if name.Valid name.String ! { query query.Where(name LIKE ?, %name.String%) } if email.Valid email.String ! { query query.Where(email ?, email.String) } var users []User err : query.Find(users).Error return users, err }封装通用方法go// 精确匹配 func WhereNullString(db *gorm.DB, field string, val null.String) *gorm.DB { if val.Valid val.String ! { return db.Where(field ?, val.String) } return db } // 模糊匹配 func WhereNullStringLike(db *gorm.DB, field string, val null.String) *gorm.DB { if val.Valid val.String ! { return db.Where(field LIKE ?, %val.String%) } return db } // 使用 query : db.Model(User{}) query WhereNullStringLike(query, name, req.Name) query WhereNullString(query, email, req.Email) query.Find(users)结合 HTTP 请求gotype UserQuery struct { Name null.String form:name Email null.String form:email Phone null.String form:phone } func ListUsers(c *gin.Context) { var q UserQuery c.ShouldBindQuery(q) query : db.Model(User{}) if q.Name.Valid q.Name.String ! { query query.Where(name LIKE ?, %q.Name.String%) } if q.Email.Valid q.Email.String ! { query query.Where(email ?, q.Email.String) } // Phone 不传Validfalse→ 不加条件 if q.Phone.Valid q.Phone.String ! { query query.Where(phone ?, q.Phone.String) } var users []User query.Find(users) c.JSON(200, users) }请求示例表格请求行为GET /users查全部无过滤GET /users?name张name LIKE %张%GET /users?nameValidtrue, String→ 通常不加条件看业务GET /users?emailnullValidfalse→ 不加条件判断 NULL vs 空字符串go// 查询 name 为 NULL 的记录 db.Where(name IS NULL).Find(users) // 查询 name 为空字符串Validtrue, String db.Where(name ?, ).Find(users) // 查询 name 为 NULL 或空字符串 db.Where(name IS NULL OR name ?, ).Find(users)核心要点表格场景判断条件不加条件!val.Valid加精确条件val.Valid val.String ! 加模糊条件val.Valid val.String ! 查 NULL单独写WHERE field IS NULLnull.String来自guregu/null和 Go 原生string的核心区别在于对数据库 NULL 值的支持和 JSON 序列化行为核心对比表格特性stringnull.String数据库 NULL❌ 不支持会报错或存空字符串✅ 支持映射为Validfalse零值空字符串null.String{Valid:false}语义上的 NULLJSON 序列化输出NULL 输出null有值输出字符串JSON 反序列化null→null→Valid:false字符串 →Valid:true是否有 Valid 字段❌ 无✅ 有可明确区分空值和 NULL适用场景必填字段可选字段、可能为 NULL 的字段代码对比结构体定义goimport github.com/guregu/null type UserWithString struct { ID uint Name string // 原生 string Bio string // 数据库 NULL 时Go 中是 } type UserWithNull struct { ID uint Name null.String // 可空 Bio null.String // 数据库 NULL 时Validfalse }数据库交互go// 假设数据库中某条记录的 bio 为 NULL var u1 UserWithString db.First(u1) // u1.Bio 丢失了是否为NULL的信息 var u2 UserWithNull db.First(u2) // u2.Bio.Valid false 明确知道是 NULL // u2.Bio.String 值本身JSON 序列化对比gou1 : UserWithString{Name: 张三, Bio: } json.Marshal(u1) // {id:0,name:张三,bio:} u2 : UserWithNull{Name: null.StringFrom(张三), Bio: null.String{}} json.Marshal(u2) // {id:0,name:张三,bio:null}实际使用选择表格场景推荐类型原因用户名、手机号等必填字段string简单直接无需判断 Valid简介、备注、中间名等可选字段null.String区分未填写和空字符串需要明确表达无值的 APInull.StringJSON 输出null更符合语义纯内部逻辑不暴露 JSONstring减少复杂度常见误区go// ❌ 错误用 string 接收可能为 NULL 的数据库字段 type User struct { Nickname string // 数据库是 NULL → 查询可能报错或数据丢失 } // ✅ 正确可选字段用 null.String type User struct { Nickname null.String } // ⚠️ 注意null.String 零值不等于 空字符串 var ns null.String ns.Valid // false表示 NULL ns.String // 值是空但语义是 NULL // 判断是否有值 if ns.Valid ns.String ! { // 有实际内容 }总结plainstring: 简单、无状态、适合必填字段 null.String: 有状态Valid、适合可选字段、前后端交互更精确一句话如果字段在数据库里可能是 NULL或者 API 需要区分和null就用null.String。场景写法存truenull.BoolFrom(true)存falsenull.BoolFrom(false)存NULLnull.Bool{}或null.NewBool(false, false)判断是否 NULL检查.Valid字段