Laravel动态约束下嵌套预加载实现同编号歌曲自关联查询
实现同number歌曲嵌套预加载方案
基础信息
songs表结构:
| id | version | number |
|---|---|---|
| 1 | AAA | 1 |
| 2 | BBB | 1 |
| 3 | CCC | 1 |
| 4 | DDD | 2 |
| 5 | EEE | 3 |
| 6 | FFF | 4 |
| 7 | GGG | 4 |
需求:为每首歌加载所有number字段值和自身相同的歌曲集合,例如id=1的歌曲number为1,需关联返回id为1、2、3的歌曲。
当前Song模型已有关联定义:
public function songs() { return $this->hasMany(Song::class, 'id_hierarchy'); }
预期效果:直接通过children.songs.sameNumber实现嵌套预加载,无需重复编写子查询逻辑。
方案可行性结论
在Song模型中定义自关联实现sameNumber关联的方案完全可行,是Laravel框架下的标准实现方式,相比临时闭包子查询更易维护、逻辑更清晰,且不会产生N+1查询问题。
具体实现步骤
1. 在Song模型中定义sameNumber自关联
同表内通过number字段匹配同值记录属于一对多自关联,直接在Song模型中添加关联方法即可:
public function sameNumber() { // 关联逻辑:外键为songs表的number字段,关联本地的number字段 return $this->hasMany(Song::class, 'number', 'number'); }
如果不需要把当前歌曲本身包含在关联结果中,可以加条件排除自身id:
public function sameNumber() { return $this->hasMany(Song::class, 'number', 'number') ->where('id', '!=', $this->id); }
2. 直接调用嵌套预加载
关联定义完成后,业务查询代码可以直接使用标准的嵌套预加载写法,无需额外编写闭包子查询:
public function readSongsVersions() { $data = Hierarchy::query() ->whereNull('parent_id') ->with([ 'children.songs', 'children.songs.sameNumber' ]) ->get(); return $data; }
原有闭包写法的问题
你之前尝试的闭包约束写法无法实现预期效果,存在两个核心问题:
- 重复定义了两次
children.songs预加载规则,后者会覆盖前者的加载逻辑 - 子查询存在语法错误(
DB::raw('i.number)')多了闭合括号),且该逻辑仅对songs主查询加过滤条件,无法生成嵌套的关联歌曲集合层级,最终返回结构不符合预期。
性能说明
该自关联预加载由Laravel框架底层自动处理,全程不会产生N+1查询,总共仅执行4类查询:
- 查询parent_id为null的Hierarchy数据
- 查询Hierarchy对应的children数据
- 查询children关联的songs数据
- 通过
where in匹配所有songs对应number值的同组歌曲,即sameNumber关联数据
性能和手写原生子查询一致。
内容的提问来源于stack exchange,提问作者jose
相关产品推荐
相关产品推荐

