go 操作 mysql 数据 DQL 查询语句范例
我们现在需要在程序中对下面这张表进行查询
# 查看 students 表的数据类型
MariaDB [hellodb]> desc students;
+-----------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+---------------------+------+-----+---------+----------------+
| StuID | int(10) unsigned | NO | PRI | NULL | auto_increment |
| Name | varchar(50) | NO | | NULL | |
| Age | tinyint(3) unsigned | NO | | NULL | |
| Gender | enum('F','M') | NO | | NULL | |
| ClassID | tinyint(3) unsigned | YES | | NULL | |
| TeacherID | int(10) unsigned | YES | | NULL | |
+-----------+---------------------+------+-----+---------+----------------+
6 rows in set (0.001 sec)
# 查看该表的数据,发现 ClassID 和 TeacherID 有两个 null 值
MariaDB [hellodb]> select * from students;
+-------+---------------+-----+--------+---------+-----------+
| StuID | Name | Age | Gender | ClassID | TeacherID |
+-------+---------------+-----+--------+---------+-----------+
| 1 | Shi Zhongyu | 22 | M | 2 | 3 |
| 2 | Shi Potian | 22 | M | 1 | 7 |
| 3 | Xie Yanke | 53 | M | 2 | 16 |
| 4 | Ding Dian | 32 | M | 4 | 4 |
| 5 | Yu Yutong | 26 | M | 3 | 1 |
| 6 | Shi Qing | 46 | M | 5 | NULL |
| 7 | Xi Ren | 19 | F | 3 | NULL |
| 8 | Lin Daiyu | 17 | F | 7 | NULL |
| 9 | Ren Yingying | 20 | F | 6 | NULL |
| 10 | Yue Lingshan | 19 | F | 3 | NULL |
| 11 | Yuan Chengzhi | 23 | M | 6 | NULL |
| 12 | Wen Qingqing | 19 | F | 1 | NULL |
| 13 | Tian Boguang | 33 | M | 2 | NULL |
| 14 | Lu Wushuang | 17 | F | 3 | NULL |
| 15 | Duan Yu | 19 | M | 4 | NULL |
| 16 | Xu Zhu | 21 | M | 1 | NULL |
| 17 | Lin Chong | 25 | M | 4 | NULL |
| 18 | Hua Rong | 23 | M | 7 | NULL |
| 19 | Xue Baochai | 18 | F | 6 | NULL |
| 20 | Diao Chan | 19 | F | 7 | NULL |
| 21 | Huang Yueying | 22 | F | 6 | NULL |
| 22 | Xiao Qiao | 20 | F | 1 | NULL |
| 23 | Ma Chao | 23 | M | 4 | NULL |
| 24 | Xu Xian | 27 | M | NULL | NULL |
| 25 | Sun Dasheng | 100 | M | NULL | NULL |
+-------+---------------+-----+--------+---------+-----------+
25 rows in set (0.000 sec)
代码:
package main
import (
"database/sql"
"fmt"
// 注册 mysql 驱动
_ "github.com/go-sql-driver/mysql"
)
func main() {
// 使用驱动,这里使用 mysql 驱动
driverName := "mysql"
// 定义 mysql 数据库链接信息
// dataSourceName 写法范例: user:password@protocol(host:port)/dbname?charset=utf8mb4&loc=Local&parseTime=true
// user:password 连接数据库的用户名和密码
// protocol(host:port) 连接时候使用的协议,以及连接的主机和端口
// dbname 连接的数据库
// charset=utf8mb4 数据库设置的字符集为 utf8mb4
// loc=Local 链接数据库的时区,Local 为本地,会自动读取操作系统的时区
// parseTime=true 通过驱动实现时区之间的转换
dsn := "root:root@tcp(10.0.0.10:3306)/hellodb?charset=utf8mb4&loc=Local&parseTime=true"
// 打开数据库链接
db, err := sql.Open(driverName, dns)
if err != nil {
fmt.Println(err)
return
}
// 延迟关闭链接
defer db.Close()
// 通过 ping 测试是否链接数据库成功,如果成功返回值 err 为 nil,否则不等于 nil
if db.Ping() != nil {
fmt.Println(err)
return
}
// 这里写的就是对应的 sql 语句
// 对数据库操作,上面链接的时候已经进入到 hellodb 库中,我对 hellodb 库中的 students 表进行操作
rows, err := db.Query("select * from students;")
if err != nil {
fmt.Println(err)
return
}
// 关闭对 students 表
defer rows.Close()
// 获取 rows 从数据库中拿到的数据信息
// rows.Next 用来移动下一条数据查询的
for rows.Next() {
/*
MariaDB [hellodb]> desc students;
+-----------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+---------------------+------+-----+---------+----------------+
| StuID | int(10) unsigned | NO | PRI | NULL | auto_increment |
| Name | varchar(50) | NO | | NULL | |
| Age | tinyint(3) unsigned | NO | | NULL | |
| Gender | enum('F','M') | NO | | NULL | |
| ClassID | tinyint(3) unsigned | YES | | NULL | |
| TeacherID | int(10) unsigned | YES | | NULL | |
+-----------+---------------------+------+-----+---------+----------------+
这里定义我们 students 表中的数据类型在 go 代码中对应的数据类型
*/
var (
StuID int32
Name string
Age int32
Gender string
// 因为 ClassID 和 TeacherID 中存在 null 值,可以通过 nullInt32 结构体就能数据类型获取
ClassID sql.NullInt32
TeacherID sql.NullInt32
)
// 数据库中有多少条信息,我们就需要 sacn 多少次,扫描的时候可能会出现错误,我们判断一下
err := rows.Scan(&StuID, &Name, &Age, &Gender, &ClassID, &TeacherID)
if err != nil {
fmt.Println(err)
break
} else {
fmt.Println(StuID, Name, Age, Gender, ClassID, TeacherID)
}
}
}
执行该程序:
[11:40:40 root@go dql]#go run main.go
1 Shi Zhongyu 22 M {2 true} {3 true}
2 Shi Potian 22 M {1 true} {7 true}
3 Xie Yanke 53 M {2 true} {16 true}
4 Ding Dian 32 M {4 true} {4 true}
5 Yu Yutong 26 M {3 true} {1 true}
6 Shi Qing 46 M {5 true} {0 false}
7 Xi Ren 19 F {3 true} {0 false}
8 Lin Daiyu 17 F {7 true} {0 false}
9 Ren Yingying 20 F {6 true} {0 false}
10 Yue Lingshan 19 F {3 true} {0 false}
11 Yuan Chengzhi 23 M {6 true} {0 false}
12 Wen Qingqing 19 F {1 true} {0 false}
13 Tian Boguang 33 M {2 true} {0 false}
14 Lu Wushuang 17 F {3 true} {0 false}
15 Duan Yu 19 M {4 true} {0 false}
16 Xu Zhu 21 M {1 true} {0 false}
17 Lin Chong 25 M {4 true} {0 false}
18 Hua Rong 23 M {7 true} {0 false}
19 Xue Baochai 18 F {6 true} {0 false}
20 Diao Chan 19 F {7 true} {0 false}
21 Huang Yueying 22 F {6 true} {0 false}
22 Xiao Qiao 20 F {1 true} {0 false}
23 Ma Chao 23 M {4 true} {0 false}
24 Xu Xian 27 M {0 false} {0 false}
25 Sun Dasheng 100 M {0 false} {0 false}
但是不推荐在代码中写 select * from
呢,假设数据库管理员在该表中加了一个字段,如下操作
# 在 students 表中添加一条 phone 信息
MariaDB [hellodb]> ALTER TABLE students ADD phone varchar(11) ;
Query OK, 0 rows affected (0.016 sec)
Records: 0 Duplicates: 0 Warnings: 0
# 查询改表已经多了一条 phone
MariaDB [hellodb]> select * from students;
+-------+---------------+-----+--------+---------+-----------+-------+
| StuID | Name | Age | Gender | ClassID | TeacherID | phone |
+-------+---------------+-----+--------+---------+-----------+-------+
| 1 | Shi Zhongyu | 22 | M | 2 | 3 | NULL |
| 2 | Shi Potian | 22 | M | 1 | 7 | NULL |
| 3 | Xie Yanke | 53 | M | 2 | 16 | NULL |
| 4 | Ding Dian | 32 | M | 4 | 4 | NULL |
| 5 | Yu Yutong | 26 | M | 3 | 1 | NULL |
| 6 | Shi Qing | 46 | M | 5 | NULL | NULL |
| 7 | Xi Ren | 19 | F | 3 | NULL | NULL |
| 8 | Lin Daiyu | 17 | F | 7 | NULL | NULL |
| 9 | Ren Yingying | 20 | F | 6 | NULL | NULL |
| 10 | Yue Lingshan | 19 | F | 3 | NULL | NULL |
| 11 | Yuan Chengzhi | 23 | M | 6 | NULL | NULL |
| 12 | Wen Qingqing | 19 | F | 1 | NULL | NULL |
| 13 | Tian Boguang | 33 | M | 2 | NULL | NULL |
| 14 | Lu Wushuang | 17 | F | 3 | NULL | NULL |
| 15 | Duan Yu | 19 | M | 4 | NULL | NULL |
| 16 | Xu Zhu | 21 | M | 1 | NULL | NULL |
| 17 | Lin Chong | 25 | M | 4 | NULL | NULL |
| 18 | Hua Rong | 23 | M | 7 | NULL | NULL |
| 19 | Xue Baochai | 18 | F | 6 | NULL | NULL |
| 20 | Diao Chan | 19 | F | 7 | NULL | NULL |
| 21 | Huang Yueying | 22 | F | 6 | NULL | NULL |
| 22 | Xiao Qiao | 20 | F | 1 | NULL | NULL |
| 23 | Ma Chao | 23 | M | 4 | NULL | NULL |
| 24 | Xu Xian | 27 | M | NULL | NULL | NULL |
| 25 | Sun Dasheng | 100 | M | NULL | NULL | NULL |
+-------+---------------+-----+--------+---------+-----------+-------+
25 rows in set (0.001 sec)
然后我们在执行该程序就会报错
[12:05:07 root@go dql]#go run main.go
sql: expected 7 destination arguments in Scan, not 6
# sql:扫描中应为7个目标参数,而不是6个
总结:所以在代码中不推荐使用 select * from
,假设以后有人把字段添加或者删除了这个程序就全崩了,但是我们在代码中固定写好想要查询的数据就不会报错如下操作
修改后的代码
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
func main() {
driveName := "mysql"
dsn := "root:root@tcp(10.0.0.10:3306)/hellodb?charset=utf8mb4&loc=Local&parseTime=true"
db, err := sql.Open(driveName, dsn)
if err != nil {
fmt.Println(err)
return
}
defer db.Close()
if db.Ping() != nil {
fmt.Println(err)
return
}
// 这里指定查询 students 表中的 StuID,Name,Age,Gender,ClassID,TeacherID 这几个字段信息
rows, err := db.Query("select StuID,Name,Age,Gender,ClassID,TeacherID from students;")
if err != nil {
fmt.Println(err)
return
}
for rows.Next() {
var (
StuID int32
Name string
Age int32
Gender string
ClassID sql.NullInt32
TeacherID sql.NullInt32
)
err := rows.Scan(&StuID, &Name, &Age, &Gender, &ClassID, &TeacherID)
if err != nil {
fmt.Println(err)
break
} else {
fmt.Println(StuID, Name, Age, Gender, ClassID, TeacherID)
}
}
}
输出不会报错,这个时候我们在做查询,数据库管理员在改表也不会有影响,除非他把字段删除或者字段类型的属性改了
[12:17:14 root@go dql]#go run main.go
1 Shi Zhongyu 22 M {2 true} {3 true}
2 Shi Potian 22 M {1 true} {7 true}
3 Xie Yanke 53 M {2 true} {16 true}
4 Ding Dian 32 M {4 true} {4 true}
5 Yu Yutong 26 M {3 true} {1 true}
6 Shi Qing 46 M {5 true} {0 false}
7 Xi Ren 19 F {3 true} {0 false}
8 Lin Daiyu 17 F {7 true} {0 false}
9 Ren Yingying 20 F {6 true} {0 false}
10 Yue Lingshan 19 F {3 true} {0 false}
11 Yuan Chengzhi 23 M {6 true} {0 false}
12 Wen Qingqing 19 F {1 true} {0 false}
13 Tian Boguang 33 M {2 true} {0 false}
14 Lu Wushuang 17 F {3 true} {0 false}
15 Duan Yu 19 M {4 true} {0 false}
16 Xu Zhu 21 M {1 true} {0 false}
17 Lin Chong 25 M {4 true} {0 false}
18 Hua Rong 23 M {7 true} {0 false}
19 Xue Baochai 18 F {6 true} {0 false}
20 Diao Chan 19 F {7 true} {0 false}
21 Huang Yueying 22 F {6 true} {0 false}
22 Xiao Qiao 20 F {1 true} {0 false}
23 Ma Chao 23 M {4 true} {0 false}
24 Xu Xian 27 M {0 false} {0 false}
25 Sun Dasheng 100 M {0 false} {0 false}
5.1.1.1 查询单条数据库信息
我们查询单条数据库信息的时候可以通过 db.QueryRow()
方法进行查询
func (*sql.DB).QueryRow(query string, args ...interface{}) *sql.Row
// QueryRow执行一个最多返回一行的查询。
代码:
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
func main() {
driverName := "mysql"
dsn := "root:root@tcp(10.0.0.10:3306)/hellodb?charset=utf8mb4&loc=Local&parseTime=true"
db, err := sql.Open(driverName, dsn)
if err != nil {
fmt.Println(err)
return
}
defer db.Close()
if db.Ping() != nil {
fmt.Println(err)
return
}
// 写入 sql 语句
row := db.QueryRow("select StuID,Name,Age,Gender,ClassID,TeacherID from students;")
var (
StuID int32
Name string
Age int32
Gender string
ClassID sql.NullInt32
TeacherID sql.NullInt32
)
// 因为ishi一条数据我们就没有必要再使用 for 循环来查询
err = row.Scan(&StuID, &Name, &Age, &Gender, &ClassID, &TeacherID)
if err != nil {
fmt.Println(err)
return
} else {
fmt.Println(StuID, Name, Age, Gender, ClassID, TeacherID)
}
}
# 拿到第一条数据
[14:48:28 root@go dql]#go run main.go
1 Shi Zhongyu 22 M {2 true} {3 true}
5.1.1.2 查询时对数据进行排序
我们只需要在 db.QueryRow()
方法中写入对应的 sql 语句即可
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
func main() {
driverName := "mysql"
dsn := "root:root@tcp(10.0.0.10:3306)/hellodb?charset=utf8mb4&loc=Local&parseTime=true"
db, err := sql.Open(driverName, dsn)
if err != nil {
fmt.Println(err)
return
}
defer db.Close()
if db.Ping() != nil {
fmt.Println(err)
return
}
// order by Age 对 age 字段进行排序,默认升序这里写入的就是 sql 语句
// select StuID,Name,Age,Gender,ClassID,TeacherID from students order by Age ; sql语句
row, err := db.Query("select StuID,Name,Age,Gender,ClassID,TeacherID from students order by Age ;")
if err != nil {
fmt.Println(err)
return
}
var (
StuID int32
Name string
Age int32
Gender string
ClassID sql.NullInt32
TeacherID sql.NullInt32
)
for row.Next() {
err = row.Scan(&StuID, &Name, &Age, &Gender, &ClassID, &TeacherID)
if err != nil {
fmt.Println(err)
return
} else {
fmt.Println(StuID, Name, Age, Gender, ClassID, TeacherID)
}
}
}
我们从截图可以看到默认是升序排序
5.1.1.3 程序中添加 where 条件
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
func main() {
driverName := "mysql"
dsn := "root:root@tcp(10.0.0.10:3306)/hellodb?charset=utf8mb4&loc=Local&parseTime=true"
db, err := sql.Open(driverName, dsn)
if err != nil {
fmt.Println(err)
return
}
defer db.Close()
if db.Ping() != nil {
fmt.Println(err)
return
}
// 也是直接在程序中的 query 查询语句中添加 where sql
row, err := db.Query("select StuID,Name,Age,Gender,ClassID,TeacherID from students where name like 's%' order by Age ;")
if err != nil {
fmt.Println(err)
return
}
var (
StuID int32
Name string
Age int32
Gender string
ClassID sql.NullInt32
TeacherID sql.NullInt32
)
for row.Next() {
err = row.Scan(&StuID, &Name, &Age, &Gender, &ClassID, &TeacherID)
if err != nil {
fmt.Println(err)
return
} else {
fmt.Println(StuID, Name, Age, Gender, ClassID, TeacherID)
}
}
}
将 name 字段 s 打头的输出
[15:04:27 root@go dql]#go run main.go
1 Shi Zhongyu 22 M {2 true} {3 true}
2 Shi Potian 22 M {1 true} {7 true}
6 Shi Qing 46 M {5 true} {0 false}
25 Sun Dasheng 100 M {0 false} {0 false}
5.1.1.4 通过变量传递我们想要查询的值
当有时候我们通过 web 界面想要查询一个值得时候,我们可以如下操作
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
func main() {
driverName := "mysql"
dsn := "root:root@tcp(10.0.0.10:3306)/hellodb?charset=utf8mb4&loc=Local&parseTime=true"
db, err := sql.Open(driverName, dsn)
if err != nil {
fmt.Println(err)
return
}
defer db.Close()
if db.Ping() != nil {
fmt.Println(err)
return
}
// 当我们想要查询 包含 s 的列表
name := "s"
// 通过字符串拼接的方式进行组合为 SQL 变量
SQL := `select StuID,Name,Age,Gender,ClassID,TeacherID from students where name like '%` + name + `%' order by Age ;`
row, err := db.Query(SQL)
if err != nil {
fmt.Println(err)
return
}
var (
StuID int32
Name string
Age int32
Gender string
ClassID sql.NullInt32
TeacherID sql.NullInt32
)
for row.Next() {
err = row.Scan(&StuID, &Name, &Age, &Gender, &ClassID, &TeacherID)
if err != nil {
fmt.Println(err)
return
} else {
fmt.Println(StuID, Name, Age, Gender, ClassID, TeacherID)
}
}
}
将包含 s 的 name 列表输出,并且 age 通过升序排序
[19:36:54 root@go dql]#go run main.go
14 Lu Wushuang 17 F {3 true} {0 false}
10 Yue Lingshan 19 F {3 true} {0 false}
1 Shi Zhongyu 22 M {2 true} {3 true}
2 Shi Potian 22 M {1 true} {7 true}
6 Shi Qing 46 M {5 true} {0 false}
25 Sun Dasheng 100 M {0 false} {0 false}
5.1.1.5 数据库预处理,解决 sql 注入
但是上面这种方式不太灵活,而且假如用户在对查询数据时,输入的值是一个带有符号的就不能够正常查找而且会报错。所以我们一般在进行拼接的时候一定要对拼接的参数进行格式化的检查。
假如我们真的需要在传入的变量中用到特殊字符的话,可以通过占位的方法进行操作,这就用到了数据库的预处理方式
占位符:?
占位只用于对 where 条件 , order by 语句 , limit 和 offset 进行占位进行占位
不能占位的字段:
-
数据库关键字不能替代
-
表名和列名不能替代
如操作
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
func main() {
driverName := "mysql"
dsn := "root:root@tcp(10.0.0.10:3306)/hellodb?charset=utf8mb4&loc=Local&parseTime=true"
db, err := sql.Open(driverName, dsn)
if err != nil {
fmt.Println(err)
return
}
defer db.Close()
if db.Ping() != nil {
fmt.Println(err)
return
}
name := "s%"
// 在 sql 语句中通过 ? 进行占位
SQL := `select StuID,Name,Age,Gender,ClassID,TeacherID from students where name like ? order by ? limit ? offset ? ;`
// query 的时候先输出 SQL 语句变量,然后第一个 like ? 占位输入 name 变量中查找出 s 开头的姓名,第二个 ? 是对 order by 的 age 就行排序,limit ? 占位 3 查询这个条件的前三条 offset ? 占位 0
row, err := db.Query(SQL, name, "age", 3, 0)
if err != nil {
fmt.Println(err)
return
}
var (
StuID int32
Name string
Age int32
Gender string
ClassID sql.NullInt32
TeacherID sql.NullInt32
)
for row.Next() {
err = row.Scan(&StuID, &Name, &Age, &Gender, &ClassID, &TeacherID)
if err != nil {
fmt.Println(err)
return
} else {
fmt.Println(StuID, Name, Age, Gender, ClassID, TeacherID)
}
}
}
执行一就能过查出 s 开头的数据
[19:36:58 root@go dql]#go run main.go
1 Shi Zhongyu 22 M {2 true} {3 true}
2 Shi Potian 22 M {1 true} {7 true}
6 Shi Qing 46 M {5 true} {0 false}
25 Sun Dasheng 100 M {0 false} {0 false}