你有没有遇到那种动态 SQL 的坑?比如前端传了 3 个搜索条件就拼 3 个 WHERE,传了 5 个就拼 5 个,这种逻辑你现在是怎么"裸奔"处理的?
如果在PHP里面大家可能会放进数组里面然后循环拼凑SQL的where部份。但是GO里面一切都是显式的,估计要写一堆判断,有没有更好的方法
在 Go 的"裸奔"世界里,处理动态 SQL 确实没有 PHP 那么潇洒,那种 foreach 拼数组的招式在这里会遇到两个尴尬:类型检查和占位符管理。
如果你现在用 if-else 硬写,代码确实会像"老太婆的裹脚布"一样又长又臭。针对这种情况,使用 Go 里面最经典的"切片拼凑法",让你既能保持显式,又不至于写出一堆烂代码。
1. 为什么不能像 PHP 那样随便拼?
在 PHP 里你可以直接拼字符串,但在 Go(配合 pgx)里,为了防止 SQL 注入,你必须管理好占位符($1, $2, $3)。
2. 裸奔选手的"优雅拼凑法"
核心思路是:用一个字符串切片存 SQL 片段,用一个 []any 切片存参数值。
func (r *ServerRepo) Search(ctx context.Context, db infra.Querier, req req.SearchReq) ([]model.Server, error) {
sql := "SELECT id, name, status FROM servers WHERE 1=1"
var args []any
var i = 1 // 占位符计数器
// 动态拼条件
if req.Name != "" {
sql += fmt.Sprintf(" AND name LIKE $%d", i)
args = append(args, "%"+req.Name+"%")
i++
}
if req.Status != 0 {
sql += fmt.Sprintf(" AND status = $%d", i)
args = append(args, req.Status)
i++
}
// 排序和分页
sql += " ORDER BY id DESC"
// 执行
rows, err := db.Query(ctx, sql, args...) // 这里的 args... 就像 PHP 的 call_user_func_array
if err != nil {
return nil, fmt.Errorf("search repo: %w", err)
}
defer rows.Close()
// ... 后面接 Scan 逻辑
}
3. 这套写法的"爽点"在哪里?
- 显式且安全:虽然有
if,但每一行 SQL 片段和它对应的参数是死死绑定在一起的。 - 动态占位符:通过
$%d和i++,你再也不用手动去数这是第几个参数了。 - 万能适配:不管前端传 1 个还是 10 个条件,这套逻辑都能自动缩放。
4. 进阶提醒:如果你真的嫌 if 太多了
当你以后业务复杂到有几十个筛选条件时,Go 社区会推荐使用像 Squirrel (github.com) 这样的 SQL 构建器。
它不是 ORM,它只是帮你把上面的 if 逻辑封装成了链式调用:
// 看起来有点像 PHP 框架的 Query Builder
users := squirrel.Select("id", "name").
From("users").
Where(squirrel.Eq{"status": 1})
sql, args, _ := users.ToSql() // 自动生成 SQL 和对应的参数数组
5. 总结建议:
- 目前阶段:用我上面写的 "切片 +
$%d" 这种纯原生方案。它最符合你"全显式"的思路,而且能帮你彻底理清 SQL 驱动是怎么处理参数化查询的。 - 别碰拼接字符串:永远记住,绝对不要把参数直接拼进 SQL 字符串里(比如
sql += "AND id=" + id),那是安全大忌,Go 开发者必须守住这条底线。