查询结果的顺序和数量控制,是分页展示和数据分析的基础。
单字段排序:
db.Order("age desc").Find(&users)
db.Order("age asc").Find(&users)
db.Order("age").Find(&users)
多字段排序:
db.Order("age desc, name asc").Find(&users)
链式排序:
db.Order("age desc").Order("name asc").Find(&users)
根据用户输入动态排序:
func GetUsers(db *gorm.DB, sortBy string, order string) []User {
var users []User
db.Order(sortBy + " " + order).Find(&users)
return users
}
注意:动态排序要验证输入,防止 SQL 注入:
allowedSort := map[string]bool{
"age": true,
"name": true,
"created_at": true,
}
allowedOrder := map[string]bool{
"asc": true,
"desc": true,
}
if !allowedSort[sortBy] {
sortBy = "id"
}
if !allowedOrder[order] {
order = "desc"
}
db.Order(sortBy + " " + order).Find(&users)
MySQL 中 NULL 值排序:
db.Order("age IS NULL, age asc").Find(&users)
PostgreSQL:
db.Order("age NULLS FIRST").Find(&users)
db.Order("age NULLS LAST").Find(&users)
限制返回条数:
db.Limit(10).Find(&users)
限制 0 条:
db.Limit(0).Find(&users)
返回空结果,某些场景有用。
跳过指定条数:
db.Offset(10).Find(&users)
跳过前 10 条,返回后面的记录。
实现分页:
page := 2
pageSize := 10
db.Offset((page - 1) * pageSize).Limit(pageSize).Find(&users)
MySQL:
db.Order("RAND()").Limit(5).Find(&users)
PostgreSQL:
db.Order("RANDOM()").Limit(5).Find(&users)
SQLite:
db.Order("RANDOM()").Limit(5).Find(&users)
最常见的场景:
db.Order("score desc").Limit(10).Find(&topUsers)
db.Order("created_at desc").Limit(5).Find(&latestPosts)
先分组,再排序:
type Result struct {
Category string
Count int
}
var results []Result
db.Model(&Article{}).
Select("category, count(*) as count").
Group("category").
Order("count desc").
Find(&results)
db.Distinct("category").Order("category asc").Find(&articles)
封装分页函数:
type Pagination struct {
Page int
PageSize int
Total int64
Data interface{}
}
func Paginate(db *gorm.DB, page, pageSize int, data interface{}) (*Pagination, error) {
if page < 1 {
page = 1
}
if pageSize < 1 || pageSize > 100 {
pageSize = 10
}
var total int64
db.Count(&total)
offset := (page - 1) * pageSize
err := db.Offset(offset).Limit(pageSize).Find(data).Error
return &Pagination{
Page: page,
PageSize: pageSize,
Total: total,
Data: data,
}, err
}
var users []User
result, err := Paginate(db.Model(&User{}), 2, 10, &users)
大偏移量问题
db.Offset(100000).Limit(10).Find(&users)
偏移量大时,数据库要扫描前 100000 条记录再跳过,性能很差。
解决方案:用上次查询的最后一条记录作为起点:
lastID := 100000
db.Where("id > ?", lastID).Order("id asc").Limit(10).Find(&users)
这叫"游标分页"或"键集分页",性能更好。
排序字段没有索引
排序字段没有索引,数据库要全表扫描后排序:
db.Order("created_at desc").Limit(10).Find(&users)
确保 created_at 有索引。
排序和限制是分页的基础。注意大偏移量问题和索引优化,能让查询更高效。