5月28日 01:10
GORM 中 First、Find、Where 等常用查询方法有哪些区别?
GORM 是 Go 语言中最流行的 ORM 库,查询方法是日常开发中使用频率最高的 API。掌握 First、Find、Where 等方法的区别和使用场景,是 GORM 面试的核心考点。
检索单条记录:First、Last、Take 的区别
这三种方法都会自动添加 LIMIT 1,且记录不存在时返回 ErrRecordNotFound,但生成的 SQL 不同:
| 方法 | 排序方式 | 生成 SQL 示例 |
|---|---|---|
| First | 主键升序 | SELECT * FROM users ORDER BY id LIMIT 1 |
| Last | 主键降序 | SELECT * FROM users ORDER BY id DESC LIMIT 1 |
| Take | 不排序 | SELECT * FROM users LIMIT 1 |
govar user User // 按主键升序取第一条 db.First(&user) // 按主键降序取第一条 db.Last(&user) // 不排序取一条 db.Take(&user) // 按主键查询(First 内联条件) db.First(&user, 10) // SELECT * FROM users WHERE id = 10
面试追问:如何避免 ErrRecordNotFound?
使用 Find 替代:db.Limit(1).Find(&user),Find 找不到记录时不报错,只返回空结果。
检索多条记录:Find
govar users []User // 查询全部 db.Find(&users) // SELECT * FROM users // 内联条件查询 db.Find(&users, []int{1, 2, 3}) // WHERE id IN (1,2,3) // struct 条件(零值字段会被忽略) db.Find(&users, User{Name: "John"}) // WHERE name = "John"
注意:用 struct 做条件时,零值字段(如 Age: 0、Active: false)不会出现在 WHERE 子句中。需要查询零值字段,改用 map:
godb.Where(map[string]interface{}{"Name": "John", "Age": 0}).Find(&users)
条件查询:Where、Or、Not
Where — 最常用的条件构造器
go// 字符串条件(参数化防注入) db.Where("name = ?", "John").First(&user) db.Where("name = ? AND age >= ?", "John", 18).Find(&users) // map 条件 db.Where(map[string]interface{}{"name": "John", "age": 30}).Find(&users) // struct 条件(零值字段忽略) db.Where(&User{Name: "John"}).Find(&users)
Or 和 Not
go// Or 条件 db.Where("name = ?", "John").Or("name = ?", "Jane").Find(&users) // Not 条件 db.Not("name = ?", "John").Find(&users)
常用条件操作符
go// IN 查询 db.Where("id IN ?", []int{1, 2, 3}).Find(&users) // LIKE 模糊查询 db.Where("name LIKE ?", "%John%").Find(&users) // BETWEEN 范围查询 db.Where("age BETWEEN ? AND ?", 18, 30).Find(&users)
排序、分页与字段选择
go// 排序 db.Order("age DESC").Find(&users) db.Order("age DESC, name ASC").Find(&users) // 分页(Limit + Offset) db.Offset(10).Limit(10).Find(&users) // 第2页,每页10条 // 选择特定字段(减少数据传输量) db.Select("name", "email").Find(&users) db.Select("name, email").Find(&users)
聚合与单列提取
go// Count 计数 var count int64 db.Model(&User{}).Where("age > ?", 18).Count(&count) // Pluck 提取单列值 var names []string db.Model(&User{}).Pluck("name", &names)
面试追问:Count 在链式调用中的位置?
Count 会覆盖 SELECT 列,必须放在链式调用最后。且 Count 之后不能再链式调用 Find 等方法。
高级查询方法
FirstOrInit 和 FirstOrCreate
go// 找到返回记录,找不到初始化一个空实例(不写入数据库) var user User db.Where("name = ?", "John").FirstOrInit(&user) // 找到返回记录,找不到创建一条新记录 db.Where("name = ?", "John").FirstOrCreate(&user)
Group 和 Having
gotype Result struct { Role string Count int64 } var results []Result db.Model(&User{}).Select("role, count(*) as count"). Group("role"). Having("count > ?", 5). Find(&results)
Distinct 去重
godb.Distinct("name").Find(&users)
SubQuery 子查询
go// WHERE age > (SELECT AVG(age) FROM users) db.Where("age > ?", db.Model(&User{}).Select("AVG(age)")).Find(&users)
Joins 关联查询
go// 内连接 db.Joins("LEFT JOIN orders ON orders.user_id = users.id").Find(&users) // 预加载关联(避免 N+1 查询) db.Preload("Orders").Find(&users)
Scopes 复用查询逻辑
gofunc Active(db *gorm.DB) *gorm.DB { return db.Where("active = ?", true) } func OlderThan(age int) func(db *gorm.DB) *gorm.DB { return func(db *gorm.DB) *gorm.DB { return db.Where("age > ?", age) } } // 链式复用 db.Scopes(Active, OlderThan(18)).Find(&users)
原生 SQL
go// Raw 查询返回数据 db.Raw("SELECT * FROM users WHERE age > ?", 18).Scan(&users) // Exec 执行不返回数据的语句 db.Exec("UPDATE users SET age = age + 1 WHERE id = ?", 1)
软删除对查询的影响
如果模型启用了软删除(gorm.DeletedAt),所有查询方法会自动添加 WHERE deleted_at IS NULL。如需查询包含已删除的记录:
godb.Unscoped().Where("age > ?", 18).Find(&users) // 包含软删除记录 db.Unscoped().Find(&users) // 查询全部(含已删除)
链式调用的注意事项
GORM 的查询方法是链式调用,但要注意:
Session方法会创建新的会话,之后的操作不影响之前的链Clauses方法可以复写 GORM 内部的 Clause 构建器- 多个
Where调用会叠加 AND 条件 Or只与上一个Where组合,不与所有条件组合
go// 多 Where 叠加 AND db.Where("age > ?", 18).Where("name = ?", "John").Find(&users) // WHERE age > 18 AND name = "John" // Or 只与上一个 Where 组合 db.Where("name = ?", "John").Or("name = ?", "Jane").Where("age > ?", 18).Find(&users) // WHERE (name = "John" OR name = "Jane") AND age > 18
常见面试考点总结
| 考点 | 关键结论 |
|---|---|
| First vs Take | First 按 PK 排序,Take 不排序 |
| First vs Find | First 找不到报 ErrRecordNotFound,Find 返回空 |
| struct 查询零值陷阱 | struct 零值字段被忽略,用 map 替代 |
| Count 的位置 | 必须放在链式调用最后 |
| 软删除影响 | 自动加 deleted_at IS NULL,Unscoped 跳过 |
| 避免 N+1 | 用 Preload 预加载关联 |
| 参数化查询 | 用 ? 占位符防 SQL 注入 |
| FirstOrCreate | 找到返回,找不到自动创建 |