Laravel/Eloquent多层关联查询:获取楼宇下所有租户的正确方式
解决Laravel多层关联获取租户的几种方案
这个问题我之前也遇到过,多层关联确实是Eloquent新手容易卡壳的点,结合你的Building→Block→Unit→Tenant层级,我给你整理几种实用的实现方式,你可以根据自己的场景选择:
一、先定义基础模型关联
首先确保你的四个模型都正确定义了关联关系,这是所有操作的基础:
Building模型
class Building extends Model { public function blocks() { return $this->hasMany(Block::class); } }
Block模型
class Block extends Model { public function units() { return $this->hasMany(Unit::class); } public function building() { return $this->belongsTo(Building::class); } }
Unit模型
class Unit extends Model { public function tenants() { return $this->hasMany(Tenant::class); } public function block() { return $this->belongsTo(Block::class); } }
Tenant模型
class Tenant extends Model { public function unit() { return $this->belongsTo(Unit::class); } }
二、具体实现方案
1. 嵌套加载+集合扁平化(适合小数据集)
这种方式最直观,先加载所有层级的关联数据,再通过集合方法把租户提取出来:
// 获取指定楼宇及其所有关联的租户 $building = Building::with('blocks.units.tenants')->find($buildingId); // 用flatMap多层扁平化,得到所有租户的集合 $tenants = $building->blocks->flatMap(function ($block) { return $block->units->flatMap(fn($unit) => $unit->tenants); });
优点:代码简单,容易理解;缺点:会加载所有Block和Unit模型实例,数据量大时性能较差。
2. 直接SQL JOIN查询(性能最优)
如果追求极致性能,直接用查询构建器做关联查询,跳过中间模型的加载:
$tenants = Tenant::query() ->join('units', 'units.id', '=', 'tenants.unit_id') ->join('blocks', 'blocks.id', '=', 'units.block_id') ->where('blocks.building_id', $buildingId) ->select('tenants.*') // 只选择租户表的字段,避免字段冲突 ->get();
优点:直接生成高效的SQL查询,性能最好;缺点:代码可读性稍差,字段关联需要自己维护。
3. 嵌套whereHas查询(优雅且平衡性能)
用Eloquent的whereHas方法嵌套检查关联条件,代码优雅且性能不错:
// 先在Building模型里添加自定义关联方法 public function tenants() { return Tenant::whereHas('unit.block', function ($query) { $query->where('building_id', $this->id); }); } // 使用时直接调用 $tenants = Building::find($buildingId)->tenants()->get();
优点:代码简洁易读,不需要手动写JOIN;缺点:底层是用EXISTS子查询,性能略逊于直接JOIN,但大多数场景足够用。
4. 第三方包实现多层HasManyThrough(适合复杂多层关联)
如果你的项目经常需要处理多层关联,可以用第三方包staudenmeir/eloquent-has-many-deep,它扩展了Eloquent支持无限层级的关联:
- 先安装包:
composer require staudenmeir/eloquent-has-many-deep - 在Building模型里定义关联:
use Staudenmeir\EloquentHasManyDeep\HasManyDeep; public function tenants(): HasManyDeep { return $this->hasManyDeep( Tenant::class, [Block::class, Unit::class], // 中间模型层级,顺序从近到远 [ 'building_id', // Block表关联Building的外键 'block_id', // Unit表关联Block的外键 'unit_id' // Tenant表关联Unit的外键 ], [ 'id', // Building表的主键 'id', // Block表的主键 'id' // Unit表的主键 ] ); } // 使用时直接像普通关联一样调用 $tenants = Building::find($buildingId)->tenants;
优点:完全符合Eloquent的使用习惯,支持所有关联操作(比如分页、排序);缺点:依赖第三方包,需要额外维护。
内容的提问来源于stack exchange,提问作者paishin
相关产品推荐
相关产品推荐

