feat(db): 新增Kingbase数据库V2迁移与回滚脚本

This commit is contained in:
wangzhibiao committed 2026-09-14 02:13:09 +08:00
1 parent 277c9e210e
commit c791e23f3e
17 files changed
+3386

No files matched your search

+435
View File
@@ -0,0 +1,435 @@
-- ==============================================================================
-- RailwaySystem 数据库 V2 迁移前置检查: 000_preflight.sql
-- 适用数据库: KingbaseES V009R003C018 (schema: public)
-- 执行目标: 严格只读核验当前数据库完全符合 Phase 0.5 精确结构骨架与数据基线
-- 设计依据: rollback/000_phase05_exact_schema.sql 与 13_live_database_snapshot.md
-- 验收机制:
-- 1. 纯只读探测,零写入,零结构修改
-- 2. 基于预期元数据集合 (VALUES CTE) 进行全集合差异断言,杜绝数量对冲假象
-- 3. 精确校验: 14旧业务表PK + 24旧业务FK + 7组业务UNIQUE + 6个identity主键 + 9个触发器
-- 4. 任何 missing / unexpected / definition mismatch 立即抛出 EXCEPTION 中止后续迁移
-- ==============================================================================
DO $$
DECLARE
v_schema TEXT;
v_cnt INT;
v_diff_text TEXT := '';
r RECORD;
BEGIN
-- ==========================================================================
-- 1. 会话 Schema 校验 (必须显式为 public)
-- ==========================================================================
SELECT current_schema() INTO v_schema;
IF v_schema <> 'public' THEN
RAISE EXCEPTION '>>> [Preflight-FATAL] 当前会话 schema 为 "%",必须显式为 "public"', v_schema;
END IF;
-- ==========================================================================
-- 2. 14 张 Phase 0.5 历史基线表双向集合差异检查 (精确存在且仅存在这 14 张 train_* 表)
-- ==========================================================================
v_diff_text := '';
FOR r IN (
WITH expected_tables(table_name) AS (
VALUES
('train_teacher'), ('train_student'), ('train_course'), ('train_class_plan'),
('train_dormitory_info'), ('train_dorm_auth'), ('train_classroom_info'), ('train_training_site'),
('train_class_report'), ('train_electronic_board'), ('train_student_checkin'), ('train_score_info'),
('train_study_duration'), ('train_student_portrait')
),
actual_tables AS (
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'public' AND table_name LIKE 'train_%'
)
SELECT 'MISSING_TABLE' AS diff_type, e.table_name
FROM expected_tables e
LEFT JOIN actual_tables a ON e.table_name = a.table_name
WHERE a.table_name IS NULL
UNION ALL
SELECT 'UNEXPECTED_TABLE' AS diff_type, a.table_name
FROM actual_tables a
LEFT JOIN expected_tables e ON a.table_name = e.table_name
WHERE e.table_name IS NULL
) LOOP
v_diff_text := v_diff_text || r.diff_type || ': ' || r.table_name || '; ';
END LOOP;
IF v_diff_text <> '' THEN
RAISE EXCEPTION '>>> [Preflight-FATAL] Phase 0.5 历史基线表集合全等校验失败: %', v_diff_text;
END IF;
-- ==========================================================================
-- 3. 检查 8 张 V2 新增表均不得存在 (杜绝半迁移脏环境)
-- ==========================================================================
v_diff_text := '';
FOR r IN (
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name IN (
'train_resource', 'train_course_resource', 'train_plan_course', 'train_enrollment',
'train_lesson', 'train_accommodation', 'train_score_item', 'train_student_score'
)
) LOOP
v_diff_text := v_diff_text || r.table_name || ', ';
END LOOP;
IF v_diff_text <> '' THEN
RAISE EXCEPTION '>>> [Preflight-FATAL] 检测到已存在 V2 新表,当前非纯净 Phase 0.5 基线: %', v_diff_text;
END IF;
-- ==========================================================================
-- 4. 数据行数基线: train_teacher 精确 1 行,其余 13 张旧表精确 0 行
-- ==========================================================================
SELECT count(*) INTO v_cnt FROM train_teacher;
IF v_cnt <> 1 THEN
RAISE EXCEPTION '>>> [Preflight-FATAL] train_teacher 行数期望为 1,实际为 %', v_cnt;
END IF;
SELECT count(*) INTO v_cnt FROM train_student;
IF v_cnt <> 0 THEN RAISE EXCEPTION '>>> [Preflight-FATAL] train_student 非空,包含 % 行', v_cnt; END IF;
SELECT count(*) INTO v_cnt FROM train_course;
IF v_cnt <> 0 THEN RAISE EXCEPTION '>>> [Preflight-FATAL] train_course 非空,包含 % 行', v_cnt; END IF;
SELECT count(*) INTO v_cnt FROM train_class_plan;
IF v_cnt <> 0 THEN RAISE EXCEPTION '>>> [Preflight-FATAL] train_class_plan 非空,包含 % 行', v_cnt; END IF;
SELECT count(*) INTO v_cnt FROM train_dormitory_info;
IF v_cnt <> 0 THEN RAISE EXCEPTION '>>> [Preflight-FATAL] train_dormitory_info 非空,包含 % 行', v_cnt; END IF;
SELECT count(*) INTO v_cnt FROM train_dorm_auth;
IF v_cnt <> 0 THEN RAISE EXCEPTION '>>> [Preflight-FATAL] train_dorm_auth 非空,包含 % 行', v_cnt; END IF;
SELECT count(*) INTO v_cnt FROM train_classroom_info;
IF v_cnt <> 0 THEN RAISE EXCEPTION '>>> [Preflight-FATAL] train_classroom_info 非空,包含 % 行', v_cnt; END IF;
SELECT count(*) INTO v_cnt FROM train_training_site;
IF v_cnt <> 0 THEN RAISE EXCEPTION '>>> [Preflight-FATAL] train_training_site 非空,包含 % 行', v_cnt; END IF;
SELECT count(*) INTO v_cnt FROM train_class_report;
IF v_cnt <> 0 THEN RAISE EXCEPTION '>>> [Preflight-FATAL] train_class_report 非空,包含 % 行', v_cnt; END IF;
SELECT count(*) INTO v_cnt FROM train_electronic_board;
IF v_cnt <> 0 THEN RAISE EXCEPTION '>>> [Preflight-FATAL] train_electronic_board 非空,包含 % 行', v_cnt; END IF;
SELECT count(*) INTO v_cnt FROM train_student_checkin;
IF v_cnt <> 0 THEN RAISE EXCEPTION '>>> [Preflight-FATAL] train_student_checkin 非空,包含 % 行', v_cnt; END IF;
SELECT count(*) INTO v_cnt FROM train_score_info;
IF v_cnt <> 0 THEN RAISE EXCEPTION '>>> [Preflight-FATAL] train_score_info 非空,包含 % 行', v_cnt; END IF;
SELECT count(*) INTO v_cnt FROM train_study_duration;
IF v_cnt <> 0 THEN RAISE EXCEPTION '>>> [Preflight-FATAL] train_study_duration 非空,包含 % 行', v_cnt; END IF;
SELECT count(*) INTO v_cnt FROM train_student_portrait;
IF v_cnt <> 0 THEN RAISE EXCEPTION '>>> [Preflight-FATAL] train_student_portrait 非空,包含 % 行', v_cnt; END IF;
-- ==========================================================================
-- 5. 14 个 Phase 0.5 旧业务主键 (PK) 精确骨架校验
-- ==========================================================================
v_diff_text := '';
FOR r IN (
WITH expected_pk(table_name, constraint_name, column_name) AS (
VALUES
('train_teacher', 'pk_train_teacher', 't_id'),
('train_student', 'pk_train_student', 's_id'),
('train_course', 'pk_train_course', 'c_id'),
('train_class_plan', 'pk_train_class_plan', 'plan_id'),
('train_dormitory_info', 'pk_train_dormitory_info', 'dorm_id'),
('train_dorm_auth', 'pk_train_dorm_auth', 'auth_id'),
('train_classroom_info', 'pk_train_classroom_info', 'class_room_id'),
('train_training_site', 'pk_train_training_site', 'site_id'),
('train_class_report', 'pk_train_class_report', 'report_id'),
('train_electronic_board', 'pk_train_electronic_board', 'board_id'),
('train_student_checkin', 'pk_train_student_checkin', 'checkin_id'),
('train_score_info', 'pk_train_score_info', 'score_id'),
('train_study_duration', 'pk_train_study_duration', 'dur_id'),
('train_student_portrait', 'pk_train_student_portrait', 'portrait_id')
),
actual_pk AS (
SELECT tc.table_name, tc.constraint_name, kcu.column_name
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
WHERE tc.constraint_type = 'PRIMARY KEY'
AND tc.table_schema = 'public'
AND tc.table_name LIKE 'train_%'
)
SELECT 'MISSING_PK' AS diff_type, e.table_name, e.constraint_name, e.column_name
FROM expected_pk e
LEFT JOIN actual_pk a
ON e.table_name = a.table_name AND e.constraint_name = a.constraint_name AND e.column_name = a.column_name
WHERE a.table_name IS NULL
UNION ALL
SELECT 'UNEXPECTED_PK' AS diff_type, a.table_name, a.constraint_name, a.column_name
FROM actual_pk a
LEFT JOIN expected_pk e
ON a.table_name = e.table_name AND a.constraint_name = e.constraint_name AND a.column_name = e.column_name
WHERE e.table_name IS NULL
) LOOP
v_diff_text := v_diff_text || r.diff_type || ': ' || r.table_name || '.' || r.constraint_name || '(' || r.column_name || '); ';
END LOOP;
IF v_diff_text <> '' THEN
RAISE EXCEPTION '>>> [Preflight-FATAL] Phase 0.5 主键 (PK) 骨架不匹配: %', v_diff_text;
END IF;
-- ==========================================================================
-- 6. 6 个 Phase 0.5 Identity 自增主键精确校验
-- ==========================================================================
v_diff_text := '';
FOR r IN (
WITH expected_identity(table_name, column_name) AS (
VALUES
('train_dorm_auth', 'auth_id'),
('train_class_report', 'report_id'),
('train_student_checkin', 'checkin_id'),
('train_score_info', 'score_id'),
('train_study_duration', 'dur_id'),
('train_student_portrait', 'portrait_id')
),
actual_identity AS (
SELECT table_name, column_name
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name LIKE 'train_%'
AND is_identity = 'YES'
)
SELECT 'MISSING_IDENTITY' AS diff_type, e.table_name, e.column_name
FROM expected_identity e
LEFT JOIN actual_identity a
ON e.table_name = a.table_name AND e.column_name = a.column_name
WHERE a.table_name IS NULL
UNION ALL
SELECT 'UNEXPECTED_IDENTITY' AS diff_type, a.table_name, a.column_name
FROM actual_identity a
LEFT JOIN expected_identity e
ON a.table_name = e.table_name AND a.column_name = e.column_name
WHERE e.table_name IS NULL
) LOOP
v_diff_text := v_diff_text || r.diff_type || ': ' || r.table_name || '.' || r.column_name || '; ';
END LOOP;
IF v_diff_text <> '' THEN
RAISE EXCEPTION '>>> [Preflight-FATAL] Phase 0.5 Identity 属性不匹配: %', v_diff_text;
END IF;
-- ==========================================================================
-- 7. 7 组 Phase 0.5 历史业务唯一约束 (UNIQUE) 骨架精确校验
-- ==========================================================================
v_diff_text := '';
FOR r IN (
WITH expected_unique(table_name, constraint_name, column_list) AS (
VALUES
('train_dormitory_info', 'uq_dorm_building_room', 'dorm_building, dorm_room'),
('train_dorm_auth', 'uq_dormauth_stu_plan', 'student_id, plan_id'),
('train_class_report', 'uq_classreport_plan_stu', 'plan_id, student_id'),
('train_electronic_board', 'uq_board_sn', 'board_sn'),
('train_student_checkin', 'uq_checkin_lesson_stu', 'plan_id, c_id, student_id, lesson_start_time'),
('train_score_info', 'uq_scoreinfo_plan_c_stu', 'plan_id, c_id, student_id'),
('train_student_portrait', 'uq_portrait_stu_plan', 'student_id, plan_id')
),
actual_unique AS (
SELECT
tc.table_name,
tc.constraint_name,
string_agg(kcu.column_name, ', ' ORDER BY kcu.ordinal_position) AS column_list
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
WHERE tc.constraint_type = 'UNIQUE'
AND tc.table_schema = 'public'
AND tc.table_name LIKE 'train_%'
GROUP BY tc.table_name, tc.constraint_name
)
SELECT 'MISSING_UNIQUE' AS diff_type, e.table_name, e.constraint_name, e.column_list
FROM expected_unique e
LEFT JOIN actual_unique a
ON e.table_name = a.table_name AND e.constraint_name = a.constraint_name AND e.column_list = a.column_list
WHERE a.table_name IS NULL
UNION ALL
SELECT 'UNEXPECTED_UNIQUE' AS diff_type, a.table_name, a.constraint_name, a.column_list
FROM actual_unique a
LEFT JOIN expected_unique e
ON a.table_name = e.table_name AND a.constraint_name = e.constraint_name AND a.column_list = e.column_list
WHERE e.table_name IS NULL
) LOOP
v_diff_text := v_diff_text || r.diff_type || ': ' || r.table_name || '.' || r.constraint_name || '(' || r.column_list || '); ';
END LOOP;
IF v_diff_text <> '' THEN
RAISE EXCEPTION '>>> [Preflight-FATAL] Phase 0.5 唯一约束 (UNIQUE) 骨架不匹配: %', v_diff_text;
END IF;
-- ==========================================================================
-- 8. 24 组 Phase 0.5 历史业务外键约束 (FK) 骨架精确校验 (名称+所属表+源列+引用表+引用列)
-- ==========================================================================
v_diff_text := '';
FOR r IN (
WITH expected_fk(table_name, constraint_name, column_name, foreign_table_name, foreign_column_name) AS (
VALUES
('train_class_plan', 'fk_classplan_course', 'c_id', 'train_course', 'c_id'),
('train_class_plan', 'fk_classplan_teacher', 't_id', 'train_teacher', 't_id'),
('train_dorm_auth', 'fk_dormauth_stu', 'student_id', 'train_student', 's_id'),
('train_dorm_auth', 'fk_dormauth_plan', 'plan_id', 'train_class_plan', 'plan_id'),
('train_dorm_auth', 'fk_dormauth_dorm', 'dorm_id', 'train_dormitory_info', 'dorm_id'),
('train_class_report', 'fk_classreport_plan', 'plan_id', 'train_class_plan', 'plan_id'),
('train_class_report', 'fk_classreport_stu', 'student_id', 'train_student', 's_id'),
('train_class_report', 'fk_classreport_dorm', 'alloc_dorm_id', 'train_dormitory_info', 'dorm_id'),
('train_class_report', 'fk_classreport_classroom', 'alloc_classroom_id', 'train_classroom_info', 'class_room_id'),
('train_class_report', 'fk_classreport_site', 'alloc_site_id', 'train_training_site', 'site_id'),
('train_electronic_board', 'fk_board_classroom', 'bind_classroom_id', 'train_classroom_info', 'class_room_id'),
('train_electronic_board', 'fk_board_plan', 'bind_plan_id', 'train_class_plan', 'plan_id'),
('train_student_checkin', 'fk_checkin_plan', 'plan_id', 'train_class_plan', 'plan_id'),
('train_student_checkin', 'fk_checkin_course', 'c_id', 'train_course', 'c_id'),
('train_student_checkin', 'fk_checkin_stu', 'student_id', 'train_student', 's_id'),
('train_student_checkin', 'fk_checkin_board', 'board_id', 'train_electronic_board', 'board_id'),
('train_score_info', 'fk_scoreinfo_plan', 'plan_id', 'train_class_plan', 'plan_id'),
('train_score_info', 'fk_scoreinfo_course', 'c_id', 'train_course', 'c_id'),
('train_score_info', 'fk_scoreinfo_stu', 'student_id', 'train_student', 's_id'),
('train_study_duration', 'fk_studydur_stu', 'student_id', 'train_student', 's_id'),
('train_study_duration', 'fk_studydur_plan', 'plan_id', 'train_class_plan', 'plan_id'),
('train_study_duration', 'fk_studydur_course', 'c_id', 'train_course', 'c_id'),
('train_student_portrait', 'fk_portrait_stu', 'student_id', 'train_student', 's_id'),
('train_student_portrait', 'fk_portrait_plan', 'plan_id', 'train_class_plan', 'plan_id')
),
actual_fk AS (
SELECT
tc.table_name,
tc.constraint_name,
kcu.column_name,
ccu.table_name AS foreign_table_name,
ccu.column_name AS foreign_column_name
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
JOIN information_schema.constraint_column_usage ccu
ON tc.constraint_name = ccu.constraint_name AND tc.table_schema = ccu.table_schema
WHERE tc.constraint_type = 'FOREIGN KEY'
AND tc.table_schema = 'public'
AND tc.table_name LIKE 'train_%'
)
SELECT 'MISSING_FK' AS diff_type, e.table_name, e.constraint_name, e.column_name, e.foreign_table_name, e.foreign_column_name
FROM expected_fk e
LEFT JOIN actual_fk a
ON e.table_name = a.table_name
AND e.constraint_name = a.constraint_name
AND e.column_name = a.column_name
AND e.foreign_table_name = a.foreign_table_name
AND e.foreign_column_name = a.foreign_column_name
WHERE a.table_name IS NULL
UNION ALL
SELECT 'UNEXPECTED_FK' AS diff_type, a.table_name, a.constraint_name, a.column_name, a.foreign_table_name, a.foreign_column_name
FROM actual_fk a
LEFT JOIN expected_fk e
ON a.table_name = e.table_name
AND a.constraint_name = e.constraint_name
AND a.column_name = e.column_name
AND a.foreign_table_name = e.foreign_table_name
AND a.foreign_column_name = e.foreign_column_name
WHERE e.table_name IS NULL
) LOOP
v_diff_text := v_diff_text || r.diff_type || ': ' || r.table_name || '.' || r.constraint_name || ' (' || r.column_name || ' -> ' || r.foreign_table_name || '.' || r.foreign_column_name || '); ';
END LOOP;
IF v_diff_text <> '' THEN
RAISE EXCEPTION '>>> [Preflight-FATAL] Phase 0.5 外键约束 (FK) 骨架不匹配: %', v_diff_text;
END IF;
-- ==========================================================================
-- 9. 9 个 Phase 0.5 历史 update_time 触发器精确校验
-- ==========================================================================
v_diff_text := '';
FOR r IN (
WITH expected_triggers(table_name, trigger_name) AS (
VALUES
('train_teacher', 'trig_train_teacher_modify'),
('train_student', 'trig_train_student_modify'),
('train_course', 'trig_train_course_modify'),
('train_classroom_info', 'trig_train_classroom_modify'),
('train_training_site', 'trig_train_trainsite_modify'),
('train_electronic_board', 'trig_train_board_modify'),
('train_dormitory_info', 'trig_train_dorminfo_modify'),
('train_class_plan', 'trig_train_classplan_modify'),
('train_dorm_auth', 'trig_train_dormauth_modify')
),
actual_triggers AS (
SELECT event_object_table AS table_name, trigger_name
FROM information_schema.triggers
WHERE trigger_schema = 'public'
AND event_object_table LIKE 'train_%'
)
SELECT 'MISSING_TRIGGER' AS diff_type, e.table_name, e.trigger_name
FROM expected_triggers e
LEFT JOIN actual_triggers a
ON e.table_name = a.table_name AND e.trigger_name = a.trigger_name
WHERE a.table_name IS NULL
UNION ALL
SELECT 'UNEXPECTED_TRIGGER' AS diff_type, a.table_name, a.trigger_name
FROM actual_triggers a
LEFT JOIN expected_triggers e
ON a.table_name = e.table_name AND a.trigger_name = e.trigger_name
WHERE e.table_name IS NULL
) LOOP
v_diff_text := v_diff_text || r.diff_type || ': ' || r.table_name || '.' || r.trigger_name || '; ';
END LOOP;
IF v_diff_text <> '' THEN
RAISE EXCEPTION '>>> [Preflight-FATAL] Phase 0.5 触发器骨架不匹配: %', v_diff_text;
END IF;
-- ==========================================================================
-- 10. 历史旧字段特征校验 (确认是旧模型结构,未提前被执行过 V2)
-- ==========================================================================
SELECT count(*) INTO v_cnt FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = 'train_class_plan' AND column_name IN ('c_id', 't_id');
IF v_cnt <> 2 THEN
RAISE EXCEPTION '>>> [Preflight-FATAL] train_class_plan 未包含旧 c_id 与 t_id 列,结构已被篡改';
END IF;
SELECT count(*) INTO v_cnt FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = 'train_student_checkin' AND column_name = 'lesson_start_time';
IF v_cnt <> 1 THEN
RAISE EXCEPTION '>>> [Preflight-FATAL] train_student_checkin 结构异常,未发现旧 lesson_start_time 列';
END IF;
SELECT count(*) INTO v_cnt FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = 'train_score_info'
AND column_name IN ('course_score', 'train_score', 'exam_score');
IF v_cnt <> 3 THEN
RAISE EXCEPTION '>>> [Preflight-FATAL] train_score_info 结构异常,未包含旧成绩列';
END IF;
SELECT count(*) INTO v_cnt FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = 'train_class_report'
AND column_name IN ('alloc_dorm_id', 'alloc_classroom_id', 'alloc_site_id');
IF v_cnt <> 3 THEN
RAISE EXCEPTION '>>> [Preflight-FATAL] train_class_report 结构异常,未包含旧预分配列';
END IF;
-- ==========================================================================
-- 11. public.sys_* 系统基础表零受损基线核验
-- ==========================================================================
SELECT count(*) INTO v_cnt FROM public.sys_user;
IF v_cnt <> 1 THEN RAISE EXCEPTION '>>> [Preflight-FATAL] 基线 sys_user 行数受损,当前为 %', v_cnt; END IF;
SELECT count(*) INTO v_cnt FROM public.sys_menu;
IF v_cnt <> 106 THEN RAISE EXCEPTION '>>> [Preflight-FATAL] 基线 sys_menu 行数受损,当前为 %', v_cnt; END IF;
SELECT count(*) INTO v_cnt FROM public.sys_dept;
IF v_cnt <> 15 THEN RAISE EXCEPTION '>>> [Preflight-FATAL] 基线 sys_dept 行数受损,当前为 %', v_cnt; END IF;
SELECT count(*) INTO v_cnt FROM public.sys_dict_type;
IF v_cnt <> 44 THEN RAISE EXCEPTION '>>> [Preflight-FATAL] 基线 sys_dict_type 行数受损,当前为 %', v_cnt; END IF;
SELECT count(*) INTO v_cnt FROM public.sys_dict_data;
IF v_cnt <> 500 THEN RAISE EXCEPTION '>>> [Preflight-FATAL] 基线 sys_dict_data 行数受损,当前为 %', v_cnt; END IF;
RAISE NOTICE '==============================================================================';
RAISE NOTICE '>>> [Preflight-PASS] Phase 0.5 精确骨架校验全部通过!';
RAISE NOTICE '>>> 核验项: 14旧表 + 14 PK + 24 FK + 7 UNIQUE + 6 Identity + 9 Triggers + 行数基线';
RAISE NOTICE '>>> 判定结果: 当前数据库处于纯净 Phase 0.5 历史基线状态,允许执行 V2 迁移。';
RAISE NOTICE '==============================================================================';
END $$;
@@ -0,0 +1,38 @@
-- ==============================================================================
-- RailwaySystem 数据库 V2 迁移安全门禁 A: 000_tx_capability_probe.sql
-- 适用数据库: KingbaseES V009R003C018 (schema: public, database_mode: mysql)
-- 执行目标: 在正式执行迁移前,实证探测当前数据库实例是否支持 DDL 事务回滚能力
-- 约束原则:
-- 1. 本文件为【前置能力探针】,绝不读取或修改任何 train_* 业务表与 sys_* 系统表
-- 2. 仅在临时对象 public.__railway_v2_tx_probe 上执行可逆测试
-- 3. 若 ROLLBACK 后探针表仍旧残留,表明 DDL 隐式自动提交,立即强行熔断后续迁移
-- ==============================================================================
-- Phase A: 启动显式事务,创建并修改探针临时表,随后主动 ROLLBACK
BEGIN;
CREATE TABLE public.__railway_v2_tx_probe (
id INTEGER
);
ALTER TABLE public.__railway_v2_tx_probe
ADD COLUMN test_col INTEGER;
ROLLBACK;
-- Phase B: 只读断言验证探针表是否被完整撤销
DO $$
DECLARE
v_probe_exists INT;
BEGIN
SELECT count(*) INTO v_probe_exists
FROM information_schema.tables
WHERE table_schema = 'public' AND table_name = '__railway_v2_tx_probe';
IF v_probe_exists > 0 THEN
-- 发现探针表未被回滚,说明数据库引擎在执行 DDL 时发生了隐式提交
RAISE EXCEPTION '>>> [Probe-FATAL] 当前 Kingbase 实例 DDL 不满足 V2 迁移事务假设 (DDL 在 ROLLBACK 后依然残留),禁止继续执行 001~010 迁移!';
ELSE
RAISE NOTICE '>>> [Probe-PASS] 数据库 DDL 事务原子性验证通过:Kingbase 实例支持 DDL 事务回滚,符合 V2 迁移安全前提。';
END IF;
END $$;
@@ -0,0 +1,103 @@
-- ==============================================================================
-- RailwaySystem 数据库 V2 迁移脚本: 001_backup_current_schema.sql
-- 适用数据库: KingbaseES V009R003C018 (schema: public)
-- 执行目标: 备份当前 14 张培训旧表的数据与列结构快照
-- 事务边界: 全程在显式事务 BEGIN ... COMMIT 中执行
-- 安全原则:
-- 1. 禁止覆盖已有备份: 执行前检查若存在任意 bak_v1_* 表,立即抛出 EXCEPTION 强行阻断
-- 2. 严禁使用 DROP TABLE IF EXISTS bak_v1_* 自动抹除已有备份
-- 3. 严禁触碰任何 sys_* 系统表
-- 4. 说明: CREATE TABLE AS SELECT 在 KingbaseES 中仅复制列名、类型与数据,
-- 不承担完整 DDL 快照职责 (完整结构备份由 rollback/000_phase05_exact_schema.sql 承担)
-- ==============================================================================
BEGIN;
-- 步骤 1: 强防重与防覆盖检查 (杜绝二次执行覆盖原始 V1 快照)
DO $$
DECLARE
v_bak_cnt INT;
v_existing_baks TEXT := '';
r RECORD;
BEGIN
FOR r IN (
SELECT table_name
FROM information_schema.tables
WHERE table_schema = current_schema() AND table_name LIKE 'bak_v1_%'
) LOOP
v_existing_baks := v_existing_baks || r.table_name || ', ';
END LOOP;
IF v_existing_baks <> '' THEN
RAISE EXCEPTION '>>> [001-FATAL] 检测到既有备份表已经存在: [%]。为防止意外覆盖真实 V1 历史数据,严禁重复执行 001 备份!如需重新备份,请由 DBA 手动确认后归档清理既有 bak_v1_* 表。', v_existing_baks;
END IF;
END $$;
-- 步骤 2: 备份具备真实数据的核心主数据表: train_teacher (当前环境精确 1 行)
CREATE TABLE bak_v1_train_teacher AS
SELECT * FROM train_teacher;
COMMENT ON TABLE bak_v1_train_teacher IS 'Phase 0.5 历史备份: 教师主档 (含唯一真实数据)';
-- 步骤 3: 备份其余 13 张现有空表 (当前行数均为 0,备份数据与字段基线)
CREATE TABLE bak_v1_train_student AS SELECT * FROM train_student;
COMMENT ON TABLE bak_v1_train_student IS 'Phase 0.5 历史备份: 学员表快照';
CREATE TABLE bak_v1_train_course AS SELECT * FROM train_course;
COMMENT ON TABLE bak_v1_train_course IS 'Phase 0.5 历史备份: 课程表快照';
CREATE TABLE bak_v1_train_class_plan AS SELECT * FROM train_class_plan;
COMMENT ON TABLE bak_v1_train_class_plan IS 'Phase 0.5 历史备份: 开班计划快照';
CREATE TABLE bak_v1_train_dormitory_info AS SELECT * FROM train_dormitory_info;
COMMENT ON TABLE bak_v1_train_dormitory_info IS 'Phase 0.5 历史备份: 宿舍信息快照';
CREATE TABLE bak_v1_train_dorm_auth AS SELECT * FROM train_dorm_auth;
COMMENT ON TABLE bak_v1_train_dorm_auth IS 'Phase 0.5 历史备份: 宿舍门禁快照';
CREATE TABLE bak_v1_train_classroom_info AS SELECT * FROM train_classroom_info;
COMMENT ON TABLE bak_v1_train_classroom_info IS 'Phase 0.5 历史备份: 教室信息快照';
CREATE TABLE bak_v1_train_training_site AS SELECT * FROM train_training_site;
COMMENT ON TABLE bak_v1_train_training_site IS 'Phase 0.5 历史备份: 实训场地快照';
CREATE TABLE bak_v1_train_class_report AS SELECT * FROM train_class_report;
COMMENT ON TABLE bak_v1_train_class_report IS 'Phase 0.5 历史备份: 报到记录快照';
CREATE TABLE bak_v1_train_electronic_board AS SELECT * FROM train_electronic_board;
COMMENT ON TABLE bak_v1_train_electronic_board IS 'Phase 0.5 历史备份: 电子班牌快照';
CREATE TABLE bak_v1_train_student_checkin AS SELECT * FROM train_student_checkin;
COMMENT ON TABLE bak_v1_train_student_checkin IS 'Phase 0.5 历史备份: 考勤签到快照';
CREATE TABLE bak_v1_train_score_info AS SELECT * FROM train_score_info;
COMMENT ON TABLE bak_v1_train_score_info IS 'Phase 0.5 历史备份: 旧成绩表快照 (待淘汰)';
CREATE TABLE bak_v1_train_study_duration AS SELECT * FROM train_study_duration;
COMMENT ON TABLE bak_v1_train_study_duration IS 'Phase 0.5 历史备份: 学习时长快照';
CREATE TABLE bak_v1_train_student_portrait AS SELECT * FROM train_student_portrait;
COMMENT ON TABLE bak_v1_train_student_portrait IS 'Phase 0.5 历史备份: 学员画像快照';
-- 步骤 4: 校验备份完整性
DO $$
DECLARE
v_teacher_cnt INT;
v_bak_total INT;
BEGIN
SELECT count(*) INTO v_teacher_cnt FROM bak_v1_train_teacher;
IF v_teacher_cnt <> 1 THEN
RAISE EXCEPTION '>>> [001-FATAL] 备份校验失败: bak_v1_train_teacher 期望 1 条数据,实际为 % 条', v_teacher_cnt;
END IF;
SELECT count(*) INTO v_bak_total
FROM information_schema.tables
WHERE table_schema = current_schema() AND table_name LIKE 'bak_v1_%';
IF v_bak_total <> 14 THEN
RAISE EXCEPTION '>>> [001-FATAL] 备份表总数异常: 期望 14 张备份表,实际生成 % 张', v_bak_total;
END IF;
RAISE NOTICE '>>> [001-PASS] 14 张表快照备份全部完成,教师唯一真实数据已安全封存。';
END $$;
COMMIT;
@@ -0,0 +1,213 @@
-- ==============================================================================
-- RailwaySystem 数据库 V2 迁移脚本: 002_create_new_tables.sql
-- 适用数据库: KingbaseES V009R003C018 (schema: public)
-- 执行目标: 创建数据库 V2 新增的 8 张核心培训业务表
-- 事务边界: 全程在显式事务 BEGIN ... COMMIT 中执行
-- 设计依据: RailwaySystem 数据库 V2 字段设计与 Kingbase DDL 规范 V1.0 及 Round 1 审查要求
-- 规范要求:
-- 1. 主键为 bigint GENERATED BY DEFAULT AS IDENTITY 或 varchar(32)
-- 2. 时间类型统一使用 timestamp,禁用 DATETIME / ON UPDATE CURRENT_TIMESTAMP
-- 3. 状态与布尔统一使用 smallint
-- 4. train_lesson.t_id 设为 NOT NULL
-- 5. train_student_score.score 设为 NOT NULL (删除 DEFAULT 0) 并增加 score >= 0 CHECK
-- 6. train_lesson 增加 venue_type 与教室/场地的单行互斥 CHECK
-- 7. 字典语义严格对齐 V2 冻结稿
-- ==============================================================================
BEGIN;
-- 1. 统一教学资源主表
CREATE TABLE train_resource (
resource_id VARCHAR(32) NOT NULL,
resource_name VARCHAR(150) NOT NULL,
resource_type SMALLINT NOT NULL,
resource_url TEXT NOT NULL,
description TEXT,
status SMALLINT NOT NULL DEFAULT 1,
create_by VARCHAR(64),
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_by VARCHAR(64),
update_time TIMESTAMP,
remark VARCHAR(500),
del_flag CHAR(1) NOT NULL DEFAULT '0',
CONSTRAINT pk_train_resource PRIMARY KEY (resource_id)
);
COMMENT ON TABLE train_resource IS '统一教学资源表 (文档、视频、3D模型、VR资源、外部教学链接、其他)';
COMMENT ON COLUMN train_resource.resource_id IS '资源唯一ID';
COMMENT ON COLUMN train_resource.resource_name IS '资源名称';
COMMENT ON COLUMN train_resource.resource_type IS '资源类型 (1:文档, 2:视频, 3:3D模型, 4:VR资源, 5:外部教学链接, 6:其他)';
COMMENT ON COLUMN train_resource.resource_url IS '资源存储地址/在线链接';
COMMENT ON COLUMN train_resource.description IS '资源描述/使用说明';
COMMENT ON COLUMN train_resource.status IS '状态 (1:正常, 0:停用)';
COMMENT ON COLUMN train_resource.del_flag IS '删除标志 (0:存在, 2:删除)';
-- 2. 课程与资源多对多关联表
CREATE TABLE train_course_resource (
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
c_id VARCHAR(32) NOT NULL,
resource_id VARCHAR(32) NOT NULL,
sort_no INTEGER DEFAULT 0,
create_by VARCHAR(64),
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT pk_train_course_resource PRIMARY KEY (id)
);
COMMENT ON TABLE train_course_resource IS '课程与教学资源关联表 (N:M)';
COMMENT ON COLUMN train_course_resource.id IS '自增主键';
COMMENT ON COLUMN train_course_resource.c_id IS '课程ID';
COMMENT ON COLUMN train_course_resource.resource_id IS '资源ID';
COMMENT ON COLUMN train_course_resource.sort_no IS '排序号';
-- 3. 培训班与课程关联表
CREATE TABLE train_plan_course (
plan_course_id BIGINT GENERATED BY DEFAULT AS IDENTITY,
plan_id VARCHAR(32) NOT NULL,
c_id VARCHAR(32) NOT NULL,
sort_no INTEGER DEFAULT 0,
create_by VARCHAR(64),
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
remark VARCHAR(500),
CONSTRAINT pk_train_plan_course PRIMARY KEY (plan_course_id)
);
COMMENT ON TABLE train_plan_course IS '培训班课程关联表 (一期培训包含哪些课程)';
COMMENT ON COLUMN train_plan_course.plan_course_id IS '培训课程实例ID (核心ID之一)';
COMMENT ON COLUMN train_plan_course.plan_id IS '培训班ID';
COMMENT ON COLUMN train_plan_course.c_id IS '课程ID';
COMMENT ON COLUMN train_plan_course.sort_no IS '排课先后顺序号';
-- 4. 参训学员登记表 (系统最核心关系: 谁参加哪个班)
CREATE TABLE train_enrollment (
enrollment_id BIGINT GENERATED BY DEFAULT AS IDENTITY,
plan_id VARCHAR(32) NOT NULL,
student_id VARCHAR(32) NOT NULL,
source_type SMALLINT NOT NULL,
enrollment_status SMALLINT NOT NULL DEFAULT 0,
accommodation_required SMALLINT NOT NULL,
join_time TIMESTAMP,
complete_time TIMESTAMP,
create_by VARCHAR(64),
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_by VARCHAR(64),
update_time TIMESTAMP,
remark VARCHAR(500),
CONSTRAINT pk_train_enrollment PRIMARY KEY (enrollment_id)
);
COMMENT ON TABLE train_enrollment IS '参训学员登记表 (学员参加培训班全生命周期主表)';
COMMENT ON COLUMN train_enrollment.enrollment_id IS '参训档案ID (核心ID之一)';
COMMENT ON COLUMN train_enrollment.plan_id IS '培训班ID';
COMMENT ON COLUMN train_enrollment.student_id IS '学员长期主档ID引用';
COMMENT ON COLUMN train_enrollment.source_type IS '参训名单来源,具体字典值由 train_enrollment_source 定义';
COMMENT ON COLUMN train_enrollment.enrollment_status IS '参训状态 (0:待报到, 1:已报到, 2:请假, 3:放弃, 4:已完成)';
COMMENT ON COLUMN train_enrollment.accommodation_required IS '是否需要住宿 (0:否, 1:是)';
COMMENT ON COLUMN train_enrollment.join_time IS '入班报到时间';
COMMENT ON COLUMN train_enrollment.complete_time IS '培训完成/结课时间';
-- 5. 教学活动排课表 (排课课次必要包含授课教师,且场地类型与教室/实训场地严格互斥)
CREATE TABLE train_lesson (
lesson_id BIGINT GENERATED BY DEFAULT AS IDENTITY,
plan_course_id BIGINT NOT NULL,
t_id VARCHAR(32) NOT NULL,
start_time TIMESTAMP NOT NULL,
end_time TIMESTAMP NOT NULL,
venue_type SMALLINT NOT NULL,
class_room_id VARCHAR(32),
site_id VARCHAR(32),
create_by VARCHAR(64),
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_by VARCHAR(64),
update_time TIMESTAMP,
remark VARCHAR(500),
CONSTRAINT pk_train_lesson PRIMARY KEY (lesson_id),
CONSTRAINT chk_lesson_venue CHECK (
(venue_type = 1 AND class_room_id IS NOT NULL AND site_id IS NULL) OR
(venue_type = 2 AND site_id IS NOT NULL AND class_room_id IS NULL)
)
);
COMMENT ON TABLE train_lesson IS '课次教学活动表 (具体一次上课/实训安排)';
COMMENT ON COLUMN train_lesson.lesson_id IS '课次ID (核心ID之一)';
COMMENT ON COLUMN train_lesson.plan_course_id IS '所属培训课程实例ID';
COMMENT ON COLUMN train_lesson.t_id IS '授课教师ID (课次必要组成)';
COMMENT ON COLUMN train_lesson.start_time IS '课次开始时间';
COMMENT ON COLUMN train_lesson.end_time IS '课次结束时间';
COMMENT ON COLUMN train_lesson.venue_type IS '场地类型 (1:普通教室, 2:实训场地)';
COMMENT ON COLUMN train_lesson.class_room_id IS '普通教室ID (当场地类型为教室时必填且场地为空)';
COMMENT ON COLUMN train_lesson.site_id IS '实训场地ID (当场地类型为实训场地时必填且教室为空)';
-- 6. 学员住宿安排表 (房间必填 + 床位可选)
CREATE TABLE train_accommodation (
accommodation_id BIGINT GENERATED BY DEFAULT AS IDENTITY,
enrollment_id BIGINT NOT NULL,
dorm_id VARCHAR(32) NOT NULL,
bed_no VARCHAR(20),
assigned_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
accommodation_status SMALLINT NOT NULL DEFAULT 0,
create_by VARCHAR(64),
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_by VARCHAR(64),
update_time TIMESTAMP,
remark VARCHAR(500),
CONSTRAINT pk_train_accommodation PRIMARY KEY (accommodation_id)
);
COMMENT ON TABLE train_accommodation IS '学员住宿安排表 (本次参训住宿分配)';
COMMENT ON COLUMN train_accommodation.accommodation_id IS '住宿安排ID';
COMMENT ON COLUMN train_accommodation.enrollment_id IS '参训档案ID';
COMMENT ON COLUMN train_accommodation.dorm_id IS '分配宿舍房间ID';
COMMENT ON COLUMN train_accommodation.bed_no IS '分配床位号 (可选)';
COMMENT ON COLUMN train_accommodation.assigned_time IS '分配时间';
COMMENT ON COLUMN train_accommodation.accommodation_status IS '住宿状态 (0:预分配, 1:已入住, 2:已退宿)';
-- 7. 培训班考核成绩项配置表 (用户自由配置分值与权重)
CREATE TABLE train_score_item (
score_item_id BIGINT GENERATED BY DEFAULT AS IDENTITY,
plan_id VARCHAR(32) NOT NULL,
plan_course_id BIGINT,
item_name VARCHAR(100) NOT NULL,
item_type SMALLINT NOT NULL DEFAULT 1,
weight NUMERIC(5,2) NOT NULL DEFAULT 0.00,
full_score NUMERIC(5,2) NOT NULL DEFAULT 100.00,
sort_no INTEGER DEFAULT 0,
create_by VARCHAR(64),
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_by VARCHAR(64),
update_time TIMESTAMP,
remark VARCHAR(500),
CONSTRAINT pk_train_score_item PRIMARY KEY (score_item_id)
);
COMMENT ON TABLE train_score_item IS '考核成绩项配置表 (某培训班的成绩构成规则)';
COMMENT ON COLUMN train_score_item.score_item_id IS '成绩项ID';
COMMENT ON COLUMN train_score_item.plan_id IS '培训班ID';
COMMENT ON COLUMN train_score_item.plan_course_id IS '关联培训课程ID (为空表示全班综合项)';
COMMENT ON COLUMN train_score_item.item_name IS '考核项目名称';
COMMENT ON COLUMN train_score_item.item_type IS '项目类型 (1:理论考试, 2:实操演练, 3:出勤考核, 4:综合评价)';
COMMENT ON COLUMN train_score_item.weight IS '所占综合权重百分比 (0~100)';
COMMENT ON COLUMN train_score_item.full_score IS '满分设定 (默认 100)';
COMMENT ON COLUMN train_score_item.sort_no IS '展示顺序';
-- 8. 学员单项得分明细表 (参训学员在某考核项上的实际成绩,无默认0,有行即有分且非负)
CREATE TABLE train_student_score (
score_id BIGINT GENERATED BY DEFAULT AS IDENTITY,
enrollment_id BIGINT NOT NULL,
score_item_id BIGINT NOT NULL,
score NUMERIC(7,2) NOT NULL,
source_type SMALLINT NOT NULL,
source_system VARCHAR(100),
source_event_id VARCHAR(100),
import_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
create_by VARCHAR(64),
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_by VARCHAR(64),
update_time TIMESTAMP,
remark VARCHAR(500),
CONSTRAINT pk_train_student_score PRIMARY KEY (score_id),
CONSTRAINT chk_student_score_nonneg CHECK (score >= 0.00)
);
COMMENT ON TABLE train_student_score IS '学员成绩明细表 (某学员某成绩项得分,未录入成绩不生成记录)';
COMMENT ON COLUMN train_student_score.score_id IS '成绩明细ID';
COMMENT ON COLUMN train_student_score.enrollment_id IS '参训档案ID';
COMMENT ON COLUMN train_student_score.score_item_id IS '关联成绩项ID';
COMMENT ON COLUMN train_student_score.score IS '实际得分 (非负,未录成绩不建行,0为真实0分)';
COMMENT ON COLUMN train_student_score.source_type IS '录入来源类型 (1:人工录入, 2:理论考试系统, 3:实操设备, 4:外部导入)';
COMMENT ON COLUMN train_student_score.source_system IS '来源外部系统名称';
COMMENT ON COLUMN train_student_score.source_event_id IS '来源外部事件/单号ID (用于幂等)';
COMMENT ON COLUMN train_student_score.import_time IS '成绩写入/同步时间';
COMMIT;
@@ -0,0 +1,251 @@
-- ==============================================================================
-- RailwaySystem 数据库 V2 迁移脚本: 003_alter_master_tables.sql
-- 适用数据库: KingbaseES V009R003C018 (schema: public)
-- 执行目标: 调整 8 张主数据表结构至 V2 规范模型
-- 事务边界: 全程在显式事务 BEGIN ... COMMIT 中执行
-- 设计依据: RailwaySystem 数据库 V2 字段设计与 Kingbase DDL 规范 V1.0 及 Round 1 审查要求
-- 包含表:
-- 1. train_teacher (保留1行数据,先加新列,旧列待008迁移并校验后设NOT NULL并删除旧列)
-- 2. train_student (0行,删除高校学籍旧列,增加企业主档与加密列,规范source_type语义)
-- 3. train_course (0行,删除旧附件字段,增加课程编码与审计列)
-- 4. train_classroom_info (0行,对齐公共审计字段)
-- 5. train_training_site (0行,对齐公共审计字段)
-- 6. train_electronic_board (0行,解绑计划ID,board_name 必填无默认空串)
-- 7. train_dormitory_info (0行,重构为容量与性别属性,删除预测默认值,增加 capacity > 0 CHECK)
-- 8. train_class_plan (0行,解绑单课程c_id与单教师t_id,升级为多课程架构)
-- ==============================================================================
BEGIN;
-- ==============================================================================
-- 步骤 1: 移除主数据表上绑定的旧 update_time 触发器
-- ==============================================================================
DROP TRIGGER IF EXISTS trig_train_teacher_modify ON train_teacher;
DROP TRIGGER IF EXISTS trig_train_student_modify ON train_student;
DROP TRIGGER IF EXISTS trig_train_course_modify ON train_course;
DROP TRIGGER IF EXISTS trig_train_classroom_modify ON train_classroom_info;
DROP TRIGGER IF EXISTS trig_train_trainsite_modify ON train_training_site;
DROP TRIGGER IF EXISTS trig_train_board_modify ON train_electronic_board;
DROP TRIGGER IF EXISTS trig_train_dorminfo_modify ON train_dormitory_info;
DROP TRIGGER IF EXISTS trig_train_classplan_modify ON train_class_plan;
-- ==============================================================================
-- 步骤 2: 显式解除阻塞主数据列删除或变更的旧外键约束 (不使用 CASCADE)
-- ==============================================================================
-- train_class_plan 上的旧外键 (指向 train_course 和 train_teacher)
ALTER TABLE train_class_plan DROP CONSTRAINT IF EXISTS fk_classplan_course;
ALTER TABLE train_class_plan DROP CONSTRAINT IF EXISTS fk_classplan_teacher;
-- train_electronic_board 上的旧外键 (指向 train_class_plan)
ALTER TABLE train_electronic_board DROP CONSTRAINT IF EXISTS fk_board_plan;
-- ==============================================================================
-- 步骤 3: 调整 train_teacher (有 1 行真实数据,暂不删除旧列,留待 008 迁移数据)
-- ==============================================================================
ALTER TABLE train_teacher
ADD COLUMN user_id BIGINT,
ADD COLUMN org_name VARCHAR(100),
ADD COLUMN job_title VARCHAR(50),
ADD COLUMN status SMALLINT DEFAULT 1,
ADD COLUMN create_by VARCHAR(64),
ADD COLUMN update_by VARCHAR(64),
ADD COLUMN del_flag CHAR(1) DEFAULT '0';
ALTER TABLE train_teacher
ALTER COLUMN t_name TYPE VARCHAR(50),
ALTER COLUMN t_gender TYPE CHAR(1),
ALTER COLUMN t_email TYPE VARCHAR(100),
ALTER COLUMN remark TYPE VARCHAR(500);
COMMENT ON COLUMN train_teacher.user_id IS 'sys_user登录账号逻辑关联ID';
COMMENT ON COLUMN train_teacher.org_name IS '所属单位/部门名称 (由旧 t_department 迁移)';
COMMENT ON COLUMN train_teacher.job_title IS '职务/职称 (由旧 t_title 迁移)';
COMMENT ON COLUMN train_teacher.status IS '状态 (1:正常, 0:停用)';
COMMENT ON COLUMN train_teacher.del_flag IS '删除标志 (0:存在, 2:删除)';
-- ==============================================================================
-- 步骤 4: 调整 train_student (当前 0 行)
-- ==============================================================================
-- 4.1 淘汰高校模式旧字段与旧审计字段
ALTER TABLE train_student
DROP COLUMN IF EXISTS s_department,
DROP COLUMN IF EXISTS s_major,
DROP COLUMN IF EXISTS s_class,
DROP COLUMN IF EXISTS create_user;
-- 4.2 增加企业培训新主档字段、加密字段与公共审计字段
ALTER TABLE train_student
ADD COLUMN user_id BIGINT,
ADD COLUMN org_name VARCHAR(100),
ADD COLUMN s_email VARCHAR(100),
ADD COLUMN id_card_ciphertext TEXT,
ADD COLUMN id_card_hash VARCHAR(128),
ADD COLUMN id_card_last4 VARCHAR(4),
ADD COLUMN face_ref VARCHAR(255),
ADD COLUMN source_type SMALLINT DEFAULT 1 NOT NULL,
ADD COLUMN status SMALLINT DEFAULT 1 NOT NULL,
ADD COLUMN create_by VARCHAR(64),
ADD COLUMN update_by VARCHAR(64),
ADD COLUMN del_flag CHAR(1) DEFAULT '0' NOT NULL;
ALTER TABLE train_student
ALTER COLUMN s_name TYPE VARCHAR(50),
ALTER COLUMN s_gender TYPE CHAR(1),
ALTER COLUMN remark TYPE VARCHAR(500);
COMMENT ON COLUMN train_student.user_id IS 'sys_user账号逻辑关联';
COMMENT ON COLUMN train_student.org_name IS '所属单位/部门';
COMMENT ON COLUMN train_student.id_card_ciphertext IS '身份证可逆加密密文';
COMMENT ON COLUMN train_student.id_card_hash IS '身份证精确匹配Hash (防重)';
COMMENT ON COLUMN train_student.id_card_last4 IS '身份证后四位展示';
COMMENT ON COLUMN train_student.face_ref IS '人脸特征或图像凭证引用';
COMMENT ON COLUMN train_student.source_type IS '人员主档进入系统的来源 (1:管理员录入, 2:Excel批量导入, 3:铁路内部业务平台同步)';
COMMENT ON COLUMN train_student.status IS '主档状态 (1:正常, 0:停用)';
COMMENT ON COLUMN train_student.del_flag IS '删除标志 (0:存在, 2:删除)';
-- ==============================================================================
-- 步骤 5: 调整 train_course (当前 0 行)
-- ==============================================================================
-- 5.1 淘汰旧集中附件字段 (已解耦迁入 train_resource 与 train_course_resource)
ALTER TABLE train_course
DROP COLUMN IF EXISTS attach_doc,
DROP COLUMN IF EXISTS attach_video,
DROP COLUMN IF EXISTS attach_model_link,
DROP COLUMN IF EXISTS model_resource_ids,
DROP COLUMN IF EXISTS create_user;
-- 5.2 增加课程编码与标准审计字段
ALTER TABLE train_course
ADD COLUMN c_code VARCHAR(50),
ADD COLUMN status SMALLINT DEFAULT 1 NOT NULL,
ADD COLUMN create_by VARCHAR(64),
ADD COLUMN update_by VARCHAR(64),
ADD COLUMN del_flag CHAR(1) DEFAULT '0' NOT NULL;
ALTER TABLE train_course
ALTER COLUMN remark TYPE VARCHAR(500);
COMMENT ON COLUMN train_course.c_code IS '课程统一业务编码';
COMMENT ON COLUMN train_course.status IS '课程状态 (1:正常, 0:停用)';
COMMENT ON COLUMN train_course.del_flag IS '删除标志 (0:存在, 2:删除)';
-- ==============================================================================
-- 步骤 6: 调整 train_classroom_info (当前 0 行)
-- ==============================================================================
ALTER TABLE train_classroom_info
DROP COLUMN IF EXISTS create_user;
ALTER TABLE train_classroom_info
ADD COLUMN create_by VARCHAR(64),
ADD COLUMN update_by VARCHAR(64),
ADD COLUMN del_flag CHAR(1) DEFAULT '0' NOT NULL;
ALTER TABLE train_classroom_info
ALTER COLUMN remark TYPE VARCHAR(500),
ALTER COLUMN room_status SET NOT NULL;
COMMENT ON COLUMN train_classroom_info.del_flag IS '删除标志 (0:存在, 2:删除)';
-- ==============================================================================
-- 步骤 7: 调整 train_training_site (当前 0 行)
-- ==============================================================================
ALTER TABLE train_training_site
DROP COLUMN IF EXISTS create_user;
ALTER TABLE train_training_site
ADD COLUMN create_by VARCHAR(64),
ADD COLUMN update_by VARCHAR(64),
ADD COLUMN del_flag CHAR(1) DEFAULT '0' NOT NULL;
ALTER TABLE train_training_site
ALTER COLUMN remark TYPE VARCHAR(500),
ALTER COLUMN site_status SET NOT NULL;
COMMENT ON COLUMN train_training_site.del_flag IS '删除标志 (0:存在, 2:删除)';
-- ==============================================================================
-- 步骤 8: 调整 train_electronic_board (当前 0 行)
-- ==============================================================================
-- 8.1 移除计划绑定列与旧播放字段
ALTER TABLE train_electronic_board
DROP COLUMN IF EXISTS bind_plan_id,
DROP COLUMN IF EXISTS board_content,
DROP COLUMN IF EXISTS board_playmode,
DROP COLUMN IF EXISTS create_user;
-- 8.2 增加设备名称 (必填,严禁空串默认)、API端点与审计字段
ALTER TABLE train_electronic_board
ADD COLUMN board_name VARCHAR(100) NOT NULL,
ADD COLUMN api_endpoint VARCHAR(255),
ADD COLUMN create_by VARCHAR(64),
ADD COLUMN update_by VARCHAR(64),
ADD COLUMN del_flag CHAR(1) DEFAULT '0' NOT NULL;
ALTER TABLE train_electronic_board
ALTER COLUMN support_checkin_type TYPE VARCHAR(50),
ALTER COLUMN remark TYPE VARCHAR(500),
ALTER COLUMN board_status SET NOT NULL;
COMMENT ON COLUMN train_electronic_board.board_name IS '电子班牌名称 (必填)';
COMMENT ON COLUMN train_electronic_board.api_endpoint IS '设备交互/推送接口地址';
COMMENT ON COLUMN train_electronic_board.bind_classroom_id IS '绑定的物理教室ID';
COMMENT ON COLUMN train_electronic_board.del_flag IS '删除标志 (0:存在, 2:删除)';
-- ==============================================================================
-- 步骤 9: 调整 train_dormitory_info (当前 0 行)
-- ==============================================================================
-- 9.1 移除静态床位统计列
ALTER TABLE train_dormitory_info
DROP COLUMN IF EXISTS bed_total,
DROP COLUMN IF EXISTS bed_used,
DROP COLUMN IF EXISTS create_user;
-- 9.2 增加楼层、房间额定容量、适用性别 (杜绝业务默认值猜测,增加容量正数 CHECK)
ALTER TABLE train_dormitory_info
ADD COLUMN dorm_floor VARCHAR(20) NOT NULL,
ADD COLUMN capacity INTEGER NOT NULL,
ADD COLUMN gender_type SMALLINT DEFAULT 0 NOT NULL,
ADD COLUMN create_by VARCHAR(64),
ADD COLUMN update_by VARCHAR(64),
ADD COLUMN del_flag CHAR(1) DEFAULT '0' NOT NULL;
ALTER TABLE train_dormitory_info
ALTER COLUMN dorm_building SET NOT NULL,
ALTER COLUMN dorm_room SET NOT NULL,
ALTER COLUMN remark TYPE VARCHAR(500),
ALTER COLUMN dorm_status SET NOT NULL;
ALTER TABLE train_dormitory_info DROP CONSTRAINT IF EXISTS chk_dorm_capacity;
ALTER TABLE train_dormitory_info ADD CONSTRAINT chk_dorm_capacity CHECK (capacity > 0);
COMMENT ON COLUMN train_dormitory_info.dorm_floor IS '所在楼层 (管理员真实录入)';
COMMENT ON COLUMN train_dormitory_info.capacity IS '房间核定床位容量 (正整数,无默认值)';
COMMENT ON COLUMN train_dormitory_info.gender_type IS '适用性别 (0:不限, 1:男, 2:女)';
COMMENT ON COLUMN train_dormitory_info.del_flag IS '删除标志 (0:存在, 2:删除)';
-- ==============================================================================
-- 步骤 10: 调整 train_class_plan (当前 0 行)
-- ==============================================================================
-- 10.1 彻底剥离旧单课 c_id 和旧单教师 t_id
ALTER TABLE train_class_plan
DROP COLUMN IF EXISTS c_id,
DROP COLUMN IF EXISTS t_id,
DROP COLUMN IF EXISTS create_user;
-- 10.2 增加报到截止时间与标准审计列
ALTER TABLE train_class_plan
ADD COLUMN report_deadline TIMESTAMP,
ADD COLUMN create_by VARCHAR(64),
ADD COLUMN update_by VARCHAR(64),
ADD COLUMN del_flag CHAR(1) DEFAULT '0' NOT NULL;
ALTER TABLE train_class_plan
ALTER COLUMN plan_name TYPE VARCHAR(150),
ALTER COLUMN train_address TYPE VARCHAR(200),
ALTER COLUMN remark TYPE VARCHAR(500),
ALTER COLUMN plan_status SET NOT NULL;
COMMENT ON COLUMN train_class_plan.report_deadline IS '学员现场报到截止时间';
COMMENT ON COLUMN train_class_plan.del_flag IS '删除标志 (0:存在, 2:删除)';
COMMIT;
@@ -0,0 +1,215 @@
-- ==============================================================================
-- RailwaySystem 数据库 V2 迁移脚本: 004_rebuild_business_tables.sql
-- 适用数据库: KingbaseES V009R003C018 (schema: public)
-- 执行目标: 重构 5 张需彻底解耦旧关系的空业务过程表
-- 事务边界: 全程在显式事务 BEGIN ... COMMIT 中执行
-- 设计依据: RailwaySystem 数据库 V2 字段设计与 Kingbase DDL 规范 V1.0 及 Round 1 审查要求
-- 规范要求:
-- 1. 校验确认各表必须为 0 行方可重建
-- 2. 显式解除旧依赖,杜绝无边界使用 CASCADE
-- 3. 为 train_study_duration 增加时间有效性与单号/系统匹配 CHECK
-- ==============================================================================
BEGIN;
-- 步骤 1: 安全阻断校验 (确保各表确为 0 行,杜绝误删未备份数据)
DO $$
DECLARE
v_cnt INT;
BEGIN
SELECT count(*) INTO v_cnt FROM train_dorm_auth;
IF v_cnt > 0 THEN RAISE EXCEPTION '>>> [004-FATAL] train_dorm_auth 包含 % 行数据,禁止重建', v_cnt; END IF;
SELECT count(*) INTO v_cnt FROM train_class_report;
IF v_cnt > 0 THEN RAISE EXCEPTION '>>> [004-FATAL] train_class_report 包含 % 行数据,禁止重建', v_cnt; END IF;
SELECT count(*) INTO v_cnt FROM train_student_checkin;
IF v_cnt > 0 THEN RAISE EXCEPTION '>>> [004-FATAL] train_student_checkin 包含 % 行数据,禁止重建', v_cnt; END IF;
SELECT count(*) INTO v_cnt FROM train_study_duration;
IF v_cnt > 0 THEN RAISE EXCEPTION '>>> [004-FATAL] train_study_duration 包含 % 行数据,禁止重建', v_cnt; END IF;
SELECT count(*) INTO v_cnt FROM train_student_portrait;
IF v_cnt > 0 THEN RAISE EXCEPTION '>>> [004-FATAL] train_student_portrait 包含 % 行数据,禁止重建', v_cnt; END IF;
END $$;
-- 步骤 2: 卸载旧触发器
DROP TRIGGER IF EXISTS trig_train_dormauth_modify ON train_dorm_auth;
-- 步骤 3: 显式卸载已知旧外键约束 (杜绝使用 CASCADE 掩盖未预期依赖)
ALTER TABLE train_dorm_auth DROP CONSTRAINT IF EXISTS fk_dormauth_stu;
ALTER TABLE train_dorm_auth DROP CONSTRAINT IF EXISTS fk_dormauth_plan;
ALTER TABLE train_dorm_auth DROP CONSTRAINT IF EXISTS fk_dormauth_dorm;
ALTER TABLE train_class_report DROP CONSTRAINT IF EXISTS fk_classreport_plan;
ALTER TABLE train_class_report DROP CONSTRAINT IF EXISTS fk_classreport_stu;
ALTER TABLE train_class_report DROP CONSTRAINT IF EXISTS fk_classreport_dorm;
ALTER TABLE train_class_report DROP CONSTRAINT IF EXISTS fk_classreport_classroom;
ALTER TABLE train_class_report DROP CONSTRAINT IF EXISTS fk_classreport_site;
ALTER TABLE train_student_checkin DROP CONSTRAINT IF EXISTS fk_checkin_plan;
ALTER TABLE train_student_checkin DROP CONSTRAINT IF EXISTS fk_checkin_course;
ALTER TABLE train_student_checkin DROP CONSTRAINT IF EXISTS fk_checkin_stu;
ALTER TABLE train_student_checkin DROP CONSTRAINT IF EXISTS fk_checkin_board;
ALTER TABLE train_study_duration DROP CONSTRAINT IF EXISTS fk_studydur_stu;
ALTER TABLE train_study_duration DROP CONSTRAINT IF EXISTS fk_studydur_plan;
ALTER TABLE train_study_duration DROP CONSTRAINT IF EXISTS fk_studydur_course;
ALTER TABLE train_student_portrait DROP CONSTRAINT IF EXISTS fk_portrait_stu;
ALTER TABLE train_student_portrait DROP CONSTRAINT IF EXISTS fk_portrait_plan;
-- 步骤 4: 执行安全常规 DROP TABLE (无 CASCADE)
DROP TABLE IF EXISTS train_dorm_auth;
DROP TABLE IF EXISTS train_class_report;
DROP TABLE IF EXISTS train_student_checkin;
DROP TABLE IF EXISTS train_study_duration;
DROP TABLE IF EXISTS train_student_portrait;
-- 步骤 5: 按照 V2 规范重建表结构
-- 5.1 门禁授权同步表
CREATE TABLE train_dorm_auth (
auth_id BIGINT GENERATED BY DEFAULT AS IDENTITY,
accommodation_id BIGINT NOT NULL,
auth_start TIMESTAMP,
auth_end TIMESTAMP,
face_sync_status SMALLINT NOT NULL DEFAULT 0,
retry_count INTEGER NOT NULL DEFAULT 0,
last_error VARCHAR(500),
external_auth_id VARCHAR(100),
last_sync_time TIMESTAMP,
create_by VARCHAR(64),
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_by VARCHAR(64),
update_time TIMESTAMP,
remark VARCHAR(500),
CONSTRAINT pk_train_dorm_auth PRIMARY KEY (auth_id)
);
COMMENT ON TABLE train_dorm_auth IS '宿舍门禁权限下发与同步表';
COMMENT ON COLUMN train_dorm_auth.auth_id IS '门禁授权记录ID';
COMMENT ON COLUMN train_dorm_auth.accommodation_id IS '住宿安排ID (一对一关联)';
COMMENT ON COLUMN train_dorm_auth.auth_start IS '权限生效开始时间';
COMMENT ON COLUMN train_dorm_auth.auth_end IS '权限失效结束时间';
COMMENT ON COLUMN train_dorm_auth.face_sync_status IS '同步状态 (0:待下发, 1:已下发, 2:下发失败, 3:已注销)';
COMMENT ON COLUMN train_dorm_auth.retry_count IS '同步失败重试次数';
COMMENT ON COLUMN train_dorm_auth.last_error IS '最后一次下发失败错误信息';
COMMENT ON COLUMN train_dorm_auth.external_auth_id IS '外部门禁硬件系统下发唯一授权凭证ID';
COMMENT ON COLUMN train_dorm_auth.last_sync_time IS '最近一次同步请求时间';
-- 5.2 报到事件记录表
CREATE TABLE train_class_report (
report_id BIGINT GENERATED BY DEFAULT AS IDENTITY,
enrollment_id BIGINT NOT NULL,
report_way SMALLINT NOT NULL,
report_device_sn VARCHAR(64),
report_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
verify_result SMALLINT NOT NULL,
verify_note VARCHAR(500),
create_by VARCHAR(64),
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
remark VARCHAR(500),
CONSTRAINT pk_train_class_report PRIMARY KEY (report_id)
);
COMMENT ON TABLE train_class_report IS '培训班学员现场报到事件表';
COMMENT ON COLUMN train_class_report.report_id IS '报到流水ID';
COMMENT ON COLUMN train_class_report.enrollment_id IS '参训档案ID (一对一关联)';
COMMENT ON COLUMN train_class_report.report_way IS '报到核验方式 (1:人脸识别, 2:身份证刷卡, 3:二维码, 4:人工录入)';
COMMENT ON COLUMN train_class_report.report_device_sn IS '核验设备硬件序列号';
COMMENT ON COLUMN train_class_report.report_time IS '报到完成时间';
COMMENT ON COLUMN train_class_report.verify_result IS '核验结果 (1:核验通过, 2:核验不匹配, 3:人工放行)';
COMMENT ON COLUMN train_class_report.verify_note IS '核验说明/异常原因';
-- 5.3 考勤签到明细表
CREATE TABLE train_student_checkin (
checkin_id BIGINT GENERATED BY DEFAULT AS IDENTITY,
enrollment_id BIGINT NOT NULL,
lesson_id BIGINT NOT NULL,
board_id VARCHAR(32),
checkin_way SMALLINT NOT NULL,
sign_in_time TIMESTAMP,
sign_out_time TIMESTAMP,
attendance_status SMALLINT NOT NULL DEFAULT 0,
manual_flag SMALLINT NOT NULL DEFAULT 0,
create_by VARCHAR(64),
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_by VARCHAR(64),
update_time TIMESTAMP,
remark VARCHAR(500),
CONSTRAINT pk_train_student_checkin PRIMARY KEY (checkin_id)
);
COMMENT ON TABLE train_student_checkin IS '课次考勤打卡与最终考勤结果表';
COMMENT ON COLUMN train_student_checkin.checkin_id IS '考勤记录ID';
COMMENT ON COLUMN train_student_checkin.enrollment_id IS '参训档案ID';
COMMENT ON COLUMN train_student_checkin.lesson_id IS '具体教学活动课次ID';
COMMENT ON COLUMN train_student_checkin.board_id IS '打卡电子班牌ID';
COMMENT ON COLUMN train_student_checkin.checkin_way IS '打卡方式 (1:人脸, 2:刷卡, 3:扫码, 4:教师代签)';
COMMENT ON COLUMN train_student_checkin.sign_in_time IS '签到时间';
COMMENT ON COLUMN train_student_checkin.sign_out_time IS '签退时间';
COMMENT ON COLUMN train_student_checkin.attendance_status IS '考勤结论 (0:缺勤, 1:正常出勤, 2:迟到, 3:早退, 4:请假)';
COMMENT ON COLUMN train_student_checkin.manual_flag IS '是否人工补录/修正 (0:自动, 1:人工补卡)';
-- 5.4 学习时长记录表 (自主/外部系统对接)
CREATE TABLE train_study_duration (
dur_id BIGINT GENERATED BY DEFAULT AS IDENTITY,
enrollment_id BIGINT NOT NULL,
plan_course_id BIGINT,
resource_id VARCHAR(32),
learning_content VARCHAR(200),
source_system VARCHAR(100),
source_event_id VARCHAR(100),
start_time TIMESTAMP,
end_time TIMESTAMP,
study_seconds INTEGER NOT NULL DEFAULT 0,
sync_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
create_by VARCHAR(64),
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
remark VARCHAR(500),
CONSTRAINT pk_train_study_duration PRIMARY KEY (dur_id),
CONSTRAINT chk_study_seconds CHECK (study_seconds >= 0),
CONSTRAINT chk_study_time CHECK (end_time IS NULL OR start_time IS NULL OR end_time >= start_time),
CONSTRAINT chk_study_source_system CHECK (source_event_id IS NULL OR source_system IS NOT NULL)
);
COMMENT ON TABLE train_study_duration IS '学员自主学习与仿真实训时长上报表';
COMMENT ON COLUMN train_study_duration.dur_id IS '学习时长记录ID';
COMMENT ON COLUMN train_study_duration.enrollment_id IS '参训档案ID';
COMMENT ON COLUMN train_study_duration.plan_course_id IS '关联培训课程ID (可选)';
COMMENT ON COLUMN train_study_duration.resource_id IS '学习资源ID (可选)';
COMMENT ON COLUMN train_study_duration.learning_content IS '学习主题/章节内容';
COMMENT ON COLUMN train_study_duration.source_system IS '上报来源系统 (如 VR仿真系统, 在线学习端)';
COMMENT ON COLUMN train_study_duration.source_event_id IS '来源系统上报事件ID (用于幂等防重)';
COMMENT ON COLUMN train_study_duration.start_time IS '学习开始时间';
COMMENT ON COLUMN train_study_duration.end_time IS '学习结束时间';
COMMENT ON COLUMN train_study_duration.study_seconds IS '有效学习时长 (秒)';
COMMENT ON COLUMN train_study_duration.sync_time IS '系统入库时间';
-- 5.5 学员参训综合画像表
CREATE TABLE train_student_portrait (
portrait_id BIGINT GENERATED BY DEFAULT AS IDENTITY,
enrollment_id BIGINT NOT NULL,
attendance_rate NUMERIC(5,2),
theory_score NUMERIC(7,2),
practical_score NUMERIC(7,2),
total_study_hour NUMERIC(8,2),
label_tag TEXT,
portrait_content TEXT,
generated_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
create_by VARCHAR(64),
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_by VARCHAR(64),
update_time TIMESTAMP,
remark VARCHAR(500),
CONSTRAINT pk_train_student_portrait PRIMARY KEY (portrait_id)
);
COMMENT ON TABLE train_student_portrait IS '学员参训全过程综合评估与画像表';
COMMENT ON COLUMN train_student_portrait.portrait_id IS '画像记录ID';
COMMENT ON COLUMN train_student_portrait.enrollment_id IS '参训档案ID (单期唯一画像)';
COMMENT ON COLUMN train_student_portrait.attendance_rate IS '出勤率百分比 (0~100)';
COMMENT ON COLUMN train_student_portrait.theory_score IS '理论考试综合分';
COMMENT ON COLUMN train_student_portrait.practical_score IS '实操演练综合分';
COMMENT ON COLUMN train_student_portrait.total_study_hour IS '累计在线与自主学习总学时';
COMMENT ON COLUMN train_student_portrait.label_tag IS '特征标签集合 (JSON或逗号分隔文本)';
COMMENT ON COLUMN train_student_portrait.portrait_content IS '综合评价文本/导师评语';
COMMENT ON COLUMN train_student_portrait.generated_time IS '画像生成时间';
COMMIT;
+245
View File
@@ -0,0 +1,245 @@
-- ==============================================================================
-- RailwaySystem 数据库 V2 迁移脚本: 005_constraints.sql
-- 适用数据库: KingbaseES V009R003C018 (schema: public)
-- 执行目标: 集中创建外键约束 (FK)、唯一约束 (UNIQUE) 与单行一致性检查约束 (CHECK)
-- 事务边界: 全程在显式事务 BEGIN ... COMMIT 中执行
-- 设计依据: RailwaySystem 数据库 V2 字段设计与 Kingbase DDL 规范 V1.0 及 Round 1 审查要求
-- 核心原则:
-- 1. 所有外键严格使用 ON DELETE NO ACTION,严禁使用 CASCADE
-- 2. 唯一约束保护核心领域关系防重
-- 3. 检查约束全面覆盖单行时间、场地互斥、分值非负、容量合法及系统单号联动逻辑
-- ==============================================================================
BEGIN;
-- ==============================================================================
-- 一、唯一约束 (UNIQUE CONSTRAINTS - 共 13 组)
-- ==============================================================================
-- 1. 学员身份证哈希唯一 (允许 NULL,防重)
ALTER TABLE train_student DROP CONSTRAINT IF EXISTS uq_student_idcard_hash;
ALTER TABLE train_student ADD CONSTRAINT uq_student_idcard_hash UNIQUE (id_card_hash);
-- 2. 课程编码唯一 (允许 NULL)
ALTER TABLE train_course DROP CONSTRAINT IF EXISTS uq_course_code;
ALTER TABLE train_course ADD CONSTRAINT uq_course_code UNIQUE (c_code);
-- 3. 电子班牌硬件序列号全局唯一
ALTER TABLE train_electronic_board DROP CONSTRAINT IF EXISTS uq_board_sn;
ALTER TABLE train_electronic_board ADD CONSTRAINT uq_board_sn UNIQUE (board_sn);
-- 4. 宿舍房间唯一 (楼栋 + 房号)
ALTER TABLE train_dormitory_info DROP CONSTRAINT IF EXISTS uq_dorm_building_room;
ALTER TABLE train_dormitory_info ADD CONSTRAINT uq_dorm_building_room UNIQUE (dorm_building, dorm_room);
-- 5. 课程与教学资源绑定唯一
ALTER TABLE train_course_resource DROP CONSTRAINT IF EXISTS uq_course_resource;
ALTER TABLE train_course_resource ADD CONSTRAINT uq_course_resource UNIQUE (c_id, resource_id);
-- 6. 培训班与课程关联唯一 (一个班不能重复加入同一门课)
ALTER TABLE train_plan_course DROP CONSTRAINT IF EXISTS uq_plan_course;
ALTER TABLE train_plan_course ADD CONSTRAINT uq_plan_course UNIQUE (plan_id, c_id);
-- 7. 参训学员档案唯一 (同一学员不能重复加入同一培训班)
ALTER TABLE train_enrollment DROP CONSTRAINT IF EXISTS uq_enrollment;
ALTER TABLE train_enrollment ADD CONSTRAINT uq_enrollment UNIQUE (plan_id, student_id);
-- 8. 住宿安排唯一 (一次参训保留一个有效住宿记录)
ALTER TABLE train_accommodation DROP CONSTRAINT IF EXISTS uq_accommodation_enroll;
ALTER TABLE train_accommodation ADD CONSTRAINT uq_accommodation_enroll UNIQUE (enrollment_id);
-- 9. 门禁授权唯一 (一次住宿安排对应一条门禁同步主记录)
ALTER TABLE train_dorm_auth DROP CONSTRAINT IF EXISTS uq_dorm_auth_accomm;
ALTER TABLE train_dorm_auth ADD CONSTRAINT uq_dorm_auth_accomm UNIQUE (accommodation_id);
-- 10. 报到记录唯一 (一次参训对应一次报到结果)
ALTER TABLE train_class_report DROP CONSTRAINT IF EXISTS uq_class_report_enroll;
ALTER TABLE train_class_report ADD CONSTRAINT uq_class_report_enroll UNIQUE (enrollment_id);
-- 11. 课次考勤打卡唯一 (一个学员在一堂课只有一条考勤主结果)
ALTER TABLE train_student_checkin DROP CONSTRAINT IF EXISTS uq_student_checkin_enroll_lesson;
ALTER TABLE train_student_checkin ADD CONSTRAINT uq_student_checkin_enroll_lesson UNIQUE (enrollment_id, lesson_id);
-- 12. 学员考核项成绩唯一 (一个学员在某考核项只有一条最终得分)
ALTER TABLE train_student_score DROP CONSTRAINT IF EXISTS uq_student_score_enroll_item;
ALTER TABLE train_student_score ADD CONSTRAINT uq_student_score_enroll_item UNIQUE (enrollment_id, score_item_id);
-- 13. 学员参训综合画像唯一 (一次参训生成一份画像)
ALTER TABLE train_student_portrait DROP CONSTRAINT IF EXISTS uq_student_portrait_enroll;
ALTER TABLE train_student_portrait ADD CONSTRAINT uq_student_portrait_enroll UNIQUE (enrollment_id);
-- ==============================================================================
-- 二、外键约束 (FOREIGN KEY CONSTRAINTS - 共 26 组,全部 ON DELETE NO ACTION)
-- ==============================================================================
-- 1. train_course_resource (2 FK)
ALTER TABLE train_course_resource DROP CONSTRAINT IF EXISTS fk_cr_course;
ALTER TABLE train_course_resource ADD CONSTRAINT fk_cr_course
FOREIGN KEY (c_id) REFERENCES train_course (c_id) ON DELETE NO ACTION;
ALTER TABLE train_course_resource DROP CONSTRAINT IF EXISTS fk_cr_resource;
ALTER TABLE train_course_resource ADD CONSTRAINT fk_cr_resource
FOREIGN KEY (resource_id) REFERENCES train_resource (resource_id) ON DELETE NO ACTION;
-- 2. train_electronic_board (1 FK)
ALTER TABLE train_electronic_board DROP CONSTRAINT IF EXISTS fk_board_classroom;
ALTER TABLE train_electronic_board ADD CONSTRAINT fk_board_classroom
FOREIGN KEY (bind_classroom_id) REFERENCES train_classroom_info (class_room_id) ON DELETE NO ACTION;
-- 3. train_plan_course (2 FK)
ALTER TABLE train_plan_course DROP CONSTRAINT IF EXISTS fk_plancourse_plan;
ALTER TABLE train_plan_course ADD CONSTRAINT fk_plancourse_plan
FOREIGN KEY (plan_id) REFERENCES train_class_plan (plan_id) ON DELETE NO ACTION;
ALTER TABLE train_plan_course DROP CONSTRAINT IF EXISTS fk_plancourse_course;
ALTER TABLE train_plan_course ADD CONSTRAINT fk_plancourse_course
FOREIGN KEY (c_id) REFERENCES train_course (c_id) ON DELETE NO ACTION;
-- 4. train_enrollment (2 FK)
ALTER TABLE train_enrollment DROP CONSTRAINT IF EXISTS fk_enrollment_plan;
ALTER TABLE train_enrollment ADD CONSTRAINT fk_enrollment_plan
FOREIGN KEY (plan_id) REFERENCES train_class_plan (plan_id) ON DELETE NO ACTION;
ALTER TABLE train_enrollment DROP CONSTRAINT IF EXISTS fk_enrollment_student;
ALTER TABLE train_enrollment ADD CONSTRAINT fk_enrollment_student
FOREIGN KEY (student_id) REFERENCES train_student (s_id) ON DELETE NO ACTION;
-- 5. train_lesson (4 FK)
ALTER TABLE train_lesson DROP CONSTRAINT IF EXISTS fk_lesson_plancourse;
ALTER TABLE train_lesson ADD CONSTRAINT fk_lesson_plancourse
FOREIGN KEY (plan_course_id) REFERENCES train_plan_course (plan_course_id) ON DELETE NO ACTION;
ALTER TABLE train_lesson DROP CONSTRAINT IF EXISTS fk_lesson_teacher;
ALTER TABLE train_lesson ADD CONSTRAINT fk_lesson_teacher
FOREIGN KEY (t_id) REFERENCES train_teacher (t_id) ON DELETE NO ACTION;
ALTER TABLE train_lesson DROP CONSTRAINT IF EXISTS fk_lesson_classroom;
ALTER TABLE train_lesson ADD CONSTRAINT fk_lesson_classroom
FOREIGN KEY (class_room_id) REFERENCES train_classroom_info (class_room_id) ON DELETE NO ACTION;
ALTER TABLE train_lesson DROP CONSTRAINT IF EXISTS fk_lesson_site;
ALTER TABLE train_lesson ADD CONSTRAINT fk_lesson_site
FOREIGN KEY (site_id) REFERENCES train_training_site (site_id) ON DELETE NO ACTION;
-- 6. train_accommodation (2 FK)
ALTER TABLE train_accommodation DROP CONSTRAINT IF EXISTS fk_accomm_enrollment;
ALTER TABLE train_accommodation ADD CONSTRAINT fk_accomm_enrollment
FOREIGN KEY (enrollment_id) REFERENCES train_enrollment (enrollment_id) ON DELETE NO ACTION;
ALTER TABLE train_accommodation DROP CONSTRAINT IF EXISTS fk_accomm_dorm;
ALTER TABLE train_accommodation ADD CONSTRAINT fk_accomm_dorm
FOREIGN KEY (dorm_id) REFERENCES train_dormitory_info (dorm_id) ON DELETE NO ACTION;
-- 7. train_dorm_auth (1 FK)
ALTER TABLE train_dorm_auth DROP CONSTRAINT IF EXISTS fk_dormauth_accomm;
ALTER TABLE train_dorm_auth ADD CONSTRAINT fk_dormauth_accomm
FOREIGN KEY (accommodation_id) REFERENCES train_accommodation (accommodation_id) ON DELETE NO ACTION;
-- 8. train_class_report (1 FK)
ALTER TABLE train_class_report DROP CONSTRAINT IF EXISTS fk_report_enrollment;
ALTER TABLE train_class_report ADD CONSTRAINT fk_report_enrollment
FOREIGN KEY (enrollment_id) REFERENCES train_enrollment (enrollment_id) ON DELETE NO ACTION;
-- 9. train_student_checkin (3 FK)
ALTER TABLE train_student_checkin DROP CONSTRAINT IF EXISTS fk_checkin_enrollment;
ALTER TABLE train_student_checkin ADD CONSTRAINT fk_checkin_enrollment
FOREIGN KEY (enrollment_id) REFERENCES train_enrollment (enrollment_id) ON DELETE NO ACTION;
ALTER TABLE train_student_checkin DROP CONSTRAINT IF EXISTS fk_checkin_lesson;
ALTER TABLE train_student_checkin ADD CONSTRAINT fk_checkin_lesson
FOREIGN KEY (lesson_id) REFERENCES train_lesson (lesson_id) ON DELETE NO ACTION;
ALTER TABLE train_student_checkin DROP CONSTRAINT IF EXISTS fk_checkin_board;
ALTER TABLE train_student_checkin ADD CONSTRAINT fk_checkin_board
FOREIGN KEY (board_id) REFERENCES train_electronic_board (board_id) ON DELETE NO ACTION;
-- 10. train_score_item (2 FK)
ALTER TABLE train_score_item DROP CONSTRAINT IF EXISTS fk_scoreitem_plan;
ALTER TABLE train_score_item ADD CONSTRAINT fk_scoreitem_plan
FOREIGN KEY (plan_id) REFERENCES train_class_plan (plan_id) ON DELETE NO ACTION;
ALTER TABLE train_score_item DROP CONSTRAINT IF EXISTS fk_scoreitem_plancourse;
ALTER TABLE train_score_item ADD CONSTRAINT fk_scoreitem_plancourse
FOREIGN KEY (plan_course_id) REFERENCES train_plan_course (plan_course_id) ON DELETE NO ACTION;
-- 11. train_student_score (2 FK)
ALTER TABLE train_student_score DROP CONSTRAINT IF EXISTS fk_score_enrollment;
ALTER TABLE train_student_score ADD CONSTRAINT fk_score_enrollment
FOREIGN KEY (enrollment_id) REFERENCES train_enrollment (enrollment_id) ON DELETE NO ACTION;
ALTER TABLE train_student_score DROP CONSTRAINT IF EXISTS fk_score_item;
ALTER TABLE train_student_score ADD CONSTRAINT fk_score_item
FOREIGN KEY (score_item_id) REFERENCES train_score_item (score_item_id) ON DELETE NO ACTION;
-- 12. train_study_duration (3 FK)
ALTER TABLE train_study_duration DROP CONSTRAINT IF EXISTS fk_study_enrollment;
ALTER TABLE train_study_duration ADD CONSTRAINT fk_study_enrollment
FOREIGN KEY (enrollment_id) REFERENCES train_enrollment (enrollment_id) ON DELETE NO ACTION;
ALTER TABLE train_study_duration DROP CONSTRAINT IF EXISTS fk_study_plancourse;
ALTER TABLE train_study_duration ADD CONSTRAINT fk_study_plancourse
FOREIGN KEY (plan_course_id) REFERENCES train_plan_course (plan_course_id) ON DELETE NO ACTION;
ALTER TABLE train_study_duration DROP CONSTRAINT IF EXISTS fk_study_resource;
ALTER TABLE train_study_duration ADD CONSTRAINT fk_study_resource
FOREIGN KEY (resource_id) REFERENCES train_resource (resource_id) ON DELETE NO ACTION;
-- 13. train_student_portrait (1 FK)
ALTER TABLE train_student_portrait DROP CONSTRAINT IF EXISTS fk_portrait_enrollment;
ALTER TABLE train_student_portrait ADD CONSTRAINT fk_portrait_enrollment
FOREIGN KEY (enrollment_id) REFERENCES train_enrollment (enrollment_id) ON DELETE NO ACTION;
-- ==============================================================================
-- 三、单行检查约束 (CHECK CONSTRAINTS)
-- ==============================================================================
-- 1. 排课时间逻辑检查: 开始时间必须早于结束时间
ALTER TABLE train_lesson DROP CONSTRAINT IF EXISTS chk_lesson_time;
ALTER TABLE train_lesson ADD CONSTRAINT chk_lesson_time
CHECK (start_time < end_time);
-- 2. 排课场地类型单行互斥约束
ALTER TABLE train_lesson DROP CONSTRAINT IF EXISTS chk_lesson_venue;
ALTER TABLE train_lesson ADD CONSTRAINT chk_lesson_venue CHECK (
(venue_type = 1 AND class_room_id IS NOT NULL AND site_id IS NULL) OR
(venue_type = 2 AND site_id IS NOT NULL AND class_room_id IS NULL)
);
-- 3. 成绩项目单项权重范围: 0 ~ 100
ALTER TABLE train_score_item DROP CONSTRAINT IF EXISTS chk_score_item_weight;
ALTER TABLE train_score_item ADD CONSTRAINT chk_score_item_weight
CHECK (weight >= 0.00 AND weight <= 100.00);
-- 4. 成绩项目满分设定: 必须大于 0
ALTER TABLE train_score_item DROP CONSTRAINT IF EXISTS chk_score_item_full;
ALTER TABLE train_score_item ADD CONSTRAINT chk_score_item_full
CHECK (full_score > 0.00);
-- 5. 学员得分非负约束 (未录成绩不建行,录入则非负)
ALTER TABLE train_student_score DROP CONSTRAINT IF EXISTS chk_student_score_nonneg;
ALTER TABLE train_student_score ADD CONSTRAINT chk_student_score_nonneg
CHECK (score >= 0.00);
-- 6. 学习时长非负检查
ALTER TABLE train_study_duration DROP CONSTRAINT IF EXISTS chk_study_seconds;
ALTER TABLE train_study_duration ADD CONSTRAINT chk_study_seconds
CHECK (study_seconds >= 0);
-- 7. 学习记录时间逻辑检查
ALTER TABLE train_study_duration DROP CONSTRAINT IF EXISTS chk_study_time;
ALTER TABLE train_study_duration ADD CONSTRAINT chk_study_time
CHECK (end_time IS NULL OR start_time IS NULL OR end_time >= start_time);
-- 8. 学习记录外部单号与来源系统一致性检查
ALTER TABLE train_study_duration DROP CONSTRAINT IF EXISTS chk_study_source_system;
ALTER TABLE train_study_duration ADD CONSTRAINT chk_study_source_system
CHECK (source_event_id IS NULL OR source_system IS NOT NULL);
-- 9. 宿舍核定容量正数检查
ALTER TABLE train_dormitory_info DROP CONSTRAINT IF EXISTS chk_dorm_capacity;
ALTER TABLE train_dormitory_info ADD CONSTRAINT chk_dorm_capacity
CHECK (capacity > 0);
COMMIT;
+82
View File
@@ -0,0 +1,82 @@
-- ==============================================================================
-- RailwaySystem 数据库 V2 迁移脚本: 006_indexes.sql
-- 适用数据库: KingbaseES V009R003C018 (schema: public)
-- 执行目标: 创建业务核心查询索引、外键索引与部分唯一索引
-- 事务边界: 全程在显式事务 BEGIN ... COMMIT 中执行
-- 设计依据: RailwaySystem 数据库 V2 字段设计与 Kingbase DDL 规范 V1.0 及 Round 1 审查要求
-- 核心原则:
-- 1. 主键与普通 UNIQUE 自动建立的唯一索引不重复创建
-- 2. 为所有外键关联列建立独立索引,杜绝全表扫描与锁表
-- 3. 排课多维冲突检测建立针对性复合时间索引
-- 4. 学习记录来源事件使用双非空过滤的部分唯一索引防逃逸
-- ==============================================================================
BEGIN;
-- 1. train_teacher 教师表索引
CREATE INDEX idx_teacher_user_id ON train_teacher (user_id);
CREATE INDEX idx_teacher_name ON train_teacher (t_name);
-- 2. train_student 学员表索引
CREATE INDEX idx_student_name ON train_student (s_name);
CREATE INDEX idx_student_org ON train_student (org_name);
CREATE INDEX idx_student_user_id ON train_student (user_id);
-- 3. train_course 课程表索引
CREATE INDEX idx_course_name ON train_course (c_name);
CREATE INDEX idx_course_status ON train_course (status);
-- 4. train_resource 教学资源表索引
CREATE INDEX idx_resource_type ON train_resource (resource_type);
CREATE INDEX idx_resource_status ON train_resource (status);
-- 5. train_course_resource 课程资源关联表索引
CREATE INDEX idx_course_resource_course ON train_course_resource (c_id);
CREATE INDEX idx_course_resource_resource ON train_course_resource (resource_id);
-- 6. train_electronic_board 电子班牌外键索引
CREATE INDEX idx_board_classroom ON train_electronic_board (bind_classroom_id);
-- 7. train_plan_course 培训班课程表索引
CREATE INDEX idx_plan_course_plan ON train_plan_course (plan_id);
CREATE INDEX idx_plan_course_course ON train_plan_course (c_id);
-- 8. train_enrollment 参训学员表索引
CREATE INDEX idx_enrollment_plan ON train_enrollment (plan_id);
CREATE INDEX idx_enrollment_student ON train_enrollment (student_id);
CREATE INDEX idx_enrollment_status ON train_enrollment (enrollment_status);
-- 9. train_lesson 排课表多维时间冲突检索复合索引
CREATE INDEX idx_lesson_plan_course ON train_lesson (plan_course_id);
CREATE INDEX idx_lesson_teacher_time ON train_lesson (t_id, start_time, end_time);
CREATE INDEX idx_lesson_classroom_time ON train_lesson (class_room_id, start_time, end_time);
CREATE INDEX idx_lesson_site_time ON train_lesson (site_id, start_time, end_time);
-- 10. train_accommodation 住宿安排表索引
CREATE INDEX idx_accommodation_dorm ON train_accommodation (dorm_id);
-- 11. train_student_checkin 考勤打卡表索引
CREATE INDEX idx_checkin_lesson ON train_student_checkin (lesson_id);
CREATE INDEX idx_checkin_enrollment ON train_student_checkin (enrollment_id);
CREATE INDEX idx_checkin_board ON train_student_checkin (board_id);
-- 12. train_score_item 成绩项配置表索引
CREATE INDEX idx_score_item_plan ON train_score_item (plan_id);
CREATE INDEX idx_score_item_plan_course ON train_score_item (plan_course_id);
-- 13. train_student_score 学员成绩明细表索引
CREATE INDEX idx_student_score_enrollment ON train_student_score (enrollment_id);
CREATE INDEX idx_student_score_item ON train_student_score (score_item_id);
-- 14. train_study_duration 学习时长表索引
CREATE INDEX idx_study_enrollment ON train_study_duration (enrollment_id);
CREATE INDEX idx_study_plan_course ON train_study_duration (plan_course_id);
CREATE INDEX idx_study_resource ON train_study_duration (resource_id);
CREATE INDEX idx_study_sync ON train_study_duration (sync_time);
-- 15. train_study_duration 幂等防重部分唯一索引 (必须 source_system 与 source_event_id 均非空方防重)
CREATE UNIQUE INDEX uq_idx_study_source_event
ON train_study_duration (source_system, source_event_id)
WHERE source_system IS NOT NULL AND source_event_id IS NOT NULL;
COMMIT;
+335
View File
@@ -0,0 +1,335 @@
-- ==============================================================================
-- RailwaySystem 数据库 V2 业务字典脚本: 007_dicts.sql
-- 适用数据库: KingbaseES V009R003C018 (schema: public)
-- 执行目标: 准备 RailwaySystem 一期 16 组培训业务字典类型与字典数据
-- 设计依据: RailwaySystem 数据库 V2 字段设计与 Kingbase DDL 规范 V1.0 及 Round 1 审查要求
-- 核心警示:
-- 1. 【本文件在当前数据库 V2 结构迁移阶段暂不执行!】留待后续 API/权限设计阶段统一接入。
-- 2. 本文件不硬编码 dict_id / dict_code 数字主键,避免与已有 500+ 条系统字典数据冲突。
-- 3. 采用 NOT EXISTS 幂等判断,保证即使重复执行也不会重复插入相同字典项。
-- ==============================================================================
BEGIN;
-- ==============================================================================
-- 一、注册 16 组业务字典类型 (sys_dict_type)
-- ==============================================================================
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
SELECT '培训系统通用状态', 'train_common_status', '0', 'admin', CURRENT_TIMESTAMP, '通用启用/停用状态 (1:正常, 0:停用)'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'train_common_status');
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
SELECT '开班计划状态', 'train_plan_status', '0', 'admin', CURRENT_TIMESTAMP, '培训班全生命周期状态 (0:未开始, 1:进行中, 2:已结束, 3:已取消)'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'train_plan_status');
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
SELECT '学员参训状态', 'train_enrollment_status', '0', 'admin', CURRENT_TIMESTAMP, '学员参训全过程状态 (0:待报到, 1:已报到, 2:请假, 3:放弃, 4:已完成)'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'train_enrollment_status');
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
SELECT '学员主档来源类型', 'train_student_source', '0', 'admin', CURRENT_TIMESTAMP, '人员主档进入系统的来源 (1:管理员录入, 2:Excel批量导入, 3:铁路内部业务平台同步)'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'train_student_source');
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
SELECT '教学资源类型', 'train_resource_type', '0', 'admin', CURRENT_TIMESTAMP, '资源载体分类 (1:文档, 2:视频, 3:3D模型, 4:VR资源, 5:外部教学链接, 6:其他)'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'train_resource_type');
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
SELECT '教室状态', 'train_room_status', '0', 'admin', CURRENT_TIMESTAMP, '教室使用状态 (1:可用, 0:维护中)'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'train_room_status');
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
SELECT '宿舍房间状态', 'train_dorm_status', '0', 'admin', CURRENT_TIMESTAMP, '宿舍可用状态 (1:可用, 0:维修中)'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'train_dorm_status');
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
SELECT '宿舍适用性别', 'train_gender_type', '0', 'admin', CURRENT_TIMESTAMP, '宿舍男女分配属性 (0:不限, 1:男, 2:女)'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'train_gender_type');
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
SELECT '住宿安排状态', 'train_accommodation_status', '0', 'admin', CURRENT_TIMESTAMP, '住宿分配状态 (0:预分配, 1:已入住, 2:已退宿)'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'train_accommodation_status');
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
SELECT '门禁授权同步状态', 'train_auth_sync_status', '0', 'admin', CURRENT_TIMESTAMP, '外部硬件权限下发状态 (0:待同步, 1:已下发, 2:下发失败, 3:已注销)'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'train_auth_sync_status');
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
SELECT '报到核验方式', 'train_report_way', '0', 'admin', CURRENT_TIMESTAMP, '现场报到凭据方式 (1:人脸识别, 2:身份证刷卡, 3:二维码, 4:人工确认)'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'train_report_way');
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
SELECT '报到核验结果', 'train_verify_result', '0', 'admin', CURRENT_TIMESTAMP, '报到比对判定结论 (1:通过, 2:失败, 3:人工放行)'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'train_verify_result');
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
SELECT '考勤打卡方式', 'train_checkin_way', '0', 'admin', CURRENT_TIMESTAMP, '课次签到签退打卡媒介 (1:人脸识别, 2:刷卡, 3:扫码, 4:教师代签)'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'train_checkin_way');
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
SELECT '课次考勤结论', 'train_attendance_status', '0', 'admin', CURRENT_TIMESTAMP, '考勤状态 (0:缺勤, 1:正常出勤, 2:迟到, 3:早退, 4:请假)'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'train_attendance_status');
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
SELECT '成绩考核项类型', 'train_score_item_type', '0', 'admin', CURRENT_TIMESTAMP, '成绩组成项类型 (1:理论考试, 2:实操演练, 3:出勤考核, 4:综合评价)'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'train_score_item_type');
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
SELECT '成绩录入来源', 'train_score_source', '0', 'admin', CURRENT_TIMESTAMP, '成绩产生源头 (1:人工录入, 2:理论考试系统, 3:实操设备, 4:外部导入)'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'train_score_source');
-- ==============================================================================
-- 二、注册各字典项数据 (sys_dict_data)
-- ==============================================================================
-- 1. train_common_status
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 1, '正常', '1', 'train_common_status', 'primary', 'Y', '0', 'admin', CURRENT_TIMESTAMP, '正常启用'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_common_status' AND dict_value = '1');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 2, '停用', '0', 'train_common_status', 'danger', 'N', '0', 'admin', CURRENT_TIMESTAMP, '停用不可用'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_common_status' AND dict_value = '0');
-- 2. train_plan_status
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 1, '未开始', '0', 'train_plan_status', 'info', 'Y', '0', 'admin', CURRENT_TIMESTAMP, '班级已建立,未到开班时间'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_plan_status' AND dict_value = '0');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 2, '进行中', '1', 'train_plan_status', 'primary', 'N', '0', 'admin', CURRENT_TIMESTAMP, '培训班正在开展'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_plan_status' AND dict_value = '1');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 3, '已结束', '2', 'train_plan_status', 'success', 'N', '0', 'admin', CURRENT_TIMESTAMP, '培训完成已结业'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_plan_status' AND dict_value = '2');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 4, '已取消', '3', 'train_plan_status', 'danger', 'N', '0', 'admin', CURRENT_TIMESTAMP, '班级已撤销'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_plan_status' AND dict_value = '3');
-- 3. train_enrollment_status
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 1, '待报到', '0', 'train_enrollment_status', 'info', 'Y', '0', 'admin', CURRENT_TIMESTAMP, '已入班名单,待现场核验报到'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_enrollment_status' AND dict_value = '0');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 2, '已报到', '1', 'train_enrollment_status', 'primary', 'N', '0', 'admin', CURRENT_TIMESTAMP, '已到场核验完成'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_enrollment_status' AND dict_value = '1');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 3, '请假', '2', 'train_enrollment_status', 'warning', 'N', '0', 'admin', CURRENT_TIMESTAMP, '全程请假或特殊事假'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_enrollment_status' AND dict_value = '2');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 4, '放弃', '3', 'train_enrollment_status', 'danger', 'N', '0', 'admin', CURRENT_TIMESTAMP, '未到或中途主动退训'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_enrollment_status' AND dict_value = '3');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 5, '已完成', '4', 'train_enrollment_status', 'success', 'N', '0', 'admin', CURRENT_TIMESTAMP, '参训合格并顺利结业'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_enrollment_status' AND dict_value = '4');
-- 4. train_student_source (人员主档来源)
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 1, '管理员录入', '1', 'train_student_source', 'primary', 'Y', '0', 'admin', CURRENT_TIMESTAMP, '管理员单条或批量手工建立主档'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_student_source' AND dict_value = '1');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 2, 'Excel批量导入', '2', 'train_student_source', 'info', 'N', '0', 'admin', CURRENT_TIMESTAMP, '线下花名册表格批量导入'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_student_source' AND dict_value = '2');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 3, '铁路内部业务平台同步', '3', 'train_student_source', 'warning', 'N', '0', 'admin', CURRENT_TIMESTAMP, '从国铁/路局内部HR或教务系统自动下发'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_student_source' AND dict_value = '3');
-- 5. train_resource_type (教学资源类型: 严格对齐 V2 冻结稿)
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 1, '文档', '1', 'train_resource_type', 'primary', 'Y', '0', 'admin', CURRENT_TIMESTAMP, 'PDF/Word/PPT等电子课件'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_resource_type' AND dict_value = '1');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 2, '视频', '2', 'train_resource_type', 'info', 'N', '0', 'admin', CURRENT_TIMESTAMP, 'MP4录播视频'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_resource_type' AND dict_value = '2');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 3, '3D模型', '3', 'train_resource_type', 'warning', 'N', '0', 'admin', CURRENT_TIMESTAMP, '3D数字化实物机车/工务模型'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_resource_type' AND dict_value = '3');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 4, 'VR资源', '4', 'train_resource_type', 'success', 'N', '0', 'admin', CURRENT_TIMESTAMP, '虚拟仿真头盔或交互式实训课件'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_resource_type' AND dict_value = '4');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 5, '外部教学链接', '5', 'train_resource_type', 'danger', 'N', '0', 'admin', CURRENT_TIMESTAMP, '第三方外部实训/考试网址URL'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_resource_type' AND dict_value = '5');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 6, '其他', '6', 'train_resource_type', 'default', 'N', '0', 'admin', CURRENT_TIMESTAMP, '其他类型教学资料'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_resource_type' AND dict_value = '6');
-- 6. train_room_status
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 1, '正常可用', '1', 'train_room_status', 'primary', 'Y', '0', 'admin', CURRENT_TIMESTAMP, '教室可正常排课'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_room_status' AND dict_value = '1');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 2, '维护保养中', '0', 'train_room_status', 'danger', 'N', '0', 'admin', CURRENT_TIMESTAMP, '检修维护,暂不可排课'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_room_status' AND dict_value = '0');
-- 7. train_dorm_status
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 1, '正常可用', '1', 'train_dorm_status', 'primary', 'Y', '0', 'admin', CURRENT_TIMESTAMP, '房间正常分配'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_dorm_status' AND dict_value = '1');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 2, '维修停用', '0', 'train_dorm_status', 'danger', 'N', '0', 'admin', CURRENT_TIMESTAMP, '房间设施报修中'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_dorm_status' AND dict_value = '0');
-- 8. train_gender_type
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 1, '不限', '0', 'train_gender_type', 'info', 'Y', '0', 'admin', CURRENT_TIMESTAMP, '混合或不限'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_gender_type' AND dict_value = '0');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 2, '男', '1', 'train_gender_type', 'primary', 'N', '0', 'admin', CURRENT_TIMESTAMP, '男生宿舍'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_gender_type' AND dict_value = '1');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 3, '女', '2', 'train_gender_type', 'warning', 'N', '0', 'admin', CURRENT_TIMESTAMP, '女生宿舍'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_gender_type' AND dict_value = '2');
-- 9. train_accommodation_status
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 1, '预分配', '0', 'train_accommodation_status', 'info', 'Y', '0', 'admin', CURRENT_TIMESTAMP, '报到前系统预先分配'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_accommodation_status' AND dict_value = '0');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 2, '已入住', '1', 'train_accommodation_status', 'primary', 'N', '0', 'admin', CURRENT_TIMESTAMP, '报到确认并已入住'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_accommodation_status' AND dict_value = '1');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 3, '已退宿', '2', 'train_accommodation_status', 'success', 'N', '0', 'admin', CURRENT_TIMESTAMP, '培训结束已离宿'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_accommodation_status' AND dict_value = '2');
-- 10. train_auth_sync_status
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 1, '待下发', '0', 'train_auth_sync_status', 'info', 'Y', '0', 'admin', CURRENT_TIMESTAMP, '未与外部门禁系统握手'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_auth_sync_status' AND dict_value = '0');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 2, '已下发', '1', 'train_auth_sync_status', 'success', 'N', '0', 'admin', CURRENT_TIMESTAMP, '门禁硬件下发生效'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_auth_sync_status' AND dict_value = '1');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 3, '下发失败', '2', 'train_auth_sync_status', 'danger', 'N', '0', 'admin', CURRENT_TIMESTAMP, '网络超时或凭据错误'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_auth_sync_status' AND dict_value = '2');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 4, '已注销', '3', 'train_auth_sync_status', 'warning', 'N', '0', 'admin', CURRENT_TIMESTAMP, '已退宿并回收门禁权限'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_auth_sync_status' AND dict_value = '3');
-- 11. train_report_way
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 1, '人脸识别', '1', 'train_report_way', 'primary', 'Y', '0', 'admin', CURRENT_TIMESTAMP, '通过人脸比对报到'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_report_way' AND dict_value = '1');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 2, '身份证刷卡', '2', 'train_report_way', 'info', 'N', '0', 'admin', CURRENT_TIMESTAMP, '二代身份证读卡器'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_report_way' AND dict_value = '2');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 3, '手机扫码', '3', 'train_report_way', 'warning', 'N', '0', 'admin', CURRENT_TIMESTAMP, '扫班级签到二维码'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_report_way' AND dict_value = '3');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 4, '人工确认', '4', 'train_report_way', 'success', 'N', '0', 'admin', CURRENT_TIMESTAMP, '班主任后台手动报到'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_report_way' AND dict_value = '4');
-- 12. train_verify_result
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 1, '通过', '1', 'train_verify_result', 'success', 'Y', '0', 'admin', CURRENT_TIMESTAMP, '身份信息核验一致'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_verify_result' AND dict_value = '1');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 2, '失败', '2', 'train_verify_result', 'danger', 'N', '0', 'admin', CURRENT_TIMESTAMP, '比对不符或未在名单'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_verify_result' AND dict_value = '2');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 3, '人工放行', '3', 'train_verify_result', 'warning', 'N', '0', 'admin', CURRENT_TIMESTAMP, '现场特殊人工核验放行'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_verify_result' AND dict_value = '3');
-- 13. train_checkin_way
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 1, '班牌人脸识别', '1', 'train_checkin_way', 'primary', 'Y', '0', 'admin', CURRENT_TIMESTAMP, '教室门口电子班牌刷脸'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_checkin_way' AND dict_value = '1');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 2, '班牌刷卡', '2', 'train_checkin_way', 'info', 'N', '0', 'admin', CURRENT_TIMESTAMP, '学员卡刷班牌打卡'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_checkin_way' AND dict_value = '2');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 3, '手机扫码打卡', '3', 'train_checkin_way', 'warning', 'N', '0', 'admin', CURRENT_TIMESTAMP, '扫班牌动态二维码'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_checkin_way' AND dict_value = '3');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 4, '教师代签/补卡', '4', 'train_checkin_way', 'success', 'N', '0', 'admin', CURRENT_TIMESTAMP, '授课老师端人工代签'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_checkin_way' AND dict_value = '4');
-- 14. train_attendance_status
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 1, '缺勤', '0', 'train_attendance_status', 'danger', 'Y', '0', 'admin', CURRENT_TIMESTAMP, '课次无打卡记录'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_attendance_status' AND dict_value = '0');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 2, '正常出勤', '1', 'train_attendance_status', 'primary', 'N', '0', 'admin', CURRENT_TIMESTAMP, '准时签到且准时签退'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_attendance_status' AND dict_value = '1');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 3, '迟到', '2', 'train_attendance_status', 'warning', 'N', '0', 'admin', CURRENT_TIMESTAMP, '超过上课设定宽限时间'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_attendance_status' AND dict_value = '2');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 4, '早退', '3', 'train_attendance_status', 'warning', 'N', '0', 'admin', CURRENT_TIMESTAMP, '下课前提早离开'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_attendance_status' AND dict_value = '3');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 5, '请假', '4', 'train_attendance_status', 'info', 'N', '0', 'admin', CURRENT_TIMESTAMP, '当堂课次有效请假'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_attendance_status' AND dict_value = '4');
-- 15. train_score_item_type
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 1, '理论考试', '1', 'train_score_item_type', 'primary', 'Y', '0', 'admin', CURRENT_TIMESTAMP, '理论闭卷/开卷机考'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_score_item_type' AND dict_value = '1');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 2, '实操演练', '2', 'train_score_item_type', 'warning', 'N', '0', 'admin', CURRENT_TIMESTAMP, '现场设备实操/VR仿真测评'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_score_item_type' AND dict_value = '2');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 3, '出勤考核', '3', 'train_score_item_type', 'info', 'N', '0', 'admin', CURRENT_TIMESTAMP, '全期考勤率折算成绩'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_score_item_type' AND dict_value = '3');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 4, '综合评价', '4', 'train_score_item_type', 'success', 'N', '0', 'admin', CURRENT_TIMESTAMP, '教师日常考评或答辩'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_score_item_type' AND dict_value = '4');
-- 16. train_score_source
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 1, '人工录入', '1', 'train_score_source', 'primary', 'Y', '0', 'admin', CURRENT_TIMESTAMP, '管理员或教师手工登记'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_score_source' AND dict_value = '1');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 2, '理论机考对接', '2', 'train_score_source', 'info', 'N', '0', 'admin', CURRENT_TIMESTAMP, '从在线考试平台接口抓取'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_score_source' AND dict_value = '2');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 3, '实操设备回传', '3', 'train_score_source', 'warning', 'N', '0', 'admin', CURRENT_TIMESTAMP, '从VR/实训台硬件自动计算上报'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_score_source' AND dict_value = '3');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 4, 'Excel批量导入', '4', 'train_score_source', 'success', 'N', '0', 'admin', CURRENT_TIMESTAMP, '线下成绩表格批量导入'
WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_score_source' AND dict_value = '4');
COMMIT;
@@ -0,0 +1,57 @@
-- ==============================================================================
-- RailwaySystem 数据库 V2 迁移脚本: 008_migrate_teacher.sql
-- 适用数据库: KingbaseES V009R003C018 (schema: public)
-- 执行目标: 迁移并保留 train_teacher 唯一样本数据,完成旧列向新列平滑过渡并安全清理
-- 事务边界: 全程在显式事务 BEGIN ... COMMIT 中执行
-- 设计依据: RailwaySystem 数据库 V2 字段设计与 Kingbase DDL 规范 V1.0 及 Round 1 审查要求
-- 安全规范:
-- 1. 严禁在脚本中出现真实教师姓名、电话、身份证、邮箱等明文数据!
-- 2. 完全采用纯 DML 数据映射: t_department -> org_name, t_title -> job_title, create_user -> create_by
-- 3. 执行后验证迁移完整性,再将 status 与 del_flag 设为 NOT NULL 并安全移除旧列
-- ==============================================================================
BEGIN;
-- 步骤 1: 数据平滑映射 (仅更新新字段为空的行,保证幂等)
UPDATE train_teacher
SET
org_name = COALESCE(org_name, t_department),
job_title = COALESCE(job_title, t_title),
create_by = COALESCE(create_by, create_user),
status = COALESCE(status, 1),
del_flag = COALESCE(del_flag, '0')
WHERE t_department IS NOT NULL OR t_title IS NOT NULL OR create_user IS NOT NULL;
-- 步骤 2: 校验迁移正确性 (数据行数精确为1,且新字段全部填充完成)
DO $$
DECLARE
v_total_cnt INT;
v_unmigrated_cnt INT;
BEGIN
SELECT count(*) INTO v_total_cnt FROM train_teacher;
SELECT count(*) INTO v_unmigrated_cnt
FROM train_teacher
WHERE org_name IS NULL OR status IS NULL OR del_flag IS NULL;
IF v_total_cnt <> 1 THEN
RAISE EXCEPTION '>>> [008-FATAL] 教师数据校验异常: 期望精确 1 条数据,实际检测到 % 条', v_total_cnt;
END IF;
IF v_unmigrated_cnt > 0 THEN
RAISE EXCEPTION '>>> [008-FATAL] 教师数据迁移失败: 存在 % 条记录的新主档字段尚未正确填充', v_unmigrated_cnt;
END IF;
RAISE NOTICE '>>> [008-PASS] 教师唯一数据迁移核验通过,准备固化 NOT NULL 约束与清理旧列。';
END $$;
-- 步骤 3: 固化新列 NOT NULL 约束
ALTER TABLE train_teacher ALTER COLUMN status SET NOT NULL;
ALTER TABLE train_teacher ALTER COLUMN del_flag SET NOT NULL;
-- 步骤 4: 安全删除已成功迁移的旧列 (杜绝 CASCADE)
ALTER TABLE train_teacher
DROP COLUMN IF EXISTS t_department,
DROP COLUMN IF EXISTS t_title,
DROP COLUMN IF EXISTS create_user;
COMMIT;
@@ -0,0 +1,46 @@
-- ==============================================================================
-- RailwaySystem 数据库 V2 迁移脚本: 009_drop_legacy_score.sql
-- 适用数据库: KingbaseES V009R003C018 (schema: public)
-- 执行目标: 淘汰旧模型表 train_score_info (由 train_score_item + train_student_score 取代)
-- 事务边界: 全程在显式事务 BEGIN ... COMMIT 中执行
-- 设计依据: RailwaySystem 数据库 V2 字段设计与 Kingbase DDL 规范 V1.0 及 Round 1 审查要求
-- 安全规范:
-- 1. 【强阻断要求】执行前必须严格断言 train_score_info 确为 0 行,非 0 立即异常中止!
-- 2. 显式解除自身约束,使用普通 DROP TABLE (杜绝使用 CASCADE 掩盖未预期依赖)
-- ==============================================================================
BEGIN;
-- 步骤 1: 强安全阻断校验 (非 0 行立即阻断并中止事务)
DO $$
DECLARE
v_table_exists INT;
v_row_cnt INT;
BEGIN
SELECT count(*) INTO v_table_exists
FROM information_schema.tables
WHERE table_schema = current_schema() AND table_name = 'train_score_info';
IF v_table_exists > 0 THEN
EXECUTE 'SELECT count(*) FROM train_score_info' INTO v_row_cnt;
IF v_row_cnt > 0 THEN
RAISE EXCEPTION '>>> [009-FATAL] train_score_info 当前包含 % 行真实数据!禁止执行 DROP 操作!', v_row_cnt;
ELSE
RAISE NOTICE '>>> [009-PASS] 安全探测通过: train_score_info 精确为 0 行,允许执行淘汰下线。';
END IF;
END IF;
END $$;
-- 步骤 2: 卸载旧触发器 (若存在)
DROP TRIGGER IF EXISTS trig_train_scoreinfo_modify ON train_score_info;
-- 步骤 3: 显式卸载已知历史约束
ALTER TABLE train_score_info DROP CONSTRAINT IF EXISTS fk_scoreinfo_plan;
ALTER TABLE train_score_info DROP CONSTRAINT IF EXISTS fk_scoreinfo_course;
ALTER TABLE train_score_info DROP CONSTRAINT IF EXISTS fk_scoreinfo_stu;
ALTER TABLE train_score_info DROP CONSTRAINT IF EXISTS uq_scoreinfo_plan_c_stu;
-- 步骤 4: 安全常规 DROP TABLE (无 CASCADE)
DROP TABLE IF EXISTS train_score_info;
COMMIT;
+500
View File
@@ -0,0 +1,500 @@
-- ==============================================================================
-- RailwaySystem 数据库 V2 迁移验收脚本: 010_verify.sql
-- 适用数据库: KingbaseES V009R003C018 (schema: public)
-- 执行目标: 实施“定义级深度验收”,核验数据库 V2 迁移后对象存在性与真实结构定义
-- 设计依据: RailwaySystem 数据库 V2 字段设计与 Kingbase DDL 规范 V1.0 及 Round 2 审查要求
-- 核心原则:
-- 1. 【失败即失败】所有验证逻辑均通过 PL/pgSQL DO 块强制断言,出现任何 FAIL 立即抛出 EXCEPTION
-- 2. 【定义级验收】拒绝仅查名字,必须校验: 表名、列列表、目标外键与删除规则、CHECK定义、索引定义
-- 3. 【默认值校验】强断言 accommodation_status 默认值为 0,强断言业务选择型字段无 DEFAULT
-- 4. 【系统零破坏】显式核验 public.sys_* 系统表行数与基线完全吻合
-- ==============================================================================
-- 步骤 1: 打印 21 张表状态清单 (供人工审计屏幕阅读)
SELECT t.table_name,
CASE WHEN c.table_name IS NOT NULL THEN 'EXISTS' ELSE 'MISSING' END AS table_status
FROM (
VALUES
('train_teacher'), ('train_student'), ('train_course'), ('train_resource'),
('train_course_resource'), ('train_classroom_info'), ('train_training_site'),
('train_electronic_board'), ('train_dormitory_info'), ('train_class_plan'),
('train_plan_course'), ('train_enrollment'), ('train_lesson'),
('train_accommodation'), ('train_dorm_auth'), ('train_class_report'),
('train_student_checkin'), ('train_score_item'), ('train_student_score'),
('train_study_duration'), ('train_student_portrait')
) AS t(table_name)
LEFT JOIN information_schema.tables c
ON c.table_schema = 'public' AND c.table_name = t.table_name
ORDER BY t.table_name;
-- 步骤 2: 核心自动化强断言块 (任何项不满足立即抛出 EXCEPTION 中止)
DO $$
DECLARE
v_cnt INT;
v_diff_text TEXT := '';
r RECORD;
BEGIN
-- ==========================================================================
-- 1. 逐名断言 21 张正式业务表完全就绪且无冗余 train_* 杂表
-- ==========================================================================
FOR r IN (
WITH expected_tables(table_name) AS (
VALUES
('train_teacher'), ('train_student'), ('train_course'), ('train_resource'),
('train_course_resource'), ('train_classroom_info'), ('train_training_site'),
('train_electronic_board'), ('train_dormitory_info'), ('train_class_plan'),
('train_plan_course'), ('train_enrollment'), ('train_lesson'),
('train_accommodation'), ('train_dorm_auth'), ('train_class_report'),
('train_student_checkin'), ('train_score_item'), ('train_student_score'),
('train_study_duration'), ('train_student_portrait')
),
actual_tables AS (
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name LIKE 'train_%'
AND table_name NOT LIKE 'bak_%'
)
SELECT 'MISSING_TABLE' AS diff_type, e.table_name
FROM expected_tables e
LEFT JOIN actual_tables a ON e.table_name = a.table_name
WHERE a.table_name IS NULL
UNION ALL
SELECT 'UNEXPECTED_TABLE' AS diff_type, a.table_name
FROM actual_tables a
LEFT JOIN expected_tables e ON a.table_name = e.table_name
WHERE e.table_name IS NULL
) LOOP
v_diff_text := v_diff_text || r.diff_type || ':' || r.table_name || '; ';
END LOOP;
IF v_diff_text <> '' THEN
RAISE EXCEPTION '>>> [Verify-FATAL] 21 张正式业务表集合不匹配: %', v_diff_text;
END IF;
-- 确认旧淘汰表 train_score_info 彻底不存在
SELECT count(*) INTO v_cnt FROM information_schema.tables
WHERE table_schema = 'public' AND table_name = 'train_score_info';
IF v_cnt > 0 THEN
RAISE EXCEPTION '>>> [Verify-FATAL] train_score_info 仍残留在 public schema 中,淘汰下线失败!';
END IF;
-- ==========================================================================
-- 2. 数据行数基线强断言 (教师精确 1 行,新字段全部填充,其余 20 表精确 0 行)
-- ==========================================================================
SELECT count(*) INTO v_cnt FROM train_teacher;
IF v_cnt <> 1 THEN
RAISE EXCEPTION '>>> [Verify-FATAL] train_teacher 行数期望为 1,实际为 % 行', v_cnt;
END IF;
SELECT count(*) INTO v_cnt
FROM train_teacher
WHERE org_name IS NULL OR status IS NULL OR del_flag IS NULL;
IF v_cnt > 0 THEN
RAISE EXCEPTION '>>> [Verify-FATAL] train_teacher 记录中新主档字段存在非法 NULL 值';
END IF;
SELECT count(*) INTO v_cnt
FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = 'train_teacher'
AND column_name IN ('t_department', 't_title', 'create_user');
IF v_cnt > 0 THEN
RAISE EXCEPTION '>>> [Verify-FATAL] train_teacher 仍残留旧字段 (t_department/t_title/create_user)';
END IF;
FOR r IN (
SELECT unnest(ARRAY[
'train_student', 'train_course', 'train_resource', 'train_course_resource',
'train_classroom_info', 'train_training_site', 'train_electronic_board',
'train_dormitory_info', 'train_class_plan', 'train_plan_course',
'train_enrollment', 'train_lesson', 'train_accommodation',
'train_dorm_auth', 'train_class_report', 'train_student_checkin',
'train_score_item', 'train_student_score', 'train_study_duration',
'train_student_portrait'
]) AS tbl
) LOOP
EXECUTE 'SELECT count(*) FROM ' || r.tbl INTO v_cnt;
IF v_cnt <> 0 THEN
RAISE EXCEPTION '>>> [Verify-FATAL] 表 % 包含非预期数据,当前行数为 % (期望 0 行)', r.tbl, v_cnt;
END IF;
END LOOP;
-- ==========================================================================
-- 3. 13 组核心唯一约束 (UNIQUE) 定义级校验 (表名 + 约束名 + 组成列列表)
-- ==========================================================================
v_diff_text := '';
FOR r IN (
WITH expected_unique(table_name, constraint_name, column_list) AS (
VALUES
('train_student', 'uq_student_idcard_hash', 'id_card_hash'),
('train_course', 'uq_course_code', 'c_code'),
('train_electronic_board', 'uq_board_sn', 'board_sn'),
('train_dormitory_info', 'uq_dorm_building_room', 'dorm_building, dorm_room'),
('train_course_resource', 'uq_course_resource', 'c_id, resource_id'),
('train_plan_course', 'uq_plan_course', 'plan_id, c_id'),
('train_enrollment', 'uq_enrollment', 'plan_id, student_id'),
('train_accommodation', 'uq_accommodation_enroll', 'enrollment_id'),
('train_dorm_auth', 'uq_dorm_auth_accomm', 'accommodation_id'),
('train_class_report', 'uq_class_report_enroll', 'enrollment_id'),
('train_student_checkin', 'uq_student_checkin_enroll_lesson', 'enrollment_id, lesson_id'),
('train_student_score', 'uq_student_score_enroll_item', 'enrollment_id, score_item_id'),
('train_student_portrait', 'uq_student_portrait_enroll', 'enrollment_id')
),
actual_unique AS (
SELECT
tc.table_name,
tc.constraint_name,
string_agg(kcu.column_name, ', ' ORDER BY kcu.ordinal_position) AS column_list
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
WHERE tc.constraint_type = 'UNIQUE'
AND tc.table_schema = 'public'
AND tc.table_name LIKE 'train_%'
GROUP BY tc.table_name, tc.constraint_name
)
SELECT 'MISSING_UQ' AS diff_type, e.table_name, e.constraint_name, e.column_list
FROM expected_unique e
LEFT JOIN actual_unique a
ON e.table_name = a.table_name
AND e.constraint_name = a.constraint_name
AND e.column_list = a.column_list
WHERE a.table_name IS NULL
UNION ALL
SELECT 'UNEXPECTED_UQ' AS diff_type, a.table_name, a.constraint_name, a.column_list
FROM actual_unique a
LEFT JOIN expected_unique e
ON a.table_name = e.table_name
AND a.constraint_name = e.constraint_name
AND a.column_list = e.column_list
WHERE e.table_name IS NULL
) LOOP
v_diff_text := v_diff_text || r.diff_type || ': ' || r.table_name || '.' || r.constraint_name || '(' || r.column_list || '); ';
END LOOP;
IF v_diff_text <> '' THEN
RAISE EXCEPTION '>>> [Verify-FATAL] 13 组唯一约束 (UNIQUE) 定义不匹配: %', v_diff_text;
END IF;
-- ==========================================================================
-- 4. 26 组外键约束 (FK) 定义级校验 (表名 + 约束名 + 本地列 + 目标表 + 目标列 + 删除规则)
-- ==========================================================================
v_diff_text := '';
FOR r IN (
WITH expected_fk(table_name, constraint_name, column_name, foreign_table_name, foreign_column_name) AS (
VALUES
('train_course_resource', 'fk_cr_course', 'c_id', 'train_course', 'c_id'),
('train_course_resource', 'fk_cr_resource', 'resource_id', 'train_resource', 'resource_id'),
('train_electronic_board', 'fk_board_classroom', 'bind_classroom_id', 'train_classroom_info', 'class_room_id'),
('train_plan_course', 'fk_plancourse_plan', 'plan_id', 'train_class_plan', 'plan_id'),
('train_plan_course', 'fk_plancourse_course', 'c_id', 'train_course', 'c_id'),
('train_enrollment', 'fk_enrollment_plan', 'plan_id', 'train_class_plan', 'plan_id'),
('train_enrollment', 'fk_enrollment_student', 'student_id', 'train_student', 's_id'),
('train_lesson', 'fk_lesson_plancourse', 'plan_course_id', 'train_plan_course', 'plan_course_id'),
('train_lesson', 'fk_lesson_teacher', 't_id', 'train_teacher', 't_id'),
('train_lesson', 'fk_lesson_classroom', 'class_room_id', 'train_classroom_info', 'class_room_id'),
('train_lesson', 'fk_lesson_site', 'site_id', 'train_training_site', 'site_id'),
('train_accommodation', 'fk_accomm_enrollment', 'enrollment_id', 'train_enrollment', 'enrollment_id'),
('train_accommodation', 'fk_accomm_dorm', 'dorm_id', 'train_dormitory_info', 'dorm_id'),
('train_dorm_auth', 'fk_dormauth_accomm', 'accommodation_id', 'train_accommodation', 'accommodation_id'),
('train_class_report', 'fk_report_enrollment', 'enrollment_id', 'train_enrollment', 'enrollment_id'),
('train_student_checkin', 'fk_checkin_enrollment', 'enrollment_id', 'train_enrollment', 'enrollment_id'),
('train_student_checkin', 'fk_checkin_lesson', 'lesson_id', 'train_lesson', 'lesson_id'),
('train_student_checkin', 'fk_checkin_board', 'board_id', 'train_electronic_board', 'board_id'),
('train_score_item', 'fk_scoreitem_plan', 'plan_id', 'train_class_plan', 'plan_id'),
('train_score_item', 'fk_scoreitem_plancourse', 'plan_course_id', 'train_plan_course', 'plan_course_id'),
('train_student_score', 'fk_score_enrollment', 'enrollment_id', 'train_enrollment', 'enrollment_id'),
('train_student_score', 'fk_score_item', 'score_item_id', 'train_score_item', 'score_item_id'),
('train_study_duration', 'fk_study_enrollment', 'enrollment_id', 'train_enrollment', 'enrollment_id'),
('train_study_duration', 'fk_study_plancourse', 'plan_course_id', 'train_plan_course', 'plan_course_id'),
('train_study_duration', 'fk_study_resource', 'resource_id', 'train_resource', 'resource_id'),
('train_student_portrait', 'fk_portrait_enrollment', 'enrollment_id', 'train_enrollment', 'enrollment_id')
),
actual_fk AS (
SELECT
tc.table_name,
tc.constraint_name,
kcu.column_name,
ccu.table_name AS foreign_table_name,
ccu.column_name AS foreign_column_name,
rc.delete_rule
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
JOIN information_schema.constraint_column_usage ccu
ON tc.constraint_name = ccu.constraint_name AND tc.table_schema = ccu.table_schema
JOIN information_schema.referential_constraints rc
ON tc.constraint_name = rc.constraint_name AND tc.table_schema = rc.constraint_schema
WHERE tc.constraint_type = 'FOREIGN KEY'
AND tc.table_schema = 'public'
AND tc.table_name LIKE 'train_%'
)
SELECT 'MISSING_FK' AS diff_type, e.table_name, e.constraint_name, e.column_name, e.foreign_table_name, e.foreign_column_name
FROM expected_fk e
LEFT JOIN actual_fk a
ON e.table_name = a.table_name
AND e.constraint_name = a.constraint_name
AND e.column_name = a.column_name
AND e.foreign_table_name = a.foreign_table_name
AND e.foreign_column_name = a.foreign_column_name
AND a.delete_rule = 'NO ACTION'
WHERE a.table_name IS NULL
UNION ALL
SELECT 'UNEXPECTED_FK' AS diff_type, a.table_name, a.constraint_name, a.column_name, a.foreign_table_name, a.foreign_column_name
FROM actual_fk a
LEFT JOIN expected_fk e
ON a.table_name = e.table_name
AND a.constraint_name = e.constraint_name
AND a.column_name = e.column_name
AND a.foreign_table_name = e.foreign_table_name
AND a.foreign_column_name = e.foreign_column_name
WHERE e.table_name IS NULL
) LOOP
v_diff_text := v_diff_text || r.diff_type || ': ' || r.table_name || '.' || r.constraint_name || '; ';
END LOOP;
IF v_diff_text <> '' THEN
RAISE EXCEPTION '>>> [Verify-FATAL] 26 组外键约束 (FK) 定义或删除规则不匹配: %', v_diff_text;
END IF;
-- ==========================================================================
-- 5. 9 组单行检查约束 (CHECK) 定义逻辑校验 (逐名检查并核验包含预期核心表达式)
-- ==========================================================================
FOR r IN (
SELECT
cc.constraint_name,
tc.table_name,
cc.check_clause
FROM information_schema.check_constraints cc
JOIN information_schema.table_constraints tc
ON cc.constraint_name = tc.constraint_name AND cc.constraint_schema = tc.constraint_schema
WHERE tc.table_schema = 'public' AND tc.table_name LIKE 'train_%'
) LOOP
CASE r.constraint_name
WHEN 'chk_lesson_time' THEN
IF r.table_name <> 'train_lesson' OR r.check_clause NOT LIKE '%start_time < end_time%' THEN
RAISE EXCEPTION '>>> [Verify-FATAL] chk_lesson_time 定义不合预期: %', r.check_clause;
END IF;
WHEN 'chk_lesson_venue' THEN
IF r.table_name <> 'train_lesson' OR (r.check_clause NOT LIKE '%venue_type = 1%' OR r.check_clause NOT LIKE '%venue_type = 2%') THEN
RAISE EXCEPTION '>>> [Verify-FATAL] chk_lesson_venue 场地互斥定义不合预期: %', r.check_clause;
END IF;
WHEN 'chk_score_item_weight' THEN
IF r.table_name <> 'train_score_item' OR (r.check_clause NOT LIKE '%weight >= 0%' OR r.check_clause NOT LIKE '%weight <= 100%') THEN
RAISE EXCEPTION '>>> [Verify-FATAL] chk_score_item_weight 权重范围定义不合预期: %', r.check_clause;
END IF;
WHEN 'chk_score_item_full' THEN
IF r.table_name <> 'train_score_item' OR r.check_clause NOT LIKE '%full_score > 0%' THEN
RAISE EXCEPTION '>>> [Verify-FATAL] chk_score_item_full 满分定义不合预期: %', r.check_clause;
END IF;
WHEN 'chk_student_score_nonneg' THEN
IF r.table_name <> 'train_student_score' OR r.check_clause NOT LIKE '%score >= 0%' THEN
RAISE EXCEPTION '>>> [Verify-FATAL] chk_student_score_nonneg 成绩非负定义不合预期: %', r.check_clause;
END IF;
WHEN 'chk_study_seconds' THEN
IF r.table_name <> 'train_study_duration' OR r.check_clause NOT LIKE '%study_seconds >= 0%' THEN
RAISE EXCEPTION '>>> [Verify-FATAL] chk_study_seconds 时长非负定义不合预期: %', r.check_clause;
END IF;
WHEN 'chk_study_time' THEN
IF r.table_name <> 'train_study_duration' OR r.check_clause NOT LIKE '%end_time >= start_time%' THEN
RAISE EXCEPTION '>>> [Verify-FATAL] chk_study_time 时间先后定义不合预期: %', r.check_clause;
END IF;
WHEN 'chk_study_source_system' THEN
IF r.table_name <> 'train_study_duration' OR r.check_clause NOT LIKE '%source_system IS NOT NULL%' THEN
RAISE EXCEPTION '>>> [Verify-FATAL] chk_study_source_system 来源系统一致性定义不合预期: %', r.check_clause;
END IF;
WHEN 'chk_dorm_capacity' THEN
IF r.table_name <> 'train_dormitory_info' OR r.check_clause NOT LIKE '%capacity > 0%' THEN
RAISE EXCEPTION '>>> [Verify-FATAL] chk_dorm_capacity 容量正数定义不合预期: %', r.check_clause;
END IF;
ELSE
NULL; -- 忽略其他系统生成的非空检查
END CASE;
END LOOP;
-- 确认 9 个显式命名的 CHECK 约束一个不少
SELECT count(*) INTO v_cnt
FROM information_schema.table_constraints
WHERE table_schema = 'public'
AND constraint_type = 'CHECK'
AND constraint_name IN (
'chk_lesson_time', 'chk_lesson_venue', 'chk_score_item_weight',
'chk_score_item_full', 'chk_student_score_nonneg', 'chk_study_seconds',
'chk_study_time', 'chk_study_source_system', 'chk_dorm_capacity'
);
IF v_cnt <> 9 THEN
RAISE EXCEPTION '>>> [Verify-FATAL] 9 个核心 CHECK 检查约束总数不匹配,实际发现 % 个', v_cnt;
END IF;
-- ==========================================================================
-- 6. 12 张业务表 Identity 自增主键精确校验 (表名 + 指定列名 + is_identity = 'YES')
-- ==========================================================================
v_diff_text := '';
FOR r IN (
WITH expected_identity(table_name, column_name) AS (
VALUES
('train_course_resource', 'id'),
('train_plan_course', 'plan_course_id'),
('train_enrollment', 'enrollment_id'),
('train_lesson', 'lesson_id'),
('train_accommodation', 'accommodation_id'),
('train_score_item', 'score_item_id'),
('train_student_score', 'score_id'),
('train_dorm_auth', 'auth_id'),
('train_class_report', 'report_id'),
('train_student_checkin', 'checkin_id'),
('train_study_duration', 'dur_id'),
('train_student_portrait', 'portrait_id')
),
actual_identity AS (
SELECT table_name, column_name
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name LIKE 'train_%'
AND is_identity = 'YES'
)
SELECT 'MISSING_IDENTITY' AS diff_type, e.table_name, e.column_name
FROM expected_identity e
LEFT JOIN actual_identity a
ON e.table_name = a.table_name AND e.column_name = a.column_name
WHERE a.table_name IS NULL
UNION ALL
SELECT 'UNEXPECTED_IDENTITY' AS diff_type, a.table_name, a.column_name
FROM actual_identity a
LEFT JOIN expected_identity e
ON a.table_name = e.table_name AND a.column_name = e.column_name
WHERE e.table_name IS NULL
) LOOP
v_diff_text := v_diff_text || r.diff_type || ': ' || r.table_name || '.' || r.column_name || '; ';
END LOOP;
IF v_diff_text <> '' THEN
RAISE EXCEPTION '>>> [Verify-FATAL] 12 张表 Identity 自增主键属性不匹配: %', v_diff_text;
END IF;
-- ==========================================================================
-- 7. 关键复合索引与部分唯一索引定义级校验
-- ==========================================================================
-- 7.1 uq_idx_study_source_event 必须为 UNIQUE 且具有双非空 WHERE 条件
SELECT count(*) INTO v_cnt FROM pg_indexes
WHERE schemaname = 'public'
AND tablename = 'train_study_duration'
AND indexname = 'uq_idx_study_source_event'
AND indexdef LIKE '%UNIQUE INDEX%'
AND indexdef LIKE '%(source_system, source_event_id)%'
AND indexdef LIKE '%source_system IS NOT NULL%'
AND indexdef LIKE '%source_event_id IS NOT NULL%';
IF v_cnt <> 1 THEN
RAISE EXCEPTION '>>> [Verify-FATAL] 部分唯一索引 uq_idx_study_source_event 定义不合要求';
END IF;
-- 7.2 排课表三大时间检索复合索引
SELECT count(*) INTO v_cnt FROM pg_indexes
WHERE schemaname = 'public' AND tablename = 'train_lesson'
AND indexname = 'idx_lesson_teacher_time' AND indexdef LIKE '%(t_id, start_time, end_time)%';
IF v_cnt <> 1 THEN RAISE EXCEPTION '>>> [Verify-FATAL] 索引 idx_lesson_teacher_time 复合列定义不合要求'; END IF;
SELECT count(*) INTO v_cnt FROM pg_indexes
WHERE schemaname = 'public' AND tablename = 'train_lesson'
AND indexname = 'idx_lesson_classroom_time' AND indexdef LIKE '%(class_room_id, start_time, end_time)%';
IF v_cnt <> 1 THEN RAISE EXCEPTION '>>> [Verify-FATAL] 索引 idx_lesson_classroom_time 复合列定义不合要求'; END IF;
SELECT count(*) INTO v_cnt FROM pg_indexes
WHERE schemaname = 'public' AND tablename = 'train_lesson'
AND indexname = 'idx_lesson_site_time' AND indexdef LIKE '%(site_id, start_time, end_time)%';
IF v_cnt <> 1 THEN RAISE EXCEPTION '>>> [Verify-FATAL] 索引 idx_lesson_site_time 复合列定义不合要求'; END IF;
-- ==========================================================================
-- 8. 默认值严格校验 (R2-01 住宿默认0预分配; R2-02 业务选择型字段严禁有DEFAULT)
-- ==========================================================================
-- 8.1 accommodation_status 默认值必须为 0 (预分配)
SELECT count(*) INTO v_cnt
FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = 'train_accommodation'
AND column_name = 'accommodation_status' AND column_default LIKE '%0%';
IF v_cnt <> 1 THEN
RAISE EXCEPTION '>>> [Verify-FATAL] train_accommodation.accommodation_status 默认值必须为 0 (预分配)';
END IF;
-- 8.2 业务选择型字段严禁存在默认值 (column_default 必须为 NULL)
FOR r IN (
SELECT table_name, column_name, column_default
FROM information_schema.columns
WHERE table_schema = 'public' AND (
(table_name = 'train_enrollment' AND column_name IN ('source_type', 'accommodation_required')) OR
(table_name = 'train_lesson' AND column_name = 'venue_type') OR
(table_name = 'train_student_score' AND column_name = 'source_type') OR
(table_name = 'train_student_checkin' AND column_name = 'checkin_way') OR
(table_name = 'train_class_report' AND column_name = 'verify_result')
)
) LOOP
IF r.column_default IS NOT NULL THEN
RAISE EXCEPTION '>>> [Verify-FATAL] 业务选择型字段 %.% 非法配置了默认值: %,必须取消!', r.table_name, r.column_name, r.column_default;
END IF;
END LOOP;
-- ==========================================================================
-- 9. 核心领域架构解耦列特征强断言
-- ==========================================================================
-- train_class_plan 必须无 c_id 与 t_id
SELECT count(*) INTO v_cnt FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = 'train_class_plan' AND column_name IN ('c_id', 't_id');
IF v_cnt > 0 THEN
RAISE EXCEPTION '>>> [Verify-FATAL] train_class_plan 仍包含旧单课或单教师外键列 c_id/t_id';
END IF;
-- train_student_checkin 必须包含 enrollment_id 与 lesson_id,且无旧冗余列
SELECT count(*) INTO v_cnt FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = 'train_student_checkin'
AND column_name IN ('enrollment_id', 'lesson_id');
IF v_cnt <> 2 THEN
RAISE EXCEPTION '>>> [Verify-FATAL] train_student_checkin 未包含核心列 enrollment_id 与 lesson_id';
END IF;
SELECT count(*) INTO v_cnt FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = 'train_student_checkin'
AND column_name IN ('plan_id', 'c_id', 'student_id', 'lesson_start_time', 'lesson_end_time');
IF v_cnt > 0 THEN
RAISE EXCEPTION '>>> [Verify-FATAL] train_student_checkin 仍残留历史冗余列';
END IF;
-- train_class_report 必须无 alloc_* 旧预分配列
SELECT count(*) INTO v_cnt FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = 'train_class_report'
AND column_name IN ('alloc_dorm_id', 'alloc_classroom_id', 'alloc_site_id');
IF v_cnt > 0 THEN
RAISE EXCEPTION '>>> [Verify-FATAL] train_class_report 仍残留旧预分配列';
END IF;
-- train_lesson.t_id 必须为 NOT NULL
SELECT count(*) INTO v_cnt FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = 'train_lesson'
AND column_name = 't_id' AND is_nullable = 'NO';
IF v_cnt <> 1 THEN
RAISE EXCEPTION '>>> [Verify-FATAL] train_lesson.t_id 必须为 NOT NULL';
END IF;
-- ==========================================================================
-- 10. public.sys_* 系统基线表行数与零受损核验
-- ==========================================================================
SELECT count(*) INTO v_cnt FROM public.sys_user;
IF v_cnt <> 1 THEN RAISE EXCEPTION '>>> [Verify-FATAL] public.sys_user 行数受损,当前为 %', v_cnt; END IF;
SELECT count(*) INTO v_cnt FROM public.sys_menu;
IF v_cnt <> 106 THEN RAISE EXCEPTION '>>> [Verify-FATAL] public.sys_menu 行数受损,当前为 %', v_cnt; END IF;
SELECT count(*) INTO v_cnt FROM public.sys_dept;
IF v_cnt <> 15 THEN RAISE EXCEPTION '>>> [Verify-FATAL] public.sys_dept 行数受损,当前为 %', v_cnt; END IF;
SELECT count(*) INTO v_cnt FROM public.sys_dict_type;
IF v_cnt <> 44 THEN RAISE EXCEPTION '>>> [Verify-FATAL] public.sys_dict_type 行数受损,当前为 %', v_cnt; END IF;
SELECT count(*) INTO v_cnt FROM public.sys_dict_data;
IF v_cnt <> 500 THEN RAISE EXCEPTION '>>> [Verify-FATAL] public.sys_dict_data 行数受损,当前为 %', v_cnt; END IF;
RAISE NOTICE '==============================================================================';
RAISE NOTICE '>>> [Verify-PASS] RailwaySystem 数据库 V2 定义级验收全量通过!';
RAISE NOTICE '>>> 验收内容: 21表 + 13 UNIQUE(定义) + 26 FK(定义+NO ACTION) + 9 CHECK(表达式) + 12 Identity + 关键复合索引 + 默认值严格断言 + sys_*零破坏';
RAISE NOTICE '==============================================================================';
END $$;
@@ -0,0 +1,356 @@
-- ==============================================================================
-- RailwaySystem 数据库 V2 回滚基线: 000_phase05_exact_schema.sql
-- 适用数据库: KingbaseES V009R003C018 (schema: public)
-- 执行目标: 精确定义 Phase 0.5 历史基线 14 张培训业务表的完整物理 DDL
-- 依据来源: codebase-audit 13_live_database_snapshot.md 真实元数据基线
-- 包含内容:
-- 1. 14 张表物理字段定义、NOT NULL、DEFAULT
-- 2. 14 个 PRIMARY KEY (8 个 varchar(32), 6 个 bigint GENERATED BY DEFAULT AS IDENTITY)
-- 3. 7 组历史业务 UNIQUE 约束
-- 4. 24 组历史业务 FOREIGN KEY 约束
-- 5. 9 个 update_time 触发器 (绑定 func_train_update_modifytime)
-- 说明: 本文件为 Phase 0.5 原始结构标准,作为回滚操作的唯一结构依据,杜绝 CTAS 推断结构
-- ==============================================================================
-- 1. train_teacher 教师主档
CREATE TABLE train_teacher (
t_id VARCHAR(32) NOT NULL,
t_name VARCHAR(30) NOT NULL,
t_gender CHAR(2),
t_birth DATE,
t_department VARCHAR(50) NOT NULL,
t_title VARCHAR(30),
t_phone VARCHAR(20),
t_email VARCHAR(50),
create_user VARCHAR(32) NOT NULL,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
remark VARCHAR(200),
CONSTRAINT pk_train_teacher PRIMARY KEY (t_id)
);
-- 2. train_student 学员表
CREATE TABLE train_student (
s_id VARCHAR(32) NOT NULL,
s_name VARCHAR(30) NOT NULL,
s_gender CHAR(2),
s_birth DATE,
s_department VARCHAR(50) NOT NULL,
s_major VARCHAR(50) NOT NULL,
s_class VARCHAR(30),
s_phone VARCHAR(20),
create_user VARCHAR(32) NOT NULL,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
remark VARCHAR(200),
CONSTRAINT pk_train_student PRIMARY KEY (s_id)
);
-- 3. train_course 课程表
CREATE TABLE train_course (
c_id VARCHAR(32) NOT NULL,
c_name VARCHAR(100) NOT NULL,
c_type VARCHAR(30),
c_duration INTEGER,
teaching_objective TEXT,
course_content TEXT,
attach_doc TEXT,
attach_video TEXT,
attach_model_link TEXT,
model_resource_ids TEXT,
create_user VARCHAR(32) NOT NULL,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
remark VARCHAR(200),
CONSTRAINT pk_train_course PRIMARY KEY (c_id)
);
-- 4. train_class_plan 开班计划表
CREATE TABLE train_class_plan (
plan_id VARCHAR(32) NOT NULL,
plan_name VARCHAR(100) NOT NULL,
c_id VARCHAR(32) NOT NULL,
start_time TIMESTAMP NOT NULL,
end_time TIMESTAMP NOT NULL,
train_address VARCHAR(100),
t_id VARCHAR(32) NOT NULL,
recruit_num INTEGER,
apply_condition TEXT,
plan_status SMALLINT DEFAULT 0,
create_user VARCHAR(32) NOT NULL,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
remark VARCHAR(200),
CONSTRAINT pk_train_class_plan PRIMARY KEY (plan_id)
);
-- 5. train_dormitory_info 宿舍信息表
CREATE TABLE train_dormitory_info (
dorm_id VARCHAR(32) NOT NULL,
dorm_building VARCHAR(50),
dorm_room VARCHAR(30) NOT NULL,
bed_total INTEGER,
bed_used INTEGER DEFAULT 0,
lock_device_sn VARCHAR(64),
dorm_status SMALLINT DEFAULT 1,
create_user VARCHAR(32) NOT NULL,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
remark VARCHAR(200),
CONSTRAINT pk_train_dormitory_info PRIMARY KEY (dorm_id),
CONSTRAINT uq_dorm_building_room UNIQUE (dorm_building, dorm_room)
);
-- 6. train_dorm_auth 宿舍门禁表
CREATE TABLE train_dorm_auth (
auth_id BIGINT GENERATED BY DEFAULT AS IDENTITY,
student_id VARCHAR(32) NOT NULL,
plan_id VARCHAR(32) NOT NULL,
dorm_id VARCHAR(32) NOT NULL,
bed_no VARCHAR(20),
face_sync_status SMALLINT DEFAULT 0,
auth_start TIMESTAMP,
auth_end TIMESTAMP,
create_user VARCHAR(32) NOT NULL,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
remark VARCHAR(200),
CONSTRAINT pk_train_dorm_auth PRIMARY KEY (auth_id),
CONSTRAINT uq_dormauth_stu_plan UNIQUE (student_id, plan_id)
);
-- 7. train_classroom_info 教室信息表
CREATE TABLE train_classroom_info (
class_room_id VARCHAR(32) NOT NULL,
room_name VARCHAR(60) NOT NULL,
room_location VARCHAR(100),
capacity INTEGER,
room_status SMALLINT DEFAULT 1,
create_user VARCHAR(32) NOT NULL,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
remark VARCHAR(200),
CONSTRAINT pk_train_classroom_info PRIMARY KEY (class_room_id)
);
-- 8. train_training_site 实训场地表
CREATE TABLE train_training_site (
site_id VARCHAR(32) NOT NULL,
site_name VARCHAR(80) NOT NULL,
site_location VARCHAR(100),
capacity INTEGER,
site_device TEXT,
site_status SMALLINT DEFAULT 1,
create_user VARCHAR(32) NOT NULL,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
remark VARCHAR(200),
CONSTRAINT pk_train_training_site PRIMARY KEY (site_id)
);
-- 9. train_class_report 报到记录表
CREATE TABLE train_class_report (
report_id BIGINT GENERATED BY DEFAULT AS IDENTITY,
plan_id VARCHAR(32) NOT NULL,
student_id VARCHAR(32) NOT NULL,
report_way SMALLINT,
report_device_sn VARCHAR(64),
report_time TIMESTAMP,
report_status SMALLINT DEFAULT 0,
alloc_dorm_id VARCHAR(32),
alloc_classroom_id VARCHAR(32),
alloc_site_id VARCHAR(32),
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT pk_train_class_report PRIMARY KEY (report_id),
CONSTRAINT uq_classreport_plan_stu UNIQUE (plan_id, student_id)
);
-- 10. train_electronic_board 电子班牌表
CREATE TABLE train_electronic_board (
board_id VARCHAR(32) NOT NULL,
board_sn VARCHAR(64) NOT NULL,
bind_classroom_id VARCHAR(32),
bind_plan_id VARCHAR(32),
board_ip VARCHAR(50),
board_content VARCHAR(500),
board_playmode SMALLINT DEFAULT 1,
support_checkin_type VARCHAR(30),
board_status SMALLINT DEFAULT 1,
last_heartbeat TIMESTAMP,
create_user VARCHAR(32) NOT NULL,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
remark VARCHAR(200),
CONSTRAINT pk_train_electronic_board PRIMARY KEY (board_id),
CONSTRAINT uq_board_sn UNIQUE (board_sn)
);
-- 11. train_student_checkin 考勤签到表
CREATE TABLE train_student_checkin (
checkin_id BIGINT GENERATED BY DEFAULT AS IDENTITY,
plan_id VARCHAR(32) NOT NULL,
c_id VARCHAR(32) NOT NULL,
student_id VARCHAR(32) NOT NULL,
board_id VARCHAR(32),
checkin_way SMALLINT,
checkin_time TIMESTAMP,
checkin_status SMALLINT,
lesson_start_time TIMESTAMP,
lesson_end_time TIMESTAMP,
CONSTRAINT pk_train_student_checkin PRIMARY KEY (checkin_id),
CONSTRAINT uq_checkin_lesson_stu UNIQUE (plan_id, c_id, student_id, lesson_start_time)
);
-- 12. train_score_info 成绩信息表 (历史旧表)
CREATE TABLE train_score_info (
score_id BIGINT GENERATED BY DEFAULT AS IDENTITY,
plan_id VARCHAR(32),
c_id VARCHAR(32),
student_id VARCHAR(32),
course_score NUMERIC(5,2),
train_score NUMERIC(5,2),
exam_score NUMERIC(5,2),
source_type SMALLINT,
source_system VARCHAR(50),
import_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT pk_train_score_info PRIMARY KEY (score_id),
CONSTRAINT uq_scoreinfo_plan_c_stu UNIQUE (plan_id, c_id, student_id)
);
-- 13. train_study_duration 学习时长表
CREATE TABLE train_study_duration (
dur_id BIGINT GENERATED BY DEFAULT AS IDENTITY,
student_id VARCHAR(32),
plan_id VARCHAR(32),
c_id VARCHAR(32),
device_type VARCHAR(50),
start_time TIMESTAMP,
end_time TIMESTAMP,
study_seconds INTEGER,
sync_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT pk_train_study_duration PRIMARY KEY (dur_id)
);
-- 14. train_student_portrait 学员画像表
CREATE TABLE train_student_portrait (
portrait_id BIGINT GENERATED BY DEFAULT AS IDENTITY,
student_id VARCHAR(32) NOT NULL,
plan_id VARCHAR(32) NOT NULL,
total_study_hour NUMERIC(8,2),
avg_score NUMERIC(5,2),
checkin_rate NUMERIC(5,2),
label_tag TEXT,
portrait_content TEXT,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT pk_train_student_portrait PRIMARY KEY (portrait_id),
CONSTRAINT uq_portrait_stu_plan UNIQUE (student_id, plan_id)
);
-- ==============================================================================
-- 历史 24 个外键约束 (FOREIGN KEY CONSTRAINTS - 全部 ON DELETE NO ACTION)
-- ==============================================================================
-- train_class_plan (2 FK)
ALTER TABLE train_class_plan ADD CONSTRAINT fk_classplan_course
FOREIGN KEY (c_id) REFERENCES train_course (c_id) ON DELETE NO ACTION;
ALTER TABLE train_class_plan ADD CONSTRAINT fk_classplan_teacher
FOREIGN KEY (t_id) REFERENCES train_teacher (t_id) ON DELETE NO ACTION;
-- train_dorm_auth (3 FK)
ALTER TABLE train_dorm_auth ADD CONSTRAINT fk_dormauth_stu
FOREIGN KEY (student_id) REFERENCES train_student (s_id) ON DELETE NO ACTION;
ALTER TABLE train_dorm_auth ADD CONSTRAINT fk_dormauth_plan
FOREIGN KEY (plan_id) REFERENCES train_class_plan (plan_id) ON DELETE NO ACTION;
ALTER TABLE train_dorm_auth ADD CONSTRAINT fk_dormauth_dorm
FOREIGN KEY (dorm_id) REFERENCES train_dormitory_info (dorm_id) ON DELETE NO ACTION;
-- train_class_report (5 FK)
ALTER TABLE train_class_report ADD CONSTRAINT fk_classreport_plan
FOREIGN KEY (plan_id) REFERENCES train_class_plan (plan_id) ON DELETE NO ACTION;
ALTER TABLE train_class_report ADD CONSTRAINT fk_classreport_stu
FOREIGN KEY (student_id) REFERENCES train_student (s_id) ON DELETE NO ACTION;
ALTER TABLE train_class_report ADD CONSTRAINT fk_classreport_dorm
FOREIGN KEY (alloc_dorm_id) REFERENCES train_dormitory_info (dorm_id) ON DELETE NO ACTION;
ALTER TABLE train_class_report ADD CONSTRAINT fk_classreport_classroom
FOREIGN KEY (alloc_classroom_id) REFERENCES train_classroom_info (class_room_id) ON DELETE NO ACTION;
ALTER TABLE train_class_report ADD CONSTRAINT fk_classreport_site
FOREIGN KEY (alloc_site_id) REFERENCES train_training_site (site_id) ON DELETE NO ACTION;
-- train_electronic_board (2 FK)
ALTER TABLE train_electronic_board ADD CONSTRAINT fk_board_classroom
FOREIGN KEY (bind_classroom_id) REFERENCES train_classroom_info (class_room_id) ON DELETE NO ACTION;
ALTER TABLE train_electronic_board ADD CONSTRAINT fk_board_plan
FOREIGN KEY (bind_plan_id) REFERENCES train_class_plan (plan_id) ON DELETE NO ACTION;
-- train_student_checkin (4 FK)
ALTER TABLE train_student_checkin ADD CONSTRAINT fk_checkin_plan
FOREIGN KEY (plan_id) REFERENCES train_class_plan (plan_id) ON DELETE NO ACTION;
ALTER TABLE train_student_checkin ADD CONSTRAINT fk_checkin_course
FOREIGN KEY (c_id) REFERENCES train_course (c_id) ON DELETE NO ACTION;
ALTER TABLE train_student_checkin ADD CONSTRAINT fk_checkin_stu
FOREIGN KEY (student_id) REFERENCES train_student (s_id) ON DELETE NO ACTION;
ALTER TABLE train_student_checkin ADD CONSTRAINT fk_checkin_board
FOREIGN KEY (board_id) REFERENCES train_electronic_board (board_id) ON DELETE NO ACTION;
-- train_score_info (3 FK)
ALTER TABLE train_score_info ADD CONSTRAINT fk_scoreinfo_plan
FOREIGN KEY (plan_id) REFERENCES train_class_plan (plan_id) ON DELETE NO ACTION;
ALTER TABLE train_score_info ADD CONSTRAINT fk_scoreinfo_course
FOREIGN KEY (c_id) REFERENCES train_course (c_id) ON DELETE NO ACTION;
ALTER TABLE train_score_info ADD CONSTRAINT fk_scoreinfo_stu
FOREIGN KEY (student_id) REFERENCES train_student (s_id) ON DELETE NO ACTION;
-- train_study_duration (3 FK)
ALTER TABLE train_study_duration ADD CONSTRAINT fk_studydur_stu
FOREIGN KEY (student_id) REFERENCES train_student (s_id) ON DELETE NO ACTION;
ALTER TABLE train_study_duration ADD CONSTRAINT fk_studydur_plan
FOREIGN KEY (plan_id) REFERENCES train_class_plan (plan_id) ON DELETE NO ACTION;
ALTER TABLE train_study_duration ADD CONSTRAINT fk_studydur_course
FOREIGN KEY (c_id) REFERENCES train_course (c_id) ON DELETE NO ACTION;
-- train_student_portrait (2 FK)
ALTER TABLE train_student_portrait ADD CONSTRAINT fk_portrait_stu
FOREIGN KEY (student_id) REFERENCES train_student (s_id) ON DELETE NO ACTION;
ALTER TABLE train_student_portrait ADD CONSTRAINT fk_portrait_plan
FOREIGN KEY (plan_id) REFERENCES train_class_plan (plan_id) ON DELETE NO ACTION;
-- ==============================================================================
-- 历史 9 个 update_time 触发器
-- ==============================================================================
CREATE OR REPLACE TRIGGER trig_train_teacher_modify
BEFORE UPDATE ON train_teacher
FOR EACH ROW EXECUTE FUNCTION func_train_update_modifytime();
CREATE OR REPLACE TRIGGER trig_train_student_modify
BEFORE UPDATE ON train_student
FOR EACH ROW EXECUTE FUNCTION func_train_update_modifytime();
CREATE OR REPLACE TRIGGER trig_train_course_modify
BEFORE UPDATE ON train_course
FOR EACH ROW EXECUTE FUNCTION func_train_update_modifytime();
CREATE OR REPLACE TRIGGER trig_train_classroom_modify
BEFORE UPDATE ON train_classroom_info
FOR EACH ROW EXECUTE FUNCTION func_train_update_modifytime();
CREATE OR REPLACE TRIGGER trig_train_trainsite_modify
BEFORE UPDATE ON train_training_site
FOR EACH ROW EXECUTE FUNCTION func_train_update_modifytime();
CREATE OR REPLACE TRIGGER trig_train_board_modify
BEFORE UPDATE ON train_electronic_board
FOR EACH ROW EXECUTE FUNCTION func_train_update_modifytime();
CREATE OR REPLACE TRIGGER trig_train_dorminfo_modify
BEFORE UPDATE ON train_dormitory_info
FOR EACH ROW EXECUTE FUNCTION func_train_update_modifytime();
CREATE OR REPLACE TRIGGER trig_train_classplan_modify
BEFORE UPDATE ON train_class_plan
FOR EACH ROW EXECUTE FUNCTION func_train_update_modifytime();
CREATE OR REPLACE TRIGGER trig_train_dormauth_modify
BEFORE UPDATE ON train_dorm_auth
FOR EACH ROW EXECUTE FUNCTION func_train_update_modifytime();
@@ -0,0 +1,45 @@
-- ==============================================================================
-- RailwaySystem 数据库 V2 回滚脚本: 001_rollback_new_tables.sql
-- 适用数据库: KingbaseES V009R003C018 (schema: public)
-- 执行目标: 安全下线并删除数据库 V2 新增的 8 张培训业务表
-- 事务边界: 全程在显式事务 BEGIN ... COMMIT 中执行
-- 说明: 先显式解除外键约束,然后按依赖反序执行常规 DROP TABLE (无 CASCADE)
-- ==============================================================================
BEGIN;
-- 步骤 1: 显式解除 V2 新增表之间的外键约束
ALTER TABLE train_student_score DROP CONSTRAINT IF EXISTS fk_score_enrollment;
ALTER TABLE train_student_score DROP CONSTRAINT IF EXISTS fk_score_item;
ALTER TABLE train_score_item DROP CONSTRAINT IF EXISTS fk_scoreitem_plan;
ALTER TABLE train_score_item DROP CONSTRAINT IF EXISTS fk_scoreitem_plancourse;
ALTER TABLE train_lesson DROP CONSTRAINT IF EXISTS fk_lesson_plancourse;
ALTER TABLE train_lesson DROP CONSTRAINT IF EXISTS fk_lesson_teacher;
ALTER TABLE train_lesson DROP CONSTRAINT IF EXISTS fk_lesson_classroom;
ALTER TABLE train_lesson DROP CONSTRAINT IF EXISTS fk_lesson_site;
ALTER TABLE train_accommodation DROP CONSTRAINT IF EXISTS fk_accomm_enrollment;
ALTER TABLE train_accommodation DROP CONSTRAINT IF EXISTS fk_accomm_dorm;
ALTER TABLE train_enrollment DROP CONSTRAINT IF EXISTS fk_enrollment_plan;
ALTER TABLE train_enrollment DROP CONSTRAINT IF EXISTS fk_enrollment_student;
ALTER TABLE train_plan_course DROP CONSTRAINT IF EXISTS fk_plancourse_plan;
ALTER TABLE train_plan_course DROP CONSTRAINT IF EXISTS fk_plancourse_course;
ALTER TABLE train_course_resource DROP CONSTRAINT IF EXISTS fk_cr_course;
ALTER TABLE train_course_resource DROP CONSTRAINT IF EXISTS fk_cr_resource;
-- 步骤 2: 按依赖拓扑反序执行常规 DROP TABLE
DROP TABLE IF EXISTS train_student_score;
DROP TABLE IF EXISTS train_score_item;
DROP TABLE IF EXISTS train_lesson;
DROP TABLE IF EXISTS train_accommodation;
DROP TABLE IF EXISTS train_enrollment;
DROP TABLE IF EXISTS train_plan_course;
DROP TABLE IF EXISTS train_course_resource;
DROP TABLE IF EXISTS train_resource;
COMMIT;
@@ -0,0 +1,337 @@
-- ==============================================================================
-- RailwaySystem 数据库 V2 回滚脚本: 002_rollback_modified_tables.sql
-- 适用数据库: KingbaseES V009R003C018 (schema: public)
-- 执行目标: 严格基于 000_phase05_exact_schema.sql 结构与 bak_v1_* 数据还原 13 张表
-- 事务边界: 全程在显式事务 BEGIN ... COMMIT 中执行
-- 规范要求:
-- 1. 彻底摒弃 CTAS 丢失约束/默认值/触发器的不可靠方案
-- 2. 精确采用 Phase 0.5 原始 DDL 重建表结构
-- 3. 从 bak_v1_* 恢复真实数据并严格校验行数
-- ==============================================================================
BEGIN;
-- 步骤 1: 显式解除既有 V2 外键约束与旧外键
ALTER TABLE train_dorm_auth DROP CONSTRAINT IF EXISTS uq_dorm_auth_accomm;
ALTER TABLE train_dorm_auth DROP CONSTRAINT IF EXISTS fk_dormauth_accomm;
ALTER TABLE train_class_report DROP CONSTRAINT IF EXISTS uq_class_report_enroll;
ALTER TABLE train_class_report DROP CONSTRAINT IF EXISTS fk_report_enrollment;
ALTER TABLE train_student_checkin DROP CONSTRAINT IF EXISTS uq_student_checkin_enroll_lesson;
ALTER TABLE train_student_checkin DROP CONSTRAINT IF EXISTS fk_checkin_enrollment;
ALTER TABLE train_student_checkin DROP CONSTRAINT IF EXISTS fk_checkin_lesson;
ALTER TABLE train_student_checkin DROP CONSTRAINT IF EXISTS fk_checkin_board;
ALTER TABLE train_study_duration DROP CONSTRAINT IF EXISTS fk_study_enrollment;
ALTER TABLE train_study_duration DROP CONSTRAINT IF EXISTS fk_study_plancourse;
ALTER TABLE train_study_duration DROP CONSTRAINT IF EXISTS fk_study_resource;
ALTER TABLE train_student_portrait DROP CONSTRAINT IF EXISTS uq_student_portrait_enroll;
ALTER TABLE train_student_portrait DROP CONSTRAINT IF EXISTS fk_portrait_enrollment;
ALTER TABLE train_electronic_board DROP CONSTRAINT IF EXISTS fk_board_classroom;
-- 步骤 2: 常规 DROP 13 张表
DROP TABLE IF EXISTS train_student_portrait;
DROP TABLE IF EXISTS train_study_duration;
DROP TABLE IF EXISTS train_student_checkin;
DROP TABLE IF EXISTS train_class_report;
DROP TABLE IF EXISTS train_dorm_auth;
DROP TABLE IF EXISTS train_electronic_board;
DROP TABLE IF EXISTS train_dormitory_info;
DROP TABLE IF EXISTS train_class_plan;
DROP TABLE IF EXISTS train_training_site;
DROP TABLE IF EXISTS train_classroom_info;
DROP TABLE IF EXISTS train_course;
DROP TABLE IF EXISTS train_student;
DROP TABLE IF EXISTS train_teacher;
-- 步骤 3: 严格依据 Phase 0.5 原始 DDL 重建 13 张表物理结构
CREATE TABLE train_teacher (
t_id VARCHAR(32) NOT NULL,
t_name VARCHAR(30) NOT NULL,
t_gender CHAR(2),
t_birth DATE,
t_department VARCHAR(50) NOT NULL,
t_title VARCHAR(30),
t_phone VARCHAR(20),
t_email VARCHAR(50),
create_user VARCHAR(32) NOT NULL,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
remark VARCHAR(200),
CONSTRAINT pk_train_teacher PRIMARY KEY (t_id)
);
CREATE TABLE train_student (
s_id VARCHAR(32) NOT NULL,
s_name VARCHAR(30) NOT NULL,
s_gender CHAR(2),
s_birth DATE,
s_department VARCHAR(50) NOT NULL,
s_major VARCHAR(50) NOT NULL,
s_class VARCHAR(30),
s_phone VARCHAR(20),
create_user VARCHAR(32) NOT NULL,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
remark VARCHAR(200),
CONSTRAINT pk_train_student PRIMARY KEY (s_id)
);
CREATE TABLE train_course (
c_id VARCHAR(32) NOT NULL,
c_name VARCHAR(100) NOT NULL,
c_type VARCHAR(30),
c_duration INTEGER,
teaching_objective TEXT,
course_content TEXT,
attach_doc TEXT,
attach_video TEXT,
attach_model_link TEXT,
model_resource_ids TEXT,
create_user VARCHAR(32) NOT NULL,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
remark VARCHAR(200),
CONSTRAINT pk_train_course PRIMARY KEY (c_id)
);
CREATE TABLE train_classroom_info (
class_room_id VARCHAR(32) NOT NULL,
room_name VARCHAR(60) NOT NULL,
room_location VARCHAR(100),
capacity INTEGER,
room_status SMALLINT DEFAULT 1,
create_user VARCHAR(32) NOT NULL,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
remark VARCHAR(200),
CONSTRAINT pk_train_classroom_info PRIMARY KEY (class_room_id)
);
CREATE TABLE train_training_site (
site_id VARCHAR(32) NOT NULL,
site_name VARCHAR(80) NOT NULL,
site_location VARCHAR(100),
capacity INTEGER,
site_device TEXT,
site_status SMALLINT DEFAULT 1,
create_user VARCHAR(32) NOT NULL,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
remark VARCHAR(200),
CONSTRAINT pk_train_training_site PRIMARY KEY (site_id)
);
CREATE TABLE train_class_plan (
plan_id VARCHAR(32) NOT NULL,
plan_name VARCHAR(100) NOT NULL,
c_id VARCHAR(32) NOT NULL,
start_time TIMESTAMP NOT NULL,
end_time TIMESTAMP NOT NULL,
train_address VARCHAR(100),
t_id VARCHAR(32) NOT NULL,
recruit_num INTEGER,
apply_condition TEXT,
plan_status SMALLINT DEFAULT 0,
create_user VARCHAR(32) NOT NULL,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
remark VARCHAR(200),
CONSTRAINT pk_train_class_plan PRIMARY KEY (plan_id),
CONSTRAINT fk_classplan_course FOREIGN KEY (c_id) REFERENCES train_course (c_id) ON DELETE NO ACTION,
CONSTRAINT fk_classplan_teacher FOREIGN KEY (t_id) REFERENCES train_teacher (t_id) ON DELETE NO ACTION
);
CREATE TABLE train_dormitory_info (
dorm_id VARCHAR(32) NOT NULL,
dorm_building VARCHAR(50),
dorm_room VARCHAR(30) NOT NULL,
bed_total INTEGER,
bed_used INTEGER DEFAULT 0,
lock_device_sn VARCHAR(64),
dorm_status SMALLINT DEFAULT 1,
create_user VARCHAR(32) NOT NULL,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
remark VARCHAR(200),
CONSTRAINT pk_train_dormitory_info PRIMARY KEY (dorm_id),
CONSTRAINT uq_dorm_building_room UNIQUE (dorm_building, dorm_room)
);
CREATE TABLE train_electronic_board (
board_id VARCHAR(32) NOT NULL,
board_sn VARCHAR(64) NOT NULL,
bind_classroom_id VARCHAR(32),
bind_plan_id VARCHAR(32),
board_ip VARCHAR(50),
board_content VARCHAR(500),
board_playmode SMALLINT DEFAULT 1,
support_checkin_type VARCHAR(30),
board_status SMALLINT DEFAULT 1,
last_heartbeat TIMESTAMP,
create_user VARCHAR(32) NOT NULL,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
remark VARCHAR(200),
CONSTRAINT pk_train_electronic_board PRIMARY KEY (board_id),
CONSTRAINT uq_board_sn UNIQUE (board_sn),
CONSTRAINT fk_board_classroom FOREIGN KEY (bind_classroom_id) REFERENCES train_classroom_info (class_room_id) ON DELETE NO ACTION,
CONSTRAINT fk_board_plan FOREIGN KEY (bind_plan_id) REFERENCES train_class_plan (plan_id) ON DELETE NO ACTION
);
CREATE TABLE train_dorm_auth (
auth_id BIGINT GENERATED BY DEFAULT AS IDENTITY,
student_id VARCHAR(32) NOT NULL,
plan_id VARCHAR(32) NOT NULL,
dorm_id VARCHAR(32) NOT NULL,
bed_no VARCHAR(20),
face_sync_status SMALLINT DEFAULT 0,
auth_start TIMESTAMP,
auth_end TIMESTAMP,
create_user VARCHAR(32) NOT NULL,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
remark VARCHAR(200),
CONSTRAINT pk_train_dorm_auth PRIMARY KEY (auth_id),
CONSTRAINT uq_dormauth_stu_plan UNIQUE (student_id, plan_id),
CONSTRAINT fk_dormauth_stu FOREIGN KEY (student_id) REFERENCES train_student (s_id) ON DELETE NO ACTION,
CONSTRAINT fk_dormauth_plan FOREIGN KEY (plan_id) REFERENCES train_class_plan (plan_id) ON DELETE NO ACTION,
CONSTRAINT fk_dormauth_dorm FOREIGN KEY (dorm_id) REFERENCES train_dormitory_info (dorm_id) ON DELETE NO ACTION
);
CREATE TABLE train_class_report (
report_id BIGINT GENERATED BY DEFAULT AS IDENTITY,
plan_id VARCHAR(32) NOT NULL,
student_id VARCHAR(32) NOT NULL,
report_way SMALLINT,
report_device_sn VARCHAR(64),
report_time TIMESTAMP,
report_status SMALLINT DEFAULT 0,
alloc_dorm_id VARCHAR(32),
alloc_classroom_id VARCHAR(32),
alloc_site_id VARCHAR(32),
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT pk_train_class_report PRIMARY KEY (report_id),
CONSTRAINT uq_classreport_plan_stu UNIQUE (plan_id, student_id),
CONSTRAINT fk_classreport_plan FOREIGN KEY (plan_id) REFERENCES train_class_plan (plan_id) ON DELETE NO ACTION,
CONSTRAINT fk_classreport_stu FOREIGN KEY (student_id) REFERENCES train_student (s_id) ON DELETE NO ACTION,
CONSTRAINT fk_classreport_dorm FOREIGN KEY (alloc_dorm_id) REFERENCES train_dormitory_info (dorm_id) ON DELETE NO ACTION,
CONSTRAINT fk_classreport_classroom FOREIGN KEY (alloc_classroom_id) REFERENCES train_classroom_info (class_room_id) ON DELETE NO ACTION,
CONSTRAINT fk_classreport_site FOREIGN KEY (alloc_site_id) REFERENCES train_training_site (site_id) ON DELETE NO ACTION
);
CREATE TABLE train_student_checkin (
checkin_id BIGINT GENERATED BY DEFAULT AS IDENTITY,
plan_id VARCHAR(32) NOT NULL,
c_id VARCHAR(32) NOT NULL,
student_id VARCHAR(32) NOT NULL,
board_id VARCHAR(32),
checkin_way SMALLINT,
checkin_time TIMESTAMP,
checkin_status SMALLINT,
lesson_start_time TIMESTAMP,
lesson_end_time TIMESTAMP,
CONSTRAINT pk_train_student_checkin PRIMARY KEY (checkin_id),
CONSTRAINT uq_checkin_lesson_stu UNIQUE (plan_id, c_id, student_id, lesson_start_time),
CONSTRAINT fk_checkin_plan FOREIGN KEY (plan_id) REFERENCES train_class_plan (plan_id) ON DELETE NO ACTION,
CONSTRAINT fk_checkin_course FOREIGN KEY (c_id) REFERENCES train_course (c_id) ON DELETE NO ACTION,
CONSTRAINT fk_checkin_stu FOREIGN KEY (student_id) REFERENCES train_student (s_id) ON DELETE NO ACTION,
CONSTRAINT fk_checkin_board FOREIGN KEY (board_id) REFERENCES train_electronic_board (board_id) ON DELETE NO ACTION
);
CREATE TABLE train_study_duration (
dur_id BIGINT GENERATED BY DEFAULT AS IDENTITY,
student_id VARCHAR(32),
plan_id VARCHAR(32),
c_id VARCHAR(32),
device_type VARCHAR(50),
start_time TIMESTAMP,
end_time TIMESTAMP,
study_seconds INTEGER,
sync_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT pk_train_study_duration PRIMARY KEY (dur_id),
CONSTRAINT fk_studydur_stu FOREIGN KEY (student_id) REFERENCES train_student (s_id) ON DELETE NO ACTION,
CONSTRAINT fk_studydur_plan FOREIGN KEY (plan_id) REFERENCES train_class_plan (plan_id) ON DELETE NO ACTION,
CONSTRAINT fk_studydur_course FOREIGN KEY (c_id) REFERENCES train_course (c_id) ON DELETE NO ACTION
);
CREATE TABLE train_student_portrait (
portrait_id BIGINT GENERATED BY DEFAULT AS IDENTITY,
student_id VARCHAR(32) NOT NULL,
plan_id VARCHAR(32) NOT NULL,
total_study_hour NUMERIC(8,2),
avg_score NUMERIC(5,2),
checkin_rate NUMERIC(5,2),
label_tag TEXT,
portrait_content TEXT,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT pk_train_student_portrait PRIMARY KEY (portrait_id),
CONSTRAINT uq_portrait_stu_plan UNIQUE (student_id, plan_id),
CONSTRAINT fk_portrait_stu FOREIGN KEY (student_id) REFERENCES train_student (s_id) ON DELETE NO ACTION,
CONSTRAINT fk_portrait_plan FOREIGN KEY (plan_id) REFERENCES train_class_plan (plan_id) ON DELETE NO ACTION
);
-- 步骤 4: 重建 9 个历史触发器
CREATE OR REPLACE TRIGGER trig_train_teacher_modify
BEFORE UPDATE ON train_teacher
FOR EACH ROW EXECUTE FUNCTION func_train_update_modifytime();
CREATE OR REPLACE TRIGGER trig_train_student_modify
BEFORE UPDATE ON train_student
FOR EACH ROW EXECUTE FUNCTION func_train_update_modifytime();
CREATE OR REPLACE TRIGGER trig_train_course_modify
BEFORE UPDATE ON train_course
FOR EACH ROW EXECUTE FUNCTION func_train_update_modifytime();
CREATE OR REPLACE TRIGGER trig_train_classroom_modify
BEFORE UPDATE ON train_classroom_info
FOR EACH ROW EXECUTE FUNCTION func_train_update_modifytime();
CREATE OR REPLACE TRIGGER trig_train_trainsite_modify
BEFORE UPDATE ON train_training_site
FOR EACH ROW EXECUTE FUNCTION func_train_update_modifytime();
CREATE OR REPLACE TRIGGER trig_train_board_modify
BEFORE UPDATE ON train_electronic_board
FOR EACH ROW EXECUTE FUNCTION func_train_update_modifytime();
CREATE OR REPLACE TRIGGER trig_train_dorminfo_modify
BEFORE UPDATE ON train_dormitory_info
FOR EACH ROW EXECUTE FUNCTION func_train_update_modifytime();
CREATE OR REPLACE TRIGGER trig_train_classplan_modify
BEFORE UPDATE ON train_class_plan
FOR EACH ROW EXECUTE FUNCTION func_train_update_modifytime();
CREATE OR REPLACE TRIGGER trig_train_dormauth_modify
BEFORE UPDATE ON train_dorm_auth
FOR EACH ROW EXECUTE FUNCTION func_train_update_modifytime();
-- 步骤 5: 从 bak_v1_* 物理快照还原数据
INSERT INTO train_teacher SELECT * FROM bak_v1_train_teacher;
INSERT INTO train_student SELECT * FROM bak_v1_train_student;
INSERT INTO train_course SELECT * FROM bak_v1_train_course;
INSERT INTO train_classroom_info SELECT * FROM bak_v1_train_classroom_info;
INSERT INTO train_training_site SELECT * FROM bak_v1_train_training_site;
INSERT INTO train_class_plan SELECT * FROM bak_v1_train_class_plan;
INSERT INTO train_dormitory_info SELECT * FROM bak_v1_train_dormitory_info;
INSERT INTO train_electronic_board SELECT * FROM bak_v1_train_electronic_board;
INSERT INTO train_dorm_auth SELECT * FROM bak_v1_train_dorm_auth;
INSERT INTO train_class_report SELECT * FROM bak_v1_train_class_report;
INSERT INTO train_student_checkin SELECT * FROM bak_v1_train_student_checkin;
INSERT INTO train_study_duration SELECT * FROM bak_v1_train_study_duration;
INSERT INTO train_student_portrait SELECT * FROM bak_v1_train_student_portrait;
-- 步骤 6: 校验教师唯一数据还原结果
DO $$
DECLARE
v_cnt INT;
BEGIN
SELECT count(*) INTO v_cnt FROM train_teacher;
IF v_cnt <> 1 THEN
RAISE EXCEPTION '>>> [Rollback-FATAL] 教师数据还原失败: 期望 1 行,实际为 % 行', v_cnt;
END IF;
RAISE NOTICE '>>> [Rollback-PASS] 13 张表结构与教师数据已严格依据 Phase 0.5 标准 DDL 恢复完毕。';
END $$;
COMMIT;
@@ -0,0 +1,37 @@
-- ==============================================================================
-- RailwaySystem 数据库 V2 回滚脚本: 003_restore_legacy_score.sql
-- 适用数据库: KingbaseES V009R003C018 (schema: public)
-- 执行目标: 严格基于 000_phase05_exact_schema.sql 结构恢复旧表 train_score_info
-- 事务边界: 全程在显式事务 BEGIN ... COMMIT 中执行
-- 说明: 彻底摒弃 CTAS 伪造结构,严格恢复原字段、默认值、外键与唯一约束
-- ==============================================================================
BEGIN;
DROP TABLE IF EXISTS train_score_info;
-- 步骤 1: 依据 Phase 0.5 原始标准物理 DDL 重建表结构
CREATE TABLE train_score_info (
score_id BIGINT GENERATED BY DEFAULT AS IDENTITY,
plan_id VARCHAR(32),
c_id VARCHAR(32),
student_id VARCHAR(32),
course_score NUMERIC(5,2),
train_score NUMERIC(5,2),
exam_score NUMERIC(5,2),
source_type SMALLINT,
source_system VARCHAR(50),
import_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT pk_train_score_info PRIMARY KEY (score_id),
CONSTRAINT uq_scoreinfo_plan_c_stu UNIQUE (plan_id, c_id, student_id),
CONSTRAINT fk_scoreinfo_plan FOREIGN KEY (plan_id) REFERENCES train_class_plan (plan_id) ON DELETE NO ACTION,
CONSTRAINT fk_scoreinfo_course FOREIGN KEY (c_id) REFERENCES train_course (c_id) ON DELETE NO ACTION,
CONSTRAINT fk_scoreinfo_stu FOREIGN KEY (student_id) REFERENCES train_student (s_id) ON DELETE NO ACTION
);
COMMENT ON TABLE train_score_info IS 'Phase 0.5 历史成绩表 (应急回滚精准恢复)';
-- 步骤 2: 从 bak_v1_train_score_info 快照表恢复数据
INSERT INTO train_score_info SELECT * FROM bak_v1_train_score_info;
COMMIT;
+91
View File
@@ -0,0 +1,91 @@
# RailwaySystem 数据库 V2 应急回滚指南 (Round 1 修订版)
> **技术基线**:KingbaseES V009R003C018 (`schema=public`)
> **回滚目标**:安全撤销 V2 数据库结构变更,将数据模型与物理约束 100% 精确还原至 Phase 0.5 历史基线。
---
## 1. 核心架构升级:解耦结构与数据
在 Round 1 审查中,彻底废除了使用 `CREATE TABLE ... AS SELECT * FROM bak_v1_*`(CTAS)推断历史结构的危险方案:
```text
┌─────────────────────────────────────────────────────────┐
│ Phase 0.5 精确物理结构 (表定义/列类型/PK/FK/UNIQUE/触发器) │
│ 来源: rollback/000_phase05_exact_schema.sql │
└───────────────────────────┬─────────────────────────────┘
│ (先精确重建物理表结构)
▼
┌─────────────────────────────────────────────────────────┐
│ Phase 0.5 真实数据 (唯一教师行 + 各表数据) │
│ 来源: 001 备份生成的 bak_v1_* 快照表 │
└───────────────────────────┬─────────────────────────────┘
│ (再通过 INSERT INTO 导入数据)
▼
【Phase 0.5 绝对还原】
```
- **`000_phase05_exact_schema.sql`**:作为唯一的物理结构基准,确保回滚后恢复所有 24 个旧外键、7 组旧业务唯一约束、NOT NULL、DEFAULT、IDENTITY 序列以及 9 个 `update_time` 触发器;
- **`bak_v1_*` 快照表**:仅承担纯粹的“数据暂存”职责。
---
## 2. 回滚脚本执行顺序
若在测试迁移或灰度验证阶段发现重大异常,需按照以下严格顺序执行回滚流水线:
```text
步骤 1: 执行 001_rollback_new_tables.sql
-> 显式解除外键依赖,安全下线并 DROP 掉 V2 新增的 8 张业务表
步骤 2: 执行 002_rollback_modified_tables.sql
-> 基于 000_phase05_exact_schema.sql 的精确结构重建 13 张表,并从 bak_v1_* 物理快照还原数据,严格断言教师数据为 1 行
步骤 3: 执行 003_restore_legacy_score.sql
-> 基于 000_phase05_exact_schema.sql 的精确结构重建 train_score_info 及 3 个历史外键与唯一约束
步骤 4: 人工核验 Phase 0.5 状态
-> 执行 000_preflight.sql 校验基线恢复,确认 14 张表、触发器与约束 100% 吻合
```
---
## 3. 强前置依赖与数据来源声明
> [!CAUTION]
> **回滚操作必须满足以下前置条件:**
1. **依赖 001 备份快照**:
- 步骤 2 与步骤 3 的数据还原依赖迁移前由 `001_backup_current_schema.sql` 生成的 `bak_v1_*` 快照表。
- 若 `bak_v1_*` 表意外被删,**严禁盲目执行数据还原语句**。
2. **唯一教师数据的物理还原**:
- 教师主档 `train_teacher` 的数据通过 `INSERT INTO train_teacher SELECT * FROM bak_v1_train_teacher;` 完整回灌。回滚脚本会严格校验恢复后的教师行数必须等于 1。
3. **事务安全性**:
- 每一个回滚 SQL 文件内部均由显式 `BEGIN ... COMMIT` 包裹,出现任何错误自动整单回滚,不产生中间半回滚状态。
---
## 4. 必须人工介入确认的场景
### 场景 A:V2 阶段已产生新业务数据
如果系统在执行 V2 DDL 之后,业务端或测试端已经在 `train_enrollment`、`train_lesson`、`train_student_score` 等表中写入了新数据:
- **直接执行回滚将导致这些新业务数据彻底物理清除!**
- **必须人工介入**:先通过数据导出工具将 V2 新增业务数据导出为 CSV/JSON 归档,确认业务无异议后方可执行回滚。
### 场景 B:bak_v1_* 备份表意外缺失
若 `bak_v1_*` 表不存在:
- 必须暂停脚本执行;
- 从数据库物理备份集(如 `sys_backup` / 逻辑转储快照)中恢复 14 张旧表数据。
---
## 5. 回滚后验收确认 Checklist
回滚完成后,在数据库客户端中直接运行 `000_preflight.sql` 即可自动化完成全量反向验收:
- [ ] 1. 运行 `000_preflight.sql` 成功输出 `[Preflight-PASS]`;
- [ ] 2. 确认已无任何 V2 独有表(如 `train_plan_course` 等);
- [ ] 3. 确认旧表 `train_score_info` 已恢复,且具备旧外键;
- [ ] 4. 确认 `train_teacher` 精确包含 1 条数据,且旧字段 `t_department` 存在且非空;
- [ ] 5. 确认 9 个旧触发器全部重新挂载生效;
- [ ] 6. 确认系统表 `sys_*` 完好无损。