Laravel Eloquent多嵌套表关联:自定义字段与性能优化问题
问题详情
现有模型A、B、C、D及A的类型表A_TYPE,D表含another_system_id字段。需获取关联指定another_system_id的A实体,同时选取A、A_TYPE、D表的指定字段(如foreign_system_id、another_entity_id等)。各模型关联定义如下:
模型关联代码
Model A
public function bs() { return $this->hasMany(B::class); }
public function b_type() { return $this->belongsTo(BType::class); }
Model B
public function cs() { return $this->hasMany(C::class); }
Model C与D的自定义关联
public function system_source() { return $this->hasOne(D::class, 'id', 'datasource_object_id'); }
使用A::with('B.C.system_source')->whereHas(...)查询时,生成的SQL采用多层exists子查询,性能比手写JOIN慢一倍,且get()->count()返回0,但替换参数后手动执行对应SQL有结果。手写的正确SQL如下:
SELECT d.form_id, d.question_id, a.id, a.`name`, a.parent_id, at.`name` as 'A Type' FROM a JOIN a_types at ON a.a_type_id = at.id JOIN b on b.a_id = a.id JOIN c on c.b_id = b.id JOIN d on d.id = c.datasource_object_id WHERE d.another_system_id in (9576, 9575);
当前需解决两个问题:
- Eloquent中如何自定义选择所需字段;
- 如何让Eloquent生成类似手写的高效JOIN查询,替代低效的
exists查询。
解决方案
1. 自定义选择所需字段
Eloquent中通过select()方法指定要查询的字段,关联表的字段需用表别名或完整表名区分,避免字段冲突。结合JOIN查询的示例写法:
$results = A::select( 'a.id', 'a.name', 'a.parent_id', 'at.name as A_Type', 'd.form_id', 'd.question_id' ) ->join('a_types as at', 'a.a_type_id', '=', 'at.id') ->join('b', 'b.a_id', '=', 'a.id') ->join('c', 'c.b_id', '=', 'b.id') ->join('d', 'd.id', '=', 'c.datasource_object_id') ->whereIn('d.another_system_id', [9576, 9575]) ->get();
若需保留Eloquent模型特性(如后续调用模型方法),可选择主表所有字段,再附加关联表所需字段:
$results = A::select('a.*', 'at.name as A_Type', 'd.form_id', 'd.question_id') ->join('a_types as at', 'a.a_type_id', '=', 'at.id') ->join('b', 'b.a_id', '=', 'a.id') ->join('c', 'c.b_id', '=', 'b.id') ->join('d', 'd.id', '=', 'c.datasource_object_id') ->whereIn('d.another_system_id', [9576, 9575]) ->get();
2. 生成高效JOIN查询替代exists
whereHas()默认生成多层exists子查询,关联层级多、数据量大时性能损耗明显。直接使用join()方法可生成与手写SQL一致的JOIN语句,规避多层子查询的性能问题。
注意事项:
- 确保JOIN关联条件与模型定义的关联逻辑完全匹配(如C和D的关联为
d.id = c.datasource_object_id); - 一对多关联(如A hasMany B)JOIN后可能出现重复A记录,需用
distinct()去重:
$results = A::select('a.*', 'at.name as A_Type', 'd.form_id', 'd.question_id') ->join('a_types as at', 'a.a_type_id', '=', 'at.id') ->join('b', 'b.a_id', '=', 'a.id') ->join('c', 'c.b_id', '=', 'b.id') ->join('d', 'd.id', '=', 'c.datasource_object_id') ->whereIn('d.another_system_id', [9576, 9575]) ->distinct() ->get();
- 统计符合条件的A实体数量时,使用
count('distinct a.id')避免重复计数:
$count = A::join('a_types as at', 'a.a_type_id', '=', 'at.id') ->join('b', 'b.a_id', '=', 'a.id') ->join('c', 'c.b_id', '=', 'b.id') ->join('d', 'd.id', '=', 'c.datasource_object_id') ->whereIn('d.another_system_id', [9576, 9575]) ->count('distinct a.id');
内容的提问来源于stack exchange,提问作者Carmageddon
相关产品推荐
相关产品推荐

