
本文讲解如何在 go 中根据条件(如 warehouseid 是否为 0)动态拼接 sql 查询语句,并统一声明数据库查询结果变量(如 rows),避免作用域错误,确保 db.query 调用正确且类型安全。
本文讲解如何在 go 中根据条件(如 warehouseid 是否为 0)动态拼接 sql 查询语句,并统一声明数据库查询结果变量(如 rows),避免作用域错误,确保 db.query 调用正确且类型安全。
在 Go 中使用 database/sql(如 go-mysql-driver)执行条件化 SQL 查询时,一个常见误区是:在 if-else 分支内分别用 := 声明同名变量(如 rows, err),导致变量作用域受限于各自分支,外部无法访问,编译报错 undefined: rows。
根本原因在于 := 是短变量声明(声明 + 赋值),它会在当前作用域(如 if 或 else 块内部)创建新变量;而 = 仅为赋值操作,要求变量已预先声明。因此,正确做法是提前声明变量,再在分支中统一赋值。
以下是推荐的完整实现方案:
func getOffers(db *sql.DB, variantId, warehouseId int) ([]Offer, error) {
// 1. 预先声明变量(注意:rows 类型为 *sql.Rows,err 为 error)
var (
rows *sql.Rows
err error
)
// 2. 动态构建查询语句
query := "SELECT id, description FROM offers INNER JOIN offer_entities ON offers.id = offer_entities.offer_id WHERE offer_entities.entity_id = ?"
if warehouseId != 0 {
query += " AND offer_entities.warehouse_id = ?"
}
query += " GROUP BY offer_id"
// 3. 统一执行查询:根据条件传入不同数量的参数
if warehouseId != 0 {
rows, err = db.Query(query, variantId, warehouseId)
} else {
rows, err = db.Query(query, variantId)
}
if err != nil {
return nil, fmt.Errorf("query failed: %w", err)
}
defer rows.Close() // 注意:应在函数返回前或显式处理后调用
// 4. 扫描结果(示例结构体)
var offers []Offer
for rows.Next() {
var o Offer
if err := rows.Scan(&o.ID, &o.Description); err != nil {
return nil, fmt.Errorf("scan failed: %w", err)
}
offers = append(offers, o)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("rows iteration error: %w", err)
}
return offers, nil
}
// 示例结构体定义(请按实际调整)
type Offer struct {
ID int
Description string
}
✅ 关键要点总结:
- ✅ 始终预先声明
rows和err:使用var rows *sql.Rows; var err error或var (rows *sql.Rows; err error),确保其作用域覆盖整个函数; - ✅ 避免在
if/else内部重复:=:分支中仅用=赋值,不引入新变量; - ✅ 注意
defer rows.Close()的位置:必须在rows成功获取后、函数退出前调用(推荐放在db.Query后立即defer,但需确保rows不为nil); - ✅ SQL 拼接要防注入?:本例中
warehouseId和variantId均为整型参数,通过?占位符传入,完全由驱动参数化处理,安全无 SQL 注入风险;切勿将变量直接拼入 SQL 字符串; - ✅ 可进一步优化:若条件更复杂(如多字段可选),建议使用
strings.Builder构建查询,或考虑 ORM(如 sqlx、squirrel)提升可维护性。
遵循以上模式,即可安全、清晰地实现基于参数数量的动态 SQL 查询逻辑。










