Add OrderByAsc and OrderByDesc methods to all builders

Implement new OrderByAsc(col) and OrderByDesc(col) methods for
SelectBuilder, UnionBuilder, UpdateBuilder, and DeleteBuilder. These
methods allow chaining multiple ORDER BY columns with different sort
directions, addressing the limitation where the old OrderBy/Asc/Desc
pattern could only apply a single direction to all columns.

Key changes:
- Add OrderByAsc(col string) and OrderByDesc(col string) to all four builders
- Each method appends the column with explicit ASC/DESC suffix
- Support chaining for complex ordering: OrderByDesc("score").OrderByAsc("name")
- Deprecate OrderBy/Asc/Desc methods with clear migration guidance
- Add comprehensive tests (TestSelectBuilder_OrderByAscDesc)
- Add example functions demonstrating usage
- Update all existing examples to use new API
- Update README.md with new "Build ORDER BY clause" section

This design is inspired by MyBatis-Plus and provides a more intuitive
and flexible API for handling multiple ORDER BY columns with different
sorting directions.

fix #214
This commit is contained in:
Huan Du
2025-10-10 16:13:30 +08:00
parent f3199b6b4e
commit 6db1108ec3
9 changed files with 249 additions and 9 deletions
+22 -1
View File
@@ -11,6 +11,7 @@
- [Pre-defined SQL builders](#pre-defined-sql-builders)
- [Build `WHERE` clause](#build-where-clause)
- [Share `WHERE` clause among builders](#share-where-clause-among-builders)
- [Build `ORDER BY` clause](#build-order-by-clause)
- [Build SQL for different systems](#build-sql-for-different-systems)
- [Using `Struct` as a light weight ORM](#using-struct-as-a-light-weight-orm)
- [Nested SQL](#nested-sql)
@@ -203,6 +204,26 @@ fmt.Println(ub)
Refer to the [WhereClause](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#WhereClause) examples to learn its usage.
### Build `ORDER BY` clause
The `ORDER BY` clause is commonly used to sort query results. This package provides convenient methods to build `ORDER BY` clauses with proper ordering directions.
For scenarios where you need to order by multiple columns with different directions (ASC/DESC), use `OrderByAsc` and `OrderByDesc` methods. These methods can be chained to add multiple columns with their specific ordering.
```go
sb := sqlbuilder.NewSelectBuilder()
sb.Select("id", "name", "score").From("users")
sb.OrderByDesc("score").OrderByAsc("name")
sql, args := sb.Build()
fmt.Println(sql)
// Output:
// SELECT id, name, score FROM users ORDER BY score DESC, name ASC
```
The older `OrderBy` method combined with `Asc`/`Desc` is still available but deprecated, as it only supports a single ordering direction for all columns. The new `OrderByAsc` and `OrderByDesc` methods provide more flexibility and clarity when working with multiple columns.
### Build SQL for different systems
SQL syntax and parameter placeholders can differ across systems. To address these variations, this package introduces a concept termed "flavor".
@@ -412,7 +433,7 @@ var baseUserSelect = sqlbuilder.NewSelectBuilder().
func ListActiveUsers(limit, offset int) (string, []interface{}) {
sb := baseUserSelect.Clone() // independent copy
sb.OrderBy("id").Asc()
sb.OrderByAsc("id")
sb.Limit(limit).Offset(offset)
return sb.Build()
}
+1 -1
View File
@@ -68,7 +68,7 @@ func ExampleCTEBuilder() {
sb.Where(
sb.LessEqualThan("orders.price", 200),
"valid_users.level < orders.min_level",
).OrderBy("orders.price").Desc()
).OrderByDesc("orders.price")
sql, args := sb.Build()
fmt.Println(sql)
+29
View File
@@ -151,13 +151,40 @@ func (db *DeleteBuilder) AddWhereClause(whereClause *WhereClause) *DeleteBuilder
}
// OrderBy sets columns of ORDER BY in DELETE.
//
// Deprecated: Use OrderByAsc or OrderByDesc instead for better support of multiple ORDER BY columns with different directions.
// OrderBy combined with Asc/Desc only supports a single direction for all columns.
func (db *DeleteBuilder) OrderBy(col ...string) *DeleteBuilder {
db.orderByCols = col
db.marker = deleteMarkerAfterOrderBy
return db
}
// OrderByAsc sets a column of ORDER BY in DELETE with ASC order.
// It supports chaining multiple calls to add multiple ORDER BY columns with different directions.
//
// db.OrderByAsc("name").OrderByDesc("id")
// // Generates: ORDER BY name ASC, id DESC
func (db *DeleteBuilder) OrderByAsc(col string) *DeleteBuilder {
db.orderByCols = append(db.orderByCols, col+" ASC")
db.marker = deleteMarkerAfterOrderBy
return db
}
// OrderByDesc sets a column of ORDER BY in DELETE with DESC order.
// It supports chaining multiple calls to add multiple ORDER BY columns with different directions.
//
// db.OrderByDesc("id").OrderByAsc("name")
// // Generates: ORDER BY id DESC, name ASC
func (db *DeleteBuilder) OrderByDesc(col string) *DeleteBuilder {
db.orderByCols = append(db.orderByCols, col+" DESC")
db.marker = deleteMarkerAfterOrderBy
return db
}
// Asc sets order of ORDER BY to ASC.
//
// Deprecated: Use OrderByAsc instead. Asc only supports a single direction for all ORDER BY columns.
func (db *DeleteBuilder) Asc() *DeleteBuilder {
db.order = "ASC"
db.marker = deleteMarkerAfterOrderBy
@@ -165,6 +192,8 @@ func (db *DeleteBuilder) Asc() *DeleteBuilder {
}
// Desc sets order of ORDER BY to DESC.
//
// Deprecated: Use OrderByDesc instead. Desc only supports a single direction for all ORDER BY columns.
func (db *DeleteBuilder) Desc() *DeleteBuilder {
db.order = "DESC"
db.marker = deleteMarkerAfterOrderBy
+29
View File
@@ -245,13 +245,40 @@ func (sb *SelectBuilder) GroupBy(col ...string) *SelectBuilder {
}
// OrderBy sets columns of ORDER BY in SELECT.
//
// Deprecated: Use OrderByAsc or OrderByDesc instead for better support of multiple ORDER BY columns with different directions.
// OrderBy combined with Asc/Desc only supports a single direction for all columns.
func (sb *SelectBuilder) OrderBy(col ...string) *SelectBuilder {
sb.orderByCols = append(sb.orderByCols, col...)
sb.marker = selectMarkerAfterOrderBy
return sb
}
// OrderByAsc sets a column of ORDER BY in SELECT with ASC order.
// It supports chaining multiple calls to add multiple ORDER BY columns with different directions.
//
// sb.OrderByAsc("name").OrderByDesc("id")
// // Generates: ORDER BY name ASC, id DESC
func (sb *SelectBuilder) OrderByAsc(col string) *SelectBuilder {
sb.orderByCols = append(sb.orderByCols, col+" ASC")
sb.marker = selectMarkerAfterOrderBy
return sb
}
// OrderByDesc sets a column of ORDER BY in SELECT with DESC order.
// It supports chaining multiple calls to add multiple ORDER BY columns with different directions.
//
// sb.OrderByDesc("id").OrderByAsc("name")
// // Generates: ORDER BY id DESC, name ASC
func (sb *SelectBuilder) OrderByDesc(col string) *SelectBuilder {
sb.orderByCols = append(sb.orderByCols, col+" DESC")
sb.marker = selectMarkerAfterOrderBy
return sb
}
// Asc sets order of ORDER BY to ASC.
//
// Deprecated: Use OrderByAsc instead. Asc only supports a single direction for all ORDER BY columns.
func (sb *SelectBuilder) Asc() *SelectBuilder {
sb.order = "ASC"
sb.marker = selectMarkerAfterOrderBy
@@ -259,6 +286,8 @@ func (sb *SelectBuilder) Asc() *SelectBuilder {
}
// Desc sets order of ORDER BY to DESC.
//
// Deprecated: Use OrderByDesc instead. Desc only supports a single direction for all ORDER BY columns.
func (sb *SelectBuilder) Desc() *SelectBuilder {
sb.order = "DESC"
sb.marker = selectMarkerAfterOrderBy
+106 -3
View File
@@ -44,7 +44,7 @@ func ExampleSelectBuilder() {
"modified_at > created_at + "+sb.Var(86400), // It's allowed to write arbitrary SQL.
)
sb.GroupBy("status").Having(sb.NotIn("status", 4, 5))
sb.OrderBy("modified_at").Asc()
sb.OrderByAsc("modified_at")
sb.Limit(10).Offset(5)
s, args := sb.Build()
@@ -73,7 +73,7 @@ func ExampleSelectBuilder_advancedUsage() {
sb.In("status", Flatten([]int{1, 2, 3})...),
sb.Between("created_at", start, end),
)
sb.OrderBy("modified_at").Desc()
sb.OrderByDesc("modified_at")
innerSb.Select("*")
innerSb.From("banned")
@@ -407,7 +407,7 @@ func ExampleSelectBuilder_LateralAs() {
Where(
"all_sales.salesperson_id = salesperson.id",
).
OrderBy("amount").Desc().Limit(1),
OrderByDesc("amount").Limit(1),
"max_sale",
),
)
@@ -449,3 +449,106 @@ func TestSelectBuilderClone(t *testing.T) {
s2After := clone.String()
a.NotEqual(s1After, s2After)
}
func ExampleSelectBuilder_OrderByAsc() {
sb := NewSelectBuilder()
sb.Select("id", "name", "score")
sb.From("users")
sb.Where(sb.GreaterThan("score", 0))
sb.OrderByAsc("name")
sql, args := sb.Build()
fmt.Println(sql)
fmt.Println(args)
// Output:
// SELECT id, name, score FROM users WHERE score > ? ORDER BY name ASC
// [0]
}
func ExampleSelectBuilder_OrderByDesc() {
sb := NewSelectBuilder()
sb.Select("id", "name", "score")
sb.From("users")
sb.Where(sb.GreaterThan("score", 0))
sb.OrderByDesc("score")
sql, args := sb.Build()
fmt.Println(sql)
fmt.Println(args)
// Output:
// SELECT id, name, score FROM users WHERE score > ? ORDER BY score DESC
// [0]
}
func ExampleSelectBuilder_OrderByAsc_multiple() {
sb := NewSelectBuilder()
sb.Select("id", "name", "score")
sb.From("users")
sb.Where(sb.GreaterThan("score", 0))
// Chain multiple OrderByAsc and OrderByDesc calls with different directions
sb.OrderByDesc("score").OrderByAsc("name").OrderByDesc("id")
sql, args := sb.Build()
fmt.Println(sql)
fmt.Println(args)
// Output:
// SELECT id, name, score FROM users WHERE score > ? ORDER BY score DESC, name ASC, id DESC
// [0]
}
func TestSelectBuilder_OrderByAscDesc(t *testing.T) {
a := assert.New(t)
// Test OrderByAsc with single column
sb := NewSelectBuilder()
sb.Select("*").From("users").OrderByAsc("name")
sql, _ := sb.Build()
a.Equal("SELECT * FROM users ORDER BY name ASC", sql)
// Test OrderByDesc with single column
sb = NewSelectBuilder()
sb.Select("*").From("users").OrderByDesc("id")
sql, _ = sb.Build()
a.Equal("SELECT * FROM users ORDER BY id DESC", sql)
// Test chaining OrderByAsc and OrderByDesc
sb = NewSelectBuilder()
sb.Select("*").From("users")
sb.OrderByDesc("score").OrderByAsc("name")
sql, _ = sb.Build()
a.Equal("SELECT * FROM users ORDER BY score DESC, name ASC", sql)
// Test multiple OrderByDesc calls
sb = NewSelectBuilder()
sb.Select("*").From("users")
sb.OrderByDesc("score").OrderByDesc("id")
sql, _ = sb.Build()
a.Equal("SELECT * FROM users ORDER BY score DESC, id DESC", sql)
// Test multiple OrderByAsc calls
sb = NewSelectBuilder()
sb.Select("*").From("users")
sb.OrderByAsc("name").OrderByAsc("email")
sql, _ = sb.Build()
a.Equal("SELECT * FROM users ORDER BY name ASC, email ASC", sql)
// Test mixed ordering with more complex scenario
sb = NewSelectBuilder()
sb.Select("id", "name", "score", "created_at").From("users")
sb.Where(sb.GreaterThan("score", 0))
sb.OrderByDesc("score").OrderByAsc("name").OrderByDesc("created_at")
sql, args := sb.Build()
a.Equal("SELECT id, name, score, created_at FROM users WHERE score > ? ORDER BY score DESC, name ASC, created_at DESC", sql)
a.Equal([]interface{}{0}, args)
// Test that OrderByAsc/OrderByDesc work with table aliases
sb = NewSelectBuilder()
sb.Select("u.id", "u.name", "o.total").From("users u")
sb.Join("orders o", "u.id = o.user_id")
sb.OrderByDesc("o.total").OrderByAsc("u.name")
sql, _ = sb.Build()
a.Equal("SELECT u.id, u.name, o.total FROM users u JOIN orders o ON u.id = o.user_id ORDER BY o.total DESC, u.name ASC", sql)
}
+29
View File
@@ -107,13 +107,40 @@ func (ub *UnionBuilder) union(opt string, builders ...Builder) *UnionBuilder {
}
// OrderBy sets columns of ORDER BY in SELECT.
//
// Deprecated: Use OrderByAsc or OrderByDesc instead for better support of multiple ORDER BY columns with different directions.
// OrderBy combined with Asc/Desc only supports a single direction for all columns.
func (ub *UnionBuilder) OrderBy(col ...string) *UnionBuilder {
ub.orderByCols = col
ub.marker = unionMarkerAfterOrderBy
return ub
}
// OrderByAsc sets a column of ORDER BY in SELECT with ASC order.
// It supports chaining multiple calls to add multiple ORDER BY columns with different directions.
//
// ub.OrderByAsc("name").OrderByDesc("id")
// // Generates: ORDER BY name ASC, id DESC
func (ub *UnionBuilder) OrderByAsc(col string) *UnionBuilder {
ub.orderByCols = append(ub.orderByCols, col+" ASC")
ub.marker = unionMarkerAfterOrderBy
return ub
}
// OrderByDesc sets a column of ORDER BY in SELECT with DESC order.
// It supports chaining multiple calls to add multiple ORDER BY columns with different directions.
//
// ub.OrderByDesc("id").OrderByAsc("name")
// // Generates: ORDER BY id DESC, name ASC
func (ub *UnionBuilder) OrderByDesc(col string) *UnionBuilder {
ub.orderByCols = append(ub.orderByCols, col+" DESC")
ub.marker = unionMarkerAfterOrderBy
return ub
}
// Asc sets order of ORDER BY to ASC.
//
// Deprecated: Use OrderByAsc instead. Asc only supports a single direction for all ORDER BY columns.
func (ub *UnionBuilder) Asc() *UnionBuilder {
ub.order = "ASC"
ub.marker = unionMarkerAfterOrderBy
@@ -121,6 +148,8 @@ func (ub *UnionBuilder) Asc() *UnionBuilder {
}
// Desc sets order of ORDER BY to DESC.
//
// Deprecated: Use OrderByDesc instead. Desc only supports a single direction for all ORDER BY columns.
func (ub *UnionBuilder) Desc() *UnionBuilder {
ub.order = "DESC"
ub.marker = unionMarkerAfterOrderBy
+2 -2
View File
@@ -26,7 +26,7 @@ func ExampleUnion() {
)
ub := Union(sb1, sb2)
ub.OrderBy("created_at").Desc()
ub.OrderByDesc("created_at")
sql, args := ub.Build()
fmt.Println(sql)
@@ -46,7 +46,7 @@ func ExampleUnionAll() {
)
ub := UnionAll(sb, Build("TABLE demo.user_profile"))
ub.OrderBy("created_at").Asc()
ub.OrderByAsc("created_at")
ub.Limit(100).Offset(5)
sql, args := ub.Build()
+29
View File
@@ -207,13 +207,40 @@ func (ub *UpdateBuilder) Div(field string, value interface{}) string {
}
// OrderBy sets columns of ORDER BY in UPDATE.
//
// Deprecated: Use OrderByAsc or OrderByDesc instead for better support of multiple ORDER BY columns with different directions.
// OrderBy combined with Asc/Desc only supports a single direction for all columns.
func (ub *UpdateBuilder) OrderBy(col ...string) *UpdateBuilder {
ub.orderByCols = col
ub.marker = updateMarkerAfterOrderBy
return ub
}
// OrderByAsc sets a column of ORDER BY in UPDATE with ASC order.
// It supports chaining multiple calls to add multiple ORDER BY columns with different directions.
//
// ub.OrderByAsc("name").OrderByDesc("id")
// // Generates: ORDER BY name ASC, id DESC
func (ub *UpdateBuilder) OrderByAsc(col string) *UpdateBuilder {
ub.orderByCols = append(ub.orderByCols, col+" ASC")
ub.marker = updateMarkerAfterOrderBy
return ub
}
// OrderByDesc sets a column of ORDER BY in UPDATE with DESC order.
// It supports chaining multiple calls to add multiple ORDER BY columns with different directions.
//
// ub.OrderByDesc("id").OrderByAsc("name")
// // Generates: ORDER BY id DESC, name ASC
func (ub *UpdateBuilder) OrderByDesc(col string) *UpdateBuilder {
ub.orderByCols = append(ub.orderByCols, col+" DESC")
ub.marker = updateMarkerAfterOrderBy
return ub
}
// Asc sets order of ORDER BY to ASC.
//
// Deprecated: Use OrderByAsc instead. Asc only supports a single direction for all ORDER BY columns.
func (ub *UpdateBuilder) Asc() *UpdateBuilder {
ub.order = "ASC"
ub.marker = updateMarkerAfterOrderBy
@@ -221,6 +248,8 @@ func (ub *UpdateBuilder) Asc() *UpdateBuilder {
}
// Desc sets order of ORDER BY to DESC.
//
// Deprecated: Use OrderByDesc instead. Desc only supports a single direction for all ORDER BY columns.
func (ub *UpdateBuilder) Desc() *UpdateBuilder {
ub.order = "DESC"
ub.marker = updateMarkerAfterOrderBy
+2 -2
View File
@@ -43,7 +43,7 @@ func ExampleUpdateBuilder() {
),
"modified_at > created_at + "+ub.Var(86400), // It's allowed to write arbitrary SQL.
)
ub.OrderBy("id").Asc()
ub.OrderByAsc("id")
sql, args := ub.Build()
fmt.Println(sql)
@@ -105,7 +105,7 @@ func ExampleUpdateBuilder_SQL() {
ub.Assign("type", "sys"),
)
ub.SQL("/* after set */")
ub.OrderBy("id").Desc()
ub.OrderByDesc("id")
ub.SQL("/* after order by */")
ub.Limit(10)
ub.SQL("/* after limit */")