
本文介绍一种简洁、安全且高效的方式,使用 go 原生字符串操作(而非 `text/template`)批量生成带 `union all` 的 sql 查询,适用于按日期分表等典型场景。
在 Go 中,若需将多个结构相同但表名/参数不同的 SQL 子查询拼接为一条 UNION ALL 查询(例如按日分表的 orderhistory_t20140101),直接使用 text/template 并非最优解——它会增加模板解析开销、降低可读性,且难以优雅处理分隔符(如首尾不加 union all)和 SQL 安全性(如字符串转义)。
更推荐的做法是:用纯 Go 代码遍历数据切片,逐条构造子查询,再通过 strings.Join() 组装。这种方式控制力强、性能高、逻辑清晰,也便于后续添加 SQL 注入防护(如参数化占位符或白名单校验)。
以下是一个完整示例:
package main
import (
"fmt"
"strings"
)
func main() {
// 示例数据:日期字符串(SELECT 中的字面量)与对应表名后缀(FROM 子句)
dates := []string{"2014-01-01", "2014-01-02", "2014-01-03"}
tableSuffixes := []string{"20140101", "20140102", "20140103"}
var parts []string
for i := range dates {
// 使用 %q 自动添加单引号并转义特殊字符(如内部单引号),提升安全性
query := fmt.Sprintf(
"SELECT %q AS date, itemid, price FROM orderhistory_t%s",
dates[i],
tableSuffixes[i],
)
parts = append(parts, query)
}
// 拼接所有子查询,用 " UNION ALL " 分隔
fullQuery := strings.Join(parts, " UNION ALL ")
fmt.Println(fullQuery)
}✅ 输出结果:
SELECT '2014-01-01' AS date, itemid, price FROM orderhistory_t20140101 UNION ALL SELECT '2014-01-02' AS date, itemid, price FROM orderhistory_t20140102 UNION ALL SELECT '2014-01-03' AS date, itemid, price FROM orderhistory_t20140103
⚠️ 注意事项:
- 安全性优先:示例中使用 %q 格式化日期字符串,可自动转义单引号、换行等危险字符;若数据来自用户输入,务必额外校验格式(如正则匹配 ^\d{4}-\d{2}-\d{2}$)或改用预编译参数化查询(如 database/sql 的 ? 占位符 + Exec())。
- 长度与性能:UNION ALL 子句过多可能影响数据库执行计划,建议单次拼接不超过 100–200 条;超量时应分批执行。
- 健壮性增强:生产环境可封装为函数,并加入切片长度校验(len(dates) == len(tableSuffixes))、空切片处理(返回空字符串或 panic)等防御逻辑。
总结:对于确定结构、高频生成的 SQL 拼接任务,Go 原生字符串操作比模板更轻量、更可控。掌握 fmt.Sprintf + strings.Join 这一组合,能快速构建清晰、安全、可维护的动态查询逻辑。










