GO裸奔【1】动态SQL

你有没有遇到那种动态 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. 这套写法的"爽点"在哪里?

  1. 显式且安全:虽然有 if,但每一行 SQL 片段和它对应的参数是死死绑定在一起的。
  2. 动态占位符:通过 $%d 和 i++,你再也不用手动去数这是第几个参数了。
  3. 万能适配:不管前端传 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 开发者必须守住这条底线。
相关推荐
小杨互联网17 小时前
Go 现代 CLI 工具离线镜像合集
开发语言·后端·golang·开源·go·命令行
hourinX19 小时前
从直接填格子到筛选图案:头像生成这部分是怎样改过来的—— gdenticon 重构日志(三)
go
我不会起名字32221 小时前
Redis 从 0 到 1(十三):分布式锁被误删、锁不住?Go 无 Lua 实现 3 级防御
redis·go·分布式锁·watch·setnx
hourinX2 天前
从 v1 到 v2,我重新拆了头像生成的整条链路—— gdenticon 重构日志(二)
go
FungLeo2 天前
成为全栈·Go 后端篇·分类树与标签关系:目录变更为何需要协调
golang·go·gorm·数据库事务·分类树·成为全栈
FungLeo2 天前
成为全栈·Go 后端篇·文章查询与展示投影:安全分页、排序和软删除
golang·go·gorm·分页查询·成为全栈·sql安全
KevinAshen673 天前
用 Go 修复旧下载工具:从接口 403 到凭证截断的完整复盘
go·github
FungLeo4 天前
成为全栈·Go 后端篇·跨语言认证兼容:Node 与 Go 如何互认 JWT 和密码
golang·go·jwt·身份认证·成为全栈·bcrypt
FungLeo4 天前
成为全栈·Go 后端篇·重写之前先锁基线:契约、Node 版本与行为清单
golang·node.js·go·openapi·契约测试·成为全栈
小满zs4 天前
Go语言第十七章(反射)
后端·go