用Joins+Where查未关联主表记录最常用:显式LEFT JOIN后Where右表ID为NULL;若外键为0需改Where条件;Not+子查询适用于外键非NULL场景;Preload和Association不适用此需求。

用 Joins + Where 查未关联的主表记录(最常用)
直接查“没有关联记录”的主表数据,本质是左连接后筛出右表为 NULL 的行。GORM 不支持 LEFT JOIN ... ON ... WHERE right.id IS NULL 这种写法直接表达“未绑定”,但可以用 Joins 显式左连再配合 Where 判断外键或关联字段是否为空。
假设你有 User 和 Profile 两个模型,User.ProfileID 是外键,且允许为 0 或 NULL:
var users []User
db.Joins("LEFT JOIN profiles ON users.profile_id = profiles.id").
Where("profiles.id IS NULL").
Find(&users)
注意点:
-
Joins默认是INNER JOIN,必须显式写"LEFT JOIN ...",否则会漏掉无关联的主表记录 -
Where("profiles.id IS NULL")依赖数据库对NULL的语义,若外键字段类型是uint且默认值为0(非 SQLNULL),就得改成Where("users.profile_id = 0") - 如果用的是 GORM v2,确保
profile_id字段在User结构体上正确打了gorm:"column:profile_id"标签,否则Joins中的列名可能不匹配
用 Not + 子查询查未关联记录(兼容外键非 NULL 场景)
当外键字段不允许为 NULL(比如是 uint 类型,默认值 0),又不想依赖具体数值语义时,更稳妥的方式是用子查询排除已关联的 ID:
var users []User
db.Not("id IN (?)", db.Table("profiles").Select("user_id")).Find(&users)
这个写法适用于 Profile 表里有个 user_id 外键指向 User.id 的场景。关键细节:
-
db.Table("profiles").Select("user_id")必须返回单列,且类型要和主表User.id兼容,否则IN会报错或静默失效 - GORM v2 对子查询的支持较稳,v1 可能需手动拼
SELECT id FROM users WHERE id NOT IN (SELECT user_id FROM profiles) - 性能上,大表要注意
NOT IN在子查询含NULL时结果为空 —— 所以务必保证子查询里user_id非空,或改用NOT EXISTS(GORM 不原生支持,得用Where("NOT EXISTS (...)")手写)
为什么不用 Preload 或 Association?
Preload 是为加载已存在的关联而设计的,它会在主查询后发起额外查询拉取关联数据,无法过滤“不存在关联”的主记录;Association 方法(如 user.Association("Profile").Count())只能对单条记录逐个判断,批量查未绑定数据时 N+1 问题严重,完全不可取。
常见误操作:
- 写
db.Preload("Profile").Find(&users)然后在 Go 层遍历判断user.Profile == nil—— 这会把所有用户都查出来,内存和网络开销翻倍 - 用
db.Model(&User{}).Association("Profile").Find(&profiles)想反向查,但Association不支持从主表出发的条件筛选
外键缺失或命名不一致时容易查不到数据
GORM 的 Joins 和自动关联推导都依赖字段命名和标签。如果 User 结构体里外键字段叫 ProfileRefID 却没加 gorm:"column:profile_id",那么 Joins("LEFT JOIN profiles ...") 里的 ON 条件列名就对不上,结果永远为空。
排查建议:
- 打开 GORM 日志:
db.Debug().Joins(...).Where(...).Find(&users),看生成的 SQL 是否真用了你预期的列名 - 确认数据库中实际外键字段名,与
Joins语句中的列名、结构体 tag 三者严格一致 - 如果用的是复合主键或软删除关联,
profiles.deleted_at IS NULL这类条件也得手动加进Where,否则逻辑删除的记录仍会被视为“已关联”
真正卡住人的往往不是语法,而是外键字段在 struct、migration、SQL 三处的命名偏差,一查日志就清楚。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











