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
相关产品推荐
相关产品推荐

