pages/ja_JP/docs/query.md
GORMは、データベースから1つのオブジェクトを取得するためにFirst, Take, Lastメソッドを提供しています。それらのメソッドは、データベースにクエリを実行する際にLIMIT 1の条件を追加し、レコードが見つからなかった場合、ErrRecordNotFoundエラーを返します。
ctx := context.Background()
// Get the first record ordered by primary key
user, err := gorm.G[User](db).First(ctx)
// SELECT * FROM users ORDER BY id LIMIT 1;
// Get one record, no specified order
user, err := gorm.G[User](db).Take(ctx)
// SELECT * FROM users LIMIT 1;
// Get last record, ordered by primary key desc
user, err := gorm.G[User](db).Last(ctx)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;
// check error ErrRecordNotFound
errors.Is(err, gorm.ErrRecordNotFound)
// Get the first record ordered by primary key
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;
// Get one record, no specified order
db.Take(&user)
// SELECT * FROM users LIMIT 1;
// Get last record, ordered by primary key desc
db.Last(&user)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;
result := db.First(&user)
result.RowsAffected // returns count of records found
result.Error // returns error or nil
// check error ErrRecordNotFound
errors.Is(result.Error, gorm.ErrRecordNotFound)
{% note warn %}
ErrRecordNotFound エラーを避けたい場合は、db.Limit(1).Find(&user)のように、Find を 使用することができます。Find メソッドは struct と slice のどちらも受け取ることができます。
{% endnote %}
{% note warn %}
Using Find without a limit for single object db.Find(&user) will query the full table and return only the first object which is non-deterministic and not performant
{% endnote %}
First メソッドと Last メソッドは、主キー順で(それぞれ)先頭または末尾のレコードを取得します。 これら2つのメソッドは、対象の構造体のポインタをメソッドの引数に渡す、もしくは db.Model() を使用してモデルを指定した場合のみ動作します。 また、モデルに主キーが定義されていない場合、モデル内の最初のフィールドで順序付けされることになります。 例:
var user User
var users []User
// 対象の構造体が渡されているため正しく動作
db.First(&user)
// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1
// `db.Model()` を使ってモデルを指定しているため正しく動作
result := map[string]interface{}{}
db.Model(&User{}).First(&result)
// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1
// 動作しない
result := map[string]interface{}{}
db.Table("users").First(&result)
// Takeメソッドであれば動作する
result := map[string]interface{}{}
db.Table("users").Take(&result)
// 主キーが未定義のため、最初のフィールド(`Code`)で順序付けされる
type Language struct {
Code string
Name string
}
db.First(&Language{})
// SELECT * FROM `languages` ORDER BY `languages`.`code` LIMIT 1
主キーが数値の場合、インライン条件 を使用してオブジェクトを取得できます。 文字列を扱う場合、SQLインジェクションを避けるためにさらなる注意が必要です。詳細についてはセキュリティセクションを参照してください。
ctx := context.Background()
// Using numeric primary key
user, err := gorm.G[User](db).Where("id = ?", 10).First(ctx)
// SELECT * FROM users WHERE id = 10;
// Using string primary key
user, err := gorm.G[User](db).Where("id = ?", "10").First(ctx)
// SELECT * FROM users WHERE id = 10;
// Using multiple primary keys
users, err := gorm.G[User](db).Where("id IN ?", []int{1,2,3}).Find(ctx)
// SELECT * FROM users WHERE id IN (1,2,3);
// If the primary key is a string (for example, like a uuid)
user, err := gorm.G[User](db).Where("id = ?", "1b74413f-f3b8-409f-ac47-e8c062e3472a").First(ctx)
// SELECT * FROM users WHERE id = "1b74413f-f3b8-409f-ac47-e8c062e3472a";
db.First(&user, 10)
// SELECT * FROM users WHERE id = 10;
db.First(&user, "10")
// SELECT * FROM users WHERE id = 10;
db.Find(&users, []int{1,2,3})
// SELECT * FROM users WHERE id IN (1,2,3);
主キーが(たとえばUUIDのような)文字列の場合、クエリは以下のように書くことができます。
db.First(&user, "id = ?", "1b74413f-f3b8-409f-ac47-e8c062e3472a")
// SELECT * FROM users WHERE id = "1b74413f-f3b8-409f-ac47-e8c062e3472a";
対象オブジェクトの主キーに値が入っている場合、その主キーは検索条件の構築に使用されます。例:
var user = User{ID: 10}
db.First(&user)
// SELECT * FROM users WHERE id = 10;
var result User
db.Model(User{ID: 10}).First(&result)
// SELECT * FROM users WHERE id = 10;
{% note warn %}
注意: gorm.DeletedAt のようなGORM用のフィールドの型を使用する場合、オブジェクトの取得の際に異なるクエリが実行されます。
{% endnote %}
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)
// SELECT * FROM `users` WHERE `users`.`id` = '15' AND `users`.`deleted_at` IS NULL ORDER BY `users`.`id` LIMIT 1
// 全レコードを取得
result := db.Find(&users)
// SELECT * FROM users;
result.RowsAffected // 見つかったレコードの件数 `len(users)` を返す
result.Error // エラーを返す
// 最初に条件に合ったレコードを取得
db.Where("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;
// 条件にあったすべてのレコードを取得
db.Where("name <> ?", "jinzhu").Find(&users)
// SELECT * FROM users WHERE name <> 'jinzhu';
// IN
db.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users)
// SELECT * FROM users WHERE name IN ('jinzhu','jinzhu 2');
// LIKE
db.Where("name LIKE ?", "%jin%").Find(&users)
// SELECT * FROM users WHERE name LIKE '%jin%';
// AND
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22;
// 日時
db.Where("updated_at > ?", lastWeek).Find(&users)
// SELECT * FROM users WHERE updated_at > '2000-01-01 00:00:00';
// BETWEEN
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)
// SELECT * FROM users WHERE created_at BETWEEN '2000-01-01 00:00:00' AND '2000-01-08 00:00:00';
{% note warn %} オブジェクトの主キーが設定されている場合、条件には主キーの値が代わりに使用されるのではなく、AND条件として使用されます。 例:
var user = User{ID: 10}
db.Where("id = ?", 20).First(&user)
// SELECT * FROM users WHERE id = 10 and id = 20 ORDER BY id ASC LIMIT 1
上記のクエリは record not found エラーになります。 そのため、データベースから新規の値を取得するときに user のような変数を使用したい場合は、id などの主キーにnilをセットしましょう。
{% endnote %}
// 構造体
db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20 ORDER BY id LIMIT 1;
// マップ
db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20;
// 主キーのスライス
db.Where([]int64{20, 21, 22}).Find(&users)
// SELECT * FROM users WHERE id IN (20, 21, 22);
{% note warn %}
注意 GORMがクエリを実行するとき、構造体のフィールドのうちゼロ値ではないもののみを使用します。 すなわち、フィールドの値が 0、 ''、false、あるいはその他のゼロ値の場合、クエリ条件の構築に使用されません。例:
{% endnote %}
db.Where(&User{Name: "jinzhu", Age: 0}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu";
クエリ条件にゼロ値を含めるには、すべてのキーと値のペアを含んだマップをクエリ条件として使用します。例:
db.Where(map[string]interface{}{"Name": "jinzhu", "Age": 0}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 0;
詳細については、 構造体の検索フィールドを指定する を参照してください。
構造体を使用して検索する場合、フィールド名またはテーブルのカラム名を Where() の引数として列挙することで、構造体の特定の値のみをクエリ条件として使用することができます。 例:
db.Where(&User{Name: "jinzhu"}, "name", "Age").Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 0;
db.Where(&User{Name: "jinzhu"}, "Age").Find(&users)
// SELECT * FROM users WHERE age = 0;
クエリ条件は、First や Find のようなメソッドにおいても Where と同様の方法でインラインで記述することができます。
// 主キーが非整数型の場合
db.First(&user, "id = ?", "string_primary_key")
// SELECT * FROM users WHERE id = 'string_primary_key';
// シンプルなSQL
db.Find(&user, "name = ?", "jinzhu")
// SELECT * FROM users WHERE name = "jinzhu";
db.Find(&users, "name <> ? AND age > ?", "jinzhu", 20)
// SELECT * FROM users WHERE name <> "jinzhu" AND age > 20;
// 構造体
db.Find(&users, User{Age: 20})
// SELECT * FROM users WHERE age = 20;
// マップ
db.Find(&users, map[string]interface{}{"age": 20})
// SELECT * FROM users WHERE age = 20;
NOT条件の構築方法は Where と同様です。
db.Not("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE NOT name = "jinzhu" ORDER BY id LIMIT 1;
// NOT IN
db.Not(map[string]interface{}{"name": []string{"jinzhu", "jinzhu 2"}}).Find(&users)
// SELECT * FROM users WHERE name NOT IN ("jinzhu", "jinzhu 2");
// 構造体
db.Not(User{Name: "jinzhu", Age: 18}).First(&user)
// SELECT * FROM users WHERE name <> "jinzhu" AND age <> 18 ORDER BY id LIMIT 1;
// 主キーのスライス内のいずれでもない
db.Not([]int64{1,2,3}).First(&user)
// SELECT * FROM users WHERE id NOT IN (1,2,3) ORDER BY id LIMIT 1;
db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)
// SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';
// 構造体
db.Where("name = 'jinzhu'").Or(User{Name: "jinzhu 2", Age: 18}).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18);
// マップ
db.Where("name = 'jinzhu'").Or(map[string]interface{}{"name": "jinzhu 2", "age": 18}).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18);
より複雑なクエリについては、 Group Conditions in Advanced Query も参照してください。
データベースから取得するフィールドを指定するには Select を使用します。 指定がない場合、GORMではデフォルトで全フィールドが選択されます。
db.Select("name", "age").Find(&users)
// SELECT name, age FROM users;
db.Select([]string{"name", "age"}).Find(&users)
// SELECT name, age FROM users;
db.Table("users").Select("COALESCE(age,?)", 42).Rows()
// SELECT COALESCE(age,'42') FROM users;
Smart Select Fields も参照してください。
データベースからレコードを取得する際の順序を指定します。
db.Order("age desc, name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;
// 複数の順序
db.Order("age desc").Order("name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;
db.Clauses(clause.OrderBy{
Expression: clause.Expr{SQL: "FIELD(id,?)", Vars: []interface{}{[]int{1, 2, 3}}, WithoutParentheses: true},
}).Find(&User{})
// SELECT * FROM users ORDER BY FIELD(id,1,2,3)
Limitは取得するレコードの上限数を指定します。Offsetはレコードを返す前にスキップする件数を指定します。
db.Limit(3).Find(&users)
// SELECT * FROM users LIMIT 3;
// -1 を指定して上限数を解除
db.Limit(10).Find(&users1).Limit(-1).Find(&users2)
// SELECT * FROM users LIMIT 10; (users1)
// SELECT * FROM users; (users2)
db.Offset(3).Find(&users)
// SELECT * FROM users OFFSET 3;
db.Limit(10).Offset(5).Find(&users)
// SELECT * FROM users OFFSET 5 LIMIT 10;
// -1 を指定して上限数を解除
db.Offset(10).Find(&users1).Offset(-1).Find(&users2)
// SELECT * FROM users OFFSET 10; (users1)
// SELECT * FROM users; (users2)
ページネーターの作成方法については、ページネーション を参照してください。
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)
// SELECT name, sum(age) as total FROM `users` WHERE name LIKE "group%" GROUP BY `name` LIMIT 1
db.Model(&User{}).Select("name, sum(age) as total").Group("name").Having("name = ?", "group").Find(&result)
// SELECT name, sum(age) as total FROM `users` GROUP BY `name` HAVING name = "group"
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)
値が重複する行を削除して取得します。
db.Distinct("name", "age").Order("name, age desc").Find(&results)
Distinct は Pluck および Count でも動作します
テーブル結合の条件を指定します。
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{})
// SELECT users.name, emails.email FROM `users` left join emails on emails.user_id = users.id
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 = ?", "[email protected]").Joins("JOIN credit_cards ON credit_cards.user_id = users.id").Where("credit_cards.number = ?", "411111111111").Find(&user)
Joins を使用することで、単一のSQLクエリで関連付けのイーガーロードを行うことができます。例:
db.Joins("Company").Find(&users)
// SELECT `users`.`id`,`users`.`name`,`users`.`age`,`Company`.`id` AS `Company__id`,`Company`.`name` AS `Company__name` FROM `users` LEFT JOIN `companies` AS `Company` ON `users`.`company_id` = `Company`.`id`;
// 内部結合
db.InnerJoins("Company").Find(&users)
// SELECT `users`.`id`,`users`.`name`,`users`.`age`,`Company`.`id` AS `Company__id`,`Company`.`name` AS `Company__name` FROM `users` INNER JOIN `companies` AS `Company` ON `users`.`company_id` = `Company`.`id`;
条件を指定して結合する
db.Joins("Company", db.Where(&Company{Alive: true})).Find(&users)
// SELECT `users`.`id`,`users`.`name`,`users`.`age`,`Company`.`id` AS `Company__id`,`Company`.`name` AS `Company__name` FROM `users` LEFT JOIN `companies` AS `Company` ON `users`.`company_id` = `Company`.`id` AND `Company`.`alive` = true;
詳細については、事前ローディング (イーガーローディング) を参照してください。
導出テーブルの結合にも 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)
// SELECT `order`.`user_id`,`order`.`finished_at` FROM `order` join (SELECT MAX(order.finished_at) as latest FROM `order` left join user user on order.user_id = user.id WHERE user.age > 18 GROUP BY `order`.`user_id`) q on order.finished_at = q.latest
Scanning results into a struct works similarly to the way we use Find
type Result struct {
Name string
Age int
}
var result Result
db.Table("users").Select("name", "age").Where("name = ?", "Antonio").Scan(&result)
// 生SQL
db.Raw("SELECT name, age FROM users WHERE name = ?", "Antonio").Scan(&result)