Beego ORM原生查询无法直接映射到自定义结构体,Raw().Values()仅支持[]orm.Params;正确方式是用QueryRows()配合字段名与SQL列名完全一致(大小写敏感)的结构体。

Beego ORM 原生查询不能直接用 QuerySeter 映射到自定义结构体
Beego ORM 的 QuerySeter(比如 o.QueryTable("user").Filter(...))只支持绑定已注册的模型结构体,且字段名、标签必须严格匹配表结构。一旦用了 Raw() 执行原生 SQL,它就脱离了 ORM 的自动映射路径——One()、All() 这些方法对 RawSeter 不可用,强行调用会 panic 或静默失败。
Raw().Values(&slice) 只能映射到 []orm.Params,不是你的结构体
这是最常踩的坑:很多人写 o.Raw("SELECT id, name FROM user").Values(&users),期望 users 是 []User,但实际只能是 []orm.Params(即 []map[string]interface{})。因为 Values() 底层按列名字符串做 key,不做结构体字段反射绑定。
-
orm.Params是 map[string]interface{},字段名全小写、无类型保证,需手动类型断言 - 若 SQL 中用了别名(如
SELECT u.id AS uid),key 就是"uid",不是结构体里的Id - 数值类字段(如
int64)可能被转成float64,直接赋值会 panic
正确做法:用 Raw().QueryRows(&slice) + 自定义结构体(字段名必须全小写)
QueryRows() 是唯一能将原生查询结果直接扫描进自定义结构体的方法,但它有硬性要求:结构体字段名必须与 SQL 返回的**列名完全一致(大小写敏感)**,且字段需为可导出(首字母大写),但标签不生效。
例如:
type UserSummary struct {
Id int `orm:"column(id)"`
Name string `orm:"column(name)"`
// 注意:这里标签无效,真正起作用的是字段名 Id/Name 和 SQL 列名是否一致
}
var users []UserSummary
err := o.Raw("SELECT id, name FROM user WHERE status = ?", 1).QueryRows(&users)
- SQL 列名必须是
id、name(小写),如果写成SELECT ID, NAME,则字段名也得改成ID、NAME - 结构体字段类型必须和数据库列类型兼容(如 MySQL
INT→ Goint或int64) - 不支持嵌套结构体或关联字段;复杂场景建议改用
QueryRow()单条扫描或手写for rows.Next()
更灵活的方案:用 QueryRow() 或 rows.Scan() 手动控制
当列名不规则、需要类型强转、或要处理 NULL(sql.NullString 等)时,QueryRows() 就不够用了。这时应退回到底层 sql.Rows:
r := o.Raw("SELECT id, name, created_at FROM user LIMIT 1")
rows, err := r.Rows()
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var u struct {
ID int64 `json:"id"`
Name string `json:"name"`
CreatedAt time.Time `json:"created_at"`
}
if err := rows.Scan(&u.ID, &u.Name, &u.CreatedAt); err != nil {
return err
}
// 处理 u
}
- 完全绕过 Beego ORM 的映射逻辑,可控性最强
- 适合报表、聚合查询、多表 JOIN 后字段混杂的场景
- 注意:必须调用
rows.Close(),否则连接泄露
Beego ORM 的原生查询映射本质是“列名到字段名的字符串级匹配”,没有 GORM 那种 column 标签解析或字段别名自动转换。只要 SQL 列名和结构体字段名不一致,或者想复用已有带大驼峰字段的模型,就得自己做一层转换——这点在写统计接口或导出功能时最容易卡住。


















