ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Oracle学生考勤系统数据库设计实战:从表空间到触发器

Oracle学生考勤系统数据库设计实战:从表空间到触发器

简介:这份Oracle数据库课程设计资料面向学习数据库管理与开发的学生及IT从业者,以“学生考勤系统”为完整案例,帮助读者掌握从需求分析到数据库落地的全流程设计方法。内容涵盖系统背景分析、多角色用户需求(学生、任课老师、班主任、院系领导、学校领导、系统管理员)、功能模块划分(请假、考勤、后台管理)、E-R模型、数据字典、表逻辑结构以及表空间与建表脚本等环节,并延伸至SQL查询、存储过程、触发器、性能优化与备份恢复等进阶主题。资源包共1个doc文件,约227KB,为辽宁工程技术大学软件学院的课程设计报告,目录结构完整、章节清晰,可直接作为课程设计参考模板或自学范例。目前已有965人学习下载,适合需要完成数据库课程设计、理解Oracle建模与实现思路的读者借鉴使用。

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

翻到这份 Oracle 数据库课程设计的时候,我第一反应是——这玩意儿太适合拿来练手了。它不是什么花哨的微服务项目,就是一个老老实实的学生考勤系统,用 Oracle 做后端,把请假审批、出勤记录、多角色权限管理全塞进了一个数据库里。你如果正在找一份能直接跑、能改、能拿去交课程设计或者当 Oracle 练手素材的东西,这份资源基本能满足。它覆盖了从需求分析、E-R 模型、数据字典到表空间创建、建表、存储过程、视图、触发器的完整链路,六大类用户角色(学生、任课老师、班主任、院系领导、学校领导、系统管理员)各有各的操作路径。说白了,这就是一个用 Oracle 把高校考勤业务逻辑从头到尾实现一遍的工程实践,适合学数据库的学生、刚接触 Oracle 的开发者,以及需要一份完整数据库设计参考的人。

2. 需求到模型:六大角色怎么映射成十二张表

2.1 用户角色与功能模块的对应关系

这个系统的业务逻辑其实不复杂,但角色多,权限层次分明。学生要在线请假、查自己的出勤记录;任课老师要录入和查看所教班级的出勤;班主任要审批本班请假、看本班出勤统计;院系领导要审批超过三天的请假、查看本院系出勤;学校领导看全校数据;系统管理员维护所有基础数据。六个角色对应三个功能模块:请假系统、考勤系统、后台管理。

我一般拿到这种需求,第一步不是急着建表,而是先把角色和操作的对应关系画清楚。这份课程设计里用 E-R 模型做了这件事,实体包括教师、班级、学生、学院领导、学院、专业、课程、假条、考勤,关系有开设、授课、聘请、班主任、管理、请假、属于等。E-R 图里标注了 1:1、1:n、m:n 的基数,这是后面建表时确定外键方向的关键依据。

2.2 数据字典:字段定义的规范化起点

数据字典这部分是很多课程设计里容易被糊弄过去的,但这份资源写得比较细。它把每个实体的属性用“名字 + 描述 + 定义”的格式列出来,比如请假条信息定义为:请假代号 + 班级代号 + 学生学号 + 请假原因 + 开始时间 + 结束时间 + 请假天数 + 申请请假时间 + 班主任审批状态 + 班主任审批时间 + 院系领导审批状态 + 院系领导代号 + 院系领导审批时间。

这种写法看起来啰嗦,但好处是后面建表的时候字段一个都不会漏。我见过太多人跳过数据字典直接建表,结果做到一半发现少了个审批时间字段,回头改表结构,外键约束全得重来。所以这一步别省。

2.3 逻辑结构设计:十二张表的字段与约束

从 E-R 模型到逻辑结构,核心工作就是把实体转成表、把关系转成外键。这份设计里一共十二张表,我挑几个关键的说一下字段设计的思路。

系统管理员表 admin 用 admin_no 做主键,admin_sex 加了 check 约束限定只能填“男”或“女”。学生表 student 的主键是 stu_no,stu_class、stu_major、stu_faculty 分别外键引用班级表、专业表、院系表。这里有个细节值得注意:学生表里同时存了班级、专业、院系三个外键,而不是通过班级表间接关联到专业和院系。这样做查询方便,但数据一致性维护成本高——如果学生转专业,三个字段都得改。常见做法是只保留班级外键,专业和院系通过班级表往上查,但这份设计选择了冗余换查询效率,在课程设计场景下也说得过去。

请假信息表 qingjia 是字段最多的,包含了完整的审批链路:班主任审批状态、班主任审批时间、院系领导审批状态、院系领导代号、院系领导审批时间。审批状态用 0/1/2 表示等待审批、同意、不同意。这种用数字编码状态的做法在数据库设计里很常见,比存中文字符串省空间,但记得在应用层做好映射。

出勤记录表 kaoqin_record 的主键是 kaoqin_id,同时外键关联学生、教师、课程三张表。这意味着一条出勤记录必须同时确定“谁、哪门课、哪个老师记录的”,粒度很清晰。

3. 表空间与建表实战:从 CREATE TABLESPACE 到十二张表落地

3.1 表空间设计:为什么先建表空间再建表

这份课程设计里专门用了一节讲表空间创建,命令是这样的:

create tablespace linpeng_data datafile '/u01/oracle/oradata/tab01.dbf' size 100M default storage( initial 512K next 128K minextents 2 maxextents 999 pctincrease 0 ) online;

逻辑说明:datafile指定物理文件路径,size 100M是初始大小。default storage子句定义了段(segment)的默认存储参数——initial 512K是初始区大小,next 128K是后续扩展区大小,minextents 2表示每个段至少分配两个区,maxextents 999限制最大区数量,pctincrease 0表示区大小不自动递增。online让表空间立即可用。

参数怎么改:如果你只是本地练习,datafile路径改成你 Oracle 安装目录下的实际路径就行。size可以调小到 50M,课程设计的数据量用不了 100M。maxextents在现代 Oracle 里其实已经不太需要手动设了,但课程设计里写上显得规范。

注意:建表空间之前确认你用的用户有CREATE TABLESPACE权限,普通用户默认没有,得用 DBA 账号授权或者直接用 DBA 账号操作。

3.2 建表语句逐段拆解:约束、外键与表空间归属

建表部分一共十二条CREATE TABLE,我挑几条有代表性的讲。

先看系统管理员表:

create table admin( admin_no char(5) not null, admin_name char(10) not null, admin_sex char(2) check (admin_sex='男' or admin_sex='女'), admin_title char(20) not null, admin_password varchar(20) not null, constraint pk_admin primary key (admin_no) ) tablespace linpeng_data;

逻辑说明:admin_no用char(5)定长,因为管理员编号格式固定。admin_sex用check约束限定取值范围,这是数据库层面保证数据质量的手段。constraint pk_admin primary key显式命名了主键约束,方便后续维护时引用。最后的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='男' or stu_sex='女'), stu_class char(5) foreign key references classes(class_no), stu_major number foreign key references major(major_id), stu_faculty number foreign key references faculty(faculty_id), constraint pk_student primary key(stu_no) ) tablespace linpeng_data;

这里stu_class、stu_major、stu_faculty三个字段都是外键,分别引用班级表、专业表、院系表。注意建表顺序——必须先建classes、major、faculty,再建student,否则外键引用会报错。这是新手最容易翻车的地方,建表脚本的执行顺序必须按依赖关系排好。

请假信息表的建表语句里有个细节值得注意:

create table qingjia( id number primary key, class_id char(10) foreign key references classes(class_no), stu_no char(10) foreign key references student(stu_no), leave_reason varchar2(200) not null, start_time datetime not null, end_time datetime not null, day_number number not null, qingjia_time datetime not null, class_tea_id char(5) foreign key references classteacher(classtea_no), class_tea_sp_status char(10), class_tea_sp_time datetime, coll_leader_sp_status char(10), coll_leader_id char(5) foreign key references collegeleader(collegeleader_no), coll_leader_sp_time datetime ) tablespace linpeng_data;

审批状态和审批时间字段允许为空,因为请假刚提交时还没有审批记录。leave_reason用varchar2(200),给的理由留了足够空间。day_number是请假天数,后面院系领导审批时会根据这个字段判断是否需要自己处理(超过三天)。

3.3 存储过程、视图与触发器:把业务逻辑下沉到数据库端

这份课程设计的一个亮点是把一些关键业务逻辑用存储过程、视图和触发器封装在数据库端。先看统计学生某门课出勤情况的存储过程:

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; total_times := absence_times; end;

逻辑说明:输入参数是学号和课程号,输出参数是该学生在这门课上的缺勤次数。select count(*) into把查询结果赋给变量,最后通过out参数返回。调用方式是在 SQL*Plus 里声明变量然后exec getMessage('学号', '课程号', :变量名)。

参数说明:stu_no和course_no都是in参数,total_times是out参数。注意原文里写的是total_times=absence_times,这在 PL/SQL 里应该是:=赋值,直接写=会报错,实际运行时需要改成total_times := absence_times;。

再看视图,作用是让不同院系的领导只能看到自己学院的数据:

create view rjxy as select kaoqin_record.kaoqin_id, kaoqin_record.sk_time, kaoqin_record.stu_number, kaoqin_record.stu_status, kaoqin_record.teacher_no, kaoqin_record.course_no from kaoqin_record, student where student.stu_no = kaoqin_record.stu_number and student.stu_faculty = 5;

这里假设软件学院的 ID 是 5,视图把出勤记录和学生表关联,过滤出该学院的学生数据。院系领导查询时直接查这个视图就行,不用关心底层表结构。如果要给其他学院建视图,把stu_faculty = 5改成对应的院系 ID 即可。

触发器的作用是当学生某门课缺勤超过三次时给出提示:

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; if (current_times >= 3) then dbms_output.put_line('学号为:' || :new.stu_number || '的学生该门课程被取消考试资格!'); end if; end;

逻辑说明:这是一个行级触发器,在kaoqin_record表每次插入新记录后触发。:new.stu_number和:new.course_no引用刚插入的那行数据。触发器统计该学生该课程的总缺勤次数,达到三次就输出提示。

注意:dbms_output.put_line的输出需要在 SQL*Plus 里先执行set serveroutput on才能看到。另外这个触发器只做了提示,没有实际修改数据或阻止插入,属于“提醒型”触发器。如果要真正限制考试资格,得配合应用层逻辑或者改成before insert触发器加raise_application_error。

4. 避坑与排查:建表和外键约束里最容易翻车的几个点

4.1 建表顺序不对导致外键创建失败

现象:执行建表脚本时报ORA-00942: table or view does not exist,提示引用的表不存在。

原因:student表的外键引用了classes、major、faculty,如果先建student再建classes,Oracle 找不到被引用的表。

解决:按依赖关系排序建表。先建无外键的表(faculty、major、course、classteacher、collegeleader、schoolleader、admin),再建依赖它们的表(classes、teacher、student),最后建kaoqin_record和qingjia。我一般会在脚本开头加注释标明执行顺序。

4.2 表空间路径不存在导致建表空间失败

现象:执行CREATE TABLESPACE时报ORA-01119: error in creating database file。

原因:datafile指定的路径/u01/oracle/oradata/tab01.dbf在你的机器上不存在,或者 Oracle 进程没有该目录的写权限。

解决:先确认 Oracle 的数据文件目录,用SELECT name FROM v$datafile;查看现有数据文件路径,照着改。Windows 下路径格式是D:\oracle\oradata\tab01.dbf,注意用反斜杠或双斜杠。

4.3 存储过程编译报错但看不到具体错误

现象:CREATE OR REPLACE PROCEDURE执行后提示“已创建过程,但存在编译错误”。

原因:PL/SQL 编译错误不会直接显示,需要额外查询。

解决:执行SHOW ERRORS PROCEDURE getMessage;或者查SELECT * FROM user_errors WHERE name = 'GETMESSAGE';。常见错误包括赋值符号写成=而不是:=、变量未声明、select into返回多行等。原文里total_times=absence_times就是典型的赋值符号错误。

4.4 触发器里 dbms_output 看不到输出

现象:触发器创建成功,插入数据也成功,但看不到提示信息。

原因:dbms_output.put_line的输出默认不显示,需要开启 serveroutput。

解决:在 SQL*Plus 里先执行SET SERVEROUTPUT ON;,再执行插入操作。如果用的是其他客户端工具,找对应的“启用 DBMS 输出”选项。

4.5 外键约束导致插入数据顺序受限

现象:往student表插入数据时报ORA-02291: integrity constraint violated - parent key not found。

原因:stu_class引用了classes表的class_no,但classes表里还没有对应的班级记录。

解决:先往被引用的表(classes、major、faculty)插入基础数据,再往student表插。删除数据时顺序反过来,先删子表再删父表,或者用ON DELETE CASCADE(但这份设计里没加级联删除,所以得手动处理)。

5. 二次开发与改造:从课程设计到能用的考勤系统还差什么

5.1 把审批状态从数字改成可读的枚举

原文里审批状态用 0/1/2 表示,查询的时候不直观。我一般会建一个状态码表:

create table approval_status ( status_code number primary key, status_name varchar2(20) not null ); insert into approval_status values (0, '等待审批'); insert into approval_status values (1, '同意请假'); insert into approval_status values (2, '不同意请假');

然后查询请假信息时关联这张表,把状态码翻译成中文。这样前端展示和报表输出都省事。

5.2 给请假表加一个自动计算天数的触发器

原文里day_number是手动填的,容易填错。可以加一个before insert or update触发器,根据start_time和end_time自动算:

create or replace trigger calc_leave_days before insert or update on qingjia for each row begin :new.day_number := trunc(:new.end_time) - trunc(:new.start_time) + 1; end;

这样插入请假记录时只需要填开始和结束时间,天数自动算出来。注意trunc是把时间部分截掉只留日期,避免同一天请假算出 0 天。

5.3 用存储过程封装完整的请假审批流程

原文的审批逻辑散落在应用层,可以封装成一个存储过程:

create or replace procedure approve_leave( p_qingjia_id in number, p_approver_type in varchar2, -- 'CLASS_TEACHER' 或 'COLLEGE_LEADER' p_approver_id in varchar2, p_approved in number -- 1 同意,2 不同意 ) as v_days number; begin select day_number into v_days from qingjia where id = p_qingjia_id; if p_approver_type = 'CLASS_TEACHER' then update qingjia set class_tea_sp_status = p_approved, class_tea_sp_time = sysdate, class_tea_id = p_approver_id where id = p_qingjia_id; elsif p_approver_type = 'COLLEGE_LEADER' then if v_days > 3 then update qingjia set coll_leader_sp_status = p_approved, coll_leader_sp_time = sysdate, coll_leader_id = p_approver_id where id = p_qingjia_id; else raise_application_error(-20001, '请假天数不超过三天,无需院系领导审批'); end if; end if; commit; end;

这个存储过程把审批逻辑收拢到数据库端,应用层只需要传参数调用。好处是审批规则变了只改存储过程,不用重新部署应用。注意raise_application_error用来抛出自定义错误,错误码 -20001 是用户自定义范围的起始值。

5.4 验证改造效果的方法

改完之后怎么确认没问题?我一般分三步走:先用SELECT查一遍基础数据,确认院系、专业、班级、学生、课程都有记录;然后模拟一次完整请假流程——插入请假记录、调用审批存储过程、查询审批状态和天数;最后往kaoqin_record表插入几条缺勤记录,看触发器有没有输出提示。每一步都手动执行 SQL,别急着写应用层代码,数据库端逻辑跑通了再往上搭。

从那以后我每次拿到这种课程设计类的数据库项目,都强制自己先把建表脚本按依赖顺序排一遍,再逐条执行确认没有外键报错,最后才去碰存储过程和触发器。这个习惯帮我省了不少回头改表结构的时间。希望帮到你。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进