preload 不自动提供关联数量,需用 len(user.orders) 计算;若仅需数量,可用 joins+group 或 association.count;混用 preload 与 count 无效。

Preload 之后怎么拿到关联数量
Preload 不会自动给你塞一个 Count 字段,它只负责把关联数据查出来并填充进结构体切片。你看到 user.Orders 是个 []Order,那数量就得自己用 len(user.Orders) 算——别指望 GORM 在 Preload 后顺手塞个 OrdersCount int 进去。
常见错误现象:
- 在模型里加了
OrdersCount uint字段,以为 Preload 会自动赋值 → 实际为 0 - 写
db.Preload("Orders").Find(&users)后直接取users[0].OrdersCount→ 值未被修改,仍是零值
正确做法是查完再遍历赋值:
for i := range users {
users[i].OrdersCount = len(users[i].Orders)
}
用 Joins + Count(*) 子查询统计关联数(不加载数据)
如果只需要数量、不需要关联记录本身(比如“每个用户有多少订单”),用 Joins 配合子查询或 Group 更高效,避免把所有订单数据拉到 Go 内存里。
关键点:
-
Joins会生成LEFT JOIN或INNER JOIN,但默认不聚合;必须显式Group+Select才能统计 - 别漏掉
GROUP BY主表主键,否则 MySQL 会报错或返回单条随机结果 - 若用户可能没订单,用
LEFT JOIN并配合COUNT(orders.id)(不是COUNT(*)),否则无订单用户也会被算作 1
示例(统计每个用户的订单数):
type UserOrderCount struct {
UserID uint `gorm:"column:user_id"`
Count int `gorm:"column:count"`
}
var results []UserOrderCount
db.Table("users").
Select("users.id as user_id, COUNT(orders.id) as count").
Joins("LEFT JOIN orders ON orders.user_id = users.id").
Group("users.id").
Scan(&results)
用 Association.Count 直接查单条记录的关联数
当已知某一个主记录(比如 user 实例),想快速知道它关联了多少条子记录,Association 方法比写完整 JOIN 更轻量。
注意条件:
- 必须先有已加载的主记录(不能是空 struct 或仅 ID 的临时变量)
- 关联字段名必须和
Preload用的字符串一致(比如"Orders") - 它底层发的是独立
SELECT COUNT(*),不复用之前的查询条件(如软删除状态),需自行补全
示例:
var user User
db.First(&user, 123)
var count int64
db.Model(&user).Association("Orders").Count(&count) // 返回该用户的订单总数
如果模型启用了软删除,记得加条件:
db.Unscoped().Model(&user).Association("Orders").Count(&count) // 忽略 DeletedAt
// 或
db.Where("deleted_at IS NULL").Model(&user).Association("Orders").Count(&count)
为什么不能在 Preload 里混用 Count 和 Joins
很多人试图这样写:db.Preload("Orders", func(db *gorm.DB) *gorm.DB { return db.Count(&count) }) —— 这完全无效。Preload 的回调函数只影响关联查询的 WHERE 条件或 Order,不支持改写为聚合查询。
更隐蔽的坑:
- 用
Preload("Orders").Joins("JOIN orders ...")混用,GORM v2 会忽略Preload,只执行Joins,且不会自动去重,导致主记录重复出现 - 在事务中先
Count再Preload,两次查询间若有并发写入,数量和实际加载的数据可能不一致 - 多对多中间表没建索引时,
Association.Count可能变慢,因为要扫中间表(如user_roles)
真正需要精确总数又怕性能问题时,优先考虑缓存计数字段(比如用户表加 order_count,每次增删订单时用 UPDATE ... SET order_count = order_count + 1 维护),而不是每次查。











