Laravel+MySQL中如何为枚举dayid设置唯一键并校验时段冲突
医生排班约束规则Laravel实现方案
两类约束分别通过数据库层索引+业务层校验组合实现,兼顾可靠性和性能,具体逻辑如下:
1. 唯一值约束实现
同一医生、同一星期(dayid)、同一开始时间仅允许存在一条排班记录,直接通过数据库复合唯一索引实现,从底层拦截重复写入,比纯代码校验更可靠,不会因为并发请求漏拦截。
注意:唯一索引必须包含
dr_id字段,否则会拦截不同医生在同一天同一时间的排班,不符合业务逻辑。
2. 时段重叠约束实现
时段重叠无法通过数据库原生索引实现,需要在写入数据前做逻辑校验,核心判断规则为:
两个时段存在重叠的充要条件是:已有时段的开始时间 < 新时段的结束时间,且新时段的开始时间 < 已有时段的结束时间
该逻辑可以覆盖所有交叉场景:新时段包含旧时段、旧时段包含新时段、首尾部分交叉,同时自动放行首尾相接的时段(例如旧时段9:00-10:00,新时段10:00-11:00不会被判定为冲突)。
校验逻辑代码示例:
// 传入参数:医生ID、星期值、新时段开始时间、新时段结束时间、编辑时排除的当前记录ID(默认null) $hasConflict = DoctorSchedule::query() ->where('dr_id', $drId) ->where('dayid', $dayId) ->where(function ($query) use ($startTime, $endTime) { $query->where('starttime', '<', $endTime) ->where('endtime', '>', $startTime); }) // 编辑排班时排除自身,避免把当前记录判定为冲突 ->when($excludeId, fn($q) => $q->where('id', '!=', $excludeId)) ->exists(); if ($hasConflict) { throw ValidationException::withMessages([ 'starttime' => '所选时段与已有排班存在重叠,请调整后提交' ]); }
为了提升冲突查询的性能,可以给dr_id和dayid加联合普通索引。
3. 调整后的完整迁移代码
Schema::create('doctorschedules', function (Blueprint $table) { $table->id(); $table->enum('dayid', ["1","2","3","4","5","6","7"])->comment('1=周日,2=周一,依此类推7=周六'); $table->time('starttime'); $table->time('endtime'); $table->unsignedBigInteger('dr_id'); $table->foreign('dr_id')->references('id')->on('doctors'); // 复合唯一索引:实现同医生同天同开始时间唯一约束 $table->unique(['dr_id', 'dayid', 'starttime'], 'unique_doctor_day_start'); // 联合索引:加快时段冲突查询速度 $table->index(['dr_id', 'dayid'], 'idx_doctor_day_schedule'); $table->timestamps(); });
执行迁移前记得先删除原有测试表,避免索引创建失败。
4. 可选优化:封装复用校验逻辑
如果多处需要用到时段冲突校验,可以生成Laravel自定义验证规则类,通过php artisan make:rule ScheduleNoOverlap创建规则,把冲突判断逻辑写到规则的passes方法中,后续在表单验证中直接调用即可,示例用法:
public function rules() { $currentScheduleId = $this->route('schedule')?->id; // 编辑场景获取当前记录ID return [ 'dr_id' => 'required|exists:doctors,id', 'dayid' => 'required|in:1,2,3,4,5,6,7', 'starttime' => ['required', 'date_format:H:i', new ScheduleNoOverlap($this->dr_id, $this->dayid, $this->endtime, $currentScheduleId)], 'endtime' => 'required|date_format:H:i|after:starttime', ]; }
注意事项
- 必须加上
endtime > starttime的基础校验,反向时段会导致冲突判断逻辑失效 - 不要删除数据库层的唯一索引,即使代码层做了重复判断,并发场景下仍可能出现脏数据
- 冲突判断不要用
<=或>=符号,否则会误拦截首尾相接的合法排班
内容的提问来源于stack exchange,提问作者hjking01072003
相关产品推荐
相关产品推荐

