raw().row()返回*sql.row,需手动scan且字段顺序、类型、数量必须严格匹配select列表,null字段须用sql.nullxxx类型,无结果时scan会报sql.errnorows而非nil。

用 Raw().Row() 获取单行原始结果,但别直接 Scan
GORM 的 Raw() 配合 Row() 是拿到单行原始 SQL 结果最直接的方式,但它返回的是 *sql.Row,不是 GORM Model。很多人误以为能像 Find() 一样直接 Scan() 到结构体里 —— 实际上可以,但字段顺序、类型、数量必须严格匹配 SELECT 列表,否则 panic 或零值。
常见错误现象:sql: expected 3 destination arguments in Scan, not 2 或扫描后字段全是零值。
- SELECT 字段顺序必须和
Scan()参数顺序完全一致 - 不能用
*,必须显式写出字段名(否则无法保证顺序,尤其跨数据库时) - 如果字段是 NULLable,对应变量需用指针或
sql.NullXXX类型 -
Row()不会自动报错“无结果”,需手动检查err == sql.ErrNoRows
Scan 到 struct 时必须用字段地址,且类型要对得上
想把结果映射到自定义 struct,关键不是“能不能”,而是“怎么对齐”。GORM 不会帮你做字段名映射(那是 Find() 干的事),Row().Scan() 只认位置。
例如执行:db.Raw("SELECT name, age, created_at FROM users WHERE id = ?", 123).Row(),就必须按这个顺序 Scan:
var name string var age int var createdAt time.Time err := row.Scan(&name, &age, &createdAt)
如果换成 struct,也得确保字段顺序和导出性正确:
type UserRow struct {
Name string
Age int
CreatedAt time.Time
}
// ❌ 错误:Scan 不认识字段名,只看参数顺序
// err := row.Scan(&u)
// ✅ 正确:传每个字段的地址
var u UserRow
err := row.Scan(&u.Name, &u.Age, &u.CreatedAt)
遇到 NULL 字段必须用 sql.NullString 这类类型
数据库字段允许 NULL,但 Go 基础类型(如 string、int)不能表示 NULL —— 直接 Scan 会报 sql: Scan error on column index X: unsupported Scan, storing driver.Value type <nil> into type *string</nil>。
- 用
sql.NullString、sql.NullInt64、sql.NullTime等标准类型 - Scan 后要检查
.Valid字段才知道是否真有值 - 别自己写指针包装(比如
*string),sql.NullXXX是官方支持且稳定的方式
示例:
var name sql.NullString
var age sql.NullInt64
err := row.Scan(&name, &age)
if err != nil { /* handle */ }
if name.Valid {
fmt.Println("name =", name.String)
}
别忘了检查 sql.ErrNoRows,Row() 不会自动返回 nil
Row() 总是返回一个 *sql.Row,哪怕查询没命中任何记录。调用 Scan() 时才会暴露结果是否存在 —— 这点和 ORM 层的直觉相反,容易漏处理空情况。
典型错误写法:
row := db.Raw("...").Row()
var x string
row.Scan(&x) // 如果无结果,这里 panic: sql: no rows in result set
正确做法:
row := db.Raw("SELECT name FROM users WHERE id = ?", 999).Row()
var name string
err := row.Scan(&name)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
// 没查到,业务上可能算正常
return "", nil
}
return "", err // 其他错误(比如类型不匹配)
}
return name, nil
真正容易被忽略的是:这个错误判断必须显式写,GORM 不会替你封装或转换它。一旦线上遇到未覆盖的 sql.ErrNoRows,就变成 panic。











