二.基础语句
一.HOOK函数
通过定义HOOK
函数,可以在每次进行特定操作前运行HOOK
,实现特定的效果。
注意:HOOK
函数的命名要遵循Before/After + 对应的操作
,如下述的BeforeCreate
和AfterUpdate
等
1
2
3
4
5
func (user *Student) BeforeCreate(db *gorm.DB) error {
age := 21
user.Age = age
return nil
}
上述的HOOK
函数在每次Create
操作前,将对应数据的Age
字段改为了21
1
2
3
DB.Create(&Student{Name: "Outer114号"})
//对应的SQL语句
INSERT INTO `students` (`name`,`age`) VALUES ('Outer114号',21);
在HOOK
定义时便确定了其对象,如:
1
2
3
4
5
func (user *Student) AfterUpdate(db *gorm.DB) error {
age := user.Age
fmt.Println("AfterUpdate used", age)
return nil
}
则这是一个在Update
操作后会实施的HOOK
函数
1
2
3
DB.Find(&student, 2).Update("age", 10)
//返回值
AfterUpdate used 10
注:创建HOOK
时一定要注意方法的对象为指针类型。
二.基础查询语句
1.WHERE查询
在GORM
中存在WHERE
函数可以便于查询对应的数据。
1
2
3
4
5
6
7
8
9
DB.Find(&slice, "name = ?", "Outer")
DB.Where("name = ?", "Outer").Find(&slice)
//对应的SQL语句
SELECT * FROM `students` WHERE name = 'Outer';
/*--------------------------------------------------*/
DB.Find(&slice, "not name = ?", "Outer")
DB.Where("not name = ?", "Outer").Find(&slice)
//对应的SQL语句
SELECT * FROM `students` WHERE not name = 'Outer';
在常见查询中,可以通过Find
直接实现查询操作。实际上,Find
函数的定义时直接使用了Where
函数来处理传入的第二行参数conds
1
2
3
4
5
6
7
8
9
func (db *DB) Find(dest interface{}, conds ...interface{}) (tx *DB) {
/*............................................*/
if len(conds) > 0 {
if exprs := tx.Statement.BuildCondition(conds[0], conds[1:]...); len(exprs) > 0 {
tx.Statement.AddClause(clause.Where{Exprs: exprs}) //在此处使用了`Where`来查询
}
}
/*............................................*/
}
相当于Find
函数的传入的第二个参数可以直接填入Where
对应的内容。但我们仍然可以在日常查询中使用Where
来提高代码可读性,便于读取查询条件。
为了便于读取查询条件,接下来的查询语句统一使用Where
Where
可以实现很多SQL
语言的高级查询操作,如模糊查询等
1
2
3
4
5
6
7
DB.Where("name like ?", "Outer%").Find(&slice)
//对应的SQL语句
SELECT * FROM `students` WHERE name like 'Outer%'
/*---------------------------------------------------------*/
DB.Where("ID between 10 AND 50").Find(&slice)
//对应的SQL语句
SELECT * FROM `students` WHERE ID between 10 AND 50;
此外,可以通过多个Where
限定条件,来实现SQL
中AND
的操作
1
2
DB.Where("Name = ? AND Age = ?", "Outer", 10).Find(&slice)
DB.Where("Name = ?", "Outer").Where("Age = ?", 10).Find(&slice)
上述两行代码的效果是一致的。
除了通过原生SQL
语句查询,还可以使用结构体查询或映射查询(虽然十分不建议就是了)
1
2
3
4
DB.Where(&Student{Name: "Outer"}).Where(&Student{Age: 10}).Find(&slice)
DB.Where(map[string]any{"Name": "Outer"}).Where(map[string]any{"Age": 10}).Find(&slice)
//对应的SQL语句
SELECT * FROM `students` WHERE `students`.`name` = 'Outer' AND `students`.`age` = 10
注意:使用映射查询时,一定要严格遵守map[string]any
的格式,不能随意将any
改动为其他数据类型,否则可能会导致查询失败。
2.NOT和OR查询
Not
与Or
是与Where
一样的查询函数,但二者均可在Where
内实现。
1
2
3
4
DB.Where("not name like ?", "Outer__号").Find(&slice)
DB.Not("name like ?", "Outer__号").Find(&slice)
//对应的SQL语句
SELECT * FROM `students` WHERE NOT name like 'Outer__号';
可以理解为Not
即为Where Not
的缩写,即在Where
函数内自带一个NOT
1
2
3
4
DB.Where("name = ?", "Outer").Or("ID = ?", 3).Find(&slice)
DB.Where("name = ? OR ID = ?", "Outer", 3).Find(&slice)
//对应的SQL语句
SELECT * FROM `students` WHERE name = 'Outer' OR ID = 3;
推荐在查询时使用Where
来直接实现这些操作,因为SQL
语言的可靠性较高。
3.SELECT语句
通过SELECT
语句,可以限定查询的内容
1
2
3
4
5
var slice []Student
DB.Select("Name").Find(&slice)
fmt.Println(slice)
//对应的SQL语句
SELECT `name` FROM `students`;
上述代码即在查询过程中,只返回查询到的Name
值,而其他的字段返回默认的空值。
对于多列数据的查询,可以直接将对应字段的字符串全部输入,也可以选择传入一个[]string
切片来实现
1
2
DB.Select("ID", "Name", "Age").Find(&slice)
DB.Select([]string{"ID", "Name", "Age"}).Find(&slice)
如果我们想去除为空的字段数据,将查询到的结果储存一个子表,则可以通过Scan
函数
1
2
3
4
5
6
7
type Outer struct {
ID int `gorm:"type:int"`
Name string `gorm:"type:varchar(20)"`
}
var outerslice []Outer
DB.Select("ID", "Name").Find(&slice).Scan(&outerslice)
fmt.Println(outerslice)
此时我们输出查询结果,便不会出现为空的字段
Scan
函数的查询是严格按照列名(Column)来判断的,若列名不同,则将不会返回结果。
4.指定表名
我们在使用上述的Scan
函数时,会发现Find
函数进行了多余的查询操作,但直接删除Find
则无法指定Scan
的表为哪张表。
此时我们可以选择直接指定表名,有两种方法
1
2
DB.Table("students").Select("ID", "Name").Scan(&outerslice)
DB.Model(&Student{}).Select("ID", "Name").Scan(&outerslice)
不难看出,二者类似于MySQL
原生语句中的USE Table
但注意Table
传入的是表名,一定是一个字符串。而Model
传入的是一个空结构体指针。
三.基础关键字
1.排序
在GORM
中,实现排序需要我们使用Order
语句,且排序分为降序DESC
和升序ASC
关键字 | 升降序 |
---|---|
DESC | 降序 |
ASC | 升序 |
且Order
内只需填一个字符串即可,内容为"字段名 升降序"
1
2
3
4
5
DB.Order("ID ASC").Where("Name Like ?", "Outer%").Find(&slice)
DB.Order("Age DESC").Where("Name Like ?", "Outer%").Find(&slice)
//对应的SQL语句
SELECT * FROM `students` WHERE Name Like 'Outer%' ORDER BY ID ASC;
SELECT * FROM `students` WHERE Name Like 'Outer%' ORDER BY Age DESC;
同样,我们可以在字符串内添加AND
关键字来实现多段排序
1
2
3
DB.Order("Age AND ID ASC").Where("Name Like ?", "Outer%").Find(&slice)
//对应的SQL语句
SELECT * FROM `students` WHERE Name Like 'Outer%' ORDER BY Age AND ID ASC;
2.分页
在SQL
中,我们通过LIMIT ... OFFSET ...
来实现分页操作,在GORM
中我们同样使用这两个关键字
1
2
3
DB.Limit(2).Offset(0).Where("Name Like ?", "Outer%").Find(&slice)
//对应的SQL语句
SELECT * FROM `students` WHERE Name Like 'Outer%' LIMIT 2 OFFSET 0;
注意,Offset
传入的是第几条数据,而不是第几页,因此每次迭代Offset
都需要加上Limit
的值来遍历所有数据。
1
2
3
4
5
6
limit, offset := 4, 0
for offset < 100 {
DB.Limit(limit).Offset(offset).Where("Name Like ?", "Outer%").Find(&slice)
offset += limit
fmt.Println(slice)
}
3.去重
在MySQL
中,去重选用的是Distinct
函数,这一点在GORM
中同样适用
1
2
3
4
5
6
7
8
type Age struct {
Age int
}
var ageSlice []Age
DB.Distinct("Age").Find(&slice).Scan(&ageSlice)
fmt.Println(ageSlice)
//对应的SQL语句
SELECT DISTINCT `age` FROM `students`;
注意,GORM
的Distinct
函数自带Select
效果,因此不应写作Select("Name").Distinct("Name")
,这样反而会造成语句冗余。
此外,我们更常用的做法是直接在Select
语句内加入Distinct
关键字
1
2
3
DB.Distinct("Distinct Age").Find(&slice)
//对应的SQL语句
SELECT DISTINCT Distinct Age FROM `students`;
4.分组
通过Group
函数,我们可以通过对对应的字段进行分组来查询数据
1
2
3
4
5
var count []int
DB.Model(&Student{}).Select("COUNT(*)").Group("Age").Scan(&count)
fmt.Println(count)
//对应的SQL语句
SELECT COUNT(*) FROM `students` GROUP BY `Age`;
其中,COUNT()
是SQL
语句中的函数,由于GORM
只是将MySQL
抽象出来,其底层仍然可以实现SQL
语句。
Group
即按照Age
的情况进行分组
此外,我们会发现,如果想查询Count
对应的是哪些数据,会出现一定的错误
1
2
3
4
5
6
type Group struct {
Count int
Age int
}
var count []Group
DB.Model(&Student{}).Select("COUNT(*)", "Age").Group("Age").Scan(&count)
上述代码的输出结果中,COUNT(*)
字段的结果全为0,这是由于COUNT(*)
在查询结果的表对应的列名就是COUNT(*)
,无法与我们构建的结构体字段名对应。
我们没必要将结构体的对应字段改为COUNT(*)
(实际上GO不允许字段名有括号),只需要在Select
语句中加上AS
关键字即可
1
DB.Model(&Student{}).Select("COUNT(*) AS count", "Age").Group("Age").Scan(&count)
此时便可以正常输出查询结果了。
此外,我们也可以加入特定的group_concat
关键字来显示分组结果
1
DB.Model(&Student{}).Select("group_concat(Name) AS count", "Age").Group("Age").Scan(&count)
四.原生SQL语句
GORM
为了保证原生SQL语句的使用,设置了Raw
函数,我们可以在Raw
函数内输入原生SQL语句的字符串来进行操作
1
DB.Raw("SELECT COUNT(*) AS count,`age` FROM `students` GROUP BY `Age`").Scan(&count)
此时,我们便实现了原生SQL语句的输入和使用,但输出的接口仍然需要自行定义。