GORM provides three methods for fetching a single row. All three set ErrRecordNotFound on the returned *DB if no match exists.
First — ordered by primary key ascending
var user User// SELECT * FROM users ORDER BY id LIMIT 1;result := db.First(&user)fmt.Println(result.RowsAffected) // 1fmt.Println(result.Error) // gorm.ErrRecordNotFound if not found
Pass inline conditions as the second argument:
// SELECT * FROM users WHERE id = 10 ORDER BY id LIMIT 1;db.First(&user, 10)// SELECT * FROM users WHERE name = 'Jinzhu' ORDER BY id LIMIT 1;db.First(&user, "name = ?", "Jinzhu")
Take — no ordering guarantee
var user User// SELECT * FROM users LIMIT 1;db.Take(&user)// With condition:db.Take(&user, "name = ?", "Jinzhu")
Take omits the ORDER BY clause. Use it when you don’t need a deterministic row and want to avoid the overhead of sorting.
Last — ordered by primary key descending
var user User// SELECT * FROM users ORDER BY id DESC LIMIT 1;db.Last(&user)// With condition:db.Last(&user, "name = ?", "Jinzhu")
First, Take, and Last return gorm.ErrRecordNotFound when no row matches. Use db.Find if you want an empty result without an error.
var user Uservar users []User// Exact match// SELECT * FROM users WHERE name = 'Jinzhu' ORDER BY id LIMIT 1;db.Where("name = ?", "Jinzhu").First(&user)// Not equaldb.Where("name <> ?", "Jinzhu").Find(&users)// INdb.Where("name IN ?", []string{"Jinzhu", "Jackson"}).Find(&users)// LIKEdb.Where("name LIKE ?", "%jin%").Find(&users)// ANDdb.Where("name = ? AND age >= ?", "Jinzhu", 18).First(&user)// Time comparisondb.Where("updated_at > ?", lastWeek).Find(&users)// BETWEENdb.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)
Non-zero fields are used as equality conditions. Zero-value fields (empty string, 0, false) are ignored.
// SELECT * FROM users WHERE name = 'Jinzhu' AND age = 20 ORDER BY id LIMIT 1;db.Where(&User{Name: "Jinzhu", Age: 20}).First(&user)// Age is zero — only Name is used as a conditiondb.Where(&User{Name: "Jinzhu", Age: 0}).Find(&users)// SELECT * FROM users WHERE name = 'Jinzhu';
// All map keys are used, including zero valuesdb.Where(map[string]interface{}{"name": "Jinzhu", "age": 20}).Find(&users)// SELECT * FROM users WHERE name = 'Jinzhu' AND age = 20;// Zero values in maps are respecteddb.Where(map[string]interface{}{"age": 0}).Find(&users)// SELECT * FROM users WHERE age = 0;
// SELECT * FROM users WHERE NOT name = 'Jinzhu' ORDER BY id LIMIT 1;db.Not("name = ?", "Jinzhu").First(&user)// NOT INdb.Not(map[string]interface{}{"name": []string{"Jinzhu", "Jackson"}}).Find(&users)// Struct: NOT (name = 'Jinzhu' AND age = 18)db.Not(User{Name: "Jinzhu", Age: 18}).First(&user)// NOT IN by primary keydb.Not([]int{1, 2, 3}).First(&user)
// SELECT * FROM users ORDER BY age DESC;db.Order("age DESC").Find(&users)// Multiple columnsdb.Order("age DESC").Order("name").Find(&users)// SELECT * FROM users ORDER BY age DESC, name;// Using clause.OrderByColumn for programmatic constructiondb.Order(clause.OrderByColumn{ Column: clause.Column{Name: "age"}, Desc: true,}).Find(&users)
// SELECT * FROM users LIMIT 3;db.Limit(3).Find(&users)// SELECT * FROM users LIMIT 10 OFFSET 5;db.Limit(10).Offset(5).Find(&users)// Cancel a limit set earlier in the chaindb.Limit(3).Find(&users1).Limit(-1).Find(&users2)// users1 → 3 rows; users2 → all rows
type AgeResult struct { Name string Total int}var results []AgeResult// SELECT name, SUM(age) AS total FROM users GROUP BY name;db.Model(&User{}). Select("name, sum(age) as total"). Group("name"). Find(&results)// With HAVING// SELECT name, SUM(age) AS total FROM users GROUP BY name HAVING name = 'Jinzhu';db.Model(&User{}). Select("name, sum(age) as total"). Group("name"). Having("name = ?", "Jinzhu"). Find(&results)
// LEFT JOIN accounts ON accounts.user_id = users.iddb.Joins("Account").Find(&users)// With additional conditions on the joined associationdb.Joins("Account", db.Select("id"). Where("user_id = users.id AND name = ?", "someName"). Model(&Account{}),).Find(&users)
var count int64// COUNT(*) across all usersdb.Model(&User{}).Count(&count)// COUNT with a conditiondb.Model(&User{}).Where("name = ?", "Jinzhu").Count(&count)// COUNT on a specific columndb.Model(&User{}).Select("name").Count(&count)// COUNT DISTINCTdb.Model(&User{}).Distinct("name").Count(&count)
Pluck queries a single column and returns the values as a slice:
var names []stringdb.Model(&User{}).Pluck("name", &names)// SELECT name FROM users;var ages []int64db.Model(&User{}).Where("name = ?", "Jinzhu").Pluck("age", &ages)// Distinct Pluckvar distinctNames []stringdb.Model(&User{}).Distinct().Pluck("name", &distinctNames)// SELECT DISTINCT name FROM users;// Pluck multiple columns using Scantype Result struct { Name string Age int}var results []Resultdb.Model(&User{}).Select("name, age").Scan(&results)
Use FindInBatches to process large result sets in chunks without loading all rows into memory at once. GORM uses keyset pagination internally (ordering by primary key) to avoid offset-based slowdowns.
var users []Userdb.Where("age > ?", 18).FindInBatches(&users, 100, func(tx *gorm.DB, batch int) error { for _, u := range users { // process each record fmt.Println(u.Name) } // tx.RowsAffected holds the count for this batch // batch is the current batch number (1-indexed) // Return an error to stop processing early return nil})
FindInBatches requires a primary key on the model. It returns gorm.ErrPrimaryKeyRequired if none is found.
Scan works like Find but maps columns to any struct, not just GORM models. It’s useful for custom projections, aggregations, and joins that don’t map to a model.
type Stats struct { Name string Total int64}var stats Statsdb.Model(&User{}). Select("name, count(*) as total"). Where("name = ?", "Jinzhu"). Group("name"). Scan(&stats)// Scan a slicevar allStats []Statsdb.Model(&User{}). Select("name, count(*) as total"). Group("name"). Scan(&allStats)