高级查询

智能选择字段

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

type User struct {
ID uint
Name string
Age int
Gender string
// hundreds of fields
}

type APIUser struct {
ID uint
Name string
}

// GORM will automatically select `id`, `name` fields when querying
db.Model(&User{}).Limit(10).Find(&APIUser{})
// SQL: SELECT `id`, `name` FROM `users` LIMIT 10

注意QueryFields 模式下,所有模型字段都通过其名称进行选择。

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

// Default behavior with QueryFields set to true
db.Find(&user)
// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users`

// Using Session Mode with QueryFields
db.Session(&gorm.Session{QueryFields: true}).Find(&user)
// SQL: SELECT `users`.`name`, `users`.`age`, ... FROM `users`

锁定

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

// Basic FOR UPDATE lock
db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users)
// SQL: SELECT * FROM `users` FOR UPDATE

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

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

db.Clauses(clause.Locking{
Strength: "SHARE",
Table: clause.Table{Name: clause.CurrentTable},
}).Find(&users)
// SQL: SELECT * FROM `users` FOR SHARE OF `users`

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

可以提供诸如 NOWAIT 之类的选项,如果锁不可用,它会尝试获取锁并立即失败并返回错误。它可以防止事务等待其他事务释放其锁。

db.Clauses(clause.Locking{
Strength: "UPDATE",
Options: "NOWAIT",
}).Find(&users)
// SQL: SELECT * FROM `users` FOR UPDATE NOWAIT

另一个选项是 SKIP LOCKED,它会跳过已被其他事务锁定的任何行。这在高并发情况下很有用,您希望处理当前未被其他事务锁定的行。

有关更高级的锁定策略,请参阅 原始 SQL 和 SQL 生成器

子查询

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

// Simple subquery
db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders)
// SQL: SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

// Nested subquery
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)
// SQL: SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%")

来自子查询

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

// Using subquery in FROM clause
db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{})
// SQL: SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18

// Combining multiple subqueries in FROM clause
subQuery1 := db.Model(&User{}).Select("name")
subQuery2 := db.Model(&Pet{}).Select("name")
db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{})
// SQL: SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p

分组条件

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

// Complex SQL query using Group Conditions
db.Where(
db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")),
).Or(
db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"),
).Find(&Pizza{})
// SQL: SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge")

多列 IN

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

// Using IN with multiple columns
db.Where("(name, age, role) IN ?", [][]interface{}{{"jinzhu", 18, "admin"}, {"jinzhu2", 19, "user"}}).Find(&users)
// SQL: SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user"));

命名参数

GORM 通过支持命名参数来增强 SQL 查询的可读性和可维护性。此功能允许更清晰、更有条理地构建查询,尤其是在具有多个参数的复杂查询中。可以使用 sql.NamedArgmap[string]interface{}{} 来利用命名参数,从而在如何构建查询方面提供灵活性。

// Example using sql.NamedArg for named arguments
db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user)
// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu"

// Example using a map for named arguments
db.Where("name1 = @name OR name2 = @name", map[string]interface{}{"name": "jinzhu"}).First(&user)
// SQL: SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1

有关更多示例和详细信息,请参阅 原始 SQL 和 SQL 生成器

查找至映射

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

在使用 Find To Map 时,在查询中包含 ModelTable 以明确指定表名至关重要。这确保 GORM 理解要查询哪个表。

// Scanning the first result into a map with Model
result := map[string]interface{}{}
db.Model(&User{}).First(&result, "id = ?", 1)
// SQL: SELECT * FROM `users` WHERE id = 1 LIMIT 1

// Scanning multiple results into a slice of maps with Table
var results []map[string]interface{}
db.Table("users").Find(&results)
// SQL: SELECT * FROM `users`

FirstOrInit

GORM 的 FirstOrInit 方法用于获取与给定条件匹配的第一条记录,如果未找到匹配记录,则初始化一个新实例。此方法兼容结构体和映射条件,并允许使用 AttrsAssign 方法进行额外的灵活操作。

// If no User with the name "non_existing" is found, initialize a new User
var user User
db.FirstOrInit(&user, User{Name: "non_existing"})
// user -> User{Name: "non_existing"} if not found

// Retrieving a user named "jinzhu"
db.Where(User{Name: "jinzhu"}).FirstOrInit(&user)
// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found

// Using a map to specify the search condition
db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"})
// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found

使用 Attrs 进行初始化

如果未找到记录,则可以使用 Attrs 使用其他属性初始化结构体。这些属性包含在新结构体中,但不用于 SQL 查询。

// If no User is found, initialize with given conditions and additional attributes
db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user)
// SQL: SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1;
// user -> User{Name: "non_existing", Age: 20} if not found

// If a User named "Jinzhu" is found, `Attrs` are ignored
db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 20}).FirstOrInit(&user)
// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1;
// user -> User{ID: 111, Name: "Jinzhu", Age: 18} if found

使用 Assign 设置属性

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

// Initialize with given conditions and Assign attributes, regardless of record existence
db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user)
// user -> User{Name: "non_existing", Age: 20} if not found

// If a User named "Jinzhu" is found, update the struct with Assign attributes
db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 20}).FirstOrInit(&user)
// SQL: SELECT * FROM USERS WHERE name = 'Jinzhu' ORDER BY id LIMIT 1;
// user -> User{ID: 111, Name: "Jinzhu", Age: 20} if found

FirstOrInit 以及 AttrsAssign 提供了一种强大而灵活的方式,只需一步即可确保记录存在并使用特定属性进行初始化或更新。

FirstOrCreate

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

// Create a new record if not found
result := db.FirstOrCreate(&user, User{Name: "non_existing"})
// SQL: INSERT INTO "users" (name) VALUES ("non_existing");
// user -> User{ID: 112, Name: "non_existing"}
// result.RowsAffected // => 1 (record created)

// If the user is found, no new record is created
result = db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user)
// user -> User{ID: 111, Name: "jinzhu", Age: 18}
// result.RowsAffected // => 0 (no record created)

Attrs 与 FirstOrCreate 一起使用

如果未找到记录,则可以使用 Attrs 为新记录指定其他属性。这些属性用于创建,但不用于初始搜索查询。

// Create a new record with additional attributes if not found
db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
// SQL: SELECT * FROM users WHERE name = 'non_existing';
// SQL: INSERT INTO "users" (name, age) VALUES ("non_existing", 20);
// user -> User{ID: 112, Name: "non_existing", Age: 20}

// If the user is found, `Attrs` are ignored
db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
// SQL: SELECT * FROM users WHERE name = 'jinzhu';
// user -> User{ID: 111, Name: "jinzhu", Age: 18}

Assign 与 FirstOrCreate 一起使用

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

// Initialize and save new record with `Assign` attributes if not found
db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user)
// SQL: SELECT * FROM users WHERE name = 'non_existing';
// SQL: INSERT INTO "users" (name, age) VALUES ("non_existing", 20);
// user -> User{ID: 112, Name: "non_existing", Age: 20}

// Update found record with `Assign` attributes
db.Where(User{Name: "jinzhu"}).Assign(User{Age: 20}).FirstOrCreate(&user)
// SQL: SELECT * FROM users WHERE name = 'jinzhu';
// SQL: UPDATE users SET age=20 WHERE id = 111;
// user -> User{ID: 111, Name: "Jinzhu", Age: 20}

优化器/索引提示

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

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

import "gorm.io/hints"

// Using an optimizer hint to set a maximum execution time
db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{})
// SQL: SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users`

索引提示

索引提示为数据库提供有关使用哪些索引的指导。如果查询计划程序没有为查询选择最有效的索引,则它们可能会有所帮助。

import "gorm.io/hints"

// Suggesting the use of a specific index
db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{})
// SQL: SELECT * FROM `users` USE INDEX (`idx_user_name`)

// Forcing the use of certain indexes for a JOIN operation
db.Clauses(hints.ForceIndex("idx_user_name", "idx_user_id").ForJoin()).Find(&User{})
// SQL: SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)

这些提示会显着影响查询性能和行为,尤其是在大型数据库或复杂数据模型中。有关更多详细信息和其他示例,请参阅 GORM 文档中的优化器提示/索引/注释

迭代

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

您可以遍历查询返回的行,将每一行扫描到一个结构体中。此方法提供了对如何处理每条记录的粒度控制。

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

for rows.Next() {
var user User
// ScanRows scans a row into a struct
db.ScanRows(rows, &user)

// Perform operations on each user
}

此方法非常适用于无法使用标准查询方法轻松实现的复杂数据处理。

FindInBatches

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

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

// Processing records in batches of 100
result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error {
for _, result := range results {
// Operations on each record in the batch
}

// Save changes to the records in the current batch
tx.Save(&results)

// tx.RowsAffected provides the count of records in the current batch
// The variable 'batch' indicates the current batch number

// Returning an error will stop further batch processing
return nil
})

// result.Error contains any errors encountered during batch processing
// result.RowsAffected provides the count of all processed records across batches

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

查询钩子

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

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

func (u *User) AfterFind(tx *gorm.DB) (err error) {
// Custom logic after finding a user
if u.Role == "" {
u.Role = "user" // Set default role if not specified
}
return
}

// Usage of AfterFind hook happens automatically when a User is queried

提取

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

如果您需要查询多个列,则可以使用 SelectScanFind

// Retrieving ages of all users
var ages []int64
db.Model(&User{}).Pluck("age", &ages)

// Retrieving names of all users
var names []string
db.Model(&User{}).Pluck("name", &names)

// Retrieving names from a different table
db.Table("deleted_users").Pluck("name", &names)

// Using Distinct with Pluck
db.Model(&User{}).Distinct().Pluck("Name", &names)
// SQL: SELECT DISTINCT `name` FROM `users`

// Querying multiple columns
db.Select("name", "age").Scan(&users)
db.Select("name", "age").Find(&users)

作用域

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

定义作用域

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

// Scope for filtering records where amount is greater than 1000
func AmountGreaterThan1000(db *gorm.DB) *gorm.DB {
return db.Where("amount > ?", 1000)
}

// Scope for orders paid with a credit card
func PaidWithCreditCard(db *gorm.DB) *gorm.DB {
return db.Where("pay_mode_sign = ?", "C")
}

// Scope for orders paid with cash on delivery (COD)
func PaidWithCod(db *gorm.DB) *gorm.DB {
return db.Where("pay_mode_sign = ?", "C")
}

// Scope for filtering orders by status
func OrderStatus(status []string) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Where("status IN (?)", status)
}
}

在查询中应用作用域

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

// Applying scopes to find all credit card orders with an amount greater than 1000
db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders)

// Applying scopes to find all COD orders with an amount greater than 1000
db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders)

// Applying scopes to find all orders with specific statuses and an amount greater than 1000
db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders)

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

计数

GORM 中的 Count 方法用于检索与给定查询匹配的记录数。它是一个有用的功能,用于了解数据集的大小,特别是在涉及条件查询或数据分析的情况下。

获取匹配记录数

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

var count int64

// Counting users with specific names
db.Model(&User{}).Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Count(&count)
// SQL: SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2'

// Counting users with a single name condition
db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count)
// SQL: SELECT count(1) FROM users WHERE name = 'jinzhu'

// Counting records in a different table
db.Table("deleted_users").Count(&count)
// SQL: SELECT count(1) FROM deleted_users

使用 Distinct 和 Group 进行计数

GORM 还允许对不同值进行计数并对结果进行分组。

// Counting distinct names
db.Model(&User{}).Distinct("name").Count(&count)
// SQL: SELECT COUNT(DISTINCT(`name`)) FROM `users`

// Counting distinct values with a custom select
db.Table("deleted_users").Select("count(distinct(name))").Count(&count)
// SQL: SELECT count(distinct(name)) FROM deleted_users

// Counting grouped records
users := []User{
{Name: "name1"},
{Name: "name2"},
{Name: "name3"},
{Name: "name3"},
}

db.Model(&User{}).Group("name").Count(&count)
// Count after grouping by name
// count => 3

铂金赞助商

黄金赞助商

铂金赞助商

黄金赞助商