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

Laravel中学校多对多关联双向查询及去重方案

实现学校关联的双向查询方案

要基于现有单向存储的关联记录实现双向查询,核心是让每个学校既能查到自己作为school_id关联的学校,也能查到自己作为related_school_id被关联的学校,以下是具体实现方式:

1. 完善模型关联定义

首先在SchoolRelation模型中定义与School的双向关联:

class SchoolRelation extends Model
{
    // 关联主学校
    public function school()
    {
        return $this->belongsTo(School::class, 'school_id');
    }

    // 关联相关学校
    public function relatedSchool()
    {
        return $this->belongsTo(School::class, 'related_school_id');
    }
}

接着在School模型中定义两个基础关联,分别对应自己作为主学校和被关联学校的记录,再通过自定义属性或方法合并结果:

方式一:通过访问器合并关联集合

这种方式会直接返回合并后的学校集合,使用起来更直观:

class School extends Model
{
    // 作为主学校的关联记录
    public function relationsAsMain()
    {
        return $this->hasMany(SchoolRelation::class, 'school_id')->with('relatedSchool');
    }

    // 作为被关联学校的关联记录
    public function relationsAsRelated()
    {
        return $this->hasMany(SchoolRelation::class, 'related_school_id')->with('school');
    }

    // 自定义访问器,获取所有关联学校
    public function getRelatedSchoolsAttribute()
    {
        // 提取两种关联下的学校实例
        $fromMain = $this->relationsAsMain->pluck('relatedSchool');
        $fromRelated = $this->relationsAsRelated->pluck('school');

        // 合并、去重并排除自身
        return $fromMain->merge($fromRelated)
            ->unique('id')
            ->filter(fn($school) => $school->id !== $this->id);
    }
}

使用时直接调用访问器即可:

$school = School::find(16);
$allRelatedSchools = $school->relatedSchools; // 会返回15和17

方式二:通过关联查询直接获取

这种方式利用Eloquent的关联查询能力,直接从数据库层面筛选出所有关联学校:

class School extends Model
{
    // 作为主学校的关联记录
    public function relationsAsMain()
    {
        return $this->hasMany(SchoolRelation::class, 'school_id');
    }

    // 作为被关联学校的关联记录
    public function relationsAsRelated()
    {
        return $this->hasMany(SchoolRelation::class, 'related_school_id');
    }

    // 获取所有关联学校的查询构造器
    public function allRelatedSchools()
    {
        return School::whereHas('relationsAsRelated', fn($query) => $query->where('school_id', $this->id))
            ->orWhereHas('relationsAsMain', fn($query) => $query->where('related_school_id', $this->id))
            ->where('id', '!=', $this->id);
    }
}

使用时可以链式调用查询方法:

$school = School::find(17);
$allRelatedSchools = $school->allRelatedSchools()->get(); // 返回15和16

2. 优化:避免重复存储关联记录(可选)

为了彻底防止双向重复存储,可在创建关联时强制school_id小于related_school_id,确保每条关联只存一次:

方式一:模型事件处理

在SchoolRelation模型中添加创建事件监听:

class SchoolRelation extends Model
{
    protected static function booted()
    {
        static::creating(function ($relation) {
            // 如果school_id大于related_school_id,交换两者
            if ($relation->school_id > $relation->related_school_id) {
                [$relation->school_id, $relation->related_school_id] = [$relation->related_school_id, $relation->school_id];
            }
        });
    }

    // 其他关联方法...
}

方式二:数据库层面约束(MySQL 8.0+支持)

在迁移文件中添加检查约束和唯一索引:

Schema::create('school_relations', function (Blueprint $table) {
    $table->id();
    $table->unsignedBigInteger('school_id');
    $table->foreign('school_id')->references('id')->on('schools');
    $table->unsignedBigInteger('related_school_id');
    $table->foreign('related_school_id')->references('id')->on('schools');
    $table->timestamps();

    // 添加唯一索引,防止重复存储
    $table->unique(['school_id', 'related_school_id']);
    // 添加检查约束,强制school_id < related_school_id(MySQL 8.0+支持)
    $table->check('school_id < related_school_id');
});

内容的提问来源于stack exchange,提问作者Austin Sanga

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 15:45:30