sql.NullString
和 sql.NullInt64
类型(以及其他类似的 sql.Null*
类型)在处理数据库操作时非常有用,尤其是在 Go 语言的 database/sql
包中。它们的主要用途包括:
-
表示 NULL 值:
- 在数据库中,NULL 表示“没有值”。
sql.Null*
类型允许你在 Go 语言中直接表示和处理这种“没有值”的状态,而不是使用 Go 的零值(如空字符串""
或整数0
)来表示 NULL,这样可以更准确地反映数据库中的数据状态。
- 在数据库中,NULL 表示“没有值”。
-
数据库交互:
- 当你从数据库查询数据时,某些字段可能是 NULL。使用
sql.Null*
类型可以区分一个字段是真正包含零值还是 NULL。这对于后续的逻辑处理非常重要,因为零值和 NULL 在逻辑上可能有不同的含义。
- 当你从数据库查询数据时,某些字段可能是 NULL。使用
-
避免错误:
- 如果不使用
sql.Null*
类型,你可能会错误地将数据库中的 NULL 值解释为 Go 语言中的零值,这可能导致逻辑错误或程序崩溃。
- 如果不使用
-
简化代码:
- 使用
sql.Null*
类型可以简化代码,因为你不需要编写额外的检查来确定一个值是否来自数据库中的 NULL。Valid
字段可以直接告诉你这个值是否有效。
- 使用
-
数据完整性:
- 在将数据写入数据库时,
sql.Null*
类型可以帮助你保持数据的完整性。你可以准确地标记哪些字段是有意设置为 NULL,哪些字段是具有实际值的。
- 在将数据写入数据库时,
-
灵活性:
sql.Null*
类型提供了灵活性,允许你在不改变现有代码结构的情况下,处理那些可能为 NULL 的字段。
示例场景:
假设你正在处理一个用户信息表,其中有一个字段表示用户的出生日期。在某些情况下,用户可能没有提供出生日期,因此这个字段在数据库中是 NULL。使用 sql.NullString
或 sql.NullInt64
(取决于出生日期如何存储)可以让你在 Go 程序中准确地表示这种情况,并在需要时进行适当的处理。
package mainimport ("database/sql""fmt"
)// DbBackedUser 用户结构体,由数据库支持
type DbBackedUser struct {Name sql.NullStringAge sql.NullInt64
}func main() {// 空值var name sql.NullStringx := DbBackedUser{Name: name, Age: sql.NullInt64{Int64: 0, Valid: false}}// 输出结果fmt.Println("Name:")fmt.Println("String:", x.Name.String) // 输出: ""fmt.Println("Valid:", x.Name.Valid) // 输出: falsefmt.Println("Age:")fmt.Println("Int64:", x.Age.Int64) // 输出: 0fmt.Println("Valid:", x.Age.Valid) // 输出: false
}
Name:
String:
Valid: false
Age:
Int64: 0
Valid: false
加入与数据库交互
1. 创建测试表
clickhouse-client
首先,我们需要在 ClickHouse 中创建一个测试表。因为你的数据结构包含 Name
和 Age
,我们可以使用以下 SQL 语句创建表:
CREATE TABLE test_table
(`Name` Nullable(String),`Age` Nullable(Int64)
) ENGINE = Memory;
2. 插入数据
接下来,我们插入一些数据,包括空值和零值:
INSERT INTO test_table VALUES ('Kimi', 30), ('', 0), (NULL, NULL);
这里,我们插入了三行数据:
- 第一行:
Name
是 "Kimi",Age
是 30。 - 第二行:
Name
是空字符串,Age
是 0。 - 第三行:
Name
和Age
都是 NULL。
3. 查询数据
查询表中的所有数据:
SELECT * FROM test_table;
package mainimport ("database/sql""fmt""log"_ "github.com/ClickHouse/clickhouse-go"
)// DbBackedUser 用户结构体,由数据库支持
type DbBackedUser struct {Name sql.NullStringAge sql.NullInt64
}func main() {// 连接到 ClickHouse 数据库connStr := "tcp://localhost:9000?username=default&password=&database=default"db, err := sql.Open("clickhouse", connStr)if err != nil {log.Fatal(err)}defer db.Close()// 查询并打印现有的数据fmt.Println("Query results before insertion:")queryData(db)fmt.Println("查询并打印现有的数据")// 开始事务tx, err := db.Begin()if err != nil {log.Fatal(err)}// 空值var name sql.NullStringx := DbBackedUser{Name: name, Age: sql.NullInt64{Int64: 1, Valid: false}}// 插入数据_, err = tx.Exec("INSERT INTO test_table2 (Name, Age) VALUES (?, ?)", x.Name, x.Age)if err != nil {tx.Rollback() // 如果出现错误,回滚事务log.Fatal(err)}// 提交事务err = tx.Commit()if err != nil {log.Fatal(err)}// 查询并打印插入后的数据fmt.Println("Query results after insertion:")queryData(db)fmt.Println("查询并打印现有的数据")}func queryData(db *sql.DB) {rows, err := db.Query("SELECT Name, Age FROM test_table2")if err != nil {log.Fatal(err)}defer rows.Close()var nameValue sql.NullStringvar ageValue sql.NullInt64for rows.Next() {if err := rows.Scan(&nameValue, &ageValue); err != nil {log.Fatal(err)}fmt.Printf("name: %v, Age: %v\n", nameValue.String, ageValue.Int64)}
}
结果
这里
给数据库传了0值
改成true后
传值成功
NameValid改成false
传null
package mainimport ("database/sql""fmt""reflect""github.com/go-playground/validator/v10"
)// DbBackedUser 用户结构体,由数据库支持
type DbBackedUser struct {Name sql.NullString `validate:"required"` // 可以为NULL的字符串,验证规则为必填Age sql.NullInt64 `validate:"required"` // 可以为NULL的整数,验证规则为必填
}// 使用单一实例的验证器,它缓存结构体信息
var validate *validator.Validatefunc main() {// 创建一个新的验证器实例validate = validator.New()// 注册所有sql.Null*类型,使用自定义的ValidateValuer函数validate.RegisterCustomTypeFunc(ValidateValuer, sql.NullString{}, sql.NullInt64{})// 构建对象进行验证// Name字段为空字符串但Valid为true,表示非NULL;Age字段为0且Valid为false,表示NULLx := DbBackedUser{Name: sql.NullString{String: "zahngsan", Valid: true}, Age: sql.NullInt64{Int64: 18, Valid: false}}// 对x进行结构体验证err := validate.Struct(x)// 如果验证失败,打印错误信息if err != nil {fmt.Printf("Err(s):\n%+v\n", err)}
}// ValidateValuer实现了validator.CustomTypeFunc接口
func ValidateValuer(field reflect.Value) interface{} {// 检查field是否为nilif !field.IsValid() {return nil}// 检查field是否实现了sql.Null*类型if nullType, ok := field.Interface().(sql.NullString); ok {// 如果Valid为true,返回字符串和非NULL的指示if nullType.Valid {fmt.Println("NullString Valid为true:",nullType.String)return []interface{}{nullType.String, true}}// 如果Valid为false,返回空字符串和NULL的指示fmt.Println("NullString Valid为false")return []interface{}{"", false}}// 检查field是否实现了sql.NullInt64类型if nullInt64, ok := field.Interface().(sql.NullInt64); ok {if nullInt64.Valid {fmt.Println("NullInt64 Valid为true:",nullInt64.Int64)return []interface{}{nullInt64.Int64,true}}fmt.Println("NullInt64 Valid为false")return []interface{}{"", false}}// 对其他sql.Null*类型进行类似的处理...// ...// 如果不是sql.Null*类型,返回nilreturn nil
}
结果