导读(3行收益):培训机构排课最常见的三个事故:教师时间撞车、教室重复占用、学员被排进冲突时段。本文给出教室/教师/时段三类资源的建模表与冲突检测规则,附可直接抄走的检测 SQL 和排课检查清单。看完能直接改进你的排课模块。
一、排课为什么总撞车
三个高频事故:
- 教师撞车:同一位老师同一时间被排了两节课,上课前才发现;
- 教室撞车:两个班被排进同一间教室,临时换教室;
- 时段冲突:学员已报名的班和新排的课时间重叠,家长投诉。
根源是排课只记了「课表」,没有先做「占用检查」:先落课、再发现冲突,冲突就成了常态。正确做法是排课前先查三类资源的占用,冲突则拒绝或提示调换。
二、资源建模:把「能不能排」变成一条 SQL
把教室、教师、时段建模成占用表,核心是「资源 + 时间区间 + 占用状态」:
CREATE TABLE `schedule_slot` (
`id` BIGINT PRIMARY KEY,
`resource_type` TINYINT NOT NULL COMMENT '1教师 2教室 3学员',
`resource_id` BIGINT NOT NULL,
`course_id` BIGINT NOT NULL,
`weekday` TINYINT NOT NULL COMMENT '1-7',
`start_time` TIME NOT NULL,
`end_time` TIME NOT NULL,
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '1占用中 0已释放',
KEY `idx_res` (`resource_type`, `resource_id`, `weekday`)
) COMMENT='排课占用表';
排课前查教师是否空闲:
SELECT COUNT(*) FROM schedule_slot
WHERE resource_type = 1 AND resource_id = 101
AND weekday = 3 AND status = 1
AND start_time < '16:00' AND end_time > '15:00';
命中大于 0 说明该教师周三 15:00-16:00 已被占用,直接拒绝。教室、学员同理,只需换 resource_type。
三、冲突检测:区间重叠的四条规则
时间区间重叠检测,记住「开始小于对方结束、结束大于对方开始」即可覆盖所有重叠形态:
| 新课区间 | 已有区间 | 是否冲突 |
|---|---|---|
| 15:00-16:00 | 14:30-15:30 | 冲突(尾部重叠) |
| 15:00-16:00 | 15:30-16:30 | 冲突(头部重叠) |
| 15:00-16:00 | 14:00-17:00 | 冲突(完全包含) |
| 15:00-16:00 | 16:00-17:00 | 不冲突(恰好衔接) |
边界注意:结束时间等于开始时间不冲突(15:00-16:00 与 16:00-17:00 可连排),SQL 里用 start_time < 新.end_time AND end_time > 新.start_time 判断,而不是 <=/>=。
四、排课流程:先查占用,再落课,失败即调换
def schedule(course, teacher, room, weekday, start, end):
conflicts = check_conflict(teacher, weekday, start, end)
if conflicts:
alt = find_free_slot(teacher, weekday) # 找教师空闲时段
if alt:
return schedule(course, teacher, room, alt[0], alt[1], alt[2])
raise ScheduleConflict("教师无空闲时段,需人工处理")
insert_slot("teacher", teacher.id, course, weekday, start, end)
insert_slot("room", room.id, course, weekday, start, end)
insert_slot("student", course.student_group, course, weekday, start, end)
要点:三类资源占用一次事务写入,任一失败全部回滚,避免出现「教室占了、教师没占上」的中间状态;调课/改期同样走这条检测,先释放旧占用再写入新占用。批量排课(一次导入整周课表)也建议逐条走同一套检测,检测失败的行单独标出,而不是整批回滚或整批忽略——把「可自动处理的」和「需要人工的」分开,运营处理成本最低。
五、调课与改期的再校验
调课最容易漏掉的是学员侧冲突:教师、教室查了,忘了学员已有课程。调课流程固定四步:
- 查教师新时段是否空闲;
- 查教室新时段是否空闲;
- 查该班学员是否有其他课落在新时段;
- 三者都通过才执行「释放旧占用 + 写入新占用」。
不同课型还要区别对待:固定班课(整班固定课表)冲突检测以班为单位,一检出冲突整班提示;一对一/小组课(学员自由约课)冲突检测要细化到每个学员,同一时段的空位是有限的,超员即冲突。两种课型建议用同一张占用表、不同资源粒度(班 vs 学员)实现,避免为每种课型各写一套排课逻辑,后期维护成本翻倍。
六、踩坑清单
- [ ] 只查教师不查教室,或反过来;
- [ ] 用
<=/>=判断区间重叠,把恰好衔接的课判成冲突; - [ ] 调课只释放旧占用、不检查新时段占用;
- [ ] 忘了学员侧时段冲突;
- [ ] 占用写入不放在事务里,出现半写状态;
- [ ] 取消课程后占用记录没释放,占着资源排不出去;
- [ ] 没有「先查占用再落课」的强制入口,绕过检测直接写课表。
七、工程落地建议
排课模块建议先做「占用表 + 冲突检测」这一层,再逐步加自动排课与调课提醒。若团队没有现成排课能力,可基于成型平台(如乔拓云教育系统)的课表与排课管理快速起步,重点核对资源占用与冲突校验逻辑。
八、复盘清单(可直接抄走)
- [ ] 三类资源(教师/教室/学员)是否都有占用记录;
- [ ] 区间重叠判断是否用「开始<对方结束 且 结束>对方开始」;
- [ ] 调课是否固定走「三查 + 事务写入」;
- [ ] 取消/结课是否及时释放占用。
结语
排课撞车的根源不是算法复杂,而是「没查占用就落课」。建模好三类资源、把冲突检测做成固定规则、调课强制再校验,撞车问题就能从偶发变成不可能。本文仅作技术分享,具体功能以各平台官方实时信息为准。