Smart Select Fields

GORM allows selecting specific fields with Select, if you often use this in your application, maybe you want to define a smaller struct for API usage which can select specific fields automatically, for example:

  1. type User struct {
  2. ID uint
  3. Name string
  4. Age int
  5. Gender string
  6. // hundreds of fields
  7. }
  8. type APIUser struct {
  9. ID uint
  10. Name string
  11. }
  12. // Select `id`, `name` automatically when querying
  13. db.Model(&User{}).Limit(10).Find(&APIUser{})
  14. // SELECT `id`, `name` FROM `users` LIMIT 10

NOTE QueryFields mode will select by all fields’ name for current model

  1. db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
  2. QueryFields: true,
  3. })
  4. db.Find(&user)
  5. // SELECT `users`.`name`, `users`.`age`, ... FROM `users` // with this option
  6. // Session Mode
  7. db.Session(&gorm.Session{QueryFields: true}).Find(&user)
  8. // SELECT `users`.`name`, `users`.`age`, ... FROM `users`

Locking (FOR UPDATE)

GORM supports different types of locks, for example:

  1. db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users)
  2. // SELECT * FROM `users` FOR UPDATE
  3. db.Clauses(clause.Locking{
  4. Strength: "SHARE",
  5. Table: clause.Table{Name: clause.CurrentTable},
  6. }).Find(&users)
  7. // SELECT * FROM `users` FOR SHARE OF `users`
  8. db.Clauses(clause.Locking{
  9. Strength: "UPDATE",
  10. Options: "NOWAIT",
  11. }).Find(&users)
  12. // SELECT * FROM `users` FOR UPDATE NOWAIT

Refer Raw SQL and SQL Builder for more detail

SubQuery

A subquery can be nested within a query, GORM can generate subquery when using a *gorm.DB object as param

  1. db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders)
  2. // SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");
  3. subQuery := db.Select("AVG(age)").Where("name LIKE ?", "name%").Table("users")
  4. db.Select("AVG(age) as avgage").Group("name").Having("AVG(age) > (?)", subQuery).Find(&results)
  5. // SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%")

From SubQuery

GORM allows you using subquery in FROM clause with the method Table, for example:

  1. db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{})
  2. // SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18
  3. subQuery1 := db.Model(&User{}).Select("name")
  4. subQuery2 := db.Model(&Pet{}).Select("name")
  5. db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{})
  6. // SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p

Group Conditions

Easier to write complicated SQL query with Group Conditions

  1. db.Where(
  2. db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")),
  3. ).Or(
  4. db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"),
  5. ).Find(&Pizza{}).Statement
  6. // SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge")

IN with multiple columns

Selecting IN with multiple columns

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

Named Argument

GORM supports named arguments with sql.NamedArg or map[string]interface{}{}, for example:

  1. db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user)
  2. // SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu"
  3. db.Where("name1 = @name OR name2 = @name", map[string]interface{}{"name": "jinzhu"}).First(&user)
  4. // SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1

Check out Raw SQL and SQL Builder for more detail

Find To Map

GORM allows scanning results to map[string]interface{} or []map[string]interface{}, don’t forget to specify Model or Table, for example:

  1. result := map[string]interface{}{}
  2. db.Model(&User{}).First(&result, "id = ?", 1)
  3. var results []map[string]interface{}
  4. db.Table("users").Find(&results)

FirstOrInit

Get first matched record or initialize a new instance with given conditions (only works with struct or map conditions)

  1. // User not found, initialize it with give conditions
  2. db.FirstOrInit(&user, User{Name: "non_existing"})
  3. // user -> User{Name: "non_existing"}
  4. // Found user with `name` = `jinzhu`
  5. db.Where(User{Name: "jinzhu"}).FirstOrInit(&user)
  6. // user -> User{ID: 111, Name: "Jinzhu", Age: 18}
  7. // Found user with `name` = `jinzhu`
  8. db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"})
  9. // user -> User{ID: 111, Name: "Jinzhu", Age: 18}

Initialize struct with more attributes if record not found, those Attrs won’t be used to build the SQL query

  1. // User not found, initialize it with give conditions and Attrs
  2. db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user)
  3. // SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1;
  4. // user -> User{Name: "non_existing", Age: 20}
  5. // User not found, initialize it with give conditions and Attrs
  6. db.Where(User{Name: "non_existing"}).Attrs("age", 20).FirstOrInit(&user)
  7. // SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1;
  8. // user -> User{Name: "non_existing", Age: 20}
  9. // Found user with `name` = `jinzhu`, attributes will be ignored
  10. db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 20}).FirstOrInit(&user)
  11. // SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1;
  12. // user -> User{ID: 111, Name: "Jinzhu", Age: 18}

Assign attributes to struct regardless it is found or not, those attributes won’t be used to build SQL query and the final data won’t be saved into database

  1. // User not found, initialize it with give conditions and Assign attributes
  2. db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user)
  3. // user -> User{Name: "non_existing", Age: 20}
  4. // Found user with `name` = `jinzhu`, update it with Assign attributes
  5. db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 20}).FirstOrInit(&user)
  6. // SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1;
  7. // user -> User{ID: 111, Name: "Jinzhu", Age: 20}

FirstOrCreate

Get first matched record or create a new one with given conditions (only works with struct, map conditions), RowsAffected returns created/updated record’s count

  1. // User not found, create a new record with give conditions
  2. result := db.FirstOrCreate(&user, User{Name: "non_existing"})
  3. // INSERT INTO "users" (name) VALUES ("non_existing");
  4. // user -> User{ID: 112, Name: "non_existing"}
  5. // result.RowsAffected // => 1
  6. // Found user with `name` = `jinzhu`
  7. result := db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user)
  8. // user -> User{ID: 111, Name: "jinzhu", "Age": 18}
  9. // result.RowsAffected // => 0

Create struct with more attributes if record not found, those Attrs won’t be used to build SQL query

  1. // User not found, create it with give conditions and Attrs
  2. db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
  3. // SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1;
  4. // INSERT INTO "users" (name, age) VALUES ("non_existing", 20);
  5. // user -> User{ID: 112, Name: "non_existing", Age: 20}
  6. // Found user with `name` = `jinzhu`, attributes will be ignored
  7. db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
  8. // SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;
  9. // user -> User{ID: 111, Name: "jinzhu", Age: 18}

Assign attributes to the record regardless it is found or not and save them back to the database.

  1. // User not found, initialize it with give conditions and Assign attributes
  2. db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user)
  3. // SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1;
  4. // INSERT INTO "users" (name, age) VALUES ("non_existing", 20);
  5. // user -> User{ID: 112, Name: "non_existing", Age: 20}
  6. // Found user with `name` = `jinzhu`, update it with Assign attributes
  7. db.Where(User{Name: "jinzhu"}).Assign(User{Age: 20}).FirstOrCreate(&user)
  8. // SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;
  9. // UPDATE users SET age=20 WHERE id = 111;
  10. // user -> User{ID: 111, Name: "jinzhu", Age: 20}

Optimizer/Index Hints

Optimizer hints allow to control the query optimizer to choose a certain query execution plan, GORM supports it with gorm.io/hints, e.g:

  1. import "gorm.io/hints"
  2. db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{})
  3. // SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users`

Index hints allow passing index hints to the database in case the query planner gets confused.

  1. import "gorm.io/hints"
  2. db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{})
  3. // SELECT * FROM `users` USE INDEX (`idx_user_name`)
  4. db.Clauses(hints.ForceIndex("idx_user_name", "idx_user_id").ForJoin()).Find(&User{})
  5. // SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)"

Refer Optimizer Hints/Index/Comment for more details

Iteration

GORM supports iterating through Rows

  1. rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows()
  2. defer rows.Close()
  3. for rows.Next() {
  4. var user User
  5. // ScanRows is a method of `gorm.DB`, it can be used to scan a row into a struct
  6. db.ScanRows(rows, &user)
  7. // do something
  8. }

FindInBatches

Query and process records in batch

  1. // batch size 100
  2. result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error {
  3. for _, result := range results {
  4. // batch processing found records
  5. }
  6. tx.Save(&results)
  7. tx.RowsAffected // number of records in this batch
  8. batch // Batch 1, 2, 3
  9. // returns error will stop future batches
  10. return nil
  11. })
  12. result.Error // returned error
  13. result.RowsAffected // processed records count in all batches

Query Hooks

GORM allows hooks AfterFind for a query, it will be called when querying a record, refer Hooks for details

  1. func (u *User) AfterFind(tx *gorm.DB) (err error) {
  2. if u.Role == "" {
  3. u.Role = "user"
  4. }
  5. return
  6. }

Pluck

Query single column from database and scan into a slice, if you want to query multiple columns, use Select with Scan instead

  1. var ages []int64
  2. db.Model(&users).Pluck("age", &ages)
  3. var names []string
  4. db.Model(&User{}).Pluck("name", &names)
  5. db.Table("deleted_users").Pluck("name", &names)
  6. // Distinct Pluck
  7. db.Model(&User{}).Distinct().Pluck("Name", &names)
  8. // SELECT DISTINCT `name` FROM `users`
  9. // Requesting more than one column, use `Scan` or `Find` like this:
  10. db.Select("name", "age").Scan(&users)
  11. db.Select("name", "age").Find(&users)

Scopes

Scopes allows you to specify commonly-used queries which can be referenced as method calls

  1. func AmountGreaterThan1000(db *gorm.DB) *gorm.DB {
  2. return db.Where("amount > ?", 1000)
  3. }
  4. func PaidWithCreditCard(db *gorm.DB) *gorm.DB {
  5. return db.Where("pay_mode_sign = ?", "C")
  6. }
  7. func PaidWithCod(db *gorm.DB) *gorm.DB {
  8. return db.Where("pay_mode_sign = ?", "C")
  9. }
  10. func OrderStatus(status []string) func (db *gorm.DB) *gorm.DB {
  11. return func (db *gorm.DB) *gorm.DB {
  12. return db.Where("status IN (?)", status)
  13. }
  14. }
  15. db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders)
  16. // Find all credit card orders and amount greater than 1000
  17. db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders)
  18. // Find all COD orders and amount greater than 1000
  19. db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders)
  20. // Find all paid, shipped orders that amount greater than 1000

Checkout Scopes for details

Count

Get matched records count

  1. var count int64
  2. db.Model(&User{}).Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Count(&count)
  3. // SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2'
  4. db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count)
  5. // SELECT count(1) FROM users WHERE name = 'jinzhu'; (count)
  6. db.Table("deleted_users").Count(&count)
  7. // SELECT count(1) FROM deleted_users;
  8. // Count with Distinct
  9. db.Model(&User{}).Distinct("name").Count(&count)
  10. // SELECT COUNT(DISTINCT(`name`)) FROM `users`
  11. db.Table("deleted_users").Select("count(distinct(name))").Count(&count)
  12. // SELECT count(distinct(name)) FROM deleted_users
  13. // Count with Group
  14. users := []User{
  15. {Name: "name1"},
  16. {Name: "name2"},
  17. {Name: "name3"},
  18. {Name: "name3"},
  19. }
  20. db.Model(&User{}).Group("name").Count(&count)
  21. count // => 3