条件、排序、分页和统计

这一节把列表接口常用查询整理到一起。

示例模型:

type Article struct {
    ID        uint      `gorm:"primaryKey"`
    UserID    uint      `gorm:"not null;index"`
    Title     string    `gorm:"size:200;not null"`
    Content   string    `gorm:"type:text;not null"`
    Status    string    `gorm:"size:20;not null;default:draft;index"`
    ViewCount int       `gorm:"not null;default:0"`
    CreatedAt time.Time
    UpdatedAt time.Time
}

一、基础条件

var articles []Article

err := db.
    Where("status = ?", "published").
    Find(&articles).Error

多个条件:

err := db.
    Where("status = ?", "published").
    Where("user_id = ?", 1).
    Find(&articles).Error

二、模糊查询

keyword := "GORM"

err := db.
    Where("title LIKE ?", "%"+keyword+"%").
    Find(&articles).Error

注意:LIKE '%关键词%' 在数据量大时可能比较慢。经常搜索标题时,要考虑索引或搜索方案。

三、排序

按创建时间倒序:

err := db.
    Where("status = ?", "published").
    Order("created_at DESC").
    Find(&articles).Error

更稳定的排序:

Order("created_at DESC, id DESC")

当创建时间相同时,再用 id 保证顺序稳定。

四、分页

page := 1
pageSize := 10
offset := (page - 1) * pageSize

err := db.
    Where("status = ?", "published").
    Order("created_at DESC, id DESC").
    Limit(pageSize).
    Offset(offset).
    Find(&articles).Error

分页参数建议做保护:

if page < 1 {
    page = 1
}
if pageSize <= 0 || pageSize > 100 {
    pageSize = 10
}

避免用户传一个很大的 pageSize 把数据库拖慢。

五、统计总数

分页接口通常需要总数:

var total int64

err := db.Model(&Article{}).
    Where("status = ?", "published").
    Count(&total).Error

列表和总数一般分开查:

query := db.Model(&Article{}).Where("status = ?", "published")

var total int64
err := query.Count(&total).Error
if err != nil {
    return err
}

var articles []Article
err = query.
    Order("created_at DESC, id DESC").
    Limit(pageSize).
    Offset(offset).
    Find(&articles).Error

注意:链式调用会复用条件,复杂场景下可以重新构造查询,避免前一次调用影响后一次。

六、选择字段

列表页不一定需要正文:

err := db.
    Select("id", "user_id", "title", "status", "view_count", "created_at").
    Where("status = ?", "published").
    Find(&articles).Error

字段少时,响应更轻,查询也更清楚。

七、扫描到自定义结构体

列表接口可以定义专门的返回结构:

type ArticleListItem struct {
    ID        uint
    Title     string
    ViewCount int
    CreatedAt time.Time
}

查询:

var list []ArticleListItem

err := db.Model(&Article{}).
    Select("id", "title", "view_count", "created_at").
    Where("status = ?", "published").
    Order("created_at DESC, id DESC").
    Limit(pageSize).
    Offset(offset).
    Scan(&list).Error

Scan 适合把查询结果映射到非模型结构体。

八、聚合查询

统计每个状态的文章数量:

type StatusCount struct {
    Status string
    Total  int64
}

var result []StatusCount

err := db.Model(&Article{}).
    Select("status, COUNT(*) AS total").
    Group("status").
    Scan(&result).Error

对应 SQL 大致是:

SELECT status, COUNT(*) AS total
FROM articles
GROUP BY status;

九、原生 SQL

复杂查询可以直接写 SQL:

type StatusCount struct {
    Status string
    Total  int64
}

var result []StatusCount

err := db.Raw(`
    SELECT status, COUNT(*) AS total
    FROM articles
    GROUP BY status
`).Scan(&result).Error

GORM 不要求所有查询都必须用链式 API。复杂 SQL 直接写,反而更清晰。

十、查询建议

场景建议
普通列表Where + Order + Limit + Offset
分页总数Model + Count
只要部分字段Select
返回结构和模型不同Scan
复杂聚合可以用 Raw