☰
Oracle数据库课设实战:学生考勤系统存储过程与触发器实现
2026/10/19 6:28:50 网站建设 项目流程

简介:这份Oracle数据库课程设计资料面向学习数据库管理与开发的学生及IT从业者,以“学生考勤系统”为完整案例,帮助读者掌握从需求分析到数据库落地的全流程设计方法。资源包内含1个doc文档,约227KB,为辽宁工程技术大学软件学院的课程设计报告,目录涵盖背景分析、需求分析、E-R模型、数据字典、逻辑结构设计、表空间与表设计、心得体会等章节。报告详细拆解了学生、任课老师、班主任、院系领导、学校领导、系统管理员六类角色的需求,并划分请假、考勤、后台管理三大功能模块,同时给出实体关系建模、字段约束定义、主外键与索引设置等具体方案。读者可借此理解Oracle中SQL查询、存储过程、触发器及性能优化、备份恢复等知识在真实项目中的组织方式,适合作为课程设计参考或数据库建模练习的对照范本。目前已有965人学习。

1. 从一份 2009 年的 Oracle 课设说起:学生考勤系统到底能跑通什么

翻到一份 2009 年底完成的学生考勤系统课程设计,技术栈是 Oracle + 存储过程 + 触发器 + 视图,业务覆盖学生、任课老师、班主任、院系领导、学校领导、系统管理员六类角色。很多人第一反应是"课设嘛,能跑就行",但这份东西真正值得拆的地方在于:它把请假审批流和考勤统计做成了数据库端的闭环,而不是在应用层拼 SQL。换句话说,核心逻辑全部下沉到存储过程、视图和触发器里,前端只负责调用和展示。这种设计思路放到今天看依然不过时——尤其是当你需要处理多级审批、按院系隔离数据、自动判定缺勤超限这类规则时,数据库端封装比在 Java 或 JSP 里写一坨 if-else 要干净得多。这份资源适合正在做数据库课设的学生、需要补 Oracle PL/SQL 实战的开发者,以及想找一个完整"需求→E-R→表结构→数据库对象"链路参考的人。下面我按实际复现的顺序,把这份课设拆成能直接抄作业的步骤。

2. 表空间与十二张表的落地顺序:先建谁后建谁有讲究

2.1 为什么先建表空间而不是直接 CREATE TABLE

Oracle 里表空间是逻辑存储单元,表建在表空间上。课设里用了一个字典管理表空间linpeng_data,数据文件放在/u01/oracle/oradata/tab01.dbf,初始 100M。这个路径是 Linux 下 Oracle 的典型安装路径,如果你在 Windows 上跑,得换成D:\oracle\oradata\tab01.dbf这类实际存在的目录,否则建表空间直接报 ORA-01119。

-- 创建字典管理表空间,指定数据文件路径和初始大小 CREATE TABLESPACE linpeng_data DATAFILE '/u01/oracle/oradata/tab01.dbf' SIZE 100M DEFAULT STORAGE ( INITIAL 512K -- 初始区大小 NEXT 128K -- 下一个区大小 MINEXTENTS 2 -- 最小区数 MAXEXTENTS 999 -- 最大区数 PCTINCREASE 0 -- 区增长百分比,0 表示不自动增长 ) ONLINE;

参数说明:INITIAL和NEXT控制区分配,课设里设得比较小,因为学生考勤数据量不大;PCTINCREASE 0是 Oracle 10g 之后推荐的做法,避免区大小按百分比膨胀导致空间碎片。如果你用的是 Oracle 11g 及以上,字典管理表空间已经不太推荐了,可以改成本地管理表空间,把DEFAULT STORAGE整段去掉,加一句EXTENT MANAGEMENT LOCAL就行。

提示:建表空间之前先确认db_create_file_dest参数或者手动确保数据文件目录存在且有写权限,否则 ORA-01119 会卡住你半小时。

2.2 十二张表的依赖关系与建表顺序

课设里一共十二张表:admin、student、faculty、major、teacher、classteacher、collegeleader、schoolleader、kaoqin_record、course、classes、qingjia。外键依赖决定了建表不能乱序——faculty 和 major 要先于 student 和 classteacher,classes 要先于 student 和 qingjia,course 和 teacher 要先于 kaoqin_record。

我一般按这个顺序建:faculty → major → classes → classteacher → teacher → course → student → collegeleader → schoolleader → admin → kaoqin_record → qingjia。下面挑几个有代表性的表把建表语句和约束逻辑说清楚。

-- 院系表:最顶层,无外键依赖 CREATE TABLE faculty ( faculty_id NUMBER NOT NULL, faculty_name CHAR(10) NOT NULL, CONSTRAINT pk_faculty PRIMARY KEY (faculty_id) ) TABLESPACE linpeng_data; -- 专业表:外键指向院系 CREATE TABLE major ( major_id NUMBER NOT NULL, major_name CHAR(10) NOT NULL, major_faculty NUMBER, CONSTRAINT pk_major PRIMARY KEY (major_id), CONSTRAINT fk_major_faculty FOREIGN KEY (major_faculty) REFERENCES faculty(faculty_id) ) TABLESPACE linpeng_data; -- 学生表:三个外键分别指向班级、专业、院系 CREATE TABLE student ( stu_no CHAR(10) NOT NULL, stu_name VARCHAR(30) NOT NULL, stu_sex CHAR(2) CHECK (stu_sex IN ('男','女')), stu_class CHAR(5), stu_major NUMBER, stu_faculty NUMBER, CONSTRAINT pk_student PRIMARY KEY (stu_no), CONSTRAINT fk_stu_class FOREIGN KEY (stu_class) REFERENCES classes(class_no), CONSTRAINT fk_stu_major FOREIGN KEY (stu_major) REFERENCES major(major_id), CONSTRAINT fk_stu_faculty FOREIGN KEY (stu_faculty) REFERENCES faculty(faculty_id) ) TABLESPACE linpeng_data;

这里有个课设原文里的坑:student 表的stu_class定义成了CHAR(5),但 classes 表的class_no是CHAR(10)。外键两边的数据类型和长度必须一致,否则 Oracle 直接报 ORA-02267。复现的时候要么把两边都改成 CHAR(10),要么都改成 CHAR(5),别一个长一个短。

请假表 qingjia 是整份课设里字段最多的表,也是审批流的核心载体。它记录了请假原因、起止时间、天数、班主任审批状态和时间、院系领导审批状态和时间。状态字段用 0/1/2 表示等待审批、同意、不同意,这个设计在存储过程和触发器里会被反复引用。

-- 请假信息表:审批流的核心表 CREATE TABLE qingjia ( id NUMBER PRIMARY KEY, class_id CHAR(10), stu_no CHAR(10), leave_reason VARCHAR2(200) NOT NULL, start_time DATE NOT NULL, end_time DATE NOT NULL, day_number NUMBER NOT NULL, qingjia_time DATE NOT NULL, class_tea_id CHAR(5), class_tea_sp_status CHAR(10), -- 0 等待 1 同意 2 不同意 class_tea_sp_time DATE, coll_leader_sp_status CHAR(10), coll_leader_id CHAR(5), coll_leader_sp_time DATE, CONSTRAINT fk_qj_class FOREIGN KEY (class_id) REFERENCES classes(class_no), CONSTRAINT fk_qj_stu FOREIGN KEY (stu_no) REFERENCES student(stu_no), CONSTRAINT fk_qj_classtea FOREIGN KEY (class_tea_id) REFERENCES classteacher(classtea_no), CONSTRAINT fk_qj_leader FOREIGN KEY (coll_leader_id) REFERENCES collegeleader(collegeleader_no) ) TABLESPACE linpeng_data;

注意课设原文里 qingjia 表的时间字段写的是datetime,但 Oracle 没有datetime这个数据类型,正确的是DATE。这是课设文档里典型的笔误,直接抄会报 ORA-00902。另外day_number原文写成了nubmer,也是拼写错误,建表时改成NUMBER。

2.3 存储过程、视图、触发器的分工

课设里三个数据库对象各司其职:存储过程getMessage用来统计某学生某课程的缺勤次数,视图rjxy用来做院系数据隔离,触发器alertMessage用来在缺勤超过 3 次时给出提示。这三个对象覆盖了 Oracle PL/SQL 课设最常考的三种题型,下面逐个拆。

-- 存储过程:统计指定学生指定课程的缺勤次数 CREATE OR REPLACE PROCEDURE getMessage ( stu_no IN VARCHAR2, course_no IN VARCHAR2, total_times OUT NUMBER ) AS absence_times NUMBER; BEGIN SELECT COUNT(*) INTO absence_times FROM kaoqin_record WHERE stu_number = stu_no AND course_no = course_no AND stu_status = '缺勤'; -- 只统计缺勤状态 total_times := absence_times; END;

逻辑说明:入参是学号和课程号,出参是缺勤次数。课设原文里total_times=absence_times少了一个冒号,PL/SQL 里赋值必须用:=,否则编译不过。另外原文没有过滤stu_status,会把请假、迟到都算进去,实际用的时候要加上状态条件。

-- 视图:软件学院领导只能看到本院学生考勤 CREATE OR REPLACE VIEW rjxy AS SELECT k.kaoqin_id, k.sk_time, k.stu_number, k.stu_status, k.teacher_no, k.course_no FROM kaoqin_record k, student s WHERE s.stu_no = k.stu_number AND s.stu_faculty = 5; -- 5 是软件学院编号

视图的逻辑是把考勤记录和学生表关联,用stu_faculty = 5过滤出软件学院的数据。这种硬编码院系 ID 的做法在课设里没问题,但实际项目里应该改成绑定变量或者用SYS_CONTEXT做行级安全策略。

-- 触发器:缺勤超过 3 次自动提示取消考试资格 CREATE OR REPLACE TRIGGER alertMessage AFTER INSERT ON kaoqin_record FOR EACH ROW DECLARE current_times NUMBER; BEGIN SELECT COUNT(*) INTO current_times FROM kaoqin_record WHERE stu_number = :NEW.stu_number AND course_no = :NEW.course_no AND stu_status = '缺勤'; IF current_times >= 3 THEN DBMS_OUTPUT.PUT_LINE('学号为:' || :NEW.stu_number || '的学生该门课程被取消考试资格!'); END IF; END;

触发器的关键点是FOR EACH ROW和:NEW伪记录。每次往 kaoqin_record 插入一条记录,触发器就统计该学生该课程的历史缺勤次数,达到 3 次就输出提示。注意DBMS_OUTPUT.PUT_LINE只在 SQL*Plus 或开启了SERVEROUTPUT的客户端里能看到,在 JDBC 里是看不到的,实际项目里应该改成写日志表或者抛异常。

3. 请假审批流与考勤统计的 SQL 实现:从申请到出结果的完整链路

3.1 请假申请与两级审批的状态流转

请假流程是这样的:学生提交申请,班主任审批,如果请假天数超过 3 天,再提交给院系领导审批。状态字段class_tea_sp_status和coll_leader_sp_status用 0/1/2 表示等待、同意、不同意。这个状态机在 SQL 层面可以用一条 UPDATE 搞定,但要注意审批顺序和条件判断。

-- 班主任审批:同意请假 UPDATE qingjia SET class_tea_sp_status = '1', class_tea_sp_time = SYSDATE WHERE id = 1001 AND class_tea_sp_status = '0'; -- 只有等待状态才能审批 -- 院系领导审批:仅当请假天数超过 3 天且班主任已同意 UPDATE qingjia SET coll_leader_sp_status = '1', coll_leader_id = 'L001', coll_leader_sp_time = SYSDATE WHERE id = 1001 AND day_number > 3 AND class_tea_sp_status = '1' AND coll_leader_sp_status = '0';

逻辑说明:第一条 UPDATE 把班主任审批状态从 0 改成 1,同时记录审批时间。第二条 UPDATE 加了三个条件——天数大于 3、班主任已同意、院系领导尚未审批。这样写的好处是防止越级审批和重复审批。实际项目里这些逻辑应该封装在存储过程里,用事务包起来,避免并发场景下两个领导同时审批同一条记录。

注意:SYSDATE包含时分秒,如果只想要日期可以用TRUNC(SYSDATE)。课设里用DATE类型存时间没问题,但如果要精确到秒,建议用TIMESTAMP。

3.2 考勤统计:按课程、按学生、按班级三个维度

考勤统计是这份课设里最实用的部分。任课老师需要看到所教班级每个学生的出勤情况,班主任需要看到本班所有课程的出勤汇总,院系领导需要看到本院系的整体数据。这三个维度对应三种 SQL 写法。

-- 维度一:某学生某课程的出勤明细 SELECT sk_time, stu_status FROM kaoqin_record WHERE stu_number = '0820980113' AND course_no = 'C001' ORDER BY sk_time; -- 维度二:某班级某课程的出勤汇总(按状态分组) SELECT stu_number, SUM(CASE WHEN stu_status = '出勤' THEN 1 ELSE 0 END) AS 出勤次数, SUM(CASE WHEN stu_status = '缺勤' THEN 1 ELSE 0 END) AS 缺勤次数, SUM(CASE WHEN stu_status = '请假' THEN 1 ELSE 0 END) AS 请假次数 FROM kaoqin_record WHERE course_no = 'C001' AND stu_number IN (SELECT stu_no FROM student WHERE stu_class = 'SJ0801') GROUP BY stu_number; -- 维度三:某院系所有课程的缺勤率 SELECT c.course_name, COUNT(*) AS 总记录数, SUM(CASE WHEN k.stu_status = '缺勤' THEN 1 ELSE 0 END) AS 缺勤数, ROUND(SUM(CASE WHEN k.stu_status = '缺勤' THEN 1 ELSE 0 END) / COUNT(*) * 100, 2) AS 缺勤率 FROM kaoqin_record k, course c, student s WHERE k.course_no = c.course_no AND k.stu_number = s.stu_no AND s.stu_faculty = 5 GROUP BY c.course_name;

参数说明:CASE WHEN是 Oracle 里做条件统计的标准写法,比多个子查询效率高。ROUND(..., 2)保留两位小数。第三个查询里s.stu_faculty = 5是硬编码院系 ID,实际用的时候可以改成绑定变量。

3.3 用存储过程封装审批逻辑

把审批逻辑封装在存储过程里,前端只需要传请假 ID 和审批人 ID,数据库端自动判断该谁审批、能不能审批。这样做的好处是业务规则集中在数据库端,应用层不用重复写判断。

CREATE OR REPLACE PROCEDURE approve_leave ( p_qingjia_id IN NUMBER, p_approver_id IN VARCHAR2, p_role IN VARCHAR2, -- 'CLASSTEACHER' 或 'LEADER' p_result IN VARCHAR2 -- '1' 同意 '2' 不同意 ) AS v_day_number NUMBER; v_class_status CHAR(10); BEGIN -- 查出请假天数和班主任审批状态 SELECT day_number, class_tea_sp_status INTO v_day_number, v_class_status FROM qingjia WHERE id = p_qingjia_id; IF p_role = 'CLASSTEACHER' THEN UPDATE qingjia SET class_tea_sp_status = p_result, class_tea_sp_time = SYSDATE WHERE id = p_qingjia_id AND class_tea_sp_status = '0'; ELSIF p_role = 'LEADER' THEN -- 只有超过 3 天且班主任已同意才能由领导审批 IF v_day_number > 3 AND v_class_status = '1' THEN UPDATE qingjia SET coll_leader_sp_status = p_result, coll_leader_id = p_approver_id, coll_leader_sp_time = SYSDATE WHERE id = p_qingjia_id AND coll_leader_sp_status = '0'; ELSE RAISE_APPLICATION_ERROR(-20001, '不满足院系领导审批条件'); END IF; END IF; COMMIT; EXCEPTION WHEN NO_DATA_FOUND THEN RAISE_APPLICATION_ERROR(-20002, '请假记录不存在'); END;

逻辑说明:入参四个——请假 ID、审批人 ID、角色、审批结果。先查出请假天数和班主任审批状态,然后按角色分支处理。院系领导审批时加了条件判断,不满足就抛自定义异常。RAISE_APPLICATION_ERROR的错误号范围是 -20000 到 -20999,这是 Oracle 留给应用自定义错误的区间。

4. 避坑与排查:这份课设里那些让人翻车的细节

4.1 数据类型不匹配导致外键建不上

现象:执行CREATE TABLE student时报 ORA-02267,提示外键列类型与引用列类型不兼容。原因:student 表的stu_class定义成CHAR(5),而 classes 表的class_no是CHAR(10),Oracle 要求外键两边的数据类型和长度完全一致。解决:把两边统一成CHAR(10)或CHAR(5),建表之前先检查所有外键列的定义。

4.2 datetime 和 nubmer 这两个拼写错误

现象:建表时报表不存在或数据类型无效。原因:课设文档里 qingjia 表的时间字段写成了datetime,Oracle 没有这个类型;day_number写成了nubmer。解决:时间字段统一用DATE或TIMESTAMP,数字字段用NUMBER。抄课设代码之前先全局搜一遍这两个词。

4.3 存储过程赋值少了冒号

现象:CREATE OR REPLACE PROCEDURE getMessage编译时报 PLS-00103。原因:原文里total_times=absence_times用的是等号,PL/SQL 里赋值必须用:=。解决:改成total_times := absence_times;。这个错误在 PL/SQL 初学者里非常常见,等号在 SQL 里是比较,在 PL/SQL 里赋值要用:=。

4.4 触发器在 JDBC 里看不到输出

现象:往 kaoqin_record 插入缺勤记录后,触发器编译通过但没有任何提示。原因:DBMS_OUTPUT.PUT_LINE的输出只在 SQLPlus 或开启了SERVEROUTPUT的客户端里显示,JDBC 连接默认不读取这个缓冲区。解决:把提示改成插入日志表,或者用RAISE_APPLICATION_ERROR抛异常让应用层捕获。如果只是课设演示,在 SQLPlus 里执行SET SERVEROUTPUT ON就能看到。

4.5 视图硬编码院系 ID 导致扩展困难

现象:新增一个院系后,视图rjxy只能看到 ID 为 5 的学院数据,其他学院看不到。原因:视图里写死了s.stu_faculty = 5。解决:课设演示没问题,但如果要扩展,可以改成带参数的存储过程或者用SYS_CONTEXT做行级安全。我一般会建一个v_kaoqin_by_faculty视图,把stu_faculty作为查询条件暴露出去,让应用层传参。

5. 进阶技巧:用分析函数和物化视图把考勤统计做扎实

课设里的统计用的是GROUP BY+CASE WHEN,能出结果但不够灵活。如果你想让这份课设的统计模块看起来更专业,可以加两个东西:分析函数和物化视图。

分析函数RANK()和SUM() OVER()可以在不分组的情况下算出排名和累计值。比如查每个学生每门课的缺勤次数排名:

SELECT stu_number, course_no, 缺勤次数, RANK() OVER (PARTITION BY course_no ORDER BY 缺勤次数 DESC) AS 排名 FROM ( SELECT stu_number, course_no, SUM(CASE WHEN stu_status = '缺勤' THEN 1 ELSE 0 END) AS 缺勤次数 FROM kaoqin_record GROUP BY stu_number, course_no ) WHERE 缺勤次数 > 0;

这个查询先在内层算出每个学生每门课的缺勤次数,外层用RANK()按课程分组排名。PARTITION BY course_no表示每门课单独排名,ORDER BY 缺勤次数 DESC表示缺勤多的排前面。这种写法在课设答辩时很加分,因为能体现你对窗口函数的理解。

物化视图适合考勤汇总这种查询频繁但数据更新不频繁的场景。建一个按天刷新的物化视图,把班级维度的出勤汇总预先算好:

CREATE MATERIALIZED VIEW mv_kaoqin_summary REFRESH COMPLETE ON DEMAND AS SELECT s.stu_class, k.course_no, COUNT(*) AS 总次数, SUM(CASE WHEN k.stu_status = '出勤' THEN 1 ELSE 0 END) AS 出勤次数, SUM(CASE WHEN k.stu_status = '缺勤' THEN 1 ELSE 0 END) AS 缺勤次数 FROM kaoqin_record k, student s WHERE k.stu_number = s.stu_no GROUP BY s.stu_class, k.course_no;

REFRESH COMPLETE ON DEMAND表示手动刷新,执行EXEC DBMS_MVIEW.REFRESH('MV_KAOQIN_SUMMARY');就能更新数据。如果数据量不大,用ON COMMIT自动刷新也行,但每次插入考勤记录都会触发刷新,性能开销要考虑。

验证方法很简单:先往 kaoqin_record 插几条测试数据,然后分别查明细表、视图和物化视图,对比结果是否一致。我一般会插三条缺勤记录,看触发器有没有输出提示,再查getMessage存储过程返回的缺勤次数是不是 3。如果都对,说明整条链路跑通了。

从那以后我每次拿到这种课设资源,都会先把建表语句里的数据类型和拼写过一遍,再按依赖顺序建表,最后才碰存储过程和触发器。这个习惯帮我省了不少返工时间。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询