微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

golang 数据库null值错误 解决方法

遇到问题:

converting NULL to string is unsupported
定义的结构体中 结构体成员类型为string,从MysqL数据库查询数据中有NULL值,go语言执行scan操作时不能转换。

查询有两种方法解决

1、定义类型为sql.NullString
共有如下类型 sql.NullString sql.NullBool sql.NullFloat64 sql.NullInt32 sql.NullInt64 sql.NullTime
2、使用IFNULL() 或者 COALESCE()
coalesce()解释:返回参数中的第一个非空表达式(从左向右依次类推)

IFNULL(expr1,expr2):如果expr1不是NULL,IFNULL()返回expr1,否则它返回expr2。IFNULL()返回一个数字或字符串值,取决于它被使用的上下文环境。

查询语句使用一个认值来替换NULL即可

解决方

由于需求是从MysqL数据库中读取数据存储到另外的数据库中,为了减少写入的修改,因此采用第二种方式,在取数据时使用IFNULL

详细实例分析如下:

  1. 数据库读取可能为null值得值时,可以选择使用sql.NULL***来读取;或者使用IFNULL、COALESCE等命令让数据库查询值返回不为”“或者NULL
  2. 若需要往数据库中插入null值,则依然可以使用sql.NULL***存储所需的值,然后进行插入NULL值
  3. 直接使用sql.NULL***类型容易出现valid遗漏设置等问题,普通int、string与其转换时,请写几个简单的get、set函数

本demo使用的数据库表以及数据如下

MysqL> desc person;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| id         | int(11)      | NO   | PRI | NULL    | auto_increment |
| first_name | varchar(100) | NO   |     | NULL    |                |
| last_name  | varchar(40)  | YES  |     | NULL    |                |
| age        | int(11)      | YES  |     | NULL    |                |
+------------+--------------+------+-----+---------+----------------+

MysqL> select * from person;
+----+------------+-----------+------+
| id | first_name | last_name | age  |
+----+------------+-----------+------+
|  1 | yousa      | NULL      | NULL |
+----+------------+-----------+------+

MysqL> show create table person;
+--------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table  | Create Table                                                                                                                                                                                                                                           |
+--------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| person | CREATE TABLE `person` (
  `id` int(11) NOT NULL AUTO_INCREMENT,`first_name` varchar(100) NOT NULL,`last_name` varchar(40) DEFAULT NULL,`age` int(11) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 |
+--------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

数据库中读取NULL值

如果不作处理直接从数据库中读取NULL值到string/int,会发生如下错误错误

Scan NULL值到string的报错
sql: Scan error on column index 1: unsupported Scan,storing driver.Value type <nil> into type *string

Scan NULL值到int的报错
sql: Scan error on column index 1: converting driver.Value type <nil> ("<nil>") to a int: invalid Syntax

使用如下的struct来读取数据库内容


type Person struct {
    firstName               string
    lastName                string 
    age                     int
}

    //由于只有一行,直接使用QueryRow
    row := db.QueryRow("SELECT first_name,last_name FROM person WHERE first_name='yousa'")
    err = row.Scan(&hello.firstName,&hello.lastName)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(hello)

    row1 := db.QueryRow("SELECT first_name,age FROM person WHERE first_name='yousa'")
    err = row1.Scan(&hello.firstName,&hello.age)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(hello)

运行代码,可以通过日志看出来,错误来自Scan将NULL值赋值给int或者string时,报错;解决这个问题可以使用sql原生结构体sql.Null***来解决

使用sqlNull***

sql.Null***在sql库中声明如下,在读取时,(比如读取的值存储到NullInt64),假如发现存储的值是NULL,则会将NullInt64的valid设置为false,然后不会将值存储到Int64中,Int64值认为0,如果是NullString则String值时nil;如果是正常值,则会将Valid赋值为true,将值存储到Int64中。

type NullInt64 struct {
    Int64 int64
    Valid bool // Valid is true if Int64 is not NULL
}
func (n *NullInt64) Scan(value interface{}) error
func (n NullInt64) Value() (driver.Value,error)

type NullString struct {
    String string
    Valid  bool // Valid is true if String is not NULL
}
func (ns *NullString) Scan(value interface{}) error
func (ns NullString) Value() (driver.Value,error)

代码修改为如下:

type Person struct {
    firstName               string
    lastNullName            sql.NullString
    nullAge                 sql.NullInt64
}

    rowNull := db.QueryRow("SELECT first_name,last_name FROM person WHERE first_name='yousa'")
    err = rowNull.Scan(&hello.firstName,&hello.lastNullName)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(hello)

    rowNull1 := db.QueryRow("SELECT first_name,age FROM person WHERE first_name='yousa'")
    err = rowNull1.Scan(&hello.firstName,&hello.nullAge)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(hello)

输出结果

{yousa  0 { false} {0 false}}
{yousa  0 { false} {0 false}}

使用IFNULL或者COALESCE

coalesce()解释:返回参数中的第一个非空表达式(从左向右依次类推)

IFNULL(expr1,expr2):如果expr1不是NULL,IFNULL()返回expr1,否则它返回expr2。IFNULL()返回一个数字或字符串值,取决于它被使用的上下文环境。

查询语句使用一个认值来替换NULL即可

SELECT first_name,COALESCE(age,0) FROM person;//
SELECT first_name,IFNULL(age,0) FROM person;//

数据库中插入NULL值

前面我们对SELECT语句使用了sql.Null***类型,同理,INSERT、UPDATE语句也可以通过使用这种类型来插入nil值

代码如下:

    hello := Person {
        firstName: "",lastName: "",age: 0,lastNullName: sql.NullString{String:"",Valid:false},nullAge: sql.NullInt64{Int64:0,Valid:false}}
    _,err = db.Exec(
        "INSERT INTO person (first_name,last_name) VALUES (?,?)","yousa1",hello.lastName)
    if err != nil {
        fmt.Println(err)
    }

    _,"yousa2",hello.lastNullName)
    if err != nil {
        fmt.Println(err)
    }


//数据库插入结果
MysqL> select * from person;
+----+------------+-----------+------+
| id | first_name | last_name | age  |
+----+------------+-----------+------+
|  1 | yousa      | NULL      | NULL |
|  2 | yousa1     |           | NULL |
|  3 | yousa2     | NULL      | NULL |
+----+------------+-----------+------+

解释下db.Exec操作hello.lastNullName的过程:

首先它会调用hello.lastNullName的Value方法获取到driver.Value,然后检验Valid值是true还是false,如果是false则会返回一个nil值(nil值传给sql driver会被认为是NULL值),如果是true则会将hello.lastNullName.String的值传过去。

PS: 为了保证你所插入的值能如你所期望是NULL值,一定记得要将sql.Null***中Valid值置为false

使用NULL还是有很多危害的,再回顾下数据库中使用NULL值的危害

为什么不建议使用NULL

  1. 所有使用NULL值的情况,都可以通过一个有意义的值的表示,这样有利于代码的可读性和可维护性,并能从约束上增强业务数据的规范性。
  2. NULL值在timestamp类型下容易出问题,特别是没有启用参数explicit_defaults_for_timestamp
  3. NOT IN、!= 等负向条件查询在有 NULL 值的情况下返回永远为空结果,查询容易出错
  4. Null 列需要更多的存储空间:需要一个额外字节作为判断是否为 NULL 的标志位
  5. NULL值到非NULL的更新无法做到原地更新,更容易发生索引分裂,从而影响性能

PS:但把NULL列改为NOT NULL带来的性能提示很小,除非确定它带来了问题,否则不要把它当成优先的优化措施,最重要的是使用的列的类型的适当性。

当然有些情况是不得不使用NULL值进行存储,或者在查询时由于left/right join等导致NULL值,但总体来说,能少用就少用。

helper func(提升效率/减少错误

如果使用sql.NULL***的话,由于其有两个字段,如果直接手动赋值的话还是很容易遗漏,所以还是需要简单的转换函数,这里给了两个简单的helper fuc,分别是将int64转换成NullInt64和将string转换成NullString

//ToNullString invalidates a sql.NullString if empty,validates if not empty
func ToNullString(s string) sql.NullString {
    return sql.NullString{String : s,Valid : s != ""}
}

//ToNullInt64 validates a sql.NullInt64 if incoming string evaluates to an integer,invalidates if it does not
func ToNullInt64(s string) sql.NullInt64 {
    i,err := strconv.Atoi(s)
    return sql.NullInt64{Int64 : int64(i),Valid : err == nil}
}

 

 

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐