
GORM 不支持直接将 JOIN 查询结果一次性扫描进包含多个嵌套 GORM 模型的结构体中;需拆分为多个独立结构体并链式调用 Scan(),或改用 Select() + Joins() 等声明式方式实现类型安全映射。
gorm 不支持直接将 join 查询结果一次性扫描进包含多个嵌套 gorm 模型的结构体中;需拆分为多个独立结构体并链式调用 `scan()`,或改用 `select()` + `joins()` 等声明式方式实现类型安全映射。
在使用 GORM 执行原生 SQL 查询(如 db.Raw().Scan())时,若试图将多表 JOIN 结果(例如 SELECT t1.*, t2.*)直接扫描进一个含多个 GORM 模型字段的结构体(如 struct { model1 Model1; model2 Model2 }),会遇到字段无法正确绑定的问题——结构体字段全为零值(如 0, "", 零时间等),正如你观察到的 0001-01-01 00:00:00 +0000 UTC。这不是数据库或连接问题,而是 GORM 不支持跨模型字段的自动列名映射:Scan() 仅按字段名(非结构体嵌套路径)匹配查询返回的列,且无法解析 model1.ID 与 t1.id 的语义关系。
✅ 正确做法一:拆分扫描(推荐用于复杂/动态 SQL)
将结果分别映射到两个独立 DTO 结构体,每个结构体字段名严格对应 SQL 中的列别名(或原始列名),并链式调用 .Scan()(注意:GORM v1 中 .Scan(&a).Scan(&b) 是合法且有效的,它复用同一查询结果集逐次解码):
type Dto1 struct {
ID uint `gorm:"column:id"`
Name string `gorm:"column:name"`
CreatedAt time.Time `gorm:"column:created_at"`
// ... 其他 t1 字段(必须与 SELECT t1.* 中实际列名/别名一致)
}
type Dto2 struct {
ID uint `gorm:"column:id"`
Title string `gorm:"column:title"`
UpdatedAt time.Time `gorm:"column:updated_at"`
// ... 其他 t2 字段
}
func main() {
db, err := gorm.Open("postgres", "host=... user=... dbname=...")
if err != nil {
panic(err)
}
defer db.Close()
var dto1s []Dto1
var dto2s []Dto2
sql := `
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.foreign_id = t2.id
WHERE t1.status = ?`
// 关键:复用同一查询结果,分两次 Scan
err = db.Raw(sql, "active").Scan(&dto1s).Scan(&dto2s).Error
if err != nil {
panic(err)
}
// 合并逻辑(按索引一一对应)
for i := range dto1s {
fmt.Printf("Pair %d: t1=%+v, t2=%+v\n", i, dto1s[i], dto2s[i])
}
}⚠️ 注意事项:
- 列名必须显式指定别名(如
t2.id AS t2_id),避免t1.id和t2.id冲突导致覆盖; - 两个切片长度必须一致(JOIN 保证一对一/一对多时需额外处理);
- 此方式适用于 GORM v1;GORM v2(
gorm.io/gorm)已移除链式Scan,需改用Rows()手动遍历解析。
✅ 更优替代:避免 Raw,使用 GORM 声明式 JOIN(推荐用于常规关联场景)
若业务逻辑允许,优先使用 GORM 内置关联能力,类型安全且无需手动映射:
type Model1 struct {
gorm.Model
Name string
ForeignID uint
Model2 Model2 `gorm:"foreignKey:ForeignID"`
}
type Model2 struct {
gorm.Model
Title string
}
// 查询时自动预加载关联模型
var results []Model1
db.Preload("Model2").Where("model1.status = ?", "active").Find(&results)
// results 中每个 Model1 的 Model2 字段已填充? 总结:
GORM 的 Scan() 对原生 SQL 的结构体映射是扁平化、基于列名的,不理解嵌套模型语义。强行使用 struct{ model1 Model1; model2 Model2 } 会导致零值——这不是 bug,而是设计限制。生产环境应根据场景选择:
? 复杂 SQL → 拆分为带明确列别名的 DTO + 分步 Scan(v1)或 Rows()(v2);
? 标准关联 → 使用 Preload / Joins + 声明式模型定义,兼顾可读性与安全性。

















