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
详细实例分析如下:
- 从数据库读取可能为null值得值时,可以选择使用sql.NULL***来读取;或者使用IFNULL、COALESCE等命令让数据库查询值返回不为”“或者NULL
- 若需要往数据库中插入null值,则依然可以使用sql.NULL***存储所需的值,然后进行插入NULL值
- 直接使用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 *stringScan 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 stringlastName string age int
}//由于只有一行,直接使用QueryRowrow := 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 int64Valid 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 stringValid 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 stringlastNullName sql.NullStringnullAge 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)}_, err = db.Exec("INSERT INTO person (first_name, last_name) VALUES (?, ?)", "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
- 所有使用NULL值的情况,都可以通过一个有意义的值的表示,这样有利于代码的可读性和可维护性,并能从约束上增强业务数据的规范性。
- NULL值在timestamp类型下容易出问题,特别是没有启用参数explicit_defaults_for_timestamp
- NOT IN、!= 等负向条件查询在有 NULL 值的情况下返回永远为空结果,查询容易出错
- Null 列需要更多的存储空间:需要一个额外字节作为判断是否为 NULL 的标志位
- 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}
}
golang 数据库null值错误 解决方法相关推荐
- 总结 db visualizer连接GBase8s数据库报-908错误解决方法
总结 db visualizer连接GBase8s数据库报-908错误解决方法 检查数据库实例是否正常运行:在服务器端使用informix用户执行命令onstat – 检查dbaccess命令能否正常 ...
- oracle数据库适配器错误,Oracle数据库协议适配器错误解决方法
Oracle协议适配器错误解决办法 作者:IT实验室出处:博客2012-01-29 15:44 在Oracle中新建了一个数据库,今天把它删了之后再登录SQL*PLUS就登不上去了,出现ORA-125 ...
- mysql 1326_mssqlserver无法远程数据库Error: 1326错误解决方法
今天我在使用mssqlserver时空出不能远程服务器了,Error: 1326错误出现,下面我们来看看关于解决办法吧. 我们在在使用SQL Server时都会遇到使用SQL Server Manag ...
- SQL2012 附加数据库提示5120错误解决方法
在win8.1 x64系统上使用sql2012进行附加数据库(包括在x86系统正在使用的数据库文件,直接拷贝附加在X64系统中)时,提示无法打开文件,5120错误. 这个错误是因为没有操作权限,所以附 ...
- oracle 4098,ORA-04098错误解决方法-数据库专栏,ORACLE
ora-04098错误解决方法 数据库版本:8.1.5 平台:solaris 5.7 背景: 用户建立了一个trigger: create or replace trigger ddl_deny be ...
- oracle数据库重建em,oracle 11g em重建报唯一约束错误解决方法
oracle 11g em重建报唯一约束错误解决方法 更新时间:2012年11月27日 15:07:33 作者: 今天在手工配置Oracle11g的EM时总是报如下错误,也没有找到解决办法,以下是 ...
- C#常见错误解决方法
1.能提供Visual Studio开发工具包吗? 解决方法: Visual Studio 2017开发环境下载地址: https://www.visualstudio.com/zh-hans/dow ...
- 模板引擎不关心内容之——art-template,碰见的同步与fs.readFile异步以及函数回调问题的描述,针对fs的readfille读取文件时,返回不了异步函数返回值的解决方法
模板引擎不关心内容 art-template art-template不仅可以在浏览器使用,也可以在node中使用 npm install art-template该命令在哪执行就会把包下载在哪里,默 ...
- GetLastError()10013错误解决方法及错误列表
10013错误解决方法 10013错误为以一种访问权限不允许的方式做了一个访问套接字的尝试. 错误原因: 1.SOCKET socketRaw = socket(AF_INET,SOCK_RAW,IP ...
最新文章
- 零基础入门学习python(24)-字典(2):字典的内置方法
- 《结网》十年,《结网2》开启产品经理的无限游戏
- java知识理论_JAVA理论知识 - OSC_rnoszD的个人空间 - OSCHINA - 中文开源技术交流社区...
- jenkins 执行构建 并查看结果
- php 发送delete请求,PHP中使用CURL实现GET、POST、PUT、DELETE请求
- Python学习笔记:交互对话环境IPython
- 一道Struts面试题
- 阿里Java开发手册思考(二)
- Nginx使用GeoIP模块来限制地区访问
- php绘制甘特图,如何用excel做甘特图?甘特图制作方法图解
- rufus-3.2制作linux/Windows启动盘,附rufus.exe软件程序下载链接
- 修改计算机管理员administrator的密码
- 一加nfc门禁卡录入_一加7t怎么开启NFC 模拟门禁卡方法介绍
- 为何能力越强越不被重用?不懂这3点,你到哪里都混不好,不服不行
- 【高效获取jpeg图片的尺寸】
- 微信录音arm格式转换为mp3(亲测解决 Java linux centos 环境)
- Java(回文数--一种比较简单的写法)
- nodejs高考志愿填报辅助系统的设计与实现vue
- USB 设备无法识别故障的排除
- 甘超波:NLP亲子教育的本质