chore(sync): 同步当前工作区全部变更(学员学号可编辑并留空自动生成等)
This commit is contained in:
1 parent
01e949093a
commit
ee629f2f3e
534 files changed
+72978
-2720
No files matched your search
@@ -6,8 +6,8 @@
|
||||
BEGIN;
|
||||
|
||||
-- 1. 教师主档
|
||||
UPDATE public.train_teacher SET org_name = '沈阳房建公寓段安全培训科', job_title = '首席技师/教授级高工', t_phone = '18800000000', t_email = 'zhaodm@sict.railway.cn', status = 1, del_flag = '0' WHERE t_id = 'T001';
|
||||
INSERT INTO public.train_teacher (t_id, t_name, t_gender, t_birth, org_name, job_title, t_phone, t_email, status, del_flag, user_id, create_by, create_time, remark)
|
||||
UPDATE public.train_teacher SET dept_name = '沈阳房建公寓段安全培训科', job_title = '首席技师/教授级高工', t_phone = '18800000000', t_email = 'zhaodm@sict.railway.cn', status = 1, del_flag = '0' WHERE t_id = 'T001';
|
||||
INSERT INTO public.train_teacher (t_id, t_name, t_gender, t_birth, dept_name, job_title, t_phone, t_email, status, del_flag, user_id, create_by, create_time, remark)
|
||||
VALUES
|
||||
('T101', '崔宏伟', '0', '1982-05-14', '沈阳房建公寓段安全培训科', '高级工程师', '18800000011', 'cuihw@sict.railway.cn', 1, '0', 11, 'admin', CURRENT_TIMESTAMP, '演示主讲教师(绑定账号demo_teacher)'),
|
||||
('T102', '张建国', '0', '1985-11-20', '沈阳房建公寓段技术科', '副高级工程师', '18800000012', 'zhangjg@sict.railway.cn', 1, '0', 12, 'admin', CURRENT_TIMESTAMP, '技术专家讲师(绑定账号demo_teacher2)'),
|
||||
@@ -16,7 +16,7 @@ VALUES
|
||||
('T105', '陈广林', '0', '1976-12-09', '大连项目部', '特级技师', '18800000015', 'chengl@sict.railway.cn', 1, '0', NULL, 'admin', CURRENT_TIMESTAMP, '防汛抢险实训专家'),
|
||||
('T106', '马志强', '0', '1990-07-18', '丹东项目部', '工程师', '18800000016', 'mazhq@sict.railway.cn', 1, '0', NULL, 'admin', CURRENT_TIMESTAMP, '电气防火与智能化安全监控讲师')
|
||||
ON CONFLICT (t_id) DO UPDATE SET
|
||||
t_name = EXCLUDED.t_name, org_name = EXCLUDED.org_name, job_title = EXCLUDED.job_title, user_id = EXCLUDED.user_id, status = EXCLUDED.status, del_flag = '0';
|
||||
t_name = EXCLUDED.t_name, dept_name = EXCLUDED.dept_name, job_title = EXCLUDED.job_title, user_id = EXCLUDED.user_id, status = EXCLUDED.status, del_flag = '0';
|
||||
|
||||
-- 2. 课程标准库
|
||||
INSERT INTO public.train_course (c_id, c_name, c_code, c_type, c_duration, teaching_objective, course_content, status, del_flag, create_by, create_time, remark)
|
||||
@@ -86,7 +86,7 @@ ON CONFLICT (dorm_id) DO UPDATE SET
|
||||
dorm_building = EXCLUDED.dorm_building, dorm_floor = EXCLUDED.dorm_floor, dorm_room = EXCLUDED.dorm_room, capacity = EXCLUDED.capacity, dorm_status = EXCLUDED.dorm_status, del_flag = '0';
|
||||
|
||||
-- 7. 学员主档 (35名)
|
||||
INSERT INTO public.train_student (s_id, s_name, s_gender, s_birth, org_name, s_phone, s_email, id_card_hash, id_card_last4, source_type, status, del_flag, user_id, create_by, create_time, remark)
|
||||
INSERT INTO public.train_student (s_id, s_name, s_gender, s_birth, dept_name, s_phone, s_email, id_card_hash, id_card_last4, source_type, status, del_flag, user_id, create_by, create_time, remark)
|
||||
VALUES
|
||||
('S101', '李强', '0', '1990-01-01', '沈阳房建公寓段沈阳车间', '18800000101', 's101@sict.railway.cn', '57ecc1436745329b620ccbfaebbed22cda4ba9b33ec44de468d2e3acd61f63c2', '1000', 1, 1, '0', 21, 'admin', CURRENT_TIMESTAMP, '演示学员样本1'),
|
||||
('S102', '王芳', '1', '1991-02-02', '沈阳房建公寓段大连车间', '18800000102', 's102@sict.railway.cn', 'f6d29eac73f7ea39158b909683cc36f50a2ea9298be8a453bf8922488e499b29', '1001', 1, 1, '0', 22, 'admin', CURRENT_TIMESTAMP, '演示学员样本2'),
|
||||
@@ -124,7 +124,7 @@ VALUES
|
||||
('S134', '吕梁', '1', '1991-10-06', '沈阳房建公寓段安全培训科', '18800000134', 's134@sict.railway.cn', '65a78d3839aef33f4059ea214e8faac94125c6c82cfa3001580fa208430b0ad3', '1033', 1, 1, '0', NULL, 'admin', CURRENT_TIMESTAMP, '演示学员样本34'),
|
||||
('S135', '卢伟', '0', '1992-11-07', '沈阳房建公寓段技术科', '18800000135', 's135@sict.railway.cn', '2df5ae45334850de9e4ba87333f2388e2b71f5d75ead96fdd7c8734dd73f7147', '1034', 1, 1, '0', NULL, 'admin', CURRENT_TIMESTAMP, '演示学员样本35')
|
||||
ON CONFLICT (s_id) DO UPDATE SET
|
||||
s_name = EXCLUDED.s_name, org_name = EXCLUDED.org_name, s_phone = EXCLUDED.s_phone, s_email = EXCLUDED.s_email,
|
||||
s_name = EXCLUDED.s_name, dept_name = EXCLUDED.dept_name, s_phone = EXCLUDED.s_phone, s_email = EXCLUDED.s_email,
|
||||
id_card_hash = EXCLUDED.id_card_hash, id_card_last4 = EXCLUDED.id_card_last4, user_id = EXCLUDED.user_id, status = EXCLUDED.status, del_flag = '0';
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,390 @@
|
||||
-- ==============================================================================
|
||||
-- RailwaySystem 演示/测试数据: 005_demo_theory_exam_score.sql
|
||||
-- 目标表: train_theory_exam_score(班级学员理论考试成绩表,先执行 026 建表脚本)
|
||||
-- 数据库: MySQL 5.7+(脚本使用 INSERT ... SELECT / INSERT IGNORE / 派生表,5.7 完全兼容)
|
||||
-- ==============================================================================
|
||||
-- 一、依赖的既有数据(脚本不新建,全部从库中实时读取,保证外键与业务一致性)
|
||||
-- train_student(s_id) —— 学员主档
|
||||
-- train_class_plan(plan_id) —— 开班计划
|
||||
-- train_plan_course(plan_course_id) —— 培训班所开课程实例(本脚本的核心关联依据)
|
||||
-- train_enrollment(enrollment_id) —— 参训档案(学员 × 班级)
|
||||
-- train_course(c_id) —— 课程主档(由 train_plan_course 带出)
|
||||
-- train_exam_subject(subject_code) —— 科目字典(本脚本不写入,subject_code 置 NULL,避免无谓依赖)
|
||||
-- train_score_item(score_item_id) —— 综合成绩项(本脚本不写入,score_item_id 置 NULL)
|
||||
--
|
||||
-- 二、生成逻辑
|
||||
-- · 每一行 = 一个 (参训档案 enrollment_id, 班级课程 plan_course_id, 考试次数 retake_no) 组合,
|
||||
-- 因此天然满足唯一键 uk_theory_enroll_course_retake;
|
||||
-- · 同一学员同一班级若存在多条参训档案,取 MIN(enrollment_id) 去重,
|
||||
-- 避免触发 uk_theory_final(每课程每学员仅一条最终成绩);
|
||||
-- · 使用 ORDER BY + LIMIT/OFFSET 把"学员×课程"笛卡尔集切成互不重叠的区间,
|
||||
-- 分别构造正常、满分、零分、缺考、免修、补考、锁定/归档等场景,区间之间不会撞唯一键;
|
||||
-- · 使用 INSERT IGNORE,重复执行不会报错也不会产生重复数据;
|
||||
-- · 若库中可用的"学员×课程"组合不足,靠后的场景会少插或插 0 行(不报错),
|
||||
-- 执行前可先跑"第 0 步"的体检 SQL 查看可用组合数。
|
||||
--
|
||||
-- 三、字段写入约定
|
||||
-- enrollment_id / plan_id / student_id ← train_enrollment
|
||||
-- plan_course_id / c_id ← train_plan_course
|
||||
-- exam_start_time / exam_end_time ← NOW() 回溯 1~30 天,开始时间比结束早 90 分钟
|
||||
-- duration_seconds ← 5400(90 分钟)
|
||||
-- retake / retake_no ← 正考 0/0;补考 1/1、1/2
|
||||
-- exam_status ← NORMAL 正常 / ABSENT 缺考 / EXEMPT 免修
|
||||
-- score ← 正常考试 0~满分;缺考/免修必须为 NULL(CHECK 约束要求)
|
||||
-- full_score ← 100.00
|
||||
-- pass_flag ← 60 分及格(与后端 60% 默认判定一致)
|
||||
-- is_final ← 正考/补考最终成绩为 1,过程性补考为 0
|
||||
-- score_status ← RECEIVED / ARCHIVED / LOCKED
|
||||
-- source_type ← 1 手工录入 / 3 批量或外部导入(演示来源差异)
|
||||
-- del_flag ← '0'
|
||||
-- create_by ← 'test-data'(便于识别与清理)
|
||||
-- ==============================================================================
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
-- ------------------------------------------------------------------------------
|
||||
-- 第 0 步:体检(只读,不影响数据)
|
||||
-- 查看当前可用的"学员 × 课程"组合数量,决定各场景实际能插入多少行
|
||||
-- ------------------------------------------------------------------------------
|
||||
SELECT COUNT(*) AS available_student_course_pairs
|
||||
FROM (
|
||||
SELECT e.student_id, pc.plan_course_id
|
||||
FROM (SELECT student_id, plan_id, MIN(enrollment_id) AS enrollment_id
|
||||
FROM train_enrollment
|
||||
GROUP BY student_id, plan_id) e
|
||||
JOIN train_plan_course pc ON pc.plan_id = e.plan_id
|
||||
JOIN train_class_plan cp ON cp.plan_id = e.plan_id
|
||||
) p;
|
||||
|
||||
-- ==============================================================================
|
||||
-- 场景 1:正常分布(正考 0 次补考、成绩 60~100、通过、最终成绩)
|
||||
-- 覆盖区间:偏移 0 ~ 11,共 12 行
|
||||
-- ==============================================================================
|
||||
INSERT IGNORE INTO train_theory_exam_score
|
||||
(enrollment_id, plan_id, plan_course_id, c_id, student_id, subject_code,
|
||||
exam_start_time, exam_end_time, duration_seconds, retake, retake_no,
|
||||
exam_status, score, full_score, pass_flag, is_final, score_status,
|
||||
source_type, source_system, source_event_id, score_item_id, remark,
|
||||
del_flag, create_by, create_time)
|
||||
SELECT t.enrollment_id, t.plan_id, t.plan_course_id, t.c_id, t.student_id, NULL,
|
||||
DATE_SUB(t.end_time, INTERVAL 90 MINUTE), t.end_time, 5400, 0, 0,
|
||||
'NORMAL',
|
||||
CAST(60 + MOD(CRC32(CONCAT(t.enrollment_id, '-', t.plan_course_id)), 41) AS DECIMAL(7,2)),
|
||||
100.00, 1, 1, 'RECEIVED',
|
||||
1, NULL, NULL, NULL, '测试数据-正常通过(60~100分)',
|
||||
'0', 'test-data', NOW()
|
||||
FROM (
|
||||
SELECT e.enrollment_id, e.plan_id, e.student_id, pc.plan_course_id, pc.c_id,
|
||||
DATE_SUB(NOW(), INTERVAL (MOD(CRC32(CONCAT(e.enrollment_id, '-', pc.plan_course_id)), 30) + 1) DAY) AS end_time
|
||||
FROM (SELECT student_id, plan_id, MIN(enrollment_id) AS enrollment_id
|
||||
FROM train_enrollment
|
||||
GROUP BY student_id, plan_id) e
|
||||
JOIN train_plan_course pc ON pc.plan_id = e.plan_id
|
||||
JOIN train_class_plan cp ON cp.plan_id = e.plan_id
|
||||
ORDER BY e.enrollment_id, pc.plan_course_id
|
||||
LIMIT 12 OFFSET 0
|
||||
) t;
|
||||
|
||||
-- ==============================================================================
|
||||
-- 场景 2:边界值-满分(score = full_score = 100.00)
|
||||
-- 覆盖区间:偏移 12 ~ 15,共 4 行
|
||||
-- ==============================================================================
|
||||
INSERT IGNORE INTO train_theory_exam_score
|
||||
(enrollment_id, plan_id, plan_course_id, c_id, student_id, subject_code,
|
||||
exam_start_time, exam_end_time, duration_seconds, retake, retake_no,
|
||||
exam_status, score, full_score, pass_flag, is_final, score_status,
|
||||
source_type, source_system, source_event_id, score_item_id, remark,
|
||||
del_flag, create_by, create_time)
|
||||
SELECT t.enrollment_id, t.plan_id, t.plan_course_id, t.c_id, t.student_id, NULL,
|
||||
DATE_SUB(t.end_time, INTERVAL 90 MINUTE), t.end_time, 5400, 0, 0,
|
||||
'NORMAL', CAST(100.00 AS DECIMAL(7,2)), 100.00, 1, 1, 'RECEIVED',
|
||||
1, NULL, NULL, NULL, '测试数据-边界值:满分100',
|
||||
'0', 'test-data', NOW()
|
||||
FROM (
|
||||
SELECT e.enrollment_id, e.plan_id, e.student_id, pc.plan_course_id, pc.c_id,
|
||||
DATE_SUB(NOW(), INTERVAL (MOD(CRC32(CONCAT(e.enrollment_id, '-', pc.plan_course_id)), 30) + 1) DAY) AS end_time
|
||||
FROM (SELECT student_id, plan_id, MIN(enrollment_id) AS enrollment_id
|
||||
FROM train_enrollment
|
||||
GROUP BY student_id, plan_id) e
|
||||
JOIN train_plan_course pc ON pc.plan_id = e.plan_id
|
||||
JOIN train_class_plan cp ON cp.plan_id = e.plan_id
|
||||
ORDER BY e.enrollment_id, pc.plan_course_id
|
||||
LIMIT 4 OFFSET 12
|
||||
) t;
|
||||
|
||||
-- ==============================================================================
|
||||
-- 场景 3:边界值-零分(score = 0.00,未通过)
|
||||
-- 覆盖区间:偏移 16 ~ 19,共 4 行
|
||||
-- ==============================================================================
|
||||
INSERT IGNORE INTO train_theory_exam_score
|
||||
(enrollment_id, plan_id, plan_course_id, c_id, student_id, subject_code,
|
||||
exam_start_time, exam_end_time, duration_seconds, retake, retake_no,
|
||||
exam_status, score, full_score, pass_flag, is_final, score_status,
|
||||
source_type, source_system, source_event_id, score_item_id, remark,
|
||||
del_flag, create_by, create_time)
|
||||
SELECT t.enrollment_id, t.plan_id, t.plan_course_id, t.c_id, t.student_id, NULL,
|
||||
DATE_SUB(t.end_time, INTERVAL 90 MINUTE), t.end_time, 5400, 0, 0,
|
||||
'NORMAL', CAST(0.00 AS DECIMAL(7,2)), 100.00, 0, 1, 'RECEIVED',
|
||||
1, NULL, NULL, NULL, '测试数据-边界值:零分',
|
||||
'0', 'test-data', NOW()
|
||||
FROM (
|
||||
SELECT e.enrollment_id, e.plan_id, e.student_id, pc.plan_course_id, pc.c_id,
|
||||
DATE_SUB(NOW(), INTERVAL (MOD(CRC32(CONCAT(e.enrollment_id, '-', pc.plan_course_id)), 30) + 1) DAY) AS end_time
|
||||
FROM (SELECT student_id, plan_id, MIN(enrollment_id) AS enrollment_id
|
||||
FROM train_enrollment
|
||||
GROUP BY student_id, plan_id) e
|
||||
JOIN train_plan_course pc ON pc.plan_id = e.plan_id
|
||||
JOIN train_class_plan cp ON cp.plan_id = e.plan_id
|
||||
ORDER BY e.enrollment_id, pc.plan_course_id
|
||||
LIMIT 4 OFFSET 16
|
||||
) t;
|
||||
|
||||
-- ==============================================================================
|
||||
-- 场景 4:缺考(ABSENT,score 必须为 NULL,pass_flag=0,仍作为该课程最终成绩)
|
||||
-- 覆盖区间:偏移 20 ~ 23,共 4 行
|
||||
-- ==============================================================================
|
||||
INSERT IGNORE INTO train_theory_exam_score
|
||||
(enrollment_id, plan_id, plan_course_id, c_id, student_id, subject_code,
|
||||
exam_start_time, exam_end_time, duration_seconds, retake, retake_no,
|
||||
exam_status, score, full_score, pass_flag, is_final, score_status,
|
||||
source_type, source_system, source_event_id, score_item_id, remark,
|
||||
del_flag, create_by, create_time)
|
||||
SELECT t.enrollment_id, t.plan_id, t.plan_course_id, t.c_id, t.student_id, NULL,
|
||||
DATE_SUB(t.end_time, INTERVAL 90 MINUTE), t.end_time, NULL, 0, 0,
|
||||
'ABSENT', NULL, 100.00, 0, 1, 'RECEIVED',
|
||||
1, NULL, NULL, NULL, '测试数据-缺考(无成绩)',
|
||||
'0', 'test-data', NOW()
|
||||
FROM (
|
||||
SELECT e.enrollment_id, e.plan_id, e.student_id, pc.plan_course_id, pc.c_id,
|
||||
DATE_SUB(NOW(), INTERVAL (MOD(CRC32(CONCAT(e.enrollment_id, '-', pc.plan_course_id)), 30) + 1) DAY) AS end_time
|
||||
FROM (SELECT student_id, plan_id, MIN(enrollment_id) AS enrollment_id
|
||||
FROM train_enrollment
|
||||
GROUP BY student_id, plan_id) e
|
||||
JOIN train_plan_course pc ON pc.plan_id = e.plan_id
|
||||
JOIN train_class_plan cp ON cp.plan_id = e.plan_id
|
||||
ORDER BY e.enrollment_id, pc.plan_course_id
|
||||
LIMIT 4 OFFSET 20
|
||||
) t;
|
||||
|
||||
-- ==============================================================================
|
||||
-- 场景 5:免修(EXEMPT,score 为 NULL,按通过计)
|
||||
-- 覆盖区间:偏移 24 ~ 26,共 3 行
|
||||
-- ==============================================================================
|
||||
INSERT IGNORE INTO train_theory_exam_score
|
||||
(enrollment_id, plan_id, plan_course_id, c_id, student_id, subject_code,
|
||||
exam_start_time, exam_end_time, duration_seconds, retake, retake_no,
|
||||
exam_status, score, full_score, pass_flag, is_final, score_status,
|
||||
source_type, source_system, source_event_id, score_item_id, remark,
|
||||
del_flag, create_by, create_time)
|
||||
SELECT t.enrollment_id, t.plan_id, t.plan_course_id, t.c_id, t.student_id, NULL,
|
||||
NULL, NULL, NULL, 0, 0,
|
||||
'EXEMPT', NULL, 100.00, 1, 1, 'ARCHIVED',
|
||||
1, NULL, NULL, NULL, '测试数据-免修(不计分,计通过)',
|
||||
'0', 'test-data', NOW()
|
||||
FROM (
|
||||
SELECT e.enrollment_id, e.plan_id, e.student_id, pc.plan_course_id, pc.c_id,
|
||||
DATE_SUB(NOW(), INTERVAL 7 DAY) AS end_time
|
||||
FROM (SELECT student_id, plan_id, MIN(enrollment_id) AS enrollment_id
|
||||
FROM train_enrollment
|
||||
GROUP BY student_id, plan_id) e
|
||||
JOIN train_plan_course pc ON pc.plan_id = e.plan_id
|
||||
JOIN train_class_plan cp ON cp.plan_id = e.plan_id
|
||||
ORDER BY e.enrollment_id, pc.plan_course_id
|
||||
LIMIT 3 OFFSET 24
|
||||
) t;
|
||||
|
||||
-- ==============================================================================
|
||||
-- 场景 6:补考过程记录(第 1 次补考,45 分未通过,is_final=0)
|
||||
-- 覆盖区间:偏移 27 ~ 32,共 6 行;与场景 7 使用同一区间,形成"正考/补考"两次成绩
|
||||
-- ==============================================================================
|
||||
INSERT IGNORE INTO train_theory_exam_score
|
||||
(enrollment_id, plan_id, plan_course_id, c_id, student_id, subject_code,
|
||||
exam_start_time, exam_end_time, duration_seconds, retake, retake_no,
|
||||
exam_status, score, full_score, pass_flag, is_final, score_status,
|
||||
source_type, source_system, source_event_id, score_item_id, remark,
|
||||
del_flag, create_by, create_time)
|
||||
SELECT t.enrollment_id, t.plan_id, t.plan_course_id, t.c_id, t.student_id, NULL,
|
||||
DATE_SUB(t.end_time, INTERVAL 90 MINUTE), t.end_time, 5400, 1, 1,
|
||||
'NORMAL', CAST(45.00 AS DECIMAL(7,2)), 100.00, 0, 0, 'RECEIVED',
|
||||
1, NULL, NULL, NULL, '测试数据-补考第1次未通过(非最终成绩)',
|
||||
'0', 'test-data', NOW()
|
||||
FROM (
|
||||
SELECT e.enrollment_id, e.plan_id, e.student_id, pc.plan_course_id, pc.c_id,
|
||||
DATE_SUB(NOW(), INTERVAL 15 DAY) AS end_time
|
||||
FROM (SELECT student_id, plan_id, MIN(enrollment_id) AS enrollment_id
|
||||
FROM train_enrollment
|
||||
GROUP BY student_id, plan_id) e
|
||||
JOIN train_plan_course pc ON pc.plan_id = e.plan_id
|
||||
JOIN train_class_plan cp ON cp.plan_id = e.plan_id
|
||||
ORDER BY e.enrollment_id, pc.plan_course_id
|
||||
LIMIT 6 OFFSET 27
|
||||
) t;
|
||||
|
||||
-- ==============================================================================
|
||||
-- 场景 7:补考后最终成绩(第 2 次补考,65~95 分通过,is_final=1)
|
||||
-- 覆盖区间:与场景 6 相同(偏移 27 ~ 32),retake_no=2,因此不违反唯一键
|
||||
-- ==============================================================================
|
||||
INSERT IGNORE INTO train_theory_exam_score
|
||||
(enrollment_id, plan_id, plan_course_id, c_id, student_id, subject_code,
|
||||
exam_start_time, exam_end_time, duration_seconds, retake, retake_no,
|
||||
exam_status, score, full_score, pass_flag, is_final, score_status,
|
||||
source_type, source_system, source_event_id, score_item_id, remark,
|
||||
del_flag, create_by, create_time)
|
||||
SELECT t.enrollment_id, t.plan_id, t.plan_course_id, t.c_id, t.student_id, NULL,
|
||||
DATE_SUB(t.end_time, INTERVAL 90 MINUTE), t.end_time, 4800, 1, 2,
|
||||
'NORMAL',
|
||||
CAST(65 + MOD(CRC32(CONCAT('retake-', t.enrollment_id, '-', t.plan_course_id)), 31) AS DECIMAL(7,2)),
|
||||
100.00, 1, 1, 'RECEIVED',
|
||||
1, NULL, NULL, NULL, '测试数据-补考第2次通过(最终成绩)',
|
||||
'0', 'test-data', NOW()
|
||||
FROM (
|
||||
SELECT e.enrollment_id, e.plan_id, e.student_id, pc.plan_course_id, pc.c_id,
|
||||
DATE_SUB(NOW(), INTERVAL 7 DAY) AS end_time
|
||||
FROM (SELECT student_id, plan_id, MIN(enrollment_id) AS enrollment_id
|
||||
FROM train_enrollment
|
||||
GROUP BY student_id, plan_id) e
|
||||
JOIN train_plan_course pc ON pc.plan_id = e.plan_id
|
||||
JOIN train_class_plan cp ON cp.plan_id = e.plan_id
|
||||
ORDER BY e.enrollment_id, pc.plan_course_id
|
||||
LIMIT 6 OFFSET 27
|
||||
) t;
|
||||
|
||||
-- ==============================================================================
|
||||
-- 场景 8:已锁定成绩(LOCKED,禁止再改分数;来源为手工录入)
|
||||
-- 覆盖区间:偏移 33 ~ 34,共 2 行
|
||||
-- ==============================================================================
|
||||
INSERT IGNORE INTO train_theory_exam_score
|
||||
(enrollment_id, plan_id, plan_course_id, c_id, student_id, subject_code,
|
||||
exam_start_time, exam_end_time, duration_seconds, retake, retake_no,
|
||||
exam_status, score, full_score, pass_flag, is_final, score_status,
|
||||
source_type, source_system, source_event_id, score_item_id, remark,
|
||||
del_flag, create_by, create_time)
|
||||
SELECT t.enrollment_id, t.plan_id, t.plan_course_id, t.c_id, t.student_id, NULL,
|
||||
DATE_SUB(t.end_time, INTERVAL 90 MINUTE), t.end_time, 5400, 0, 0,
|
||||
'NORMAL', CAST(88.00 AS DECIMAL(7,2)), 100.00, 1, 1, 'LOCKED',
|
||||
1, NULL, NULL, NULL, '测试数据-已锁定成绩',
|
||||
'0', 'test-data', NOW()
|
||||
FROM (
|
||||
SELECT e.enrollment_id, e.plan_id, e.student_id, pc.plan_course_id, pc.c_id,
|
||||
DATE_SUB(NOW(), INTERVAL 10 DAY) AS end_time
|
||||
FROM (SELECT student_id, plan_id, MIN(enrollment_id) AS enrollment_id
|
||||
FROM train_enrollment
|
||||
GROUP BY student_id, plan_id) e
|
||||
JOIN train_plan_course pc ON pc.plan_id = e.plan_id
|
||||
JOIN train_class_plan cp ON cp.plan_id = e.plan_id
|
||||
ORDER BY e.enrollment_id, pc.plan_course_id
|
||||
LIMIT 2 OFFSET 33
|
||||
) t;
|
||||
|
||||
-- ==============================================================================
|
||||
-- 场景 9:外部批量导入 + 已归档(source_type=3,带导入幂等键)
|
||||
-- 覆盖区间:偏移 35 ~ 36,共 2 行
|
||||
-- ==============================================================================
|
||||
INSERT IGNORE INTO train_theory_exam_score
|
||||
(enrollment_id, plan_id, plan_course_id, c_id, student_id, subject_code,
|
||||
exam_start_time, exam_end_time, duration_seconds, retake, retake_no,
|
||||
exam_status, score, full_score, pass_flag, is_final, score_status,
|
||||
source_type, source_system, source_event_id, score_item_id, remark,
|
||||
del_flag, create_by, create_time)
|
||||
SELECT t.enrollment_id, t.plan_id, t.plan_course_id, t.c_id, t.student_id, NULL,
|
||||
DATE_SUB(t.end_time, INTERVAL 90 MINUTE), t.end_time, 5400, 0, 0,
|
||||
'NORMAL', CAST(72.50 AS DECIMAL(7,2)), 100.00, 1, 1, 'ARCHIVED',
|
||||
3, 'TMS-EXAM', CONCAT('IMP-', t.enrollment_id, '-', t.plan_course_id), NULL,
|
||||
'测试数据-外部导入并归档',
|
||||
'0', 'test-data', NOW()
|
||||
FROM (
|
||||
SELECT e.enrollment_id, e.plan_id, e.student_id, pc.plan_course_id, pc.c_id,
|
||||
DATE_SUB(NOW(), INTERVAL 3 DAY) AS end_time
|
||||
FROM (SELECT student_id, plan_id, MIN(enrollment_id) AS enrollment_id
|
||||
FROM train_enrollment
|
||||
GROUP BY student_id, plan_id) e
|
||||
JOIN train_plan_course pc ON pc.plan_id = e.plan_id
|
||||
JOIN train_class_plan cp ON cp.plan_id = e.plan_id
|
||||
ORDER BY e.enrollment_id, pc.plan_course_id
|
||||
LIMIT 2 OFFSET 35
|
||||
) t;
|
||||
|
||||
-- ==============================================================================
|
||||
-- 第 10 步:结果校验(只读)
|
||||
-- 10.1 场景分布统计
|
||||
-- 10.2 关联一致性检查(均应为 0 行)
|
||||
-- 10.3 唯一性检查(均应为 0 行)
|
||||
-- 10.4 分数与状态合法性检查(均应为 0 行)
|
||||
-- ==============================================================================
|
||||
-- 10.1 场景分布
|
||||
SELECT exam_status, score_status, retake, is_final, COUNT(*) AS row_count
|
||||
FROM train_theory_exam_score
|
||||
WHERE create_by = 'test-data'
|
||||
GROUP BY exam_status, score_status, retake, is_final
|
||||
ORDER BY exam_status, score_status, retake, is_final;
|
||||
|
||||
-- 10.2 关联一致性:学员 / 班级计划 / 课程实例 / 课程主档 是否都能对上
|
||||
SELECT t.theory_score_id, '学员不存在' AS problem
|
||||
FROM train_theory_exam_score t
|
||||
LEFT JOIN train_student s ON s.s_id = t.student_id
|
||||
WHERE t.create_by = 'test-data' AND s.s_id IS NULL
|
||||
UNION ALL
|
||||
SELECT t.theory_score_id, '班级计划不存在'
|
||||
FROM train_theory_exam_score t
|
||||
LEFT JOIN train_class_plan cp ON cp.plan_id = t.plan_id
|
||||
WHERE t.create_by = 'test-data' AND cp.plan_id IS NULL
|
||||
UNION ALL
|
||||
SELECT t.theory_score_id, '课程实例不存在或与计划/课程不匹配'
|
||||
FROM train_theory_exam_score t
|
||||
LEFT JOIN train_plan_course pc
|
||||
ON pc.plan_course_id = t.plan_course_id
|
||||
AND pc.plan_id = t.plan_id
|
||||
AND pc.c_id = t.c_id
|
||||
WHERE t.create_by = 'test-data' AND pc.plan_course_id IS NULL
|
||||
UNION ALL
|
||||
SELECT t.theory_score_id, '参训档案与学员/班级不匹配'
|
||||
FROM train_theory_exam_score t
|
||||
LEFT JOIN train_enrollment e
|
||||
ON e.enrollment_id = t.enrollment_id
|
||||
AND e.student_id = t.student_id
|
||||
AND e.plan_id = t.plan_id
|
||||
WHERE t.create_by = 'test-data' AND e.enrollment_id IS NULL;
|
||||
|
||||
-- 10.3 唯一性:同一参训档案 + 课程 + 考试次数重复
|
||||
SELECT enrollment_id, plan_course_id, retake_no, COUNT(*) AS cnt
|
||||
FROM train_theory_exam_score
|
||||
WHERE del_flag = '0'
|
||||
GROUP BY enrollment_id, plan_course_id, retake_no
|
||||
HAVING COUNT(*) > 1;
|
||||
|
||||
-- 10.3.1 唯一性:同一课程同一学员出现多条最终成绩
|
||||
SELECT plan_course_id, student_id, COUNT(*) AS final_cnt
|
||||
FROM train_theory_exam_score
|
||||
WHERE del_flag = '0' AND is_final = 1
|
||||
GROUP BY plan_course_id, student_id
|
||||
HAVING COUNT(*) > 1;
|
||||
|
||||
-- 10.4 合法性:缺考/免修携带分数、正常考试无分数、分数越界
|
||||
SELECT theory_score_id, exam_status, score, full_score, '分数状态不匹配' AS problem
|
||||
FROM train_theory_exam_score
|
||||
WHERE create_by = 'test-data'
|
||||
AND (
|
||||
(exam_status = 'NORMAL' AND score IS NULL)
|
||||
OR (exam_status IN ('ABSENT', 'EXEMPT') AND score IS NOT NULL)
|
||||
OR (score IS NOT NULL AND (score < 0 OR score > full_score))
|
||||
);
|
||||
|
||||
-- ==============================================================================
|
||||
-- 清理(如需删除本脚本产生的全部数据,取消注释后执行)
|
||||
-- 注意:逻辑删除会释放 uk_theory_final 的占用(生成列 final_flag 随 del_flag 变为 NULL)
|
||||
-- ==============================================================================
|
||||
-- DELETE FROM train_theory_exam_score WHERE create_by = 'test-data';
|
||||
|
||||
-- ==============================================================================
|
||||
-- 附:无法通过 DB 约束落库的"异常场景"(仅作验证说明,脚本故意不插入)
|
||||
-- 1) 重复录入:(enrollment_id, plan_course_id, retake_no) 重复
|
||||
-- → 被 uk_theory_enroll_course_retake 拦截(INSERT IGNORE 也会被忽略)
|
||||
-- 2) 同一课程同一学员两条 is_final=1
|
||||
-- → 被 uk_theory_final(生成列 final_flag)拦截
|
||||
-- 3) 缺考/免修携带分数,或正常考试分数为空
|
||||
-- → 被 chk_theory_score_filled / chk_theory_score_range 拦截(MySQL 8.0 强制,5.7 由应用层校验)
|
||||
-- 4) 考试结束时间早于开始时间
|
||||
-- → 被 chk_theory_exam_time 拦截
|
||||
-- 5) 引用不存在的学员/课程
|
||||
-- → 被外键 fk_theory_student / fk_theory_plan_course 拦截
|
||||
-- ==============================================================================
|
||||
@@ -0,0 +1,359 @@
|
||||
-- ==============================================================================
|
||||
-- RailwaySystem 演示数据: 006_demo_theory_score_test01.sql(关系型重建版)
|
||||
-- 用途: 为“测试01班”构建一套完整的班级—学员—选课—理论成绩关联模型,并填充演示数据
|
||||
-- 数据库: MySQL 5.7+(无 CTE / 窗口函数 / CHECK 约束;全部为普通表 + 外键,5.7 完全兼容)
|
||||
-- 字符集: utf8mb4 / utf8mb4_general_ci(支持中文班级名与姓名)
|
||||
--
|
||||
-- 数据模型(7 张表,演示专用,前缀 demo_,不触碰正式业务表 train_*):
|
||||
--
|
||||
-- demo_class_plan 开班计划(1) ──┬── demo_plan_course 计划开设课程(N)
|
||||
-- │ │
|
||||
-- demo_course 课程字典(1) ──┘ │
|
||||
-- │ │
|
||||
-- demo_student 学生主档(1) ── demo_class_member 班级成员(N) [计划 × 学生]
|
||||
-- │
|
||||
-- ├── demo_student_course 学生选课 [计划 × 学生 × 课程]
|
||||
-- │ │
|
||||
-- └── demo_theory_score 理论成绩
|
||||
-- [计划 × 学生 × 课程 × 考试类型]
|
||||
--
|
||||
-- 关联一致性由数据库外键强制保证:
|
||||
-- 1) 班级成员必须属于已存在的开班计划与已存在的学生;
|
||||
-- 2) 学生选课必须是“该班成员 + 该班已开设课程”的交集(双外键);
|
||||
-- 3) 理论成绩必须落在学生已选的课程上(外键指向 demo_student_course),
|
||||
-- 因此不可能出现“成绩挂在未选课/非本班/未开课程上”的脏数据。
|
||||
--
|
||||
-- 数据规模:1 个开班计划、3 门课程、24 名学生、24 条班级成员、
|
||||
-- 39 条选课记录、39 条正考成绩 + 2 条补考 = 41 条成绩
|
||||
-- 边界/异常:满分 100、零分 0、及格线 60、59.5 差 0.5 分、缺考(成绩 NULL)、补考提升
|
||||
--
|
||||
-- 可重复执行:脚本开头统一 DROP(仅 demo_ 前缀表),如需保留请注释 DROP 段
|
||||
-- ==============================================================================
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
-- ------------------------------------------------------------------------------
|
||||
-- 0. 清理旧演示表(按外键依赖倒序)
|
||||
-- ------------------------------------------------------------------------------
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
DROP TABLE IF EXISTS `demo_theory_score`;
|
||||
DROP TABLE IF EXISTS `demo_student_course`;
|
||||
DROP TABLE IF EXISTS `demo_class_member`;
|
||||
DROP TABLE IF EXISTS `demo_student`;
|
||||
DROP TABLE IF EXISTS `demo_plan_course`;
|
||||
DROP TABLE IF EXISTS `demo_course`;
|
||||
DROP TABLE IF EXISTS `demo_class_plan`;
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
-- ------------------------------------------------------------------------------
|
||||
-- 1. 开班计划
|
||||
-- ------------------------------------------------------------------------------
|
||||
CREATE TABLE `demo_class_plan` (
|
||||
`plan_id` VARCHAR(32) NOT NULL COMMENT '开班计划ID',
|
||||
`plan_name` VARCHAR(150) NOT NULL COMMENT '开班计划名称',
|
||||
`class_name` VARCHAR(50) NOT NULL COMMENT '班级名称',
|
||||
`start_time` DATETIME NOT NULL COMMENT '开班时间',
|
||||
`end_time` DATETIME NOT NULL COMMENT '结班时间',
|
||||
`plan_status` TINYINT NOT NULL DEFAULT 1 COMMENT '计划状态(1进行中 0已结束)',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
PRIMARY KEY (`plan_id`),
|
||||
UNIQUE KEY `uk_plan_class_name` (`class_name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
|
||||
COMMENT='开班计划演示表';
|
||||
|
||||
INSERT INTO `demo_class_plan` (`plan_id`, `plan_name`, `class_name`, `start_time`, `end_time`, `plan_status`) VALUES
|
||||
('PLAN-TEST01', '2026年接触网安全理论培训班(测试01班)', '测试01班', '2026-10-08 08:30:00', '2026-12-31 18:00:00', 1);
|
||||
|
||||
-- ------------------------------------------------------------------------------
|
||||
-- 2. 课程字典
|
||||
-- ------------------------------------------------------------------------------
|
||||
CREATE TABLE `demo_course` (
|
||||
`c_id` VARCHAR(32) NOT NULL COMMENT '课程ID',
|
||||
`c_name` VARCHAR(100) NOT NULL COMMENT '课程名称',
|
||||
`c_type` VARCHAR(30) DEFAULT NULL COMMENT '课程类型',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
PRIMARY KEY (`c_id`),
|
||||
UNIQUE KEY `uk_course_name` (`c_name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
|
||||
COMMENT='课程字典演示表';
|
||||
|
||||
INSERT INTO `demo_course` (`c_id`, `c_name`, `c_type`) VALUES
|
||||
('C01', '接触网安全理论', '理论'),
|
||||
('C02', '铁路电力安全规程', '理论'),
|
||||
('C03', '应急处置与救援', '理论');
|
||||
|
||||
-- ------------------------------------------------------------------------------
|
||||
-- 3. 开班计划课程(计划开设了哪些课)
|
||||
-- ------------------------------------------------------------------------------
|
||||
CREATE TABLE `demo_plan_course` (
|
||||
`plan_id` VARCHAR(32) NOT NULL COMMENT '开班计划ID',
|
||||
`c_id` VARCHAR(32) NOT NULL COMMENT '课程ID',
|
||||
`sort_no` INT NOT NULL DEFAULT 0 COMMENT '排课顺序',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
PRIMARY KEY (`plan_id`, `c_id`),
|
||||
KEY `idx_plan_course_course` (`c_id`),
|
||||
CONSTRAINT `fk_plan_course_plan` FOREIGN KEY (`plan_id`) REFERENCES `demo_class_plan` (`plan_id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT `fk_plan_course_course` FOREIGN KEY (`c_id`) REFERENCES `demo_course` (`c_id`) ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
|
||||
COMMENT='开班计划课程演示表';
|
||||
|
||||
INSERT INTO `demo_plan_course` (`plan_id`, `c_id`, `sort_no`) VALUES
|
||||
('PLAN-TEST01', 'C01', 1),
|
||||
('PLAN-TEST01', 'C02', 2),
|
||||
('PLAN-TEST01', 'C03', 3);
|
||||
|
||||
-- ------------------------------------------------------------------------------
|
||||
-- 4. 学生主档(学号 + 姓名)
|
||||
-- ------------------------------------------------------------------------------
|
||||
CREATE TABLE `demo_student` (
|
||||
`student_no` VARCHAR(32) NOT NULL COMMENT '学号',
|
||||
`student_name` VARCHAR(50) NOT NULL COMMENT '姓名',
|
||||
`gender` VARCHAR(4) DEFAULT NULL COMMENT '性别',
|
||||
`dept_name` VARCHAR(100) DEFAULT NULL COMMENT '所属单位',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
PRIMARY KEY (`student_no`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
|
||||
COMMENT='学生主档演示表';
|
||||
|
||||
INSERT INTO `demo_student` (`student_no`, `student_name`, `gender`, `dept_name`) VALUES
|
||||
('TEST01-001', '张伟', '男', '供电段'),
|
||||
('TEST01-002', '王芳', '女', '供电段'),
|
||||
('TEST01-003', '李强', '男', '供电段'),
|
||||
('TEST01-004', '刘洋', '男', '电务段'),
|
||||
('TEST01-005', '陈静', '女', '电务段'),
|
||||
('TEST01-006', '杨帆', '男', '电务段'),
|
||||
('TEST01-007', '赵敏', '女', '工务段'),
|
||||
('TEST01-008', '黄磊', '男', '工务段'),
|
||||
('TEST01-009', '周涛', '男', '工务段'),
|
||||
('TEST01-010', '吴倩', '女', '供电段'),
|
||||
('TEST01-011', '徐鹏', '男', '供电段'),
|
||||
('TEST01-012', '孙丽', '女', '供电段'),
|
||||
('TEST01-013', '马超', '男', '电务段'),
|
||||
('TEST01-014', '朱婷', '女', '电务段'),
|
||||
('TEST01-015', '胡军', '男', '电务段'),
|
||||
('TEST01-016', '郭鹏', '男', '工务段'),
|
||||
('TEST01-017', '何梅', '女', '工务段'),
|
||||
('TEST01-018', '高翔', '男', '工务段'),
|
||||
('TEST01-019', '林芳', '女', '供电段'),
|
||||
('TEST01-020', '罗文', '男', '供电段'),
|
||||
('TEST01-021', '梁静', '女', '供电段'),
|
||||
('TEST01-022', '宋波', '男', '电务段'),
|
||||
('TEST01-023', '谢娜', '女', '电务段'),
|
||||
('TEST01-024', '韩雪', '女', '工务段');
|
||||
|
||||
-- ------------------------------------------------------------------------------
|
||||
-- 5. 班级成员(计划 × 学生,明确“谁在这个班”)
|
||||
-- ------------------------------------------------------------------------------
|
||||
CREATE TABLE `demo_class_member` (
|
||||
`plan_id` VARCHAR(32) NOT NULL COMMENT '开班计划ID',
|
||||
`student_no` VARCHAR(32) NOT NULL COMMENT '学号',
|
||||
`join_time` DATETIME NOT NULL COMMENT '入班时间',
|
||||
`member_status` TINYINT NOT NULL DEFAULT 1 COMMENT '成员状态(1在班 0已退班)',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
PRIMARY KEY (`plan_id`, `student_no`),
|
||||
KEY `idx_member_student` (`student_no`),
|
||||
CONSTRAINT `fk_member_plan` FOREIGN KEY (`plan_id`) REFERENCES `demo_class_plan` (`plan_id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT `fk_member_student` FOREIGN KEY (`student_no`) REFERENCES `demo_student` (`student_no`) ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
|
||||
COMMENT='班级成员演示表';
|
||||
|
||||
INSERT INTO `demo_class_member` (`plan_id`, `student_no`, `join_time`, `member_status`)
|
||||
SELECT 'PLAN-TEST01', `student_no`, '2026-10-08 09:00:00', 1
|
||||
FROM `demo_student`;
|
||||
|
||||
-- ------------------------------------------------------------------------------
|
||||
-- 6. 学生选课(计划 × 学生 × 课程;必须同时是班级成员且课程为计划已开设课程)
|
||||
-- 选课规则:全部 24 人必选 C01;002~013 选 C02;003/009/015 选 C03(共 39 条)
|
||||
-- ------------------------------------------------------------------------------
|
||||
CREATE TABLE `demo_student_course` (
|
||||
`plan_id` VARCHAR(32) NOT NULL COMMENT '开班计划ID',
|
||||
`student_no` VARCHAR(32) NOT NULL COMMENT '学号',
|
||||
`c_id` VARCHAR(32) NOT NULL COMMENT '课程ID',
|
||||
`select_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '选课时间',
|
||||
PRIMARY KEY (`plan_id`, `student_no`, `c_id`),
|
||||
KEY `idx_sc_course` (`plan_id`, `c_id`),
|
||||
CONSTRAINT `fk_sc_member` FOREIGN KEY (`plan_id`, `student_no`) REFERENCES `demo_class_member` (`plan_id`, `student_no`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT `fk_sc_plan_course` FOREIGN KEY (`plan_id`, `c_id`) REFERENCES `demo_plan_course` (`plan_id`, `c_id`) ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
|
||||
COMMENT='学生选课演示表';
|
||||
|
||||
-- 6.1 全员必选 C01
|
||||
INSERT INTO `demo_student_course` (`plan_id`, `student_no`, `c_id`)
|
||||
SELECT 'PLAN-TEST01', `student_no`, 'C01'
|
||||
FROM `demo_student`;
|
||||
|
||||
-- 6.2 002~013 加选 C02
|
||||
INSERT INTO `demo_student_course` (`plan_id`, `student_no`, `c_id`)
|
||||
SELECT 'PLAN-TEST01', `student_no`, 'C02'
|
||||
FROM `demo_student`
|
||||
WHERE `student_no` BETWEEN 'TEST01-002' AND 'TEST01-013';
|
||||
|
||||
-- 6.3 003/009/015 加选 C03
|
||||
INSERT INTO `demo_student_course` (`plan_id`, `student_no`, `c_id`) VALUES
|
||||
('PLAN-TEST01', 'TEST01-003', 'C03'),
|
||||
('PLAN-TEST01', 'TEST01-009', 'C03'),
|
||||
('PLAN-TEST01', 'TEST01-015', 'C03');
|
||||
|
||||
-- ------------------------------------------------------------------------------
|
||||
-- 7. 理论成绩(计划 × 学生 × 课程 × 考试类型)
|
||||
-- 外键指向选课表,确保“成绩只能落在已选课程上”;唯一键防重复录入
|
||||
-- ------------------------------------------------------------------------------
|
||||
CREATE TABLE `demo_theory_score` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`plan_id` VARCHAR(32) NOT NULL COMMENT '开班计划ID',
|
||||
`student_no` VARCHAR(32) NOT NULL COMMENT '学号',
|
||||
`c_id` VARCHAR(32) NOT NULL COMMENT '课程ID',
|
||||
`exam_type` VARCHAR(16) NOT NULL DEFAULT '正考' COMMENT '考试类型(正考/补考)',
|
||||
`exam_status` VARCHAR(16) NOT NULL DEFAULT '正常' COMMENT '考试状态(正常/缺考/免修)',
|
||||
`score` DECIMAL(5,2) DEFAULT NULL COMMENT '理论成绩(0-100,缺考为NULL)',
|
||||
`full_score` DECIMAL(5,2) NOT NULL DEFAULT 100.00 COMMENT '满分',
|
||||
`pass_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '是否通过(1通过 0未通过)',
|
||||
`exam_time` DATETIME DEFAULT NULL COMMENT '考试时间',
|
||||
`remark` VARCHAR(200) DEFAULT NULL COMMENT '备注(边界/异常样本)',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_score_member_course_exam` (`plan_id`, `student_no`, `c_id`, `exam_type`),
|
||||
KEY `idx_score_student` (`student_no`),
|
||||
KEY `idx_score_course` (`plan_id`, `c_id`),
|
||||
CONSTRAINT `fk_score_student_course` FOREIGN KEY (`plan_id`, `student_no`, `c_id`)
|
||||
REFERENCES `demo_student_course` (`plan_id`, `student_no`, `c_id`)
|
||||
ON DELETE CASCADE ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
|
||||
COMMENT='学生理论成绩演示表(测试01班)';
|
||||
|
||||
-- 7.1 正考成绩:按“学生 × 已选课程”自动生成,保证与选课/班级/计划完全一致
|
||||
-- 分数规则:001=100(满分)、013=0(零分)、019=59.5(差0.5分)、021=60(及格线)、
|
||||
-- 024 的 C01 为缺考(NULL),其余 45~100 之间按学号+课程确定性分布
|
||||
INSERT INTO `demo_theory_score`
|
||||
(`plan_id`, `student_no`, `c_id`, `exam_type`, `exam_status`, `score`, `full_score`, `pass_flag`, `exam_time`, `remark`)
|
||||
SELECT t.plan_id,
|
||||
t.student_no,
|
||||
t.c_id,
|
||||
'正考',
|
||||
t.exam_status,
|
||||
t.score,
|
||||
100.00,
|
||||
CASE WHEN t.score IS NOT NULL AND t.score >= 60 THEN 1 ELSE 0 END,
|
||||
CASE t.c_id
|
||||
WHEN 'C01' THEN '2026-10-20 09:00:00'
|
||||
WHEN 'C02' THEN '2026-10-22 09:00:00'
|
||||
ELSE '2026-10-24 14:00:00'
|
||||
END,
|
||||
t.remark
|
||||
FROM (
|
||||
SELECT sc.plan_id,
|
||||
sc.student_no,
|
||||
sc.c_id,
|
||||
CASE
|
||||
WHEN sc.student_no = 'TEST01-024' AND sc.c_id = 'C01' THEN NULL
|
||||
WHEN sc.student_no = 'TEST01-001' THEN 100.00
|
||||
WHEN sc.student_no = 'TEST01-013' THEN 0.00
|
||||
WHEN sc.student_no = 'TEST01-019' THEN 59.50
|
||||
WHEN sc.student_no = 'TEST01-021' THEN 60.00
|
||||
ELSE CAST(45 + MOD(CRC32(CONCAT(sc.student_no, '-', sc.c_id)), 56) AS DECIMAL(5,2))
|
||||
END AS score,
|
||||
CASE
|
||||
WHEN sc.student_no = 'TEST01-024' AND sc.c_id = 'C01' THEN '缺考'
|
||||
ELSE '正常'
|
||||
END AS exam_status,
|
||||
CASE
|
||||
WHEN sc.student_no = 'TEST01-001' THEN '边界值:满分'
|
||||
WHEN sc.student_no = 'TEST01-013' THEN '边界值:零分'
|
||||
WHEN sc.student_no = 'TEST01-019' THEN '边界值:差0.5分未通过'
|
||||
WHEN sc.student_no = 'TEST01-021' THEN '边界值:及格线'
|
||||
WHEN sc.student_no = 'TEST01-024' AND sc.c_id = 'C01' THEN '异常样本:缺考无成绩'
|
||||
ELSE NULL
|
||||
END AS remark
|
||||
FROM `demo_student_course` sc
|
||||
) t;
|
||||
|
||||
-- 7.2 补考成绩:019、022 的 C01 正考未通过后补考通过(与正考类型不同,不违反唯一键)
|
||||
INSERT INTO `demo_theory_score`
|
||||
(`plan_id`, `student_no`, `c_id`, `exam_type`, `exam_status`, `score`, `full_score`, `pass_flag`, `exam_time`, `remark`)
|
||||
VALUES
|
||||
('PLAN-TEST01', 'TEST01-019', 'C01', '补考', '正常', 72.00, 100.00, 1, '2026-11-03 14:00:00', '异常样本:补考成绩提升'),
|
||||
('PLAN-TEST01', 'TEST01-022', 'C01', '补考', '正常', 78.00, 100.00, 1, '2026-11-03 14:00:00', '异常样本:正考未通过后补考通过');
|
||||
|
||||
-- ==============================================================================
|
||||
-- 8. 结果校验(只读)
|
||||
-- ==============================================================================
|
||||
|
||||
-- 8.1 数据规模总览
|
||||
SELECT '开班计划' AS table_name, COUNT(*) AS row_count FROM `demo_class_plan`
|
||||
UNION ALL SELECT '课程', COUNT(*) FROM `demo_course`
|
||||
UNION ALL SELECT '计划课程', COUNT(*) FROM `demo_plan_course`
|
||||
UNION ALL SELECT '学生', COUNT(*) FROM `demo_student`
|
||||
UNION ALL SELECT '班级成员', COUNT(*) FROM `demo_class_member`
|
||||
UNION ALL SELECT '学生选课', COUNT(*) FROM `demo_student_course`
|
||||
UNION ALL SELECT '理论成绩', COUNT(*) FROM `demo_theory_score`;
|
||||
|
||||
-- 8.2 班级成员与选课明细(含学生姓名、课程名、班级名)
|
||||
SELECT p.class_name, m.student_no, s.student_name, c.c_name, sc.select_time
|
||||
FROM `demo_class_member` m
|
||||
JOIN `demo_class_plan` p ON p.plan_id = m.plan_id
|
||||
JOIN `demo_student` s ON s.student_no = m.student_no
|
||||
JOIN `demo_student_course` sc ON sc.plan_id = m.plan_id AND sc.student_no = m.student_no
|
||||
JOIN `demo_course` c ON c.c_id = sc.c_id
|
||||
ORDER BY m.student_no, c.c_id;
|
||||
|
||||
-- 8.3 学生成绩明细(正考/补考、含班级与课程)
|
||||
SELECT p.class_name, s.student_no, s.student_name, c.c_name,
|
||||
t.exam_type, t.exam_status, t.score, t.full_score, t.pass_flag, t.exam_time, t.remark
|
||||
FROM `demo_theory_score` t
|
||||
JOIN `demo_student` s ON s.student_no = t.student_no
|
||||
JOIN `demo_course` c ON c.c_id = t.c_id
|
||||
JOIN `demo_class_plan` p ON p.plan_id = t.plan_id
|
||||
ORDER BY t.student_no, t.c_id, t.exam_type;
|
||||
|
||||
-- 8.4 课程维度统计:参考人数、平均分、最高分、最低分、及格率
|
||||
SELECT c.c_name,
|
||||
COUNT(t.score) AS scored_count,
|
||||
ROUND(AVG(t.score), 2) AS avg_score,
|
||||
MAX(t.score) AS max_score,
|
||||
MIN(t.score) AS min_score,
|
||||
ROUND(SUM(t.pass_flag) / COUNT(*) * 100, 2) AS pass_rate_percent
|
||||
FROM `demo_theory_score` t
|
||||
JOIN `demo_course` c ON c.c_id = t.c_id
|
||||
WHERE t.exam_type = '正考'
|
||||
GROUP BY c.c_name;
|
||||
|
||||
-- 8.5 关联一致性自检(三项均应返回 0 行)
|
||||
-- a) 成绩存在、但对应选课关系不存在(外键已保护,此处为双重验证)
|
||||
SELECT t.id, '成绩缺少选课关系' AS problem
|
||||
FROM `demo_theory_score` t
|
||||
LEFT JOIN `demo_student_course` sc
|
||||
ON sc.plan_id = t.plan_id AND sc.student_no = t.student_no AND sc.c_id = t.c_id
|
||||
WHERE sc.student_no IS NULL
|
||||
-- b) 班级成员不在学生主档 / 不在开班计划
|
||||
UNION ALL
|
||||
SELECT m.student_no, '班级成员缺少学生或计划'
|
||||
FROM `demo_class_member` m
|
||||
LEFT JOIN `demo_student` s ON s.student_no = m.student_no
|
||||
LEFT JOIN `demo_class_plan` p ON p.plan_id = m.plan_id
|
||||
WHERE s.student_no IS NULL OR p.plan_id IS NULL
|
||||
-- c) 选课不是本班已开设课程
|
||||
UNION ALL
|
||||
SELECT sc.student_no, '选课不在计划课程内'
|
||||
FROM `demo_student_course` sc
|
||||
LEFT JOIN `demo_plan_course` pc ON pc.plan_id = sc.plan_id AND pc.c_id = sc.c_id
|
||||
WHERE pc.c_id IS NULL;
|
||||
|
||||
-- 8.6 成绩合法性自检(应返回 0 行)
|
||||
SELECT id, student_no, c_id, exam_type, exam_status, score, full_score, pass_flag
|
||||
FROM `demo_theory_score`
|
||||
WHERE (score IS NOT NULL AND (score < 0 OR score > 100 OR score > full_score))
|
||||
OR (exam_status = '正常' AND score IS NULL)
|
||||
OR (score IS NOT NULL AND pass_flag <> IF(score >= 60, 1, 0));
|
||||
|
||||
-- ------------------------------------------------------------------------------
|
||||
-- 9. 清理(如需删除全部演示数据,取消注释执行;按依赖倒序删除)
|
||||
-- ------------------------------------------------------------------------------
|
||||
-- SET FOREIGN_KEY_CHECKS = 0;
|
||||
-- DROP TABLE IF EXISTS `demo_theory_score`;
|
||||
-- DROP TABLE IF EXISTS `demo_student_course`;
|
||||
-- DROP TABLE IF EXISTS `demo_class_member`;
|
||||
-- DROP TABLE IF EXISTS `demo_student`;
|
||||
-- DROP TABLE IF EXISTS `demo_plan_course`;
|
||||
-- DROP TABLE IF EXISTS `demo_course`;
|
||||
-- DROP TABLE IF EXISTS `demo_class_plan`;
|
||||
-- SET FOREIGN_KEY_CHECKS = 1;
|
||||
@@ -0,0 +1,330 @@
|
||||
-- ==============================================================================
|
||||
-- RailwaySystem 测试数据: 007_demo_theory_score_kbjh2026_11.sql
|
||||
-- 目标表: train_theory_exam_score(班级学员理论考试成绩表,需先执行 026 建表脚本)
|
||||
-- 指定班级: 开班计划名称 = 'KBJH2026-11现场测试开班'
|
||||
-- 数据库: MySQL 5.7+(仅使用派生表 / 临时表 / INSERT...SELECT / INSERT IGNORE,未用 CTE、窗口函数)
|
||||
-- ==============================================================================
|
||||
-- 一、依赖表结构与字段说明(均需已存在,本脚本不建业务表)
|
||||
-- ------------------------------------------------------------------------------
|
||||
-- 1) train_student(学员主档,本脚本只读)
|
||||
-- s_id VARCHAR(32) PK 学员ID
|
||||
-- s_name VARCHAR(50) 姓名(仅用于结果核对,不参与写入)
|
||||
-- del_flag CHAR(1) 删除标志(0存在;若该列不存在请忽略相关注释)
|
||||
--
|
||||
-- 2) train_class_plan(开班计划,本脚本只读)
|
||||
-- plan_id VARCHAR(32) PK 开班计划ID
|
||||
-- plan_name VARCHAR(150) 计划名称 —— 本脚本按 'KBJH2026-11现场测试开班' 过滤
|
||||
--
|
||||
-- 3) train_plan_course(班级开设课程,本脚本只读)
|
||||
-- plan_course_id BIGINT PK 培训课程实例ID(成绩表的课程维度键)
|
||||
-- plan_id VARCHAR(32) 所属开班计划
|
||||
-- c_id VARCHAR(32) 课程主档ID
|
||||
-- UNIQUE (plan_id, c_id)
|
||||
--
|
||||
-- 4) train_enrollment(参训档案,本脚本只读)
|
||||
-- enrollment_id BIGINT PK 参训档案ID(学员 × 班级唯一事实)
|
||||
-- plan_id VARCHAR(32) 开班计划ID
|
||||
-- student_id VARCHAR(32) 学员ID
|
||||
-- —— 无 del_flag 列
|
||||
--
|
||||
-- 5) train_theory_exam_score(写入目标,字段含义见 026 脚本)
|
||||
-- enrollment_id / plan_id / plan_course_id / c_id / student_id —— 维度
|
||||
-- exam_start_time / exam_end_time / duration_seconds —— 考试过程
|
||||
-- retake / retake_no —— 正考(0/0) / 补考(1/1..n)
|
||||
-- exam_status (NORMAL/ABSENT/EXEMPT) —— 考试状态
|
||||
-- score DECIMAL(7,2) NULL / full_score / pass_flag —— 成绩
|
||||
-- is_final (1/0) —— 是否该课程最终成绩
|
||||
-- score_status (RECEIVED/ARCHIVED/LOCKED) —— 成绩状态
|
||||
-- source_type (1/2/3) / source_system / source_event_id —— 来源
|
||||
-- del_flag ('0'/'2')
|
||||
--
|
||||
-- 二、生成逻辑
|
||||
-- · 事实源 = 该计划下“学员 × 该班已开设课程”的笛卡尔组合:
|
||||
-- train_enrollment (按 plan_id + student_id 取 MIN(enrollment_id) 去重)
|
||||
-- JOIN train_class_plan (plan_name = 'KBJH2026-11现场测试开班')
|
||||
-- JOIN train_plan_course (pc.plan_id = e.plan_id)
|
||||
-- JOIN train_student (保证学员主档存在)
|
||||
-- 由此保证 student_id / plan_id / plan_course_id / c_id 四者同源一致;
|
||||
-- · 组合先落到临时表 tmp_pair,避免重复计算,也保证同一组合只生成一次;
|
||||
-- · 再从 tmp_pair 中按 (student_id, plan_course_id) 升序取前 8 个组合,
|
||||
-- 分别打上 FULL / ZERO / ABSENT / PASS_LINE / NEAR_LINE / EXEMPT / RETAKE1 / RETAKE2 场景标签,
|
||||
-- 确保边界与异常样本**一定存在**(组合数 ≥ 8 时全部覆盖,不足时自动降级);
|
||||
-- · 其余组合按成绩 45~100 的确定性分布生成(CRC32(学号+课程) 取模);
|
||||
-- · 补考:对 RETAKE1 / RETAKE2 两个组合额外生成一条补考记录(retake=1, retake_no=1, is_final=1),
|
||||
-- 其正考记录 is_final=0,既展示“补考后成为最终成绩”,又不违反 uk_theory_final;
|
||||
-- · 全程 INSERT IGNORE:重复执行不报错、不产生重复数据(不会掩盖外键错误)。
|
||||
--
|
||||
-- 三、数据量
|
||||
-- 正考行数 = 该计划“学员数 × 开设课程数”(本脚本生成全部组合)
|
||||
-- 补考行数 = 2(RETAKE1、RETAKE2 两个组合)
|
||||
-- 例如 20 名学员 × 3 门课程 → 60 条正考 + 2 条补考 = 62 行
|
||||
-- 若该计划不存在或无学员/无课程,则插入 0 行(第 0 步的体检查询可确认)
|
||||
-- ==============================================================================
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
-- 目标班级(如需换班,只改这一行)
|
||||
SET @plan_name = 'KBJH2026-11现场测试开班';
|
||||
|
||||
-- ------------------------------------------------------------------------------
|
||||
-- 第 0 步:体检(只读)
|
||||
-- 0.1 计划是否存在
|
||||
-- 0.2 该计划的学员数 / 课程数 / 可生成组合数
|
||||
-- ------------------------------------------------------------------------------
|
||||
SELECT plan_id, plan_name, class_name, start_time, end_time, plan_status
|
||||
FROM train_class_plan
|
||||
WHERE plan_name = @plan_name;
|
||||
|
||||
SELECT COUNT(*) AS available_pairs
|
||||
FROM (
|
||||
SELECT e.plan_id, e.student_id, pc.plan_course_id
|
||||
FROM (SELECT plan_id, student_id, MIN(enrollment_id) AS enrollment_id
|
||||
FROM train_enrollment
|
||||
GROUP BY plan_id, student_id) e
|
||||
JOIN train_class_plan cp ON cp.plan_id = e.plan_id AND cp.plan_name = @plan_name
|
||||
JOIN train_plan_course pc ON pc.plan_id = e.plan_id
|
||||
JOIN train_student s ON s.s_id = e.student_id
|
||||
) p;
|
||||
|
||||
-- ------------------------------------------------------------------------------
|
||||
-- 第 1 步:构建“学员 × 课程”组合临时表
|
||||
-- ------------------------------------------------------------------------------
|
||||
DROP TEMPORARY TABLE IF EXISTS tmp_pair;
|
||||
CREATE TEMPORARY TABLE tmp_pair (
|
||||
enrollment_id BIGINT NOT NULL,
|
||||
plan_id VARCHAR(32) NOT NULL,
|
||||
student_id VARCHAR(32) NOT NULL,
|
||||
plan_course_id BIGINT NOT NULL,
|
||||
c_id VARCHAR(32) NOT NULL,
|
||||
PRIMARY KEY (plan_course_id, student_id),
|
||||
KEY idx_tmp_pair_student (student_id)
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
INSERT INTO tmp_pair (enrollment_id, plan_id, student_id, plan_course_id, c_id)
|
||||
SELECT e.enrollment_id, e.plan_id, e.student_id, pc.plan_course_id, pc.c_id
|
||||
FROM (SELECT plan_id, student_id, MIN(enrollment_id) AS enrollment_id
|
||||
FROM train_enrollment
|
||||
GROUP BY plan_id, student_id) e
|
||||
JOIN train_class_plan cp ON cp.plan_id = e.plan_id AND cp.plan_name = @plan_name
|
||||
JOIN train_plan_course pc ON pc.plan_id = e.plan_id
|
||||
JOIN train_course c ON c.c_id = pc.c_id
|
||||
JOIN train_student s ON s.s_id = e.student_id;
|
||||
|
||||
-- ------------------------------------------------------------------------------
|
||||
-- 第 2 步:为前 8 个组合打场景标签(边界 / 异常样本)
|
||||
-- FULL 满分 100
|
||||
-- ZERO 零分 0
|
||||
-- ABSENT 缺考(score = NULL)
|
||||
-- PASS_LINE 及格线 60
|
||||
-- NEAR_LINE 59.5(差 0.5 分未通过)
|
||||
-- EXEMPT 免修(score = NULL,计通过)
|
||||
-- RETAKE1 正考 45 分未通过,后续补考 72 分通过
|
||||
-- RETAKE2 正考 50 分未通过,后续补考 78 分通过
|
||||
-- ------------------------------------------------------------------------------
|
||||
DROP TEMPORARY TABLE IF EXISTS tmp_seed;
|
||||
CREATE TEMPORARY TABLE tmp_seed (
|
||||
student_id VARCHAR(32) NOT NULL,
|
||||
plan_course_id BIGINT NOT NULL,
|
||||
scenario VARCHAR(16) NOT NULL,
|
||||
PRIMARY KEY (student_id, plan_course_id)
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
INSERT INTO tmp_seed (student_id, plan_course_id, scenario)
|
||||
SELECT student_id, plan_course_id, 'FULL'
|
||||
FROM tmp_pair ORDER BY student_id, plan_course_id LIMIT 1 OFFSET 0;
|
||||
|
||||
INSERT INTO tmp_seed (student_id, plan_course_id, scenario)
|
||||
SELECT student_id, plan_course_id, 'ZERO'
|
||||
FROM tmp_pair ORDER BY student_id, plan_course_id LIMIT 1 OFFSET 1;
|
||||
|
||||
INSERT INTO tmp_seed (student_id, plan_course_id, scenario)
|
||||
SELECT student_id, plan_course_id, 'ABSENT'
|
||||
FROM tmp_pair ORDER BY student_id, plan_course_id LIMIT 1 OFFSET 2;
|
||||
|
||||
INSERT INTO tmp_seed (student_id, plan_course_id, scenario)
|
||||
SELECT student_id, plan_course_id, 'PASS_LINE'
|
||||
FROM tmp_pair ORDER BY student_id, plan_course_id LIMIT 1 OFFSET 3;
|
||||
|
||||
INSERT INTO tmp_seed (student_id, plan_course_id, scenario)
|
||||
SELECT student_id, plan_course_id, 'NEAR_LINE'
|
||||
FROM tmp_pair ORDER BY student_id, plan_course_id LIMIT 1 OFFSET 4;
|
||||
|
||||
INSERT INTO tmp_seed (student_id, plan_course_id, scenario)
|
||||
SELECT student_id, plan_course_id, 'EXEMPT'
|
||||
FROM tmp_pair ORDER BY student_id, plan_course_id LIMIT 1 OFFSET 5;
|
||||
|
||||
INSERT INTO tmp_seed (student_id, plan_course_id, scenario)
|
||||
SELECT student_id, plan_course_id, 'RETAKE1'
|
||||
FROM tmp_pair ORDER BY student_id, plan_course_id LIMIT 1 OFFSET 6;
|
||||
|
||||
INSERT INTO tmp_seed (student_id, plan_course_id, scenario)
|
||||
SELECT student_id, plan_course_id, 'RETAKE2'
|
||||
FROM tmp_pair ORDER BY student_id, plan_course_id LIMIT 1 OFFSET 7;
|
||||
|
||||
-- ------------------------------------------------------------------------------
|
||||
-- 第 3 步:生成正考成绩(覆盖该计划全部“学员 × 课程”组合)
|
||||
-- RETAKE1 / RETAKE2 组合的 is_final = 0(其最终成绩由补考记录承担)
|
||||
-- ------------------------------------------------------------------------------
|
||||
INSERT IGNORE INTO train_theory_exam_score
|
||||
(enrollment_id, plan_id, plan_course_id, c_id, student_id, subject_code,
|
||||
exam_start_time, exam_end_time, duration_seconds, retake, retake_no,
|
||||
exam_status, score, full_score, pass_flag, is_final, score_status,
|
||||
source_type, source_system, source_event_id, score_item_id, remark,
|
||||
del_flag, create_by, create_time)
|
||||
SELECT x.enrollment_id, x.plan_id, x.plan_course_id, x.c_id, x.student_id, NULL,
|
||||
DATE_SUB(x.end_time, INTERVAL 90 MINUTE), x.end_time, 5400, 0, 0,
|
||||
x.exam_status, x.score, 100.00,
|
||||
CASE
|
||||
WHEN x.scenario = 'EXEMPT' THEN 1 -- 免修不计分,按通过计
|
||||
WHEN x.score IS NOT NULL AND x.score >= 60 THEN 1
|
||||
ELSE 0
|
||||
END,
|
||||
CASE WHEN x.scenario IN ('RETAKE1', 'RETAKE2') THEN 0 ELSE 1 END,
|
||||
'RECEIVED',
|
||||
1, NULL, NULL, NULL,
|
||||
CASE x.scenario
|
||||
WHEN 'FULL' THEN '测试数据-边界值:满分100'
|
||||
WHEN 'ZERO' THEN '测试数据-边界值:零分0'
|
||||
WHEN 'ABSENT' THEN '测试数据-异常:缺考无成绩'
|
||||
WHEN 'PASS_LINE' THEN '测试数据-边界值:及格线60'
|
||||
WHEN 'NEAR_LINE' THEN '测试数据-边界值:59.5差0.5分未通过'
|
||||
WHEN 'EXEMPT' THEN '测试数据-异常:免修不计分'
|
||||
WHEN 'RETAKE1' THEN '测试数据-异常:正考45分未通过(待补考)'
|
||||
WHEN 'RETAKE2' THEN '测试数据-异常:正考50分未通过(待补考)'
|
||||
ELSE NULL
|
||||
END,
|
||||
'0', 'test-data', NOW()
|
||||
FROM (
|
||||
SELECT p.enrollment_id, p.plan_id, p.plan_course_id, p.c_id, p.student_id,
|
||||
COALESCE(sd.scenario, 'NORMAL') AS scenario,
|
||||
CASE COALESCE(sd.scenario, 'NORMAL')
|
||||
WHEN 'FULL' THEN CAST(100.00 AS DECIMAL(7,2))
|
||||
WHEN 'ZERO' THEN CAST(0.00 AS DECIMAL(7,2))
|
||||
WHEN 'ABSENT' THEN NULL
|
||||
WHEN 'EXEMPT' THEN NULL
|
||||
WHEN 'PASS_LINE' THEN CAST(60.00 AS DECIMAL(7,2))
|
||||
WHEN 'NEAR_LINE' THEN CAST(59.50 AS DECIMAL(7,2))
|
||||
WHEN 'RETAKE1' THEN CAST(45.00 AS DECIMAL(7,2))
|
||||
WHEN 'RETAKE2' THEN CAST(50.00 AS DECIMAL(7,2))
|
||||
ELSE CAST(45 + MOD(CRC32(CONCAT(p.student_id, '-', p.plan_course_id)), 56) AS DECIMAL(7,2))
|
||||
END AS score,
|
||||
CASE WHEN COALESCE(sd.scenario, 'NORMAL') IN ('ABSENT', 'EXEMPT') THEN
|
||||
CASE WHEN sd.scenario = 'EXEMPT' THEN 'EXEMPT' ELSE 'ABSENT' END
|
||||
ELSE 'NORMAL'
|
||||
END AS exam_status,
|
||||
DATE_SUB(NOW(), INTERVAL (MOD(CRC32(CONCAT(p.student_id, '-', p.plan_course_id)), 20) + 1) DAY) AS end_time
|
||||
FROM tmp_pair p
|
||||
LEFT JOIN tmp_seed sd
|
||||
ON sd.student_id = p.student_id AND sd.plan_course_id = p.plan_course_id
|
||||
) x;
|
||||
|
||||
-- ------------------------------------------------------------------------------
|
||||
-- 第 4 步:生成补考成绩(RETAKE1 → 72 分,RETAKE2 → 78 分,均为最终成绩)
|
||||
-- ------------------------------------------------------------------------------
|
||||
INSERT IGNORE INTO train_theory_exam_score
|
||||
(enrollment_id, plan_id, plan_course_id, c_id, student_id, subject_code,
|
||||
exam_start_time, exam_end_time, duration_seconds, retake, retake_no,
|
||||
exam_status, score, full_score, pass_flag, is_final, score_status,
|
||||
source_type, source_system, source_event_id, score_item_id, remark,
|
||||
del_flag, create_by, create_time)
|
||||
SELECT p.enrollment_id, p.plan_id, p.plan_course_id, p.c_id, p.student_id, NULL,
|
||||
DATE_SUB(NOW(), INTERVAL 3 DAY),
|
||||
DATE_ADD(DATE_SUB(NOW(), INTERVAL 3 DAY), INTERVAL 90 MINUTE),
|
||||
5400, 1, 1,
|
||||
'NORMAL',
|
||||
CASE sd.scenario WHEN 'RETAKE1' THEN CAST(72.00 AS DECIMAL(7,2)) ELSE CAST(78.00 AS DECIMAL(7,2)) END,
|
||||
100.00, 1, 1, 'RECEIVED',
|
||||
1, NULL, NULL, NULL, '测试数据-异常:补考通过(最终成绩)',
|
||||
'0', 'test-data', NOW()
|
||||
FROM tmp_pair p
|
||||
JOIN tmp_seed sd
|
||||
ON sd.student_id = p.student_id AND sd.plan_course_id = p.plan_course_id
|
||||
WHERE sd.scenario IN ('RETAKE1', 'RETAKE2');
|
||||
|
||||
-- ==============================================================================
|
||||
-- 第 5 步:结果校验(只读)
|
||||
-- ==============================================================================
|
||||
-- 5.1 该计划成绩总量与场景分布
|
||||
SELECT t.exam_status, t.retake, t.is_final, COUNT(*) AS row_count
|
||||
FROM train_theory_exam_score t
|
||||
JOIN train_class_plan cp ON cp.plan_id = t.plan_id
|
||||
WHERE cp.plan_name = @plan_name AND t.create_by = 'test-data'
|
||||
GROUP BY t.exam_status, t.retake, t.is_final
|
||||
ORDER BY t.exam_status, t.retake, t.is_final;
|
||||
|
||||
-- 5.2 成绩明细(含学员姓名、课程名、计划名)
|
||||
SELECT cp.plan_name, s.s_id, s.s_name, c.c_name,
|
||||
t.retake, t.retake_no, t.exam_status, t.score, t.full_score, t.pass_flag,
|
||||
t.is_final, t.exam_end_time, t.remark
|
||||
FROM train_theory_exam_score t
|
||||
JOIN train_class_plan cp ON cp.plan_id = t.plan_id
|
||||
JOIN train_student s ON s.s_id = t.student_id
|
||||
JOIN train_course c ON c.c_id = t.c_id
|
||||
WHERE cp.plan_name = @plan_name AND t.create_by = 'test-data'
|
||||
ORDER BY s.s_id, c.c_id, t.retake_no;
|
||||
|
||||
-- 5.3 关联一致性自检(学员 / 课程实例 / 参训档案三项,均应 0 行)
|
||||
SELECT t.theory_score_id, '学员不存在' AS problem
|
||||
FROM train_theory_exam_score t
|
||||
JOIN train_class_plan cp ON cp.plan_id = t.plan_id
|
||||
LEFT JOIN train_student s ON s.s_id = t.student_id
|
||||
WHERE cp.plan_name = @plan_name AND s.s_id IS NULL
|
||||
UNION ALL
|
||||
SELECT t.theory_score_id, '课程实例与计划/课程不匹配'
|
||||
FROM train_theory_exam_score t
|
||||
JOIN train_class_plan cp ON cp.plan_id = t.plan_id
|
||||
LEFT JOIN train_plan_course pc
|
||||
ON pc.plan_course_id = t.plan_course_id
|
||||
AND pc.plan_id = t.plan_id
|
||||
AND pc.c_id = t.c_id
|
||||
WHERE cp.plan_name = @plan_name AND pc.plan_course_id IS NULL
|
||||
UNION ALL
|
||||
SELECT t.theory_score_id, '参训档案与学员/班级不匹配'
|
||||
FROM train_theory_exam_score t
|
||||
JOIN train_class_plan cp ON cp.plan_id = t.plan_id
|
||||
LEFT JOIN train_enrollment e
|
||||
ON e.enrollment_id = t.enrollment_id
|
||||
AND e.student_id = t.student_id
|
||||
AND e.plan_id = t.plan_id
|
||||
WHERE cp.plan_name = @plan_name AND e.enrollment_id IS NULL;
|
||||
|
||||
-- 5.4 唯一性自检(均应为 0 行)
|
||||
SELECT enrollment_id, plan_course_id, retake_no, COUNT(*) AS cnt
|
||||
FROM train_theory_exam_score
|
||||
WHERE del_flag = '0'
|
||||
GROUP BY enrollment_id, plan_course_id, retake_no
|
||||
HAVING COUNT(*) > 1;
|
||||
|
||||
SELECT plan_course_id, student_id, COUNT(*) AS final_cnt
|
||||
FROM train_theory_exam_score
|
||||
WHERE del_flag = '0' AND is_final = 1
|
||||
GROUP BY plan_course_id, student_id
|
||||
HAVING COUNT(*) > 1;
|
||||
|
||||
-- 5.5 成绩合法性自检(应 0 行)
|
||||
SELECT t.theory_score_id, t.exam_status, t.score, t.full_score, t.pass_flag
|
||||
FROM train_theory_exam_score t
|
||||
JOIN train_class_plan cp ON cp.plan_id = t.plan_id
|
||||
WHERE cp.plan_name = @plan_name AND t.create_by = 'test-data'
|
||||
AND (
|
||||
(t.exam_status = 'NORMAL' AND t.score IS NULL)
|
||||
OR (t.exam_status IN ('ABSENT', 'EXEMPT') AND t.score IS NOT NULL)
|
||||
OR (t.score IS NOT NULL AND (t.score < 0 OR t.score > t.full_score))
|
||||
OR (t.score IS NOT NULL AND t.pass_flag <> IF(t.score >= 60, 1, 0))
|
||||
);
|
||||
|
||||
-- ------------------------------------------------------------------------------
|
||||
-- 第 6 步:清理(仅删除本脚本为该计划生成的数据,取消注释后执行)
|
||||
-- ------------------------------------------------------------------------------
|
||||
-- DELETE t FROM train_theory_exam_score t
|
||||
-- JOIN train_class_plan cp ON cp.plan_id = t.plan_id
|
||||
-- WHERE cp.plan_name = @plan_name AND t.create_by = 'test-data';
|
||||
|
||||
-- ==============================================================================
|
||||
-- 附:无法落库的异常场景(DB 约束拦截,脚本故意不插,可用于负向验证)
|
||||
-- 1) 同一 (enrollment_id, plan_course_id, retake_no) 重复 → uk_theory_enroll_course_retake
|
||||
-- 2) 同一课程同一学员两条 is_final = 1 → uk_theory_final(生成列 final_flag)
|
||||
-- 3) 缺考/免修带分数、正常考试无分数 → chk_theory_score_filled(MySQL 8.0 强制)
|
||||
-- 4) 考试结束时间早于开始时间 → chk_theory_exam_time
|
||||
-- 5) 引用不存在的学员/课程/计划 → 外键拦截
|
||||
-- ==============================================================================
|
||||
Reference in new issue
Block a user