569 字
3 分钟
条件查询与分页
GORM 条件查询与分页
Where 的几种写法
字符串条件适合少量固定条件,值始终使用占位符:
db.Where("name = ? AND age >= ?", name, minAge).Find(&users)结构体查询只使用非零字段:
db.Where(&User{Name: "Alice", Age: 18}).Find(&users)如果 Age 的 0 也是有效筛选条件,改用 map 或显式字段:
db.Where(map[string]any{"age": 0}).Find(&users)db.Where(&User{}, "Age").Find(&users)切片条件:
db.Where("id IN ?", []uint{1, 2, 3}).Find(&users)db.Where("name LIKE ?", "%alice%").Find(&users)Select、Omit 和 Pluck
db.Select("id", "name", "email").Find(&users)db.Omit("password").Find(&users)
var names []stringdb.Model(&User{}).Where("active = ?", true).Pluck("name", &names)查询字段越少,网络、扫描和内存开销通常越低;但要确保业务真的不需要被省略的字段。
排序与分组
db.Where("status = ?", "active"). Order("created_at DESC, id DESC"). Find(&users)
var stats []struct { Status string Count int64}db.Model(&User{}). Select("status, COUNT(*) AS count"). Group("status"). Scan(&stats)排序列通常来自服务端白名单,而不是直接拼接用户输入:
allowedSort := map[string]string{ "newest": "created_at DESC, id DESC", "name": "name ASC, id ASC",}order, ok := allowedSort[sortKey]if !ok { order = allowedSort["newest"]}db.Order(order).Find(&users)通用分页
type Page[T any] struct { Items []T `json:"items"` Page int `json:"page"` PageSize int `json:"page_size"` Total int64 `json:"total"` TotalPages int `json:"total_pages"`}
func ListUsers(db *gorm.DB, page, pageSize int) (Page[User], error) { if page < 1 { page = 1 } if pageSize < 1 { pageSize = 20 } if pageSize > 100 { pageSize = 100 }
query := db.Model(&User{}).Where("deleted_at IS NULL") var total int64 if err := query.Count(&total).Error; err != nil { return Page[User]{}, err }
items := make([]User, 0, pageSize) offset := (page - 1) * pageSize if err := query. Select("id", "name", "email", "created_at"). Order("created_at DESC, id DESC"). Offset(offset). Limit(pageSize). Find(&items).Error; err != nil { return Page[User]{}, err }
totalPages := int((total + int64(pageSize) - 1) / int64(pageSize)) return Page[User]{ Items: items, Page: page, PageSize: pageSize, Total: total, TotalPages: totalPages, }, nil}分页必须固定排序,否则数据插入或删除时可能出现重复、遗漏。深分页场景可以考虑基于 (created_at, id) 的游标分页,避免很大的 OFFSET。
Scopes
Scopes 是可组合的查询函数:
func Active(db *gorm.DB) *gorm.DB { return db.Where("status = ?", "active")}
func CreatedAfter(t time.Time) func(*gorm.DB) *gorm.DB { return func(db *gorm.DB) *gorm.DB { return db.Where("created_at >= ?", t) }}
db.Scopes(Active, CreatedAfter(startAt)).Find(&users)Scope 只负责构造查询,不应该偷偷执行查询、提交事务或修改全局状态。
批量读取
err := db.Where("status = ?", "pending"). FindInBatches(&users, 500, func(tx *gorm.DB, batch int) error { for _, user := range users { // 处理当前批次 _ = user } return tx.Error }).Error批量读取适合导出、离线处理和数据修复;不要在回调里为每条记录再发一个查询,否则容易形成 N+1。
官方资料
分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
换条路线
从其他文章中稳定抽取
最后更新于 ,距今已过 77 天
部分内容可能已过时