GORM provides a chainable API for building complex SQL queries. Most query methods return *DB, allowing you to chain multiple methods together.
Chainable methods build up the query but don't execute it. Append a finisher method to execute the query.
Specify the table/model for the query.
func (db *DB) Model(value interface{}) *DBUsage:
// Use model type to determine table
db.Model(&User{}).Where("age > ?", 18).Find(&users)
// Use model value
user := User{ID: 10}
db.Model(&user).Update("Age", 26)Explicitly specify the table name.
func (db *DB) Table(name string, args ...interface{}) *DBUsage:
// Simple table name
db.Table("users").Where("age > ?", 18).Find(&users)
// Dynamic table name with arguments
db.Table("users_?", "2023").Find(&users)
// Use for raw queries
var result []map[string]interface{}
db.Table("users").Select("name, age").Where("age > ?", 18).Find(&result)Add WHERE conditions to filter query results.
func (db *DB) Where(query interface{}, args ...interface{}) *DBUsage:
// String conditions with placeholders
db.Where("name = ?", "Alice").Find(&users)
db.Where("name = ? AND age >= ?", "Alice", 18).Find(&users)
// Struct conditions (only non-zero fields)
db.Where(&User{Name: "Alice", Age: 18}).Find(&users)
// Map conditions
db.Where(map[string]interface{}{
"name": "Alice",
"age": 0, // Will be included in query
}).Find(&users)
// Slice of primary keys
db.Where([]int64{1, 2, 3}).Find(&users)
// Multiple WHERE calls are combined with AND
db.Where("name = ?", "Alice").Where("age > ?", 18).Find(&users)Specify conditions directly in query methods.
// In First, Take, Last, Find
db.First(&user, "name = ?", "Alice")
db.Find(&users, "age > ?", 18)
// Primary key
db.First(&user, 10) // WHERE id = 10
db.Find(&users, []int{1, 2, 3}) // WHERE id IN (1, 2, 3)Add NOT conditions.
func (db *DB) Not(query interface{}, args ...interface{}) *DBUsage:
db.Not("name = ?", "Alice").Find(&users)
db.Not(User{Name: "Alice"}).Find(&users)
db.Not(map[string]interface{}{"name": []string{"Alice", "Bob"}}).Find(&users)
// NOT IN
db.Not("name IN ?", []string{"Alice", "Bob"}).Find(&users)Add OR conditions.
func (db *DB) Or(query interface{}, args ...interface{}) *DBUsage:
db.Where("name = ?", "Alice").Or("name = ?", "Bob").Find(&users)
db.Where("name = ?", "Alice").Or(User{Name: "Bob"}).Find(&users)
// Complex OR groups
db.Where("age > ?", 18).Or(
db.Where("country = ?", "US").Where("state = ?", "CA"),
).Find(&users)Specify which fields to retrieve.
func (db *DB) Select(query interface{}, args ...interface{}) *DBUsage:
// Select specific fields
db.Select("name", "age").Find(&users)
db.Select("name, age").Find(&users)
// Select with expressions
db.Select("name, age, age * 2 as double_age").Find(&users)
// Select with aggregate functions
db.Model(&User{}).Select("COUNT(*)").Row().Scan(&count)
db.Model(&User{}).Select("COALESCE(age, 0)").Find(&users)Omit specific fields from the query.
func (db *DB) Omit(columns ...string) *DBUsage:
// Omit password field
db.Omit("password").Find(&users)
// Omit multiple fields
db.Omit("password", "secret_token").Find(&users)
// Useful in updates
db.Model(&user).Omit("UpdatedAt").Updates(map[string]interface{}{
"Name": "Alice",
})Specify the order of query results.
func (db *DB) Order(value interface{}) *DBUsage:
// Simple ordering
db.Order("age DESC").Find(&users)
db.Order("age").Find(&users) // ASC by default
// Multiple order columns
db.Order("age DESC, name").Find(&users)
// Order by expression
db.Order("FIELD(id, 3, 1, 2)").Find(&users)
// Multiple Order calls
db.Order("age DESC").Order("name ASC").Find(&users)Limit the number of records returned.
func (db *DB) Limit(limit int) *DBUsage:
// Get first 10 records
db.Limit(10).Find(&users)
// Cancel limit with -1
db.Limit(10).Find(&users1)
db.Limit(-1).Find(&users2) // Get all recordsSkip a specified number of records.
func (db *DB) Offset(offset int) *DBUsage:
// Skip first 10 records
db.Offset(10).Find(&users)
// Pagination
page := 3
pageSize := 20
db.Offset((page - 1) * pageSize).Limit(pageSize).Find(&users)
// Cancel offset with -1
db.Offset(10).Find(&users1)
db.Offset(-1).Find(&users2)Select distinct values.
func (db *DB) Distinct(args ...interface{}) *DBUsage:
// Distinct all columns
db.Distinct("name").Find(&users)
// Count distinct
var count int64
db.Model(&User{}).Distinct("name").Count(&count)
// Distinct with multiple columns
db.Distinct("name", "age").Find(&users)Group query results.
func (db *DB) Group(name string) *DBUsage:
type Result struct {
Name string
Total int
}
var results []Result
db.Model(&User{}).Select("name, sum(age) as total").Group("name").Find(&results)
// Group by multiple columns
db.Model(&User{}).Select("name, age, count(*) as total").Group("name").Group("age").Find(&results)Filter grouped results.
func (db *DB) Having(query interface{}, args ...interface{}) *DBUsage:
type Result struct {
Name string
Total int
}
var results []Result
db.Model(&User{}).Select("name, sum(age) as total").
Group("name").
Having("sum(age) > ?", 100).
Find(&results)
// Multiple HAVING conditions
db.Model(&User{}).
Select("name, count(*) as count, avg(age) as avg_age").
Group("name").
Having("count(*) > ?", 5).
Having("avg(age) > ?", 18).
Find(&results)Add JOIN clauses to the query.
// Add JOIN clause
func (db *DB) Joins(query string, args ...interface{}) *DB
// Add INNER JOIN clause
func (db *DB) InnerJoins(query string, args ...interface{}) *DBUsage:
type Result struct {
UserName string
OrderID int
}
var results []Result
// Simple join
db.Model(&User{}).Select("users.name, orders.id").
Joins("left join orders on orders.user_id = users.id").
Find(&results)
// Join with conditions
db.Model(&User{}).
Joins("JOIN orders ON orders.user_id = users.id AND orders.status = ?", "completed").
Find(&users)
// Multiple joins
db.Model(&User{}).
Joins("JOIN orders ON orders.user_id = users.id").
Joins("JOIN items ON items.order_id = orders.id").
Find(&users)
// Preload with join (to avoid N+1)
db.Joins("Company").Find(&users) // Preload Company with INNER JOINPreload associations to avoid N+1 queries.
func (db *DB) Preload(query string, args ...interface{}) *DBUsage:
// Preload single association
db.Preload("Orders").Find(&users)
// Preload with conditions
db.Preload("Orders", "status = ?", "completed").Find(&users)
// Preload nested associations
db.Preload("Orders.Items").Find(&users)
db.Preload("Orders.Items.Product").Find(&users)
// Multiple preloads
db.Preload("Orders").Preload("Profile").Find(&users)
// Preload all associations
db.Preload(clause.Associations).Find(&users)
// Custom preload query
db.Preload("Orders", func(db *gorm.DB) *gorm.DB {
return db.Order("orders.created_at DESC").Limit(10)
}).Find(&users)Apply reusable query logic.
func (db *DB) Scopes(funcs ...func(*DB) *DB) *DBUsage:
// Define scopes
func ActiveUsers(db *gorm.DB) *gorm.DB {
return db.Where("active = ?", true)
}
func RecentUsers(db *gorm.DB) *gorm.DB {
return db.Where("created_at > ?", time.Now().AddDate(0, 0, -7))
}
func Paginate(page, pageSize int) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
offset := (page - 1) * pageSize
return db.Offset(offset).Limit(pageSize)
}
}
// Apply scopes
db.Scopes(ActiveUsers, RecentUsers).Find(&users)
// Apply parameterized scope
db.Scopes(Paginate(2, 20)).Find(&users)
// Chain with other methods
db.Scopes(ActiveUsers).Where("age > ?", 18).Find(&users)Execute raw SQL queries.
func (db *DB) Raw(sql string, values ...interface{}) *DBUsage:
// Raw query into struct
var users []User
db.Raw("SELECT * FROM users WHERE age > ?", 18).Scan(&users)
// Raw query into custom type
type Result struct {
Name string
Total int
}
var results []Result
db.Raw("SELECT name, COUNT(*) as total FROM users GROUP BY name").Scan(&results)
// Raw query with named parameters
db.Raw("SELECT * FROM users WHERE name = @name AND age > @age",
sql.Named("name", "Alice"),
sql.Named("age", 18)).Scan(&users)Use SQL expressions in queries.
import "gorm.io/gorm/clause"
// In WHERE
db.Where("age > ?", 18).Or(
clause.Expr("name LIKE ?", "%test%"),
).Find(&users)
// In SELECT
db.Select(clause.Expr("COALESCE(name, ?)", "unknown")).Find(&users)
// In UPDATE
db.Model(&User{}).
Where("id = ?", 1).
Update("balance", clause.Expr("balance + ?", 100))Add custom SQL clauses.
import "gorm.io/gorm/clause"
func (db *DB) Clauses(conds ...clause.Expression) *DBUsage:
import "gorm.io/gorm/clause"
// Add ON CONFLICT clause for upsert
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "id"}},
DoUpdates: clause.AssignmentColumns([]string{"name", "age"}),
}).Create(&users)
// Add locking clause
db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users)
// Add specific hint
db.Clauses(clause.Expr{SQL: "USE INDEX (idx_name)"}).Find(&users)Scan query results into variables.
func (db *DB) Scan(dest interface{}) *DBUsage:
// Scan into struct
type Result struct {
Name string
Age int
}
var result Result
db.Table("users").Select("name, age").Where("id = ?", 1).Scan(&result)
// Scan into slice of structs
var results []Result
db.Table("users").Select("name, age").Scan(&results)
// Scan into map
var result map[string]interface{}
db.Table("users").Select("name, age").Where("id = ?", 1).Scan(&result)
// Scan single row
var name string
var age int
db.Table("users").Where("id = ?", 1).Row().Scan(&name, &age)Convert query to SQL string without executing.
func (db *DB) ToSQL(queryFn func(tx *DB) *DB) stringUsage:
sql := db.ToSQL(func(tx *gorm.DB) *gorm.DB {
return tx.Model(&User{}).Where("age > ?", 18).
Order("age DESC").
Limit(10).
Find(&[]User{})
})
fmt.Println(sql) // Prints the generated SQLGet raw sql.Row or sql.Rows from query.
// Get sql.Row from query
func (db *DB) Row() *sql.Row
// Get sql.Rows from query
func (db *DB) Rows() (*sql.Rows, error)
// Scan sql.Rows into struct
func (db *DB) ScanRows(rows *sql.Rows, dest interface{}) errorUsage:
// Single row
var name string
var age int
row := db.Table("users").Select("name, age").Where("id = ?", 1).Row()
row.Scan(&name, &age)
// Multiple rows
rows, err := db.Model(&User{}).Where("age > ?", 18).Rows()
defer rows.Close()
for rows.Next() {
var user User
db.ScanRows(rows, &user)
// Process user
}Process records in batches with a callback.
func (db *DB) FindInBatches(dest interface{}, batchSize int, fc func(tx *DB, batch int) error) *DBUsage:
var users []User
result := db.Where("age > ?", 18).FindInBatches(&users, 100, func(tx *gorm.DB, batch int) error {
// Process users in this batch
for _, user := range users {
// Process user
}
// Return error to stop batching
return nil
})
// Check for errors
if result.Error != nil {
// Handle error
}
// Get total rows processed
fmt.Println(result.RowsAffected)Use subqueries in your queries.
// Subquery in WHERE
db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders)
// Subquery in FROM
db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).
Where("age > ?", 18).
Find(&users)
// Subquery in SELECT
db.Model(&User{}).
Select("users.*, (?) as order_count",
db.Model(&Order{}).Select("count(*)").Where("orders.user_id = users.id"),
).
Find(&users)Use named arguments in queries.
import "database/sql"
// Named arguments in raw query
db.Raw("SELECT * FROM users WHERE name = @name AND age > @age",
sql.Named("name", "Alice"),
sql.Named("age", 18),
).Scan(&users)
// Named arguments in where
db.Where("name = @name AND age > @age",
sql.Named("name", "Alice"),
sql.Named("age", 18),
).Find(&users)Use a specific connection from the pool for the query.
func (db *DB) Connection(fc func(tx *DB) error) errorUsage:
err := db.Connection(func(tx *gorm.DB) error {
// All operations in this function use the same connection
tx.First(&user)
tx.Create(&order)
return nil
})