查询数据方法回顾整理

上一篇博客中,主要是快速过了一遍 demo 代码和 DB 类型对象中方法的使用

在整理查询数据方法的时候,使用了 Query() 方法,其实 sqlx 还提供了 QueryRow() 方法,查询单行记录,以及 Queryx() 和 QueryRowx() 方法,将查询的结果保存到结构体

所以我们通过 DB 查询数据的方法一共就有三对:

  • Query()  和  QueryRow()            分别返回 sql.Rows 和 sql.Row 类型
  • Queryx() 和 QueryRowx()             分别返回 sql.Rows 和 sql.Row 类型,支持将查询记录保存到结构体
  • Get()  和  Select()  将查询记录保存到结构体 和 结构体切片中

Query() 方法

func (db *DB) Query(query string, args ...interface{}) (*Rows, error)

使用场景:查询字段较少的情况下使用,比如 select uid, username from userinfo; 这样的语句,如果是 select * from userinfo,就使用 Get() 或 Select() 好了

Query() 返回的结果集是 sql.Rows 类型,它有一个 Next() 方法,可以迭代数据库的游标,进而获取下一条记录,结果集使用完毕之后需要调用 rows.Close() 手动关闭连接

其实通过 for 循环迭代数据的时候,当迭代到最后一行记录时,会发出一个 io.EOF(与读文件类似),引发一个错误,同时 Go 会自动调用 rows.Close() 方法释放连接,然后返回 false,此时循环结束退出

通常情况下,会正常迭代完数据然后退出循环,可是如果因为循环语句中的其它错误导致退出了循环,此时 rows.Next() 处理结果集的过程并没有完成,归属于 rows 的数据库连接不会释放回到连接池,因此十分有必要正确的处理 rows 的连接,如果没有关闭 rows 连接,将导致大量的连接并且不会被其它方法重用,就像溢出了一样,最终导致数据库无法使用(提示数据库有过多的连接)

rows.Next循环迭代的时候,因为触发了io.EOF而退出循环。为了检查是否是迭代正常退出还是异常退出,需要检查rows.Err

package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
) var (
userName string = "root"
password string = "seemmo"
ipAddrees string = "10.10.4.80"
port int = 3306
dbName string = "golang_db"
charset string = "utf8"
) func connectMysql() *sqlx.DB {
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s", userName, password, ipAddrees, port, dbName, charset)
Db, err := sqlx.Open("mysql", dsn)
if err != nil {
fmt.Printf("mysql connect failed, detail is [%v]", err.Error())
}
return Db
} func queryData(Db *sqlx.DB) {
rows, err := Db.Query("select uid, username, create_time from userinfo")
if err != nil {
fmt.Printf("query data failed, error is [%v]", err.Error())
return
} for rows.Next() {
var uid int
var userName, createTime string
err := rows.Scan(&uid, &userName, &createTime)
if err != nil {
fmt.Printf("scan data failed, error is [%v]", err.Error())
return
}
fmt.Println(uid, userName, createTime)
} err = rows.Close()
if err != nil {
fmt.Println(err.Error())
}
} func main (){
var Db *sqlx.DB = connectMysql()
defer Db.Close() queryData(Db)
} //运行结果:
//1 johny 2019-07-08 10:43:21
//2 anson 2019-07-08 10:52:46

  

QueryRow() 方法

func (db *DB) QueryRow(query string, args ...interface{}) *Row

Query() 方法是查询多行结果集的(sqlx.Rows),QueryRow() 方法用来查询单行结果集(sqlx.Row),不需要通过 Next() 方法迭代

QueryRow() 方法的返回值与 Query() 不同,它要么返回一个 sqlx.Row 类型,要么返回一个 error 类型,如果是发生了 error,则会延迟到 Scan() 方法调用结束后返回,如果没有错误,则 Scan 正常执行,只有当查询结果为空的时候,会触发一个 sqlx.ErrNoRows 错误,你可以先调用 Scan() 方法再检查错误(也可以先检查错误再调用 Scan() 方法)

在没有过滤条件的情况下,默认返回第一条数据,不用调用 Close() 方法释放连接(因为只有一条记录)

package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
"time"
) var (
userName string = "root"
password string = "seemmo"
ipAddrees string = "10.10.4.80"
port int = 3306
dbName string = "golang_db"
charset string = "utf8"
) func connectMysql() *sqlx.DB {
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s", userName, password, ipAddrees, port, dbName, charset)
Db, err := sqlx.Open("mysql", dsn)
if err != nil {
fmt.Printf("mysql connect failed, detail is [%v]", err.Error())
}
return Db
} func queryRow(Db *sqlx.DB) {
row := Db.QueryRow("select uid, username, create_time from userinfo") var uid int
var userName, createTime string
err := row.Scan(&uid, &userName, &createTime)
if err != nil {
fmt.Printf("scan data failed, error is [%v]", err.Error())
return
}
fmt.Println(uid, userName, createTime)
} func main (){
var Db *sqlx.DB = connectMysql()
defer Db.Close() queryRow(Db)
} 运行结果:
1 johny 2019-07-08 10:43:21

  

查询方法补充

Queryx() 和 QueryRowx(),不仅支持 Scan() 方法,同时可将数据与结构体进行转换

1)Queryx()

func (db *DB) Queryx(query string, args ...interface{}) (*Rows, error)

代码示例

func queryx(Db *sqlx.DB) {
//定义结构体保存数据
type userinfo struct {
Uid int `db:"uid"`
UserName string `db:"username"`
CreateTime string `db:"create_time"`
} var userData userinfo
rows, err := Db.Queryx("select uid, username, create_time from userinfo")
if err != nil {
fmt.Printf("query data failed, error is [%v]", err.Error())
return
} var userDataSlice []userinfo
for rows.Next() {
err := rows.StructScan(&userData)
if err != nil {
fmt.Printf("scan data failed, error is [%v]", err.Error())
return
}
userDataSlice = append(userDataSlice, userData)
}
fmt.Println(userDataSlice) err = rows.Close()
if err != nil {
fmt.Println(err.Error())
}
} 运行结果:
[{1 johny 2019-07-08 14:05:40} {2 anson 2019-07-08 16:33:19}]

2)QueryRowx()

func (db *DB) QueryRowx(query string, args ...interface{}) *Row

代码示例

func queryRowx(Db *sqlx.DB) {
//定义结构体保存数据
type userinfo struct {
Uid int `db:"uid"`
UserName string `db:"username"`
CreateTime string `db:"create_time"`
} var userData *userinfo = new(userinfo)
row := Db.QueryRowx("select uid, username, create_time from userinfo where uid = 1")
err := row.StructScan(userData)
if err != nil {
fmt.Printf("scan data failed, error is [%v]", err.Error())
} fmt.Println(userData.Uid, userData.UserName, userData.CreateTime)
} 运行结果:
1 johny 2019-07-08 14:05:40

说了这么多,Query(),QueryRow() 不如 Get(),Select() 方法简洁

空值处理

Scan() 方法处理数据库中的 null

1)使用标准库中的数据类型

数据库中有一个特殊的类型,null 空值,可是 null 不能通过 scan 直接给变量赋值,也不能将 null 赋值给 nil,对于 null 必须指定特殊的类型,这些类型定义在 sqlx 扩展库中,例如 sql.NullFloat64,sql.NullString,sql.NullBool,sql.NullInt64,如果在扩展库中找不到匹配的值,可以尝试在驱动中寻找,下面的 demo,当数据表中 create_time 字段为 null 时,如果直接这样查询,会提示错误:

sql: Scan error on column index 2, name "create_time": unsupported Scan, storing driver.Value type <nil> into type *string

所以需要将代码改为:

func queryRow(Db *sqlx.DB) {
row := Db.QueryRow("select uid, username, create_time from userinfo") var uid int
var userName string
var createTime sql.NullString
err := row.Scan(&uid, &userName, &createTime)
if err != nil {
fmt.Printf("scan data failed, error is [%v]", err.Error())
return
}
fmt.Println(uid, userName, createTime)
} 运行结果:
1 johny { false}

  

上面的运行结果中 {  false},其实是 空字符串  与 string 类型的判断结果

在查询数据之前,查询结果有两种情况,null 与 非null,所以是需要验证的,如果值为 null,则会输出 NullString 的默认值,否则输出查询的值,demo 如下:

func queryRow(Db *sqlx.DB) {
row := Db.QueryRow("select uid, username, create_time from userinfo") var uid int
var userName string
var createTime sql.NullString
err := row.Scan(&uid, &userName, &createTime)
if err != nil {
fmt.Printf("scan data failed, error is [%v]", err.Error())
return
}
fmt.Printf("%d %s\n", uid, userName)
fmt.Printf("createTime.String: '%v'\n", createTime.String)
fmt.Printf("createTime.Valid: %v\n", createTime.Valid)
} 运行结果:
//null值的情况
1 johny
createTime.String: ''
createTime.Valid: false //值存在的情况
1 johny
createTime.String: '2019-07-08 12:53:18'
createTime.Valid: true

  

2)使用 []byte 接收数据

如果我们不关心查询的字段数据是不是 null 的时候,只是想把它当做空字符串处理就行,可以定义 []byte 接收数据,这样处理后,如果有值就获取值([]byte),如果没有则获取的为空字符串,demo 如下:

func queryRow(Db *sqlx.DB) {
row := Db.QueryRow("select uid, username, create_time from userinfo") var uid []byte
var userName []byte
var createTime []byte
err := row.Scan(&uid, &userName, &createTime)
if err != nil {
fmt.Printf("scan data failed, error is [%v]", err.Error())
return
}
fmt.Printf("%s %s\n", uid, userName)
fmt.Printf("createTime.String: '%s'\n", createTime)
} 运行结果:
//有值的情况
1 johny
createTime.String: '2019-07-08 12:53:18' //null值的情况
1 johny
createTime.String: ''

  

自动匹配字段数据

竟然所有的数据都能通过 []byte 进行接收,而字段名都是 string 类型,那么可以就可以把查询的数据放到 map 中保存,然后根据 key 进行取值,这样就方便多了

demo:

package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
) var (
userName string = "root"
password string = "seemmo"
ipAddrees string = "10.10.4.80"
port int = 3306
dbName string = "golang_db"
charset string = "utf8"
) func connectMysql() *sqlx.DB {
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s", userName, password, ipAddrees, port, dbName, charset)
Db, err := sqlx.Open("mysql", dsn)
if err != nil {
fmt.Printf("mysql connect failed, detail is [%v]", err.Error())
}
return Db
} func queryData(Db *sqlx.DB) {
rows, err := Db.Query("select uid, username, create_time from userinfo")
if err != nil {
fmt.Printf("query data failed, error is [%v]", err.Error())
return
} cols, err := rows.Columns()
if err != nil {
fmt.Errorf("get rows columns failed, error is [%v]", err.Error())
} var vals = make([][]byte, len(cols)) //用来存放查询数据
var scanSlice = make([]interface{}, len(cols)) //用来当做参数,Scan 接收接口类型的参数
////将 []byte 放入接口
for i := range cols {
scanSlice[i] = &vals[i]
} var sliceMapData = make([]map[string]string, 0) for rows.Next() {
err := rows.Scan(scanSlice...)
if err != nil {
fmt.Printf("scan data failed, error is [%v]", err.Error())
return
} var mapData = make(map[string]string)
//这里遍历的是 字节切片
for i, value := range vals {
mapData[cols[i]] = string(value)
}
fmt.Println(mapData)
sliceMapData = append(sliceMapData, mapData)
}
fmt.Println(sliceMapData) err = rows.Close()
if err != nil {
fmt.Println(err.Error())
}
} func main (){
var Db *sqlx.DB = connectMysql()
defer Db.Close() queryData(Db)
} 运行结果:
map[create_time:2019-07-08 14:05:40 uid:1 username:johny]
map[create_time: uid:2 username:anson]
[map[create_time:2019-07-08 14:05:40 uid:1 username:johny] map[create_time: uid:2 username:anson]]

查询的是全部字段的数据,使用 rows.Columns() 方法可以获取到字段数据的切片([]string)

然后创建一个切片 vals,用来存放所取出来的数据结果

接下来又定义一个切片 scanSlice,在 Scan() 中使用,因为Scan() 方法接收的数据是接口类型,将数据库的查询结果复制给到它

vals 则得到了 scanSlice 复制给它的值,因为是 byte 切片,因此在循环一次,将其转换成 string,最后添加到 map 类型中

参考链接:https://www.cnblogs.com/zhaof/p/8509164.html

ending ~

Go 操作 Mysql(二)的更多相关文章

  1. Go基础之--操作Mysql(二)

    在上一篇文章中主要整理了Golang连接mysql以及一些基本的操作,并进行了大概介绍,这篇文章对增删查改进行详细的整理 读取数据 在上一篇文章中整理查询数据的时候,使用了Query的方法查询,其实d ...

  2. python操作mysql二

    游标 游标是一种能从包括多条数据记录的结果集中每次提取一条记录的机制,游标充当指针的作用,尽管游标能遍历结果中的所有行,但它一次只指向一行,游标的作用就是用于对查询数据库所返回的记录进行遍历,以便进行 ...

  3. MySQL之命令行简单操作MySQL(二)

    一:命令行连接数据库 打开终端,运行命令mysql -uroot -p (p后面加密码,可以直接加,也可以回车在下一行输入,为了不暴露密码,回车在下行输入 退出:exit或quit 查看版本信息: s ...

  4. Python全栈开发之MySQL(二)------navicate和python操作MySQL

    一:Navicate的安装 1.什么是navicate? Navicat是一套快速.可靠并价格相宜的数据库管理工具,专为简化数据库的管理及降低系统管理成本而设.它的设计符合数据库管理员.开发人员及中小 ...

  5. 一、初识MySQL数据库 二、搭建MySQL数据库(重点) 三、使用MySQL数据库 四、认识MySQL数据库的数据类型 五、操作MySQL数据库的数据(重点)

    一.初识MySQL数据库 ###<1>数据库概述     1. 数据库         长期存储在计算机内的,由组织的可共享的数据集合         存储数据的仓库         文件 ...

  6. MySQL数据库(三)—— 表相关操作(二)之约束条件、关联关系、复制表

    表相关操作(二)之约束条件.关联关系.复制表 一.约束条件  1.何为约束 除了数据类型以外额外添加的约束 2.约束条件的作用 为了保证数据的合法性,完整性 3.主要的约束条件 NOT NULL # ...

  7. python【第十二篇下】操作MySQL数据库以及ORM之 sqlalchemy

    内容一览: 1.Python操作MySQL数据库 2.ORM sqlalchemy学习 1.Python操作MySQL数据库 2. ORM sqlachemy 2.1 ORM简介 对象关系映射(英语: ...

  8. python + docker, 实现天气数据 从FTP获取以及持久化(二)-- python操作MySQL数据库

    前言 在这一节中,我们主要介绍如何使用python操作MySQL数据库. 准备 MySQL数据库使用的是上一节中的docker容器 “test-mysql”. Python 操作 MySQL 我们使用 ...

  9. Python操作MySQL数据库(二)

    pymsql是Python中操作MySQL的模块,其使用方法和MySQLdb几乎相同. 下载安装: pip install pymysql 1.执行SQL语句 #!/usr/bin/env pytho ...

  10. 【翻译】MongoDB指南/CRUD操作(二)

    [原文地址]https://docs.mongodb.com/manual/ MongoDB CRUD操作(二) 主要内容: 更新文档,删除文档,批量写操作,SQL与MongoDB映射图,读隔离(读关 ...

随机推荐

  1. ubuntu18源码包安装openresty

    author: headsen chen date : 2019-07-30  15:42:24 #在ubuntu18.04 环境下,openresty的依赖库有:PCRE.OpenSSL.zlib, ...

  2. Flutter Drawer 侧边栏、以及侧边栏内 容布局

    Flutter Drawer 侧边栏 在 Scaffold 组件里面传入 drawer 参数可以定义左侧边栏,传入 endDrawer 可以定义右侧边 栏.侧边栏默认是隐藏的,我们可以通过手指滑动显示 ...

  3. 005-guava 集合-集合工具类-java.util.Collections中未包含的集合工具[Maps,Lists,Sets],Iterables、Multisets、Multimaps、Tables

    一.概述 工具类与特定集合接口的对应关系归纳如下: 集合接口 属于JDK还是Guava 对应的Guava工具类 Collection JDK Collections2:不要和java.util.Col ...

  4. shop++上传图片失败

    如题上传shop++商城商品图片失败. 其主要原因是图片过大.本人使用测试需要在500KB以下才能成功 1.点击编辑缩小尺寸.这个缩小有限 2.用QQ截图另存为png

  5. k8s记录-kubeadm安装(一)(转载)

    配置 kubeadm 概述 安装 kubernetes 主要是安装它的各个镜像,而 kubeadm 已经为我们集成好了运行 kubernetes 所需的基本镜像.但由于国内的网络原因,在搭建环境时,无 ...

  6. Java分布式:分布式事务

    Java分布式:分布式事务 分布式事务 分布式事务就是指事务的参与者.支持事务的服务器.资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上.简单的说,就是一次大的操作由不同的小操作组成,这 ...

  7. iOS-UIPasteboard的使用

    剪贴板的使用以及自定义剪贴板. 系统剪贴板的直接调用 其实整个过程非常的简单,我就用我写的一个自定义UILable来说明调用系统剪贴板. 首先,因为苹果只放出来了 UITextView,UITextF ...

  8. 【GStreamer开发】GStreamer基础教程11——调试工具

    目标 有时我们的应用并没有按照我们的预期来工作,并且在总线上获得的错误信息也没有足够的内容.这时我们该怎么办呢?幸运的时,GStreamer自身提供了大量的调试信息,通常这些信息会给出一些线索,指向出 ...

  9. 大师Geoff Hinton关于Deep Neural Networks的建议

    大师Geoff Hinton关于Deep Neural Networks的建议 Note: This covers suggestions from Geoff Hinton's talk given ...

  10. C/C++ 多线程(程序猿面试重点)CodeBlocks-CB的pthreads使用

    C++ 多线程 本文主要讲一下C++多线程 线程好处 ·使用线程可以把占据长时间的程序中的任务放到后台去处理 ·程序的运行速度可能加快 可以释放一些珍贵的资源如内存占用等等. 但是多线程是为了同步完成 ...