分组与聚合

分组查询用于数据统计和分析。按某个字段分组,然后对每组进行聚合计算。

Group 分组

基本分组:

type Result struct {
    Age   int
    Count int
}

var results []Result
db.Model(&User{}).Select("age, count(*) as count").Group("age").Find(&results)

生成的 SQL:

SELECT age, count(*) as count FROM users GROUP BY age

Having 过滤

Having 过滤分组结果:

db.Model(&User{}).
    Select("age, count(*) as count").
    Group("age").
    Having("count > ?", 1).
    Find(&results)

生成的 SQL:

SELECT age, count(*) as count FROM users GROUP BY age HAVING count > 1

聚合函数

Count 计数

var count int64
db.Model(&User{}).Count(&count)
db.Model(&User{}).Where("age > ?", 20).Count(&count)

Sum 求和

var totalAge int
db.Model(&User{}).Select("sum(age)").Scan(&totalAge)

var totalAmount float64
db.Model(&Order{}).Select("sum(amount)").Scan(&totalAmount)

Avg 平均值

var avgAge float64
db.Model(&User{}).Select("avg(age)").Scan(&avgAge)

Max 最大值

var maxAge int
db.Model(&User{}).Select("max(age)").Scan(&maxAge)

Min 最小值

var minAge int
db.Model(&User{}).Select("min(age)").Scan(&minAge)

多字段分组

type Result struct {
    Status string
    Age    int
    Count  int
}

var results []Result
db.Model(&User{}).
    Select("status, age, count(*) as count").
    Group("status, age").
    Find(&results)

分组统计示例

按状态统计用户:

type StatusCount struct {
    Status string
    Count  int
}

var statusCounts []StatusCount
db.Model(&User{}).
    Select("status, count(*) as count").
    Group("status").
    Find(&statusCounts)

按日期统计注册量:

type DailyCount struct {
    Date  string
    Count int
}

var dailyCounts []DailyCount
db.Model(&User{}).
    Select("DATE(created_at) as date, count(*) as count").
    Where("created_at >= ?", time.Now().AddDate(0, -1, 0)).
    Group("DATE(created_at)").
    Order("date desc").
    Find(&dailyCounts)

按分类统计文章:

type CategoryCount struct {
    Category string
    Count    int
}

var categoryCounts []CategoryCount
db.Model(&Article{}).
    Select("category, count(*) as count").
    Group("category").
    Order("count desc").
    Find(&categoryCounts)

Distinct 去重

统计不重复的值:

var count int64
db.Model(&User{}).Distinct("name").Count(&count)

查询不重复的值:

var names []string
db.Model(&User{}).Distinct("name").Pluck("name", &names)

复杂聚合

多个聚合函数:

type Stats struct {
    Total   int
    AvgAge  float64
    MaxAge  int
    MinAge  int
}

var stats Stats
db.Model(&User{}).
    Select("count(*) as total, avg(age) as avg_age, max(age) as max_age, min(age) as min_age").
    Scan(&stats)

条件聚合:

type Result struct {
    Status    string
    Total     int
    ActiveCnt int
}

var results []Result
db.Model(&User{}).
    Select("status, count(*) as total, sum(case when is_active = 1 then 1 else 0 end) as active_cnt").
    Group("status").
    Find(&results)

分组后排序

db.Model(&User{}).
    Select("age, count(*) as count").
    Group("age").
    Order("count desc").
    Find(&results)

分组后限制

db.Model(&User{}).
    Select("age, count(*) as count").
    Group("age").
    Order("count desc").
    Limit(5).
    Find(&results)

分组与连接

分组统计关联数据:

type UserOrderCount struct {
    UserName string
    OrderCnt int
}

var results []UserOrderCount
db.Table("users").
    Select("users.name, count(orders.id) as order_cnt").
    Joins("left join orders on orders.user_id = users.id").
    Group("users.id").
    Find(&results)

常见问题

字段不在 GROUP BY 中

db.Model(&User{}).Select("name, age, count(*)").Group("age")

某些数据库会报错,因为 name 不在 GROUP BY 中。MySQL 在严格模式下也会报错。

聚合结果为 NULL

没有匹配记录时,聚合函数返回 NULL:

var total *int
db.Model(&User{}).Where("age > ?", 100).Select("sum(age)").Scan(&total)

total 可能是 nil,要处理这种情况。

Count 与 Find 分开

var users []User
var count int64

db.Find(&users)
db.Count(&count)

两次查询,条件可能不一致。正确做法:

query := db.Model(&User{}).Where("status = ?", "active")
query.Count(&count)
query.Find(&users)

小结

分组和聚合是数据分析的基础。掌握 Group、Having、各种聚合函数,能应对大部分统计需求。注意 SQL 标准对分组的要求,避免踩坑。