第20章:查询总结与对比表
本章目标
学完本章后,你将能够:
- 快速查找到需要的方法和用法
- 根据场景选择正确的查询方法
- 了解各查询方法的性能特征
- 对比 GORM 和原生 SQL 的代码量
前置知识
- 需要先学习:第10-19章(所有查询相关章节)
- 这是一个总结速查章节,覆盖 Phase 3-4 的全部内容
20.1 查询方法速查表
获取记录
| 方法 | SQL | 找不到时 | 备注 |
|---|---|---|---|
First(&user) | ORDER BY id LIMIT 1 | ErrRecordNotFound | 按主键第一条 |
First(&user, id) | WHERE id=? LIMIT 1 | ErrRecordNotFound | 主键快捷查询 |
Take(&user) | LIMIT 1 | ErrRecordNotFound | 不排序,任意一条 |
Last(&user) | ORDER BY id DESC LIMIT 1 | ErrRecordNotFound | 按主键最后一条 |
Find(&users) | SELECT * | nil,空切片 | 所有记录 |
Find(&users, ids) | WHERE id IN ? | nil,空切片 | IN 查询 |
条件查询
| 方法 | 示例 |
|---|---|
Where(string) | db.Where("name = ?", "张三") |
Where(struct) | db.Where(&User{Name:"张三"}) ⚠️ 零值跳过 |
Where(map) | db.Where(map[string]interface{}{"name":"张三","age":0}) ✅ 零值不跳过 |
Not() | db.Not("age = ?", 25) |
Or() | db.Where("a=1").Or("b=2") |
Where().Where() | AND 链式组合 |
排序与分页
| 方法 | 示例 |
|---|---|
Order("col desc") | db.Order("age desc, name asc") |
Limit(n) | db.Limit(10) |
Offset(n) | db.Offset(20) |
Limit(-1) | 取消之前的 Limit |
字段选择与聚合
| 方法 | 示例 |
|---|---|
Select("a", "b") | db.Select("name, age").Find(&users) |
Select("SUM(x)") | db.Select("AVG(age)").Scan(&avg) |
Group("col") | db.Group("city").Find(&results) |
Having("cond") | db.Having("COUNT(*) > ?", 5) |
Distinct("col") | db.Distinct("status").Pluck("status", &list) |
Join
| 方法 | 示例 |
|---|---|
Joins("LEFT JOIN ...") | db.Joins("LEFT JOIN c ON u.cid = c.id") |
Joins("Relation") | db.Joins("Company") 利用关联定义 |
InnerJoins("Relation") | db.InnerJoins("Company") 内连接 |
其他
| 方法 | 示例 |
|---|---|
Count(&n) | db.Model(&User{}).Count(&total) |
Pluck("col", &slice) | db.Pluck("name", &names) |
Scan(&struct) | db.Raw("...").Scan(&custom) |
Scopes(s1, s2) | db.Scopes(ActiveUsers, Paginate(1,10)) |
20.2 方法选择决策树
你想做什么?
│
├─ 查一条记录
│ ├─ 知道主键 → db.First(&user, id) ← 最简洁
│ ├─ 知道条件 → db.Where(...).First(&user)
│ └─ 任意一条 → db.Take(&user)
│
├─ 查多条记录
│ ├─ 查全部 → db.Find(&users)
│ ├─ 带条件 → db.Where(...).Find(&users)
│ ├─ 需要分页 → db.Scopes(Paginate(1,10)).Find(&users)
│ ├─ 需要排序 → db.Order("col desc").Find(&users)
│ └─ 需要分组 → db.Select("col, COUNT(*)").Group("col").Scan(&result)
│
├─ 只查一列
│ └─ db.Pluck("col", &slice)
│
├─ 只计数
│ └─ db.Model(&User{}).Count(&total)
│
├─ 多表关联
│ ├─ 需要过滤关联表 → db.Joins("Relation").Where(...).Find(&users)
│ └─ 纯展示关联数据 → db.Preload("Relation").Find(&users) (Phase 5)
│
└─ GORM API 不够用
└─ db.Raw("SQL").Scan(&result)20.3 查询性能参考
| 查询类型 | 数据量 | 期望时间 | 优化建议 |
|---|---|---|---|
| 主键查询 | 任意 | < 1ms | 已是最优 |
| WHERE 索引列 | 100万 | < 5ms | 确保索引覆盖 |
| WHERE 无索引列 | 10万 | > 100ms | 添加索引 |
| LIKE '%x%' | 100万 | > 1s | 考虑全文索引 |
| OFFSET 10000 LIMIT 10 | 100万 | 100ms+ | 改用游标分页 |
| JOIN 2-3表(有索引) | 10万 | 10-50ms | 确保 JOIN 列有索引 |
| GROUP BY(有索引) | 100万 | 50-500ms | 考虑汇总表 |
| COUNT(*) | 100万 | 取决于引擎 | MyISAM 快(精确计数),InnoDB 慢(扫描索引) |
20.4 GORM vs 原生 SQL 代码量对比
简单查询
go
// 原生 SQL(database/sql)
rows, _ := db.Query("SELECT id, name, age, email, created_at, updated_at FROM users WHERE age > ?", 18)
defer rows.Close()
var users []User
for rows.Next() {
var u User
rows.Scan(&u.ID, &u.Name, &u.Age, &u.Email, &u.CreatedAt, &u.UpdatedAt)
users = append(users, u)
}
// GORM(等价)
db.Where("age > ?", 18).Find(&users)
// 9行 → 1行带分页的查询
go
// 原生 SQL
var total int64
db.QueryRow("SELECT COUNT(*) FROM users WHERE age > ?", 18).Scan(&total)
rows, _ := db.Query("SELECT * FROM users WHERE age > ? LIMIT 10 OFFSET 20", 18)
defer rows.Close()
// ...扫描...
// GORM(等价)
db.Model(&User{}).Where("age > ?", 18).Count(&total)
db.Where("age > ?", 18).Limit(10).Offset(20).Find(&users)复杂关联查询
go
// 原生 SQL
rows, _ := db.Query(`
SELECT u.id, u.name, COUNT(o.id) as order_count, SUM(o.amount) as total
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.created_at > ?
GROUP BY u.id
HAVING order_count > 0
ORDER BY total DESC
LIMIT 10
`, startDate)
defer rows.Close()
// ...定义结构体,手动扫描...
// GORM(等价)
type UserStats struct {
ID uint
Name string
OrderCount int64
Total float64
}
var stats []UserStats
db.Model(&User{}).Select(`
users.id, users.name,
COUNT(orders.id) as order_count,
COALESCE(SUM(orders.amount), 0) as total
`).
Joins("LEFT JOIN orders ON users.id = orders.user_id").
Where("users.created_at > ?", startDate).
Group("users.id").
Having("order_count > 0").
Order("total DESC").
Limit(10).
Scan(&stats)20.5 常见查询模式汇总
模式1:分页查询(最常用)
go
func ListUsers(db *gorm.DB, page, pageSize int, filters map[string]interface{}) ([]User, int64, error) {
var users []User
var total int64
query := db.Model(&User{})
for k, v := range filters {
query = query.Where(k+" = ?", v)
}
query.Count(&total)
err := query.Scopes(Paginate(page, pageSize)).Order("id desc").Find(&users).Error
return users, total, err
}模式2:条件筛选
go
func SearchUsers(db *gorm.DB, req SearchRequest) ([]User, error) {
var users []User
query := db.Model(&User{})
if req.Keyword != "" {
query = query.Where("name LIKE ? OR email LIKE ?",
"%"+req.Keyword+"%", "%"+req.Keyword+"%")
}
if req.Status != "" {
query = query.Where("status = ?", req.Status)
}
if req.MinAge > 0 {
query = query.Where("age >= ?", req.MinAge)
}
err := query.Find(&users).Error
return users, err
}模式3:聚合统计
go
type StatsResult struct {
Label string
Count int64
}
func CountByStatus(db *gorm.DB) []StatsResult {
var results []StatsResult
db.Model(&User{}).Select("status as label, COUNT(*) as count").
Group("status").Scan(&results)
return results
}模式4:关联查询
go
func GetUserWithCompany(db *gorm.DB, userID uint) (*UserWithCompany, error) {
var result UserWithCompany
err := db.Model(&User{}).
Select("users.*, companies.name as company_name").
Joins("LEFT JOIN companies ON users.company_id = companies.id").
Where("users.id = ?", userID).
Scan(&result).Error
return &result, err
}本章小结
本章是 Phase 4 的终点站。回顾一下你已经掌握的查询技能:
- ✅ 单条查询:First、Take、Last、Find
- ✅ 条件查询:Where(字符串/结构体/Map)、Not、Or
- ✅ 排序分页:Order、Limit、Offset
- ✅ 字段选择:Select、Omit
- ✅ 聚合统计:Group、Having、Count、Sum、Avg、Max、Min
- ✅ 连接查询:Joins、InnerJoins
- ✅ 子查询:WHERE/ FROM/ SELECT 中的子查询
- ✅ Scope 封装:可复用的查询逻辑
- ✅ 原生 SQL:Raw + Scan
- ✅ 调试工具:DryRun、Debug、Logger
从下一章开始,进入 Phase 5——GORM 最强大的功能:关联关系。
练习题
- 不用翻书,把上面 20.1 的速查表默写一遍(或整理到你的笔记中)。
- 回答:查一条记录用
First还是Find?查不到时行为有何不同? - 回答:
Where(struct)和Where(map)有什么区别?为什么? - 写一个综合查询:按状态筛选、按年龄排序、分页展示、返回总数。
- (思考题)GORM 的链式调用和 SQL 的语法有哪些相似之处?把 GORM 的链式 API 翻译成 SQL 的思维过程是怎样的?