CRUD 接口

高级查询

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

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

智能选择字段(Smart Select Fields)

在 GORM 中,你可以使用 Select 方法高效地选择特定字段。当处理大型模型但只需要部分字段时,这尤其有用,特别是在 API 响应中。

type User struct {
  ID     uint
  Name   string
  Age    int
  Gender string
  
}

type APIUser struct {
  ID   uint
  Name string
}


db.Model(&User{}).Limit(10).Find(&APIUser{})

注意 在 QueryFields 模式下,所有模型字段都会按其名称被选中。

db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
  QueryFields: true,
})


db.Find(&user)



db.Session(&gorm.Session{QueryFields: true}).Find(&user)

锁(Locking)

GORM 支持不同类型的锁,例如:


db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users)

上述语句将在事务持续期间锁定所选行。这可用于你准备更新行并希望在事务完成之前防止其他事务修改它们的场景。

Strength 也可以设置为 SHARE,它以允许其他事务读取锁定行但不允许更新或删除它们的方式锁定行。

db.Clauses(clause.Locking{
  Strength: "SHARE",
  Table: clause.Table{Name: clause.CurrentTable},
}).Find(&users)

Table 选项可用于指定要锁定的表。当你连接多个表并只想锁定其中一个时,这非常有用。

还可以提供 NOWAIT 等选项,它尝试获取锁,如果锁不可用则立即返回错误。这可以防止事务等待其他事务释放锁。

db.Clauses(clause.Locking{
  Strength: "UPDATE",
  Options: "NOWAIT",
}).Find(&users)

另一个选项是 SKIP LOCKED,它会跳过已被其他事务锁定的行。这在高并发场景中非常有用,你希望处理当前未被其他事务锁定的行。

如需更高级的锁定策略,请参考 原始 SQL 和 SQL 构建器。

子查询(SubQuery)

子查询是 SQL 中的强大功能,允许嵌套查询。当使用 *gorm.DB 对象作为参数时,GORM 可以自动生成子查询。


db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders)



subQuery := db.Select("AVG(age)").Where("name LIKE ?", "name%").Table("users")
db.Select("AVG(age) as avgage").Group("name").Having("AVG(age) > (?)", subQuery).Find(&results)

FROM 子查询

GORM 允许在 FROM 子句中使用子查询,实现复杂的查询和数据组织。


db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{})



subQuery1 := db.Model(&User{}).Select("name")
subQuery2 := db.Model(&Pet{}).Select("name")
db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{})

分组条件(Group Conditions)

GORM 中的分组条件提供了一种更具可读性和可维护性的方式来编写涉及多个条件的复杂 SQL 查询。


db.Where(
  db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")),
).Or(
  db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"),
).Find(&Pizza{})

多列 IN 查询

GORM 支持多列 IN 子句,允许你在单个查询中基于多个字段值过滤数据。


db.Where("(name, age, role) IN ?", [][]interface{}{{"jinzhu", 18, "admin"}, {"jinzhu2", 19, "user"}}).Find(&users)

命名参数(Named Argument)

GORM 通过支持命名参数来增强 SQL 查询的可读性和可维护性。此功能允许更清晰、更有组织的查询构建,特别是在包含多个参数的复杂查询中。命名参数可以使用 sql.NamedArg 或 map[string]interface{}{},为查询结构提供了灵活性。


db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user)



db.Where("name1 = @name OR name2 = @name", map[string]interface{}{"name": "jinzhu"}).First(&user)

更多示例和详情请参考 原始 SQL 和 SQL 构建器

Find 转 Map(Find To Map)

GORM 通过允许将结果扫描到 map[string]interface{} 或 []map[string]interface{} 中,为数据查询提供了灵活性,这对动态数据结构非常有用。

使用 Find To Map 时,在查询中包含 Model 或 Table 以明确指定表名至关重要。这确保 GORM 知道要查询哪个表。


result := map[string]interface{}{}
db.Model(&User{}).First(&result, "id = ?", 1)



var results []map[string]interface{}
db.Table("users").Find(&results)

FirstOrInit

GORM 的 FirstOrInit 方法用于获取与给定条件匹配的第一条记录,如果未找到匹配的记录则初始化一个新实例。该方法同时兼容结构体和 map 条件,并允许通过 Attrs 和 Assign 方法提供额外的灵活性。


var user User
db.FirstOrInit(&user, User{Name: "non_existing"})



db.Where(User{Name: "jinzhu"}).FirstOrInit(&user)



db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"})

使用 Attrs 进行初始化

当未找到记录时,你可以使用 Attrs 用额外的属性初始化结构体。这些属性包含在新结构体中,但不会用于 SQL 查询。


db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user)




db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 20}).FirstOrInit(&user)

使用 Assign 设置属性

Assign 方法允许你无论是否找到记录都在结构体上设置属性。这些属性会设置在结构体上,但不会用于构建 SQL 查询,最终数据也不会保存到数据库中。


db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user)



db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 20}).FirstOrInit(&user)

FirstOrInit 与 Attrs 和 Assign 一起,提供了一种强大而灵活的方式来确保记录存在,并在单个步骤中用特定属性初始化或更新。

FirstOrCreate

GORM 中的 FirstOrCreate 用于获取与给定条件匹配的第一条记录,如果未找到匹配的记录则创建一个新记录。该方法对结构体和 map 条件都有效。RowsAffected 属性可用于确定创建或更新的记录数量。


result := db.FirstOrCreate(&user, User{Name: "non_existing"})





result = db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user)

在 FirstOrCreate 中使用 Attrs

如果未找到记录,Attrs 可用于为新记录指定额外的属性。这些属性用于创建,但不用于初始搜索查询。


db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user)





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

在 FirstOrCreate 中使用 Assign

Assign 方法无论是否找到记录都会在记录上设置属性,并且这些属性会被保存回数据库。


db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user)





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


优化器/索引提示(Optimizer/Index Hints)

GORM 支持优化器和索引提示,允许你影响查询优化器的执行计划。这在优化查询性能或处理复杂查询时尤其有用。

优化器提示是指令,建议数据库的查询优化器应该如何执行查询。GORM 通过 gorm.io/hints 包提供了使用优化器提示的支持。

import "gorm.io/hints"


db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{})

索引提示(Index Hints)

索引提示为数据库提供使用哪些索引的指导。如果查询规划器没有为查询选择最有效的索引,这些提示会非常有益。

import "gorm.io/hints"


db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{})



db.Clauses(hints.ForceIndex("idx_user_name", "idx_user_id").ForJoin()).Find(&User{})

这些提示可以显著影响查询性能和行为,特别是在大型数据库或复杂数据模型中。有关更详细的信息和额外示例,请参考 GORM 文档中的 优化器提示/索引/注释。

迭代(Iteration)

GORM 支持使用 Rows 方法迭代查询结果。当你需要处理大型数据集或逐条执行操作时,此功能特别有用。

你可以迭代查询返回的行,将每行扫描到结构体中。这种方法提供了对每条记录处理方式的细粒度控制。

rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows()
defer rows.Close()

for rows.Next() {
  var user User
  
  db.ScanRows(rows, &user)

  
}

这种方法是复杂数据处理的理想选择,标准查询方法无法轻松实现这些处理。

FindInBatches(批量查找)

FindInBatches 允许分批查询和处理记录。这对于高效处理大型数据集特别有用,可以减少内存使用并提高性能。

使用 FindInBatches,GORM 按指定的批处理大小处理记录。在批处理函数内部,你可以对每批记录应用操作。


result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error {
  for _, result := range results {
    
  }

  
  tx.Save(&results)

  
  

  
  return nil
})


FindInBatches 是以可管理的块处理大量数据的有效工具,可优化资源使用和性能。

查询钩子(Query Hooks)

GORM 提供了使用钩子的能力,例如 AfterFind,它们在查询生命周期中被触发。这些钩子允许在特定点执行自定义逻辑,例如在从数据库检索记录之后。

此钩子对于查询后的数据操作或默认值设置非常有用。有关更详细的信息和其他钩子类型,请参考 GORM 文档中的 钩子。

func (u *User) AfterFind(tx *gorm.DB) (err error) {
  
  if u.Role == "" {
    u.Role = "user" 
  }
  return
}

Pluck(提取单列)

GORM 中的 Pluck 方法用于从数据库查询单个列,并将结果扫描到切片中。当你需要从模型中检索特定字段时,此方法是理想选择。

如果你需要查询多个列,可以改用 Select 搭配 Scan 或 Find。


var ages []int64
db.Model(&User{}).Pluck("age", &ages)


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


db.Table("deleted_users").Pluck("name", &names)


db.Model(&User{}).Distinct().Pluck("Name", &names)



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

作用域(Scopes)

GORM 中的 Scopes 是一个强大的功能,允许你将常用的查询条件定义为可重用的方法。这些作用域可以在查询中轻松引用,使你的代码更加模块化和可读。

定义作用域

Scopes 被定义为修改并返回 gorm.DB 实例的函数。你可以根据应用的需求将各种条件定义为作用域。


func AmountGreaterThan1000(db *gorm.DB) *gorm.DB {
  return db.Where("amount > ?", 1000)
}


func PaidWithCreditCard(db *gorm.DB) *gorm.DB {
  return db.Where("pay_mode_sign = ?", "C")
}


func PaidWithCod(db *gorm.DB) *gorm.DB {
  return db.Where("pay_mode_sign = ?", "COD")
}


func OrderStatus(status []string) func(db *gorm.DB) *gorm.DB {
  return func(db *gorm.DB) *gorm.DB {
    return db.Where("status IN (?)", status)
  }
}

在查询中应用作用域

你可以使用 Scopes 方法将一个或多个作用域应用于查询。这允许你动态地链接多个条件。


db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders)


db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders)


db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders)

Scopes 是一种封装常见查询逻辑的简洁高效方式,可以增强代码的可维护性和可读性。有关更详细的示例和用法,请参考 GORM 文档中的 作用域。

Count(计数)

GORM 中的 Count 方法用于检索匹配给定查询的记录数量。这是了解数据集大小的有用功能,特别是在涉及条件查询或数据分析的场景中。

获取匹配记录的计数

你可以使用 Count 来确定查询中满足特定条件的记录数量。

var count int64


db.Model(&User{}).Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Count(&count)



db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count)



db.Table("deleted_users").Count(&count)

使用 Distinct 和 Group 进行计数

GORM 还支持对不同值进行计数和分组结果。


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



db.Table("deleted_users").Select("count(distinct(name))").Count(&count)



users := []User{
  {Name: "name1"},
  {Name: "name2"},
  {Name: "name3"},
  {Name: "name3"},
}

db.Model(&User{}).Group("name").Count(&count)

GitHub tag (latest SemVer)

评论

登录后参与评论

正在加载评论…