高级查询操作2 | 青训营

204 阅读5分钟

排序

排序方式和 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 表中选择前两行的 idnameage 字段的值。

使用 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`

运行结果如下: image.png

但是这个的作用仅仅只是将分组的数据存储起来,并不能说明哪一个是男生的个数哪一个是女生的个数

所以我们可以创建一个结构体,表示数量和男女:

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 结构体定义中的字段名称是 CountGender,而 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,由于这一行太长了,所以可以向上面那样进行换行,以保证代码的可读性。

运行结果如下: image.png

执行原生 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)

image.png 效果是一模一样的。

子查询

查询表中年龄大于平均年龄的人。

首先的查询到平均年龄,然后以这个查询到的平均年龄为依据查询大于平均年龄的人。

使用 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)