Laravel 6.2关联查询数据缺失:with预加载hasMany关联部分结果为空
问题原因分析
- 关联键类型/格式不匹配
Laravel预加载关联的底层逻辑是:先查询主表(articles)获取所有主键ID,再用whereIn('article_id', [主键数组])查询关联表(comments),最后将关联结果按article_id和主表主键匹配赋值。这个匹配过程是严格类型对比的,比如主表ID是int(2),关联表查出来的article_id是带不可见空格的字符串"2 "、字符串格式的"2",或者反过来主表ID是字符串类型、关联表article_id是int,都会导致匹配失败,关联结果为空。你手动修改article_id后生效,大概率是修改时清除了字段里的不可见字符,或者统一了值的存储格式。多表关联都出现该问题时,大概率是这个类型匹配的共性问题导致的。 - 关联模型存在全局作用域过滤
如果Comment模型开启了软删除,或者你自定义了全局查询作用域(比如只返回审核通过的评论),那些不符合作用域条件的评论会被过滤,导致关联结果为空。 - 模型主键配置错误
Laravel模型默认$primaryKey = 'id'、$keyType = 'int'、$incrementing = true,如果你的articles表主键不是id,或者主键是字符串类型(比如UUID)但没有修改对应配置,会导致预加载时拿不到正确的主键值,匹配失败。 - 关联外键拼写/大小写不匹配
未显式指定外键的关联,如果外键拼写和数据库字段大小写不一致(比如数据库字段是ArticleId,Laravel默认拼接的外键是article_id)也会导致匹配失败。
解决方案
- 统一关联键的类型和存储格式
- 先检查数据库字段类型:确保
articles.id和comments.article_id的字段类型完全一致,推荐统一设为int unsigned,避免一个是int一个是varchar的情况。 - 清理字段里的不可见字符:对关联失败的
article_id执行去空格/特殊字符处理,SQL参考:
UPDATE comments SET article_id = TRIM(article_id) WHERE 筛选你要处理的行;
- 在模型中配置正确的类型转换:给两个模型的关联键加casts配置,强制类型一致:
在Article模型中添加:
protected $casts = [ 'id' => 'integer', ];
在Comment模型中添加:
protected $casts = [ 'article_id' => 'integer', ];
如果主键是字符串类型,要额外修改Article模型的配置:
protected $keyType = 'string'; public $incrementing = false;
- 排查全局作用域
查询时临时关闭所有全局作用域测试,看是否能拿到关联数据:
Article::with(['comments' => function($query) { $query->withoutGlobalScopes(); }])->get();
如果能拿到数据,说明你的全局作用域过滤了评论,调整作用域逻辑即可。
- 打印SQL校验查询结果
打开Laravel的查询日志,打印预加载的两条SQL,拿到SQL手动到数据库执行,确认关联查询返回的article_id是否和主表ID完全一致:
\DB::enableQueryLog(); Article::with('comments')->get(); dd(\DB::getQueryLog());
拿第二条查询comments的SQL手动执行,看返回结果里是否包含对应article_id的评论,以及值是否和主表ID完全一致。
内容的提问来源于stack exchange,提问作者Michel Gisenaël
相关产品推荐
相关产品推荐

