OOro

关联聚合

使用 WithCount、WithExists、WithSum、WithAvg、WithMin、WithMax 和关系聚合表达式加载统计。

关系聚合把关系统计结果加载到主模型上,常用于列表页展示评论数、是否有封面、订单总额、最高评分等。

聚合结果会写入主模型上的字段。建议把接收字段声明为 Virtual(),让“这是查询派生字段”在模型定义里可见。

定义虚拟字段

type Article struct {
    oro.Model
    Title           string
    CommentsCount   int64
    CommentsExists  bool
    CommentsScoreSum int64
}

func (Article) Define(s *oro.SchemaBuilder) {
    s.Table("articles")
    s.Field("Title").String()
    s.Field("CommentsCount").Virtual()
    s.Field("CommentsExists").Virtual()
    s.Field("CommentsScoreSum").Column("comments_score_sum").Virtual()
}

Virtual 字段不参与建表,只接收查询结果。

WithCount

articles, err := db.Use[Article]().
    WithCount(Article{}.Comments()).
    Get(ctx)

默认别名按关系名生成,例如 CommentsCount

如果字段名不符合默认规则,可以用表达式自定义别名:

articles, err := db.Use[Article]().
    Select(
        "ID",
        "Title",
        oro.CountOf(Article{}.Comments()).As("comments_total"),
    ).
    Get(ctx)

然后定义对应虚拟字段:

CommentsTotal int64

func (Article) Define(s *oro.SchemaBuilder) {
    s.Field("CommentsTotal").Column("comments_total").Virtual()
}

WithExists

articles, err := db.Use[Article]().
    WithExists(Article{}.Comments()).
    Get(ctx)

适合只关心是否存在关系,不需要加载完整关系对象。相比 With 预加载,WithExists 返回数据更小。

Sum / Avg / Min / Max

articles, err := db.Use[Article]().
    WithSum(Article{}.Comments(), "Score").
    WithAvg(Article{}.Comments(), "Score").
    WithMin(Article{}.Comments(), "Score").
    WithMax(Article{}.Comments(), "Score").
    Get(ctx)

关系字段使用目标模型的 Go 字段名。

默认别名为 关系名 + 字段名 + 聚合函数 的 snake_case,例如 WithSum(Article{}.Comments(), "Score") 会映射到 comments_score_sum。如果 Go 字段名不能自然匹配,使用 Column("comments_score_sum") 或在 Select 表达式里自定义 As(...)

这些方法都支持可选回调:

articles, err := db.Use[Article]().
    WithSum(Article{}.Comments(), "Score", func(q *oro.RelationQuery) {
        q.Where("Status", "approved")
    }).
    Get(ctx)

Select 表达式写法

所有关系聚合都有表达式形式:

articles, err := db.Use[Article]().Select(
    "ID",
    "Title",
    oro.CountOf(Article{}.Comments()).As("comments_count"),
    oro.ExistsOf(Article{}.Cover()).As("has_cover"),
    oro.SumOf(Article{}.Comments(), "Score").As("score_sum"),
    oro.AvgOf(Article{}.Comments(), "Score").As("score_avg"),
    oro.MinOf(Article{}.Comments(), "Score").As("score_min"),
    oro.MaxOf(Article{}.Comments(), "Score").As("score_max"),
).Get(ctx)

带过滤:

oro.CountOf(Article{}.Comments()).
    As("approved_comments").
    Filter(func(q *oro.RelationQuery) {
        q.Where("Status", "approved")
    })

字符串关系

动态关系或插件化场景可以使用字符串关系:

articles, err := db.Use[Article]().WithCount("Comments").Get(ctx)

普通代码优先使用关系方法,重构更安全。

和 With 的区别

需求 推荐
展示数量、是否存在、总额 关系聚合
展示关系明细 With 预加载
按关系过滤主数据 WhereHas
已有实例查关系目标 For

关系聚合不会加载关系对象,不能通过 article.Comments().Many[Comment]() 读取明细。

性能建议

  • 列表页需要数量时,用 WithCount,不要预加载全部关系再计数;
  • 只需要布尔状态时,用 WithExists
  • 聚合字段要定义为 Virtual,避免同步建表;
  • 复杂聚合建议配合 Select 表达式显式命名;
  • 同时使用多个聚合时,关注生成 SQL 和数据库索引。
编辑此页