高度なクエリ

スマートセレクトフィールド

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

上記のステートメントは、トランザクションの間、選択された行をロックします。これは、行を更新する準備をしていて、トランザクションが完了するまで他のトランザクションがそれらを変更するのを防ぎたいシナリオで使用できます。

StrengthSHARE に設定することもでき、これにより、他のトランザクションがロックされた行を読み取れるようにロックしますが、更新または削除はできません。

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

Table オプションは、ロックするテーブルを指定するために使用できます。これは、複数のテーブルを結合していて、そのうちの 1 つだけをロックしたい場合に便利です。

オプションには、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.NamedArg または map[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{} にスキャンできるようにすることで、データのクエリに柔軟性を提供します。これは、動的なデータ構造に役立ちます。

マップへの検索 を使用する場合は、クエリに Model または Table を含めてテーブル名を明示的に指定することが重要です。これにより、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 メソッドは、指定された条件に一致する最初のレコードを取得するか、一致するレコードが見つからない場合は新しいインスタンスを初期化するために使用されます。このメソッドは、struct 条件と map 条件の両方と互換性があり、Attrs および Assign メソッドを使用することでさらに柔軟性が高まります。

// 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 を使用して、追加の属性で struct を初期化できます。これらの属性は新しい struct に含まれますが、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 メソッドを使用すると、レコードが見つかったかどうかに関係なく、struct に属性を設定できます。これらの属性は struct に設定されますが、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 は、Attrs および Assign と共に、レコードが存在し、特定の属性で初期化または更新されることを単一のステップで保証するための、強力で柔軟な方法を提供します。

FirstOrCreate

GORM の FirstOrCreate は、指定された条件に一致する最初のレコードを取得するか、一致するレコードが見つからない場合は新しいレコードを作成するために使用されます。このメソッドは、struct 条件と map 条件の両方で効果的です。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 メソッドを使用してクエリ結果の反復処理をサポートしています。この機能は、大規模なデータセットを処理したり、各レコードで個別に操作を実行する必要がある場合に特に役立ちます。

クエリによって返された行を反復処理し、各行を struct にスキャンできます。このメソッドは、各レコードの処理方法を詳細に制御できます。

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 メソッドは、データベースから単一の列をクエリし、結果をスライスにスキャンするために使用されます。このメソッドは、モデルから特定のフィールドを取得する必要がある場合に最適です。

複数の列をクエリする必要がある場合は、代わりに Scan または Find と共に Select を使用できます。

// 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 メソッドを使用して、1 つ以上のスコープをクエリに適用できます。これにより、複数の条件を動的にチェーンできます。

// 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ドキュメントのスコープを参照してください。

Count

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を使用したCount

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

プラチナスポンサー

ゴールドスポンサー

プラチナスポンサー

ゴールドスポンサー