【Golang】sql.Null* 类型使用(处理空值和零值)

sql.NullStringsql.NullInt64 类型(以及其他类似的 sql.Null* 类型)在处理数据库操作时非常有用,尤其是在 Go 语言的 database/sql 包中。它们的主要用途包括:

  1. 表示 NULL 值

    • 在数据库中,NULL 表示"没有值"。sql.Null* 类型允许你在 Go 语言中直接表示和处理这种"没有值"的状态,而不是使用 Go 的零值(如空字符串 "" 或整数 0)来表示 NULL,这样可以更准确地反映数据库中的数据状态。
  2. 数据库交互

    • 当你从数据库查询数据时,某些字段可能是 NULL。使用 sql.Null* 类型可以区分一个字段是真正包含零值还是 NULL。这对于后续的逻辑处理非常重要,因为零值和 NULL 在逻辑上可能有不同的含义。
  3. 避免错误

    • 如果不使用 sql.Null* 类型,你可能会错误地将数据库中的 NULL 值解释为 Go 语言中的零值,这可能导致逻辑错误或程序崩溃。
  4. 简化代码

    • 使用 sql.Null* 类型可以简化代码,因为你不需要编写额外的检查来确定一个值是否来自数据库中的 NULL。Valid 字段可以直接告诉你这个值是否有效。
  5. 数据完整性

    • 在将数据写入数据库时,sql.Null* 类型可以帮助你保持数据的完整性。你可以准确地标记哪些字段是有意设置为 NULL,哪些字段是具有实际值的。
  6. 灵活性

    • sql.Null* 类型提供了灵活性,允许你在不改变现有代码结构的情况下,处理那些可能为 NULL 的字段。

示例场景:

假设你正在处理一个用户信息表,其中有一个字段表示用户的出生日期。在某些情况下,用户可能没有提供出生日期,因此这个字段在数据库中是 NULL。使用 sql.NullStringsql.NullInt64(取决于出生日期如何存储)可以让你在 Go 程序中准确地表示这种情况,并在需要时进行适当的处理。

复制代码
package main

import (
	"database/sql"
	"fmt"
)

// DbBackedUser 用户结构体,由数据库支持
type DbBackedUser struct {
	Name sql.NullString
	Age  sql.NullInt64
}

func main() {
	// 空值
	var name sql.NullString
	x := 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)   // 输出: false

	fmt.Println("Age:")
	fmt.Println("Int64:", x.Age.Int64)  // 输出: 0
	fmt.Println("Valid:", x.Age.Valid)   // 输出: false
}

Name:
String: 
Valid: false
Age:
Int64: 0
Valid: false

加入与数据库交互

1. 创建测试表

clickhouse-client

首先,我们需要在 ClickHouse 中创建一个测试表。因为你的数据结构包含 NameAge,我们可以使用以下 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。
  • 第三行:NameAge 都是 NULL。

3. 查询数据

查询表中的所有数据:

复制代码
SELECT * FROM test_table;
复制代码
package main

import (
	"database/sql"
	"fmt"
	"log"

	_ "github.com/ClickHouse/clickhouse-go"
)

// DbBackedUser 用户结构体,由数据库支持
type DbBackedUser struct {
	Name sql.NullString
	Age  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.NullString
	x := 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.NullString
	var ageValue sql.NullInt64
	for 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 main

import (
	"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.Validate

func main() {
	// 创建一个新的验证器实例
	validate = validator.New()

	// 注册所有sql.Null*类型,使用自定义的ValidateValuer函数
	validate.RegisterCustomTypeFunc(ValidateValuer, sql.NullString{}, sql.NullInt64{})

	// 构建对象进行验证
	// Name字段为空字符串但Valid为true,表示非NULL;Age字段为0且Valid为false,表示NULL
	x := 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是否为nil
	if !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*类型,返回nil
	return nil
}

结果

相关推荐
不剪发的Tony老师15 小时前
Valentina Studio:一款跨平台的数据库管理工具
数据库·sql
重生之我要当java大帝15 小时前
java微服务-尚医通-编写医院设置接口下
java·开发语言·sql
weixin_3077791315 小时前
在 Microsoft Azure 上部署 ClickHouse 数据仓库:托管服务与自行部署的全面指南
开发语言·数据库·数据仓库·云计算·azure
六元七角八分15 小时前
pom.xml
xml·数据库
Achou.Wang15 小时前
源码分析 golang bigcache 高性能无 GC 开销的缓存设计实现
开发语言·缓存·golang
虚行16 小时前
Mysql 数据同步中间件 对比
数据库·mysql·中间件
奥尔特星云大使16 小时前
mysql读写分离中间件Atlas安装部署及使用
数据库·mysql·中间件·读写分离·atlas
牛马baby16 小时前
【mysql】in 用到索引了吗?
数据库·mysql·in
杀气丶16 小时前
L2JBR - 修复数据库编码为UTF8
数据库·sql·oracle
-Xie-16 小时前
Mysql杂志(三十)——索引失效情况
数据库·mysql