排序
排序方式和 SQL 原生语句很像。
desc表示降序排序asc表示升序排序
使用 Order 函数来进行升序降序排序
DB.Order("age desc").Find(&studentList) //按照年龄降序
DB.Order("age asc").Find(&studentList) //按照年龄升序
SQL 语句分别为:
- SELECT * FROM `student1` ORDER BY age desc
- SELECT * FROM `student1` ORDER BY age asc
分页查询
使用 SQL 语句:select id,name,age from students1 limit 2 offset 0;
LIMIT子句用于限制结果集返回的行数,2表示返回两行数据。OFFSET子句用于指定结果集的起始位置,0表示从第一行开始。所以这个查询语句的意思是从students1表中选择前两行的id、name和age字段的值。
使用 GORM 的方法实现这个:
DB.Limit(2).Offset(0).Find(&studentList)
与 SQL 语句十分相似。
分页查询在实际中使用的非常多,比如在网页上翻页搜寻等等。
所以更通用的写法是传入一个 limit 表示每一个显示几个数据和page 变量表示第几页:
limit := 2
page := 1
DB.Limit(limit).Offset((page - 1) * limit).Find(&studentList)
公式为:( page - 1 ) * limit
去重
获取表中的年龄数据,有的年龄是重复的可以去掉,这里就用到了 GORM 中去重的方法:
在这里不使用 Find 函数,因为这样就会查询两次比较浪费时间。所以使用 Model 函数指定要操作的数据库模型或表,然后将去重数据扫描到创建的 int 类型的切片中:
var ageList []int
DB.Model(&Student1{}).Select("age").Distinct("age").Scan(&ageList) //第一种写法
DB.Model(&Student1{}).Select("distinct age").Scan(&ageList) //第二种写法
第一种写法是将 Distinct 写在外面作为一个函数;
第二种写法是将 distinct 写在 Select 里面
分组查询
比如我们需要找到男生个数和女生个数:
DB.Model(Student1{}).Select("count(id)").Group("gender").Scan(&countList)
它使用了 DB.Model() 函数来指定要操作的表, .Select("count(id)") 表示选择 id 字段并计算数量,.Group("gender") 表示按照 gender 字段进行分组。.Scan(&countList) 表示将结果扫描到 countList 变量中。
相当于将表数据按照 gender 字段分组,并计算每一组的 id 数量,然后将结果扫描到 countList 中。
它的 SQL 语句为:SELECT count (id) FROM `student1` GROUP BY `gender`
运行结果如下:
但是这个的作用仅仅只是将分组的数据存储起来,并不能说明哪一个是男生的个数哪一个是女生的个数。
所以我们可以创建一个结构体,表示数量和男女:
type Group struct {
Count int
Gender string
}
这个时候 Select 就需要返回两个值,一个是数量,一个是男女:
var groupList []Group
DB.Model(Student1{}).Select("count(id)", "gender").Group("gender").Scan(&groupList)
但是这样会出现一个问题,就是能够成功接收到 gender 的数据,但是并不能接收到 count 的数据。
原因是 Group 结构体定义中的字段名称是 Count 和 Gender,而 DB.Model().Select() 方法中指定的字段名称是 count(id) 和 gender,它们并不匹配。
所以有两种解决方式:
- 修改结构体中 Count 的字段名:
type Group struct {
Count int `gorm:"column:count(id)"`
Gender string
}
- 修改虚拟表的字段名
DB.Model(Student1{}).Select("count(id) as count", "gender").Group("gender").Scan(&groupList)
使用 as 修改字段名
更进一步,我想知道男生和女生的名字,这就需要使用一个函数 group_concat。
type Group struct {
Count int
Gender string
Name string
}
var groupList []Group
DB.Model(Student1{}).
Select(
"count(id) as count",
"gender",
"group_concat(name) as name",
).
Group("gender").
Scan(&groupList)
这下就有了三个参数,并且使用 as 将字段名修改成 name,由于这一行太长了,所以可以向上面那样进行换行,以保证代码的可读性。
运行结果如下:
执行原生 SQL
GORM 对数据库的操作基本相当于在写原生的 SQL,所以学习 GORM 还能学习一下 SQL 语句。
这里 GORM 也提供执行原生 SQL 语句的功能。
比如将上面的操作使用原生 SQL 语句执行:
DB.Raw("SELECT count(id) as count,`gender`,group_concat(name) as name FROM `student1` GROUP BY `gender`;").Scan(&groupList)
效果是一模一样的。
子查询
查询表中年龄大于平均年龄的人。
首先的查询到平均年龄,然后以这个查询到的平均年龄为依据查询大于平均年龄的人。
使用 SQL 语句是这样的:
select * from students where age > (select avg(age) from students);
使用 GORM 的操作:
DB.Where("age > (?)", DB.Model(Student1{}).Select("avg(age)")).Find(&studentList)
先使用 DB.Model(Student1{}).Select("avg(age)") 找到平均年龄,然后作为参数放进去继续查询,类似于套娃。
命名参数
之前是使用占位符 ? 表示参数的位置,但是如果后面用的占位符多了就全部是问号很难分辨。所以可以使用命名参数来带起占位符。
命名参数是@加一个字符串,一般是写那个字段的名字。
使用方式有两种,第二种 map 的更简洁
DB.Where("name = @name", sql.Named("name", "李元芳")).Find(&studentList)
DB.Where("name = @name", map[string]any{"name": "李元芳"}).Find(&studentList)
都表示查询名字为李元芳的数据。
使用 map 存数据
前面的示例都是创建一个该结构体的切片然后存储数据,现在还可以使用 map 来存储,这样写的更加的简洁一些。
var res []map[string]any
DB.Model(&Student1{}).Find(&res)
因为使用的 map 和表没有联系,所以需要使用 Model 函数来建立和表的联系。
查询引用
我们可以使用在 model 层写一些通用的查询方式,这样既避免了大龄重复的代码,提高了写代码的效率。
比如写一个查询年龄大于 23 的查询方式:
func Age23(db *gorm.DB) *gorm.DB {
return db.Where("age > ?", 23)
}
然后在 main 函数中使用 Scopes 函数调用这个函数:
DB.Scopes(Age23).Find(&studentList)