应显式为同名列添加sql别名并同步更新结构体gorm标签;scan按列位置映射,需严格保持select顺序与结构体字段顺序一致;可能为null的关联字段用指针类型;软删除表join需调用unscoped()。

JOIN后id、created_at等字段被覆盖怎么办
直接用 Joins 查多张表,只要两张表都有 id 或 created_at,GORM 就会按字段名匹配结构体字段,后出现的同名列直接覆盖前面的值——比如 users.id 覆盖了 posts.id,最终结构体里只剩用户ID,文章ID丢了。
这不是bug,是GORM默认按列名(非全限定名)映射的机制。它不区分 posts.id 和 users.id,只看 id 这个名字。
- 必须在
SELECT里显式加别名:SELECT posts.id AS post_id, users.id AS user_id, posts.title, users.name AS user_name - 结构体字段的
gorm:"column:xxx"标签必须和SQL别名完全一致,大小写敏感 - 不能只改SQL别名却不改结构体tag,GORM不会自动推导;也不能只改tag不改SQL,字段会为零值且无报错
- 嵌套结构体(如
User User)在这里无效——Scan不支持自动解包,必须平铺字段
用Scan接收联合查询结果时字段顺序错位怎么防
Scan 是纯位置映射:第1列填结构体第1个字段,第2列填第2个字段……一旦SQL里调整了 SELECT 顺序,或中间插入了计算字段(比如 COUNT(*)),字段就会整体偏移,所有后续字段都错位,且GORM完全不报错,只默默填零值。
- 定义结构体时,字段顺序必须严格对应
SELECT的列顺序 - 新增字段时,优先追加到
SELECT末尾,而不是插在中间 - 避免在联合查询中混用
*和显式字段(如SELECT posts.*, users.name),列序不可控 - 临时调试可用
rows.Columns()打印实际列名和顺序,验证是否对齐
结构体字段类型选值还是指针
LEFT JOIN 后关联字段可能为 NULL,比如用户被软删除或订单没绑定用户。用值类型(string、uint)会导致“空字符串”和“无数据”无法区分;JSON序列化时还会因零值被忽略(除非加 json:",omitempty"),前端收不到 null。
- 对可能为空的关联字段,结构体用指针类型:
*string、*uint、*time.Time - 指针能明确表达“数据库该列为 NULL”,JSON里自然输出
null - 注意:指针字段在Scan前无需初始化,GORM会自动分配内存;但访问前要判空,避免panic
- 主表必有字段(如
posts.id)仍可用值类型,更安全
软删除表参与JOIN时记录意外丢失
如果关联表(如 users)启用了软删除(deleted_at),用 Joins("LEFT JOIN users ...") 默认会把已软删除的用户过滤掉——GORM在生成SQL时自动加了 AND users.deleted_at IS NULL,LEFT JOIN 变相成了 INNER JOIN。
- 需要保留软删除记录时,必须显式调用
Unscoped():db.Unscoped().Joins(...).Select(...).Scan(&res) -
Unscoped()影响整条链,不只是JOIN部分;若主表也要查软删数据,同样需要 - 慎用:暴露软删数据可能违反业务规则,建议仅在审计、后台管理等明确场景启用
- 不要只给关联表加
Unscoped(),GORM不支持局部取消软删除











