news 2026/5/26 18:27:41

gorm sqlexpression如何公用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 包,这样可以在不同业务模块中灵活组合复用。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/22 9:51:15

抖音内容高效保存指南:从单视频到批量下载的完整解决方案

抖音内容高效保存指南&#xff1a;从单视频到批量下载的完整解决方案 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback su…

作者头像 李华
网站建设 2026/5/26 18:26:55

华为CANN/ops-math坐标转换算子

Coordinates1DTo2D 【免费下载链接】ops-math 本项目是CANN提供的数学类基础计算算子库&#xff0c;实现网络在NPU上加速计算。 项目地址: https://gitcode.com/cann/ops-math 产品支持情况 产品是否支持Ascend 950PR/Ascend 950DT√Atlas A3 训练系列产品/Atlas A3 推…

作者头像 李华
网站建设 2026/5/26 18:26:17

CANN/pyto向下取整除法操作API文档

&#xfeff;# pypto.floor_div 【免费下载链接】pypto PyPTO&#xff08;发音: pai p-t-o&#xff09;&#xff1a;Parallel Tensor/Tile Operation编程范式。 项目地址: https://gitcode.com/cann/pypto 产品支持情况 产品是否支持Ascend 950PR/Ascend 950DT√Atlas …

作者头像 李华
网站建设 2026/5/26 18:26:55

智能AI识别之电梯按钮目标检测数据集 电梯按钮自动识别数据集 数字识别 符号识别 图像识别数据集 AI电梯内标识识别 10194期

电梯按钮目标检测数据集简介类别 钥匙孔 led 发光二极管 light 光 open 打开 overloaded 过载的 s s1 s2 sG sL sP speaker 扬声器 stop 停止 switch 开关 text 文本 text_INUSE 文本正使用中 text_LL1R text_LL2R 文本_LL2R text_MEZZ 中层文本 text_Mez 文本_Mez text_attend…

作者头像 李华
网站建设 2026/5/22 9:44:02

如何快速定位Windows热键冲突:终极检测工具使用指南

如何快速定位Windows热键冲突&#xff1a;终极检测工具使用指南 【免费下载链接】hotkey-detective A small program for investigating stolen key combinations under Windows 7 and later. 项目地址: https://gitcode.com/gh_mirrors/ho/hotkey-detective 当你的Wind…

作者头像 李华
网站建设 2026/5/22 9:43:08

HarmonyOS 6 Chip 组件:设置默认后缀图标使用文档

文章目录代码默认后缀图标核心配置1. 启用默认关闭图标2. 显示优先级规则3. 关联配置项代码解析1. 启用默认后缀图标2. 不冲突条件3. 整体结构总结默认后缀图标即 Chip 内置关闭图标&#xff0c;由系统提供样式、尺寸、交互逻辑&#xff0c;无需配置图片资源&#xff0c;只需开…

作者头像 李华