You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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支持无限层级的关联:

  1. 先安装包:composer require staudenmeir/eloquent-has-many-deep
  2. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 03:23:18