Query Builder
Introduction
The database query builder provides a convenient interface to create and execute database queries. It can be used to perform most database operations in your application and works with all supported database systems.
Query builder uses parameter binding to protect your application from SQL injection. You don't need to clean or escape strings passed to the query builder.
Running Queries
Framework provides various query methods, you can query, create, update and delete data in the database. Note that when you want to bind data to struct
or model, you need to add the db
tag to the field:
type User struct {
ID string `db:"id"`
Name string `db:"name"`
}
type User struct {
orm.BaseModel
orm.NullableSoftDeletes
Name string `db:"name"`
}
Retrieving All Rows
You can use the facades.DB()
provided table
method to start a query. The table
method returns a chainable query builder instance for the specified table, allowing you to chain more constraints, and finally use the Get
method to retrieve the query results:
var users []User
err := facades.DB().Table("users").Get(&users)
Retrieving a Single Row or Column
If you only need to retrieve a single row of data from the database table, you can use the First
method.
var user User
err := facades.DB().Table("users").Where("id", 1).First(&user)
You can use the Value
method to retrieve the value of a single column:
var name string
err := facades.DB().Table("users").Where("id", 1).Value("name", &name)
You can use the Find
method to retrieve a single row of data by passing the id
:
var user User
err := facades.DB().Table("users").Find(&user, 1)
// You can also pass a collection of `id` to retrieve multiple rows of data
var users []User
err := facades.DB().Table("users").Find(&users, []int{1, 2, 3})
// Find defaults the table primary key to `id`, if the table primary key is not `id`, you can pass the `id` field name
var user User
err := facades.DB().Table("users").Find(&users, "uuid", "1")
You can use the FindOrFail
or FirstOrFail
method, if the record is not found, it will throw a sql.ErrNoRows
error:
var user User
err := facades.DB().Table("users").FindOrFail(&user, 1)
You can use the FindOr
or FirstOr
method, if the record is not found, it will execute the closure function:
var user *User
user, err = facades.DB().Table("users").Where("name", "John").FirstOr(&user, func() error {
return errors.New("no rows")
})
Retrieving a Single Column Value
If you want to retrieve a list of records containing a single column value, you can use the Pluck
method:
var emails []string
err := facades.DB().Table("users").Pluck("email", &emails)
Traversing Results
You can use the Each
method to traverse all results:
var products []Product
err := facades.DB().Table("products").Each(func(rows []db.Row) error {
for _, row := range rows {
var product Product
err := row.Scan(&product)
s.NoError(err)
products = append(products, product)
}
return nil
})
Chunking Results
If you need to process thousands of database records, consider using the Chunk
method. This method retrieves a small chunk of results at a time and passes each chunk to a closure function for processing:
var products []Product
err := facades.DB().Table("products").Chunk(2, func(rows []db.Row) error {
for _, row := range rows {
var product Product
err := row.Scan(&product)
s.NoError(err)
products = append(products, product)
}
return nil
})
Note: When modifying records in the Chunk callback, it may result in records not being included in the chunked results.
Cursor
A cursor can be used to process large amounts of data without loading all data into memory at once. It processes data one by one instead of loading all data at once.
rows, err := facades.DB().Table("products").Cursor()
var products []Product
for row := range rows {
var product Product
err = row.Scan(&product)
s.NoError(err)
s.True(product.ID > 0)
products = append(products, product)
}
Aggregates
The query builder provides aggregate methods: Count
Sum
.
count, err := facades.DB().Table("users").Count()
sum, err := facades.DB().Table("users").Sum("age")
Checking if a Record Exists
You can use the Exists
and DoesntExist
methods to determine if the result of a query condition exists:
exists, err := facades.DB().Table("users").Where("votes", ">", 100).Exists()
exists, err := facades.DB().Table("users").Where("votes", ">", 100).DoesntExist()
Pagination
You can use the Paginate
method to paginate the query results:
var (
users []User
total int64
)
err := facades.DB().Table("users").Where("name", "John").Paginate(1, 10, &users, &total)
Select
You may not always want to retrieve all columns from a database table. Use the Select
method to customize a "select" query statement to query the specified fields:
var users []User
err := facades.DB().Table("users").Select("name", "email as user_email").Get(&users)
The Distinct
method will force the query to return unique results:
var users []User
err := facades.DB().Table("users").Distinct().Select("name").Get(&users)
Raw Expressions
Sometimes you may need to use raw expressions in your queries. You can use the db.Raw
method to create a raw expression:
import "github.com/goravel/framework/database/db"
facades.DB().Model(&user).Update("age", db.Raw("age - ?", 1))
Select
Specifying a Select Clause
Of course, you may not always want to retrieve all columns from a database table. Use the Select
method to specify a custom select clause for your query:
// Select specific fields
facades.DB().Select("name", "age").Get(&users)
// Use a subquery
facades.DB().Select("name", db.Raw("(SELECT COUNT(*) FROM posts WHERE users.id = posts.user_id) as post_count")).Get(&users)
Distinct
The Distinct
method will force the query to return unique results:
var users []models.User
facades.DB().Distinct("name").Find(&users)
Raw Methods
WhereRaw / OrWhereRaw
The WhereRaw
and OrWhereRaw
methods can be used to inject raw "where" clauses into your query. These methods accept an optional binding array as their second parameter:
var users []User
err := facades.DB().WhereRaw("age = ? or age = ?", []any{25, 30}).Get(&users)
err := facades.DB().OrWhereRaw("age = ? or age = ?", []any{25, 30}).Get(&users)
OrderByRaw
The OrderByRaw
method can be used to set a raw string as the value of the "order by" clause:
var users []User
err := facades.DB().OrderByRaw("age DESC, id ASC").Get(&users)
Joins
Inner Join
The query builder can be used to write join statements. To execute a basic SQL "inner join", you can use the Join
method on the query builder instance:
var users []User
err := facades.DB().Table("users").Join("posts as p ON users.id = p.user_id AND p.id = ?", 1).Where("age", 25).Get(&users)
Left Join / Right Join
If you want to execute a "left join" or "right join", you can use the LeftJoin
or RightJoin
methods:
var users []User
err := facades.DB().Table("users").LeftJoin("posts as p ON users.id = p.user_id AND p.id = ?", 1).Where("age", 25).Get(&users)
err = facades.DB().Table("users").RightJoin("posts as p ON users.id = p.user_id AND p.id = ?", 1).Where("age", 25).Get(&users)
Cross Join
The CrossJoin
method can be used to execute a "cross join":
var users []User
err := facades.DB().Table("users").CrossJoin("posts as p ON users.id = p.user_id AND p.id = ?", 1).Where("age", 25).Get(&users)
Basic Where Clauses
Where / OrWhere
You can use the Where
method on the query builder instance to add where clauses to the query.
import "github.com/goravel/framework/contracts/database/db"
var users []User
err := facades.DB().Where("votes", 100).Get(&users)
err := facades.DB().Where("votes >= ?", 100).Get(&users)
err := facades.DB().Where("votes LIKE ?", "%goravel%").Get(&users)
err := facades.DB().Where("votes", []int{1, 2, 3}).Get(&users)
err := facades.DB().Where(func(query db.Query) db.Query {
return query.Where("age", []int{25, 30}).Where("name", "Tom")
}).OrWhere("name", "John").Get(&users)
WhereNot / OrWhereNot
The WhereNot
and OrWhereNot
methods can be used to negate a given set of query conditions.
import "github.com/goravel/framework/contracts/database/db"
var users []User
err := facades.DB().WhereNot("votes", 100).Get(&users)
err := facades.DB().WhereNot("votes >= ?", 100).Get(&users)
err := facades.DB().WhereNot("votes LIKE ?", "%goravel%").Get(&users)
err := facades.DB().WhereNot("votes", []int{1, 2, 3}).Get(&users)
err := facades.DB().WhereNot(func(query db.Query) db.Query {
return query.Where("age", []int{25, 30}).Where("name", "Tom")
}).OrWhereNot("name", "John").Get(&users)
WhereExists / WhereNotExists
The WhereExists
method allows you to write exists SQL clauses:
var users []User
err := facades.DB().Table("users").Where("name", "John").WhereExists(func() db.Query {
return facades.DB().Table("posts").WhereColumn("posts.user_id", "users.id")
}).Get(&users)
Other Where Clauses
WhereBetween / OrWhereBetween
The WhereBetween
method verifies that a field value is between two given values:
facades.DB().Table("users").WhereBetween("votes", 1, 100)
WhereNotBetween / OrWhereNotBetween
The WhereNotBetween
method verifies that a field value is not between two given values:
facades.DB().Table("users").WhereNotBetween("votes", 1, 100)
WhereIn / WhereNotIn / OrWhereIn / OrWhereNotIn
The WhereIn
method verifies that a field value must exist in the specified array:
facades.DB().Table("users").WhereIn("id", []any{1, 2, 3})
WhereNull / WhereNotNull / OrWhereNull / OrWhereNotNull
The WhereNull
method verifies that a specified field must be NULL
:
facades.DB().Table("users").WhereNull("updated_at")
WhereLike / WhereNotLike / OrWhereLike / OrWhereNotLike
The WhereLike
method verifies that a field value contains a given value:
facades.DB().Table("users").WhereLike("name", "%goravel%")
WhereColumn / OrWhereColumn
The WhereColumn
method verifies that two fields are equal:
facades.DB().Table("users").WhereColumn("first_name", "last_name")
Logical Grouping
Sometimes you may need to group several "where" clauses within parentheses to achieve the logical grouping required by your query.
facades.DB().Table("users").Where("age", 25).Where(func(query db.Query) db.Query {
return query.Where("votes", 100).OrWhere("votes", 200)
})
Ordering, Grouping, Limit & Offset
Ordering
OrderBy / OrderByDesc
facades.DB().OrderBy("name")
facades.DB().OrderByDesc("name")
Latest
The Latest
method makes it easy to sort results by date. By default, results will be sorted by the created_at
column:
facades.DB().Table("users").Latest().First(&user)
facades.DB().Table("users").Latest("updated_at").First(&user)
InRandomOrder
The InRandomOrder
method is used to sort results randomly:
facades.DB().Table("users").InRandomOrder().First(&user)
Grouping
The GroupBy
and Having
methods can be used to group results:
err := facades.DB().Table("users").Where("age", 25).GroupBy("name").Having("name = ?", "John").OrderBy("name").Get(&users)
Limiting and Offset
You can use the Limit
and Offset
methods to limit the number of results, or skip a specified number of results in the query:
err := facades.DB().Table("users").Offset(10).Limit(5).Get(&users)
Conditional Clauses
Sometimes you may want a clause to only execute when a given condition is true. For example, you may only want to apply a where clause when a given value exists in the request. You can accomplish this by using the When
method:
import "github.com/goravel/framework/contracts/database/db"
facades.DB().Table("users").When(1 == 1, func(query db.Query) db.Query {
return query.Where("age", 25)
}).First(&user)
You can also pass another closure as the third parameter to the When
method. This closure will execute if the first parameter results in false:
facades.DB().Table("users").When(1 != 1, func(query db.Query) db.Query {
return query.OrderBy("name")
}, func(query db.Query) db.Query {
return query.OrderBy("id")
}).First(&user)
Insert
The query builder provides the Insert
method for inserting records into the database:
// Insert by struct
result, err := facades.DB().Table("products").Insert(Product{
Name: "goravel",
})
// Insert by slice struct
result, err := facades.DB().Table("products").Insert([]Product{
{
Name: "goravel",
},
{
Name: "go",
},
})
// Insert by map
result, err := facades.DB().Table("products").Insert(map[string]any{
"name": "goravel",
"created_at": time.Now(),
"updated_at": time.Now(),
})
// Insert by slice map
result, err := facades.DB().Table("products").Insert([]map[string]any{
{
"name": "goravel",
"created_at": time.Now(),
"updated_at": time.Now(),
},
{
"name": "go",
"created_at": time.Now(),
"updated_at": time.Now(),
},
})
Auto Increment ID
If the table's primary key is auto increment, you can use the LastInsertID
method to get the auto increment ID, only supported for mysql
and sqlite
databases:
id, err := facades.DB().Table("products").InsertGetID(Product{
Name: "goravel",
})
Update
The query builder provides the Update
method for updating existing records in the database:
// Update by field name
result, err := facades.DB().Table("products").Where("id", 1).Update("phone", "1234567890")
// Update by struct
result, err := facades.DB().Table("products").Where("id", 1).Update(Product{
Name: "goravel",
})
// Update by map
result, err := facades.DB().Table("products").Where("id", 1).Update(map[string]any{
"name": "goravel",
"created_at": time.Now(),
"updated_at": time.Now(),
})
Update JSON fields
facades.DB().Table("users").Where("id", 1).Update("options->enabled", true)
facades.DB().Table("users").Where("id", 1).Update("options->languages[0]", "en")
facades.DB().Table("users").Where("id", 1).Update("options->languages", []string{"en", "de"})
facades.DB().Table("users").Where("id", 1).Update(map[string]any{
"preferences->dining->meal": "salad",
"options->languages[0]": "en",
"options->enabled": true,
})
Update or Insert
Sometimes you may want to update a record in the database, but if the specified record does not exist, create it. This can be done using the UpdateOrInsert
method. The UpdateOrInsert
method accepts two parameters: a condition for finding the record, and a key-value pair containing the values to update the record with.
The UpdateOrInsert
method will attempt to locate a matching database record using the column names and values from the first parameter. If a record exists, its values will be updated using the second parameter. If no matching record is found, a record will be created and its values will be merged from the two parameters:
// use struct
result, err := facades.DB().Table("users").Where("id", 1).UpdateOrInsert(TestUser{
Email: "john@example.com",
}, TestUser{
Phone: "1234567890",
})
// use map
result, err := facades.DB().Table("users").Where("id", 1).UpdateOrInsert(map[string]any{
"email": "john@example.com",
}, map[string]any{
"phone": "1234567890",
})
Increment and Decrement
The Increment
and Decrement
methods can be used to increment or decrement the value of a specified field:
err := facades.DB().Table("users").Where("id", 1).Increment("votes")
err := facades.DB().Table("users").Where("id", 1).Increment("votes", 2)
err := facades.DB().Table("users").Where("id", 1).Decrement("votes")
err := facades.DB().Table("users").Where("id", 1).Decrement("votes", 2)
Delete
The query builder also includes some functions that can help you implement "pessimistic locking" in your select
statements:
result, err := facades.DB().Table("users").Where("id", 1).Delete()
Pessimistic Locking
The query builder also includes some functions that can help you implement "pessimistic locking" in your select
statements:
To use a "shared lock", you may use the SharedLock
method. A shared lock prevents the selected rows from being modified until the transaction is committed:
facades.DB().Table("users").Where("votes", ">", 100).SharedLock().Get(&users)
You can also use the LockForUpdate
method. Using the "update" lock can prevent rows from being modified or selected by other shared locks:
facades.DB().Table("users").Where("votes", ">", 100).LockForUpdate().Get(&users)
Debugging
You can use the ToSQL
and ToRawSql
methods to get the current query binding and SQL.
With placeholder SQL:
facades.DB().Table("users").Where("id", 1).ToSql().Get(models.User{})
With bound values SQL:
facades.DB().Table("users").Where("id", 1).ToRawSql().Get(models.User{})
The methods that can be called after ToSql
and ToRawSql
: Count
, Insert
, Delete
, First
, Get
, Pluck
, Update
.