left join 必须用 joins("left join ... on ..."),preload 仅支持 inner join;on 条件需用数据库列名,select 需显式指定别名及空值类型字段,where 过滤用户侧条件会退化为 inner join。

左连接必须用 Joins,不能用 Preload
Preload 只支持 INNER JOIN 和一次性加载关联表(N+1 优化),它底层生成的是独立的 SELECT 查询,不是 JOIN。要做 LEFT JOIN,必须显式调用 Joins 并手动写 JOIN 条件。
常见错误是以为 Preload("User") 能实现左连接效果,结果发现没有关联数据的主记录直接被过滤掉了——那是 INNER JOIN 的行为,不是 LEFT JOIN。
-
Joins是唯一能生成LEFT JOINSQL 的 GORM 方法(配合Find或Scan) - JOIN 条件必须完整写出,GORM 不自动推导外键关系(不像
Preload那样依赖 struct tag) - 如果只查主表字段,
Joins后仍需用Select显式指定,否则可能因字段冲突报错
Joins 写法:表名、别名、ON 条件一个都不能少
GORM 的 Joins 默认生成 INNER JOIN,要左连接必须写成 Joins("LEFT JOIN ... ON ..."),且表名建议带别名,避免字段歧义。
例如查询订单及其用户信息(用户可能为空):
var orders []struct {
OrderID uint
Title string
Name sql.NullString // 注意:可能为 NULL
}
db.Joins("LEFT JOIN users ON users.id = orders.user_id").
Select("orders.id as order_id, orders.title, users.name").
Find(&orders)
- ON 条件里必须用原始数据库列名(如
orders.user_id),不是 struct 字段名(如UserID) - SELECT 中建议用别名(如
orders.id as order_id),否则 Go struct 字段无法绑定(GORM 不自动映射带点的列名) - 关联字段类型要用
sql.NullString、sql.NullInt64等,否则 NULL 值会触发 scan error
想复用模型结构?得用 Scan + 自定义 struct
GORM 不允许直接把 JOIN 结果 Scan 到带嵌套 struct 的模型(比如 Order{User: User{}}),因为 LEFT JOIN 后的字段扁平化了,没有嵌套层级。
正确做法是定义一个纯字段平铺的 struct,或用 map[string]interface{} 接收再手动赋值:
type OrderWithUser struct {
ID uint `gorm:"column:id"`
Title string `gorm:"column:title"`
UserID sql.NullUint64 `gorm:"column:user_id"`
UserName sql.NullString `gorm:"column:name"`
}
db.Joins("LEFT JOIN users ON users.id = orders.user_id").
Select("orders.*, users.name").
Find(&results)
- struct tag 必须用
column:明确对应 SELECT 出的列别名或原名 - 不要试图在 struct 里嵌套
User User—— GORM 不解析 JOIN 后的嵌套关系 - 如果需要返回 JSON,直接用这个平铺 struct 更稳定,避免空指针 panic
WHERE 条件写在 JOIN 后还是 ON 里,结果完全不同
LEFT JOIN 中,把过滤条件放在 ON 子句和放在 WHERE 子句,语义差异极大:
-
ON users.deleted_at IS NULL:只影响 JOIN 匹配逻辑,不影响主表记录存在性 -
WHERE users.deleted_at IS NULL:等价于强制 INNER JOIN,没匹配到用户的订单会被筛掉
真正想要“查所有订单,附带未删除的用户信息”,就得把用户侧过滤写进 ON:
db.Joins("LEFT JOIN users ON users.id = orders.user_id AND users.deleted_at IS NULL").
Find(&orders)
这个细节漏掉,左连接就退化成内连接,而且很难排查——SQL 日志里看着像执行成功,但业务数据就是不对。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











