命名参数

SQL 语句中多个占位符 ? 时,容易搞混顺序。命名参数让 SQL 更清晰,减少出错。

基本用法

使用 @ 前缀命名参数:

db.Where("name = @name AND age > @age", sql.Named("name", "张三"), sql.Named("age", 20)).Find(&users)

使用 Map

更简洁的写法:

db.Where("name = @name AND age > @age", map[string]interface{}{
    "name": "张三",
    "age":  20,
}).Find(&users)

Raw 中的命名参数

db.Raw("SELECT * FROM users WHERE name = @name AND status = @status", map[string]interface{}{
    "name":   "张三",
    "status": "active",
}).Scan(&users)

Exec 中的命名参数

db.Exec(`
    UPDATE users 
    SET name = @name, age = @age, updated_at = @now 
    WHERE id = @id
`, map[string]interface{}{
    "name": "新名字",
    "age":  25,
    "now":  time.Now(),
    "id":   1,
})

结构体参数

用结构体作为参数:

type UserQuery struct {
    Name   string
    Status string
    MinAge int
}

query := UserQuery{
    Name:   "张三",
    Status: "active",
    MinAge: 20,
}

db.Where("name = @Name AND status = @Status AND age >= @MinAge", query).Find(&users)

结构体字段名作为参数名。

命名参数的优势

可读性

// 占位符方式
db.Where("name = ? AND age > ? AND status = ? AND city = ?", 
    "张三", 20, "active", "北京")

// 命名参数方式
db.Where("name = @name AND age > @age AND status = @status AND city = @city", map[string]interface{}{
    "name":   "张三",
    "age":    20,
    "status": "active",
    "city":   "北京",
})

可维护性

参数顺序无关,增删参数不影响其他:

// 占位符方式:插入参数要调整后面所有位置
db.Where("name = ? AND age > ? AND status = ?", "张三", 20, "active")

// 命名参数方式:直接添加
db.Where("name = @name AND age > @age AND status = @status AND city = @city", map[string]interface{}{
    "name":   "张三",
    "age":    20,
    "status": "active",
    "city":   "北京",
})

复用参数

同一个参数多次使用:

db.Where("name = @name OR nickname = @name", sql.Named("name", "张三")).Find(&users)

复杂查询示例

多条件搜索

func SearchUsers(db *gorm.DB, params map[string]interface{}) []User {
    var users []User
    db.Where(`
        (@name IS NULL OR name LIKE CONCAT('%', @name, '%'))
        AND (@status IS NULL OR status = @status)
        AND (@min_age IS NULL OR age >= @min_age)
        AND (@max_age IS NULL OR age <= @max_age)
        AND (@city IS NULL OR city = @city)
    `, params).Find(&users)
    return users
}

SearchUsers(db, map[string]interface{}{
    "name":     "张",
    "status":   "active",
    "min_age":  18,
    "max_age":  nil,
    "city":     nil,
})

批量更新

db.Exec(`
    UPDATE products 
    SET 
        price = @price,
        stock = @stock,
        updated_at = @now
    WHERE id = @id
`, []map[string]interface{}{
    {"id": 1, "price": 99.9, "stock": 100, "now": time.Now()},
    {"id": 2, "price": 199.9, "stock": 50, "now": time.Now()},
    {"id": 3, "price": 299.9, "stock": 30, "now": time.Now()},
})

与 ORM 方法结合

db.Model(&User{}).
    Where("status = @status", sql.Named("status", "active")).
    Where("age > @age", sql.Named("age", 20)).
    Find(&users)

数据库差异

不同数据库的命名参数语法:

数据库语法
MySQL@name
PostgreSQL@name$1
SQLite@name?
SQL Server@name

GORM 会自动处理转换。

小结

命名参数提高 SQL 可读性和可维护性。参数多时推荐使用,避免占位符顺序错误。