CRUD 接口

查询

qianmoQqianmoQ· 更新于 2026-09-23· 阅读 26 分钟· 0 次阅读

登录后可跨设备保存划线和私人笔记登录

检索单个对象

GORM 提供了 First、Take、Last 方法从数据库中检索单个对象,查询数据库时会添加 LIMIT 1 条件,如果没有找到记录则会返回 ErrRecordNotFound 错误。

泛型 API

ctx := context.Background()


user, err := gorm.G[User](db).First(ctx)



user, err := gorm.G[User](db).Take(ctx)



user, err := gorm.G[User](db).Last(ctx)



errors.Is(err, gorm.ErrRecordNotFound)

传统 API


db.First(&user)



db.Take(&user)



db.Last(&user)


result := db.First(&user)
result.RowsAffected 
result.Error        


errors.Is(result.Error, gorm.ErrRecordNotFound)

如果你想避免 ErrRecordNotFound 错误,可以使用 Find,例如 db.Limit(1).Find(&user),Find 方法同时接受结构体和切片数据

对单个对象不加限制地使用 Find,例如 db.Find(&user),会查询整个表并只返回第一个对象,这既不确定性能也不好

First 和 Last 方法会分别查找按主键排序的第一条和最后一条记录。它们只有在向方法传入目标结构体的指针作为参数,或使用 db.Model() 指定了模型时才有效。此外,如果相关模型没有定义主键,模型将按第一个字段排序。例如:

var user User
var users []User


db.First(&user)



result := map[string]interface{}{}
db.Model(&User{}).First(&result)



result := map[string]interface{}{}
db.Table("users").First(&result)


result := map[string]interface{}{}
db.Table("users").Take(&result)



type Result struct {
  ID   int
  Name string
}
var joinResult Result
db.Table("user_languages ul").
  Select("l.id, l.name").
  Joins("JOIN languages l ON l.code = ul.language_code").
  Order("l.name DESC").
  First(&joinResult)



db.Table("user_languages ul").
  Select("l.id, l.name").
  Joins("JOIN languages l ON l.code = ul.language_code").
  Order("l.name DESC").
  Take(&joinResult)



type Language struct {
  Code string
  Name string
}
db.First(&Language{})

通过主键检索对象

如果主键是数字,可以使用内联条件通过主键检索对象。当使用字符串时,需要格外小心以避免 SQL 注入;详情请查看安全章节。

泛型 API

ctx := context.Background()


user, err := gorm.G[User](db).Where("id = ?", 10).First(ctx)



user, err := gorm.G[User](db).Where("id = ?", "10").First(ctx)



users, err := gorm.G[User](db).Where("id IN ?", []int{1,2,3}).Find(ctx)



user, err := gorm.G[User](db).Where("id = ?", "1b74413f-f3b8-409f-ac47-e8c062e3472a").First(ctx)

传统 API

db.First(&user, 10)


db.First(&user, "10")


db.Find(&users, []int{1,2,3})

如果主键是字符串(例如 uuid),查询将如下写法:

db.First(&user, "id = ?", "1b74413f-f3b8-409f-ac47-e8c062e3472a")

当目标对象已有主键值时,将使用主键构建条件,例如:

var user = User{ID: 10}
db.First(&user)


var result User
db.Model(User{ID: 10}).First(&result)

注意: 如果你使用了 GORM 的特殊字段类型,如 gorm.DeletedAt,它会执行不同的查询来检索对象。

type User struct {
  ID           string `gorm:"primarykey;size:16"`
  Name         string `gorm:"size:24"`
  DeletedAt    gorm.DeletedAt `gorm:"index"`
}

var user = User{ID: 15}
db.First(&user)

检索所有对象


result := db.Find(&users)


result.RowsAffected 
result.Error        

条件

字符串条件


db.Where("name = ?", "jinzhu").First(&user)



db.Where("name <> ?", "jinzhu").Find(&users)



db.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users)



db.Where("name LIKE ?", "%jin%").Find(&users)



db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)



db.Where("updated_at > ?", lastWeek).Find(&users)



db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)

如果对象的主键已被设置,那么条件查询不会覆盖主键的值,而是将其作为一个 and 条件使用。例如:

var user = User{ID: 10}
db.Where("id = ?", 20).First(&user)

这个查询会返回 `record not found` 错误。所以在你想使用 `user` 变量从数据库获取新值之前,需要将主键属性(如 `id`)设置为 nil。

结构体和 Map 条件


db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)



db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)



db.Where([]int64{20, 21, 22}).Find(&users)

注意 使用结构体查询时,GORM 只会用非零字段进行查询,也就是说如果你的字段值为 0、''、false 或其他零值,它不会被用来构建查询条件,例如:

db.Where(&User{Name: "jinzhu", Age: 0}).Find(&users)

要在查询条件中包含零值,可以使用 Map,它会将所有键值对作为查询条件,例如:

db.Where(map[string]interface{}{"Name": "jinzhu", "Age": 0}).Find(&users)

更多信息请查看指定结构体搜索字段。

指定结构体搜索字段

使用结构体搜索时,你可以通过向 Where() 传入相关的字段名或数据库名来指定要用于查询条件的特定值,例如:

db.Where(&User{Name: "jinzhu"}, "name", "Age").Find(&users)


db.Where(&User{Name: "jinzhu"}, "Age").Find(&users)

内联条件

查询条件可以像 Where 一样内联到 First 和 Find 等方法中。


db.First(&user, "id = ?", "string_primary_key")



db.Find(&user, "name = ?", "jinzhu")


db.Find(&users, "name <> ? AND age > ?", "jinzhu", 20)



db.Find(&users, User{Age: 20})



db.Find(&users, map[string]interface{}{"age": 20})

NOT 条件

构建 NOT 条件,用法与 Where 类似

db.Not("name = ?", "jinzhu").First(&user)



db.Not(map[string]interface{}{"name": []string{"jinzhu", "jinzhu 2"}}).Find(&users)



db.Not(User{Name: "jinzhu", Age: 18}).First(&user)



db.Not([]int64{1,2,3}).First(&user)

OR 条件

db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)



db.Where("name = 'jinzhu'").Or(User{Name: "jinzhu 2", Age: 18}).Find(&users)



db.Where("name = 'jinzhu'").Or(map[string]interface{}{"name": "jinzhu 2", "age": 18}).Find(&users)

更复杂的 SQL 查询,请参考高级查询中的分组条件。

选择特定字段

Select 允许你指定要从数据库中检索的字段。否则,GORM 默认会选择所有字段。

db.Select("name", "age").Find(&users)


db.Select([]string{"name", "age"}).Find(&users)


db.Table("users").Select("COALESCE(age,?)", 42).Rows()

另请查看智能选择字段

排序

指定从数据库检索记录时的排序方式

db.Order("age desc, name").Find(&users)



db.Order("age desc").Order("name").Find(&users)


db.Clauses(clause.OrderBy{
  Expression: clause.Expr{SQL: "FIELD(id,?)", Vars: []interface{}{[]int{1, 2, 3}}, WithoutParentheses: true},
}).Find(&User{})

Limit & Offset

Limit 指定要检索的最大记录数
Offset 指定在开始返回记录之前要跳过的记录数

db.Limit(3).Find(&users)



db.Limit(10).Find(&users1).Limit(-1).Find(&users2)



db.Offset(3).Find(&users)


db.Limit(10).Offset(5).Find(&users)



db.Offset(10).Find(&users1).Offset(-1).Find(&users2)

关于如何制作分页器的详情请参考分页

Group By & Having

type result struct {
  Date  time.Time
  Total int
}

db.Model(&User{}).Select("name, sum(age) as total").Where("name LIKE ?", "group%").Group("name").First(&result)



db.Model(&User{}).Select("name, sum(age) as total").Group("name").Having("name = ?", "group").Find(&result)


rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Rows()
defer rows.Close()
for rows.Next() {
  ...
}

rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Rows()
defer rows.Close()
for rows.Next() {
  ...
}

type Result struct {
  Date  time.Time
  Total int64
}
db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Scan(&results)

Distinct

从模型中选择不同的值

db.Distinct("name", "age").Order("name, age desc").Find(&results)

Distinct 也可以与 Pluck 和 Count 一起使用

Joins

泛型 API

新的 GORM 泛型接口为关联查询(Joins)带来了增强的支持,提供了更灵活的关联方法、更强大的查询能力,以及大幅简化的复杂查询构建方式。

  • Joins:轻松指定不同的连接类型(如 InnerJoin、LeftJoin),并基于关联自定义连接条件,使复杂的跨表查询更加清晰直观。

users, err := gorm.G[User](db).Joins(clause.Has("Company"), nil).Find(ctx)


user, err = gorm.G[User](db).Joins(clause.LeftJoin.Association("Company"), func(db gorm.JoinBuilder, joinTable clause.Table, curTable clause.Table) error {
    db.Where(map[string]any{"name": company.Name})
    return nil
}).Where(map[string]any{"name": user.Name}).First(ctx)


users, err = gorm.G[User](db).Joins(clause.LeftJoin.AssociationFrom("Company", gorm.G[Company](DB).Select("Name")).As("t"),
    func(db gorm.JoinBuilder, joinTable clause.Table, curTable clause.Table) error {
        db.Where("?.name = ?", joinTable, u.Company.Name)
        return nil
    },
).Find(ctx)

传统 API

指定 Joins 条件

type result struct {
  Name  string
  Email string
}

db.Model(&User{}).Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&result{})


rows, err := db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Rows()
for rows.Next() {
  ...
}

db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&results)


db.Joins("JOIN emails ON emails.user_id = users.id AND emails.email = ?", "jinzhu@example.org").Joins("JOIN credit_cards ON credit_cards.user_id = users.id").Where("credit_cards.number = ?", "411111111111").Find(&user)

Joins 预加载

你可以使用 Joins 通过单条 SQL 进行关联的即时加载,例如:

db.Joins("Company").Find(&users)



db.InnerJoins("Company").Find(&users)

带条件的连接

db.Joins("Company", db.Where(&Company{Alive: true})).Find(&users)

更多信息请参考预加载(即时加载)。

连接派生表

你还可以使用 Joins 连接一个派生表。

type User struct {
  Id  int
  Age int
}

type Order struct {
  UserId     int
  FinishedAt *time.Time
}

query := db.Table("order").Select("MAX(order.finished_at) as latest").Joins("left join user user on order.user_id = user.id").Where("user.age > ?", 18).Group("order.user_id")
db.Model(&Order{}).Joins("join (?) q on order.finished_at = q.latest", query).Scan(&results)

Scan

将结果扫描到结构体中的方式与我们使用 Find 的方式类似

type Result struct {
  Name string
  Age  int
}

var result Result
db.Table("users").Select("name", "age").Where("name = ?", "Antonio").Scan(&result)


db.Raw("SELECT name, age FROM users WHERE name = ?", "Antonio").Scan(&result)

GitHub tag (latest SemVer)

评论

登录后参与评论

正在加载评论…