原生 SQL

GORM 的链式 API 覆盖了大部分场景,但复杂查询、数据库特有功能、性能优化时,原生 SQL 仍然是必要的。

Raw 查询

执行原生查询:

var users []User
db.Raw("SELECT * FROM users WHERE age > ?", 20).Scan(&users)

Raw 方法执行 SQL,Scan 将结果映射到结构体。

Exec 执行

执行不返回数据的 SQL:

db.Exec("UPDATE users SET status = ? WHERE age < ?", "minor", 18)
db.Exec("DELETE FROM users WHERE created_at < ?", time.Now().AddDate(-1, 0, 0))

参数绑定

使用占位符防止 SQL 注入:

db.Raw("SELECT * FROM users WHERE name = ? AND age > ?", "张三", 20).Scan(&users)

不同数据库的占位符:

  • MySQL: ?
  • PostgreSQL: $1, $2, $3
  • SQLite: ?$1
  • SQL Server: @p1, @p2

GORM 会自动转换。

查询单条

var user User
db.Raw("SELECT * FROM users WHERE id = ?", 1).Scan(&user)

查询标量

查询单个值:

var name string
db.Raw("SELECT name FROM users WHERE id = ?", 1).Scan(&name)

var count int64
db.Raw("SELECT count(*) FROM users").Scan(&count)

查询到 Map

var result map[string]interface{}
db.Raw("SELECT * FROM users WHERE id = ?", 1).Scan(&result)

var results []map[string]interface{}
db.Raw("SELECT * FROM users").Scan(&results)

命名参数

使用命名参数提高可读性:

db.Raw("SELECT * FROM users WHERE name = @name AND age > @age",
    sql.Named("name", "张三"),
    sql.Named("age", 20),
).Scan(&users)

或者用 map:

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

原生 SQL 与 GORM 混合

var users []User
db.Raw("SELECT * FROM users WHERE id IN (?)", 
    db.Model(&Order{}).Select("user_id").Where("amount > ?", 1000),
).Scan(&users)

事务中的原生 SQL

db.Transaction(func(tx *gorm.DB) error {
    if err := tx.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", 100, 1).Error; err != nil {
        return err
    }
    if err := tx.Exec("UPDATE accounts SET balance = balance + ? WHERE id = ?", 100, 2).Error; err != nil {
        return err
    }
    return nil
})

Rows 迭代

大数据量时用 Rows 迭代:

rows, err := db.Raw("SELECT * FROM users").Rows()
if err != nil {
    panic(err)
}
defer rows.Close()

for rows.Next() {
    var user User
    db.ScanRows(rows, &user)
    // 处理 user
}

数据库特有语法

MySQL

db.Raw("SELECT * FROM users WHERE MATCH(name) AGAINST(?)", "张").Scan(&users)

db.Exec(`
    INSERT INTO users (name, age) 
    VALUES (?, ?) 
    ON DUPLICATE KEY UPDATE age = VALUES(age)
`, "张三", 25)

PostgreSQL

db.Raw("SELECT * FROM users WHERE name ILIKE ?", "%张%").Scan(&users)

db.Exec(`
    INSERT INTO users (name, age) 
    VALUES ($1, $2) 
    ON CONFLICT (name) DO UPDATE SET age = $2
`, "张三", 25)

SQLite

db.Raw("SELECT * FROM users WHERE json_extract(profile, '$.age') > ?", 20).Scan(&users)

复杂查询示例

分组统计

type Stats struct {
    Date    string
    Count   int
    Total   float64
}

var stats []Stats
db.Raw(`
    SELECT 
        DATE(created_at) as date,
        COUNT(*) as count,
        SUM(amount) as total
    FROM orders
    WHERE created_at >= ?
    GROUP BY DATE(created_at)
    ORDER BY date
`, time.Now().AddDate(0, -1, 0)).Scan(&stats)

多表关联

type UserOrder struct {
    UserName  string
    OrderID   uint
    Amount    float64
}

var results []UserOrder
db.Raw(`
    SELECT u.name as user_name, o.id as order_id, o.amount
    FROM users u
    LEFT JOIN orders o ON o.user_id = u.id
    WHERE o.amount > ?
`, 1000).Scan(&results)

窗口函数

type RankResult struct {
    ID    uint
    Name  string
    Score int
    Rank  int
}

var results []RankResult
db.Raw(`
    SELECT id, name, score,
           RANK() OVER (ORDER BY score DESC) as rank
    FROM users
`).Scan(&results)

安全考虑

SQL 注入防护

始终使用参数绑定:

// 安全
db.Raw("SELECT * FROM users WHERE name = ?", input)

// 危险:SQL 注入风险
db.Raw(fmt.Sprintf("SELECT * FROM users WHERE name = '%s'", input))

动态表名

表名不能参数化,需要验证:

func QueryTable(db *gorm.DB, table string) error {
    allowed := map[string]bool{
        "users":    true,
        "orders":   true,
        "products": true,
    }
    if !allowed[table] {
        return errors.New("无效的表名")
    }
    
    var results []map[string]interface{}
    return db.Raw(fmt.Sprintf("SELECT * FROM %s", table)).Scan(&results).Error
}

小结

原生 SQL 是 GORM 的补充,适合复杂查询和数据库特有功能。注意 SQL 注入防护,始终使用参数绑定。