
GORM 的 Raw().Scan() 不支持直接将 JOIN 查询结果映射到嵌套结构体(如含多个 GORM 模型字段的 struct),需通过分步扫描或自定义字段映射实现正确赋值。
gorm 的 `raw().scan()` 不支持直接将 join 查询结果映射到嵌套结构体(如含多个 gorm 模型字段的 struct),需通过分步扫描或自定义字段映射实现正确赋值。
在使用 GORM 执行原生 SQL JOIN 查询时,开发者常期望将结果一次性扫描进一个包含多个 GORM 模型字段的结构体(如 type Result struct { Model1 Model1; Model2 Model2 })。但实际运行后发现:结构体字段全为零值(如 0, "", time.Time{}),尽管查询返回行数正确——这并非 GORM Bug,而是其 Scan() 方法的设计限制:Scan(&dst) 仅支持扁平化、一对一的字段映射,无法自动解析嵌套结构或跨表字段前缀。
✅ 正确方案一:分步 Scan(推荐,兼容性好)
GORM 支持链式调用多个 Scan(),将同一查询结果按顺序分别映射到不同结构体。前提是 SQL 中各表字段不重名(或使用别名避免冲突),且结构体字段命名与数据库列名(或别名)严格匹配:
type Dto1 struct {
model.Model1 // 注意:字段名需与 SELECT t1.* 中的列名一致(如 ID, Name 等)
}
type Dto2 struct {
model.Model2 // 同理,字段名需与 t2.* 列名一致
}
func main() {
db, err := gorm.Open("postgres", "host=localhost user=... dbname=... sslmode=disable")
if err != nil {
panic(err)
}
defer db.Close()
var dto1 []Dto1
var dto2 []Dto2
// 关键:同一 Raw 查询,连续 Scan 到不同目标
err = db.Raw(`
SELECT t1.id, t1.name, t1.created_at,
t2.id AS t2_id, t2.title, t2.updated_at
FROM t1
INNER JOIN t2 ON t1.t2_id = t2.id
WHERE t1.status = ?`, "active").
Scan(&dto1).Scan(&dto2).Error
if err != nil {
panic(err)
}
// 组合数据(需确保索引一一对应)
for i := range dto1 {
fmt.Printf("Model1: %+v, Model2: %+v\n", dto1[i].Model1, dto2[i].Model2)
}
}⚠️ 注意事项:
Scan(&dto1).Scan(&dto2)会将结果集按列顺序依次分配:前 N 列给dto1,后 M 列给dto2;务必保证 SQL 中t1.*和t2.*字段顺序与结构体字段声明顺序一致。- 若存在同名列(如
t1.id和t2.id),必须显式使用别名(如t2.id AS t2_id)并在结构体中定义对应字段(如T2ID uint),否则 GORM 无法区分。- 此方式要求两个切片长度相同,适用于 1:1 关联场景;若为 1:N,需改用
Rows()手动解析。
✅ 正确方案二:自定义 Flat 结构体(更可控)
定义一个扁平化结构体,显式声明所有需映射的字段(含前缀),再手动构造模型实例:
type JoinResult struct {
T1ID uint `gorm:"column:id"`
T1Name string `gorm:"column:name"`
T1CreatedAt time.Time `gorm:"column:created_at"`
T2ID uint `gorm:"column:t2_id"`
T2Title string `gorm:"column:title"`
T2UpdatedAt time.Time `gorm:"column:updated_at"`
}
func main() {
var results []JoinResult
err := db.Raw(`
SELECT t1.id, t1.name, t1.created_at,
t2.id AS t2_id, t2.title, t2.updated_at
FROM t1
INNER JOIN t2 ON t1.t2_id = t2.id
`).Scan(&results).Error
// 手动转换为模型
for _, r := range results {
m1 := model.Model1{
ID: r.T1ID,
Name: r.T1Name,
CreatedAt: r.T1CreatedAt,
}
m2 := model.Model2{
ID: r.T2ID,
Title: r.T2Title,
UpdatedAt: r.T2UpdatedAt,
}
fmt.Printf("M1: %+v | M2: %+v\n", m1, m2)
}
}❌ 错误写法解析
原代码中 type Result struct { model1 model.Model1; model2 model.Model2 } 失败的根本原因:
- GORM
Scan不识别嵌套结构体,它只尝试将查询结果的每一列按顺序匹配到Result的顶层字段(即model1和model2两个字段本身),而非其内部字段; -
model1是一个结构体值(非指针),GORM 无法递归解析其成员,最终全部设为零值。
总结
- GORM
Raw().Scan()不支持嵌套模型结构体,这是设计使然,非 Bug; - 推荐优先使用分步 Scan(
Scan(&slice1).Scan(&slice2)),简洁且复用现有模型; - 对复杂 JOIN 或字段冲突场景,采用扁平结构体 + 手动赋值,控制力最强;
- 避免在
Scan目标中使用未导出字段、嵌套结构体或非标准标签,确保字段名与 SQL 列名精确匹配。

















