進階查詢

智慧選擇欄位

在 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 能在使用 *gorm.DB 物件作為參數時自動產生子查詢。

// 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%")

From 子查詢

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 建構器

查詢到 Map

GORM 提供查詢資料的彈性,允許將結果掃描到 map[string]interface{}[]map[string]interface{},這對於動態資料結構很有用。

在使用 查詢到 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 方法用於擷取與給定條件相符的第一筆記錄,或在找不到相符記錄時初始化新的執行個體。此方法與結構和 Map 條件相容,並允許使用 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)

FirstOrCreate 中使用 Attrs

如果找不到新記錄,可以使用 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}

FirstOrCreate 中使用 Assign

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

Pluck

GORM 中的 Pluck 方法用於從資料庫查詢單一欄,並將結果掃描到區段中。當您需要從模型擷取特定欄位時,此方法非常理想。

如果您需要查詢多個欄位,您可以改用 Select 搭配 ScanFind

// 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 文件中的 Scopes

計數

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

白金贊助商

金牌贊助商

白金贊助商

金牌贊助商