
本文介绍如何在 go 中高效生成由多个 select 语句通过 union all 连接的动态 sql,重点对比模板渲染与字符串拼接两种方式,并推荐更安全、清晰的纯代码构建方案。
在 Go 应用开发中,尤其面对分表(如按日期命名的 orderhistory_t20140101)场景时,常需将多个结构相同但数据源不同的查询合并为一条 SQL。虽然 text/template 支持循环(如 {{range}}),但对 UNION ALL 这类需在元素间插入分隔符的场景,原生模板缺乏“非首/末项添加前缀”的内置能力(类似 join 或 sep),强行使用易导致末尾多余 union all 或逻辑冗余。
因此,更推荐直接使用 Go 原生字符串操作构建 SQL——简洁、可控、可读性强,且便于注入防护(如预处理占位符)。以下为推荐实现:
package main
import (
"fmt"
"strings"
)
func buildUnionQuery(dates, tags []string) string {
if len(dates) == 0 || len(dates) != len(tags) {
return ""
}
var parts []string
for i := range dates {
// 使用 %q 自动添加单引号并转义特殊字符(如日期中的单引号)
query := fmt.Sprintf(
"SELECT %q AS date, itemid, price FROM orderhistory_t%s",
dates[i], tags[i],
)
parts = append(parts, query)
}
return strings.Join(parts, "\nUNION ALL\n")
}
func main() {
slice1 := []string{"2014-01-01", "2014-01-02", "2014-01-03"}
slice2 := []string{"20140101", "20140102", "20140103"}
sql := buildUnionQuery(slice1, slice2)
fmt.Println(sql)
}✅ 输出结果(格式化后):
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
⚠️ 注意事项:
- SQL 注入防护:示例中使用 %q 对日期字符串进行安全转义(自动加单引号+转义),但若数据来自不可信输入,强烈建议改用参数化查询(如 database/sql 的 ? 占位符),而非字符串拼接;
- 长度与性能:UNION ALL 子句过多可能影响数据库解析性能,建议单次查询控制在 100 个以内,超量时考虑临时表或应用层聚合;
- 模板替代方案(仅作了解):若必须用 template,可通过自定义函数(如 join)或预处理切片为带分隔符的结构,但复杂度远高于直接拼接,不推荐用于此场景。
总结:对于结构固定、需连接分隔符的 SQL 片段生成,Go 原生字符串操作比模板更直接、健壮。将逻辑封装为可测试函数(如 buildUnionQuery),既提升复用性,也便于后续扩展(如添加 WHERE 条件、字段映射等)。










