gorm sqlexpression如何公用
我来为你介绍 GORM 中 SQL Expression 的公用方式,让你能复用复杂的 SQL 表达式逻辑。
核心方式:使用 gorm.Expr + 自定义函数
GORM 的 Expr 方法允许你构建可复用的 SQL 表达式。
1. 基础封装:通用表达式函数
package expr
import (
"fmt"
"gorm.io/gorm"
)
// 通用表达式构建器
type SQLExpr struct {
SQL string
Vars []interface{}
}
func (e SQLExpr) Expression() string {
return e.SQL
}
// ========== 可复用的表达式函数 ==========
// DateEqual 日期相等(处理时区/格式问题)
func DateEqual(column string, date string) *gorm.SqlExpr {
return gorm.Expr("DATE(?) = ?", gorm.Expr(column), date)
}
// JSONExtract JSON字段提取
func JSONExtract(column, path string) *gorm.SqlExpr {
return gorm.Expr("JSON_UNQUOTE(JSON_EXTRACT(?, ?))", gorm.Expr(column), path)
}
// Coalesce 空值处理
func Coalesce(columns ...string) *gorm.SqlExpr {
if len(columns) == 0 {
return nil
}
sql := "COALESCE(" + columns[0]
for i := 1; i < len(columns); i++ {
sql += ", " + columns[i]
}
sql += ")"
return gorm.Expr(sql)
}
// IfNull NULL值替换
func IfNull(column string, defaultVal interface{}) *gorm.SqlExpr {
return gorm.Expr("IFNULL(?, ?)", gorm.Expr(column), defaultVal)
}
// Round 四舍五入
func Round(column string, decimals int) *gorm.SqlExpr {
return gorm.Expr("ROUND(?, ?)", gorm.Expr(column), decimals)
}
// Concat 字符串拼接
func Concat(columns ...string) *gorm.SqlExpr {
sql := "CONCAT("
for i, col := range columns {
if i > 0 {
sql += ", "
}
sql += "?"
}
sql += ")"
exprs := make([]interface{}, len(columns))
for i, col := range columns {
exprs[i] = gorm.Expr(col)
}
return gorm.Expr(sql, exprs...)
}
// AgeFromBirthday 根据生日计算年龄
func AgeFromBirthday(birthdayCol string) *gorm.SqlExpr {
return gorm.Expr(
"TIMESTAMPDIFF(YEAR, ?, CURDATE())",
gorm.Expr(birthdayCol),
)
}
// FullTextSearch 全文搜索(MySQL)
func FullTextSearch(columns []string, keyword string) *gorm.SqlExpr {
cols := ""
for i, c := range columns {
if i > 0 {
cols += ", "
}
cols += c
}
return gorm.Expr(
fmt.Sprintf("MATCH(%s) AGAINST (? IN BOOLEAN MODE)", cols),
keyword,
)
}
2. 使用方式
package main
import (
"expr" // 上面定义的包
"gorm.io/gorm"
)
type User struct {
ID uint
Name string
Birthday *time.Time
Profile string `gorm:"type:json"`
Score float64
BackupName string
}
// ========== 在查询中复用 ==========
// 查询:计算年龄
var users []struct {
User
Age int
}
db.Model(&User{}).
Select("*, ?", expr.AgeFromBirthday("birthday")).
Scan(&users)
// 查询:JSON提取 + 空值处理
db.Model(&User{}).
Where("? = ?", expr.JSONExtract("profile", "$.city"), "北京").
Find(&users)
// 查询:使用Coalesce
db.Model(&User{}).
Select("id, ? as display_name", expr.Coalesce("name", "backup_name", "'未知'")).
Find(&users)
// 查询:分数四舍五入
db.Model(&User{}).
Select("id, ? as rounded_score", expr.Round("score", 2)).
Find(&users)
// 查询:全文搜索
db.Model(&User{}).
Where("? > 0", expr.FullTextSearch([]string{"name", "profile"}, "gorm")).
Find(&users)
3. 高级封装:Scope 模式(最推荐)
使用 GORM 的 Scopes 实现链式复用:
package scopes
import (
"gorm.io/gorm"
"yourapp/expr"
)
// 可复用的 Scope 函数
// WithAge 添加年龄计算字段
func WithAge() func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Select("*, ? as age", expr.AgeFromBirthday("birthday"))
}
}
// WithDisplayName 添加显示名称(处理空值)
func WithDisplayName() func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Select(
"*, ? as display_name",
expr.Coalesce("name", "backup_name"),
)
}
}
// WhereCity 按城市筛选(JSON字段)
func WhereCity(city string) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Where("? = ?", expr.JSONExtract("profile", "$.city"), city)
}
}
// WhereActive 筛选活跃用户(综合条件)
func WhereActive() func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Where("last_login_at > DATE_SUB(NOW(), INTERVAL 30 DAY)")
}
}
// OrderByScore 按分数排序(处理NULL)
func OrderByScore(desc bool) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
order := "ASC"
if desc {
order = "DESC"
}
return db.Order(gorm.Expr("? "+order, expr.Coalesce("score", "0")))
}
}
使用 Scope:
// 链式组合,高度复用
db.Model(&User{}).
Scopes(
scopes.WithAge(),
scopes.WithDisplayName(),
scopes.WhereCity("北京"),
scopes.WhereActive(),
scopes.OrderByScore(true),
).
Find(&users)
// 也可以单独使用
db.Model(&User{}).Scopes(scopes.WithAge()).Find(&users)
4. 复杂表达式:子查询封装
package expr
// SubQueryExpr 子查询表达式
type SubQueryExpr struct {
DB *gorm.DB
Alias string
}
func (s SubQueryExpr) Expression() string {
return fmt.Sprintf("(%s) as %s", s.DB.ToSQL(func(db *gorm.DB) *gorm.DB {
return db
}), s.Alias)
}
// AvgScoreByGroup 分组平均分子查询
func AvgScoreByGroup(groupCol string) *gorm.DB {
return db.Model(&User{}).
Select(groupCol, "AVG(score) as avg_score").
Group(groupCol)
}
// RankByScore 分数排名子查询
func RankByScore() *gorm.DB {
return db.Model(&User{}).
Select("id", "RANK() OVER (ORDER BY score DESC) as rank")
}
5. 完整实战示例
package main
import (
"gorm.io/gorm"
"yourapp/expr"
"yourapp/scopes"
)
// 业务场景:获取用户列表,包含计算字段,支持筛选和排序
func GetUserList(ctx context.Context, req UserListReq) ([]UserDTO, error) {
var result []UserDTO
query := db.Model(&User{}).
Scopes(
scopes.WithAge(), // 复用:计算年龄
scopes.WithDisplayName(), // 复用:显示名称
)
// 动态条件
if req.City != "" {
query = query.Scopes(scopes.WhereCity(req.City))
}
if req.ActiveOnly {
query = query.Scopes(scopes.WhereActive())
}
// 排序
switch req.SortBy {
case "score":
query = query.Scopes(scopes.OrderByScore(req.Desc))
case "age":
query = query.Order(expr.AgeFromBirthday("birthday"))
}
err := query.Find(&result).Error
return result, err
}
总结对比
方式 适用场景 复用级别
expr 函数包 简单SQL表达式 表达式级
Scopes 模式 完整的查询条件/字段 查询片段级
子查询封装 复杂统计/排名 查询块级
推荐实践:将常用的表达式抽到 expr 包,将完整的查询逻辑抽到 scopes 包,这样可以在不同业务模块中灵活组合复用。
