☰
Oracle课设实战:2009年考勤系统拆解与避坑指南
2026/10/12 0:44:13 网站建设 项目流程

简介:本资源是一份完整的Oracle数据库课程设计实践报告,面向高校计算机、软件工程等专业学习数据库原理与应用的学生,聚焦学生考勤系统这一典型教学管理场景,系统覆盖需求分析、E-R建模、数据字典、表结构设计、表空间与对象创建等核心开发环节,助力初学者掌握Oracle数据库从逻辑设计到物理实现的全流程能力。压缩包为单个Word文档(.doc),大小227KB,内容详实,含背景分析、六类用户角色需求拆解、三大功能模块说明(请假/考勤/后台管理)、E-R图描述、10余张核心表字段定义及创建脚本,结尾附心得体会与参考文献。已有964人学习下载,适合作为课程设计参考范本、期末项目模板或Oracle实践入门的结构化学习材料。

1. Oracle数据库课程设计:一份2009年辽宁工大考勤系统实录,为什么今天还值得拆?

这不是一份“过时”的课设报告——它是一份被时间验证过的、完整闭环的Oracle工程切片。2009年,当多数高校还在用Excel手工统计出勤、用纸质假条层层审批时,辽宁工程技术大学软件学院的学生已用Oracle 10g(从表空间路径/u01/oracle/oradata/可推断)落地了覆盖6类角色、含审批流+状态机+权限隔离的考勤系统。它没用Java Web框架,没上Tomcat,核心逻辑全压在数据库层:12张表定义清晰、3类数据库对象(存储过程/视图/触发器)全部实现、连datetime字段类型都按当时Oracle主流写法(虽然后来知道该用DATE或TIMESTAMP)。你可能觉得“老”,但恰恰是这份“老”,暴露了现代课程设计最缺的东西:不依赖中间件的纯数据库思维。学生要自己建表空间、手写外键约束、用CHECK限制性别值、用DBMS_OUTPUT做业务告警——没有ORM遮羞,没有Spring Boot自动装配,所有数据一致性、权限边界、状态流转都得靠SQL和PL/SQL硬刚。如果你正卡在“学完Oracle语法却不会搭真实系统”、或“课程设计只会增删改查三板斧”,这份报告就是你的后悔药:它把需求分析→E-R建模→表结构→对象实现→业务验证的全链路,钉死在16页PDF里。新手能照着建库跑通,熟手能从中抠出权限分层、审批状态机、跨院系数据隔离等实战范式。

2. 从需求到E-R模型:六类用户如何驱动12张表的设计决策

2.1 六类用户不是功能罗列,而是权限与数据边界的源头

课程设计开篇就明确划分学生、任课老师、班主任、院系领导、学校领导、系统管理员六大角色。这不是为了凑数,而是直接决定数据库的访问控制粒度和数据可见范围。比如:

  • 学生只能查自己stu_no关联的考勤记录,所以kaoqin_record表必须有stu_number外键指向student;
  • 院系领导只能看本院学生,所以collegeleader表需关联faculty(院系),而考勤视图rjxy的WHERE条件强制student.stu_faculty=5;
  • 学校领导要全局数据,但schoolleader表本身不存院系ID,说明其查询需跨faculty表JOIN,权限控制靠应用层而非DB层。

提示:这种“用户角色→数据范围→表关联”的映射,是避免后期加权限时推倒重来的关键。很多课程设计失败,根源在于初期没把用户需求翻译成外键和视图。

2.2 E-R图里的“n1”和“mn”不是画着玩的,它决定了主外键怎么建

报告第3页的E-R图看似简单,但每个连线都对应着物理表的关键设计:

  • 学生与请假条是“1对多”(一个学生可多次请假),所以qingjia表中stu_no是外键,且允许重复;
  • 班级与班主任是“1对1”(一个班一个班主任),但classes表中classtea_no是外键,而classteacher表主键是classtea_no,说明班主任身份需独立建表,而非直接存在班级表里——这是为后续班主任轮岗留扩展性;
  • 教师与课程是“m对n”(一门课多个老师教,一个老师教多门课),但报告中未建关联表!实际kaoqin_record表里有teacher_no和course_no,说明考勤记录承担了“授课关系”的事实表功能。这是典型的关系型数据库权衡:宁可让考勤表冗余承载关系,也不额外建teacher_course关联表——因为考勤是高频操作,减少JOIN更高效。

2.3 数据字典不是文档摆设,它是DDL语句的原始依据

第4页数据字典定义了10个核心实体,每个“定义”都直指建表语句:

  • 请假条信息 = 请假代号 + 班级代号 + 学生学号 + ...→ 对应qingjia表字段id,class_id,stu_no;
  • 学生信息 = 学号 + 姓名 + 性别 + 专业 + 院系 + 班级→ 解释了student表为何有stu_major,stu_faculty,stu_class三个外键字段;
  • 特别注意status字段的业务含义:“0等待审批,1同意,2不同意”,这直接决定qingjia表中class_tea_sp_status和coll_leader_sp_status的CHECK约束写法(报告里没写,但你应该补上:CHECK (class_tea_sp_status IN ('0','1','2')))。

2.4 表结构设计中的“Oracle味”:字符长度、空值、约束的取舍逻辑

第5页表结构设计暴露了Oracle初学者的真实思考:

  • admin_no char(5)用CHAR而非VARCHAR2:因管理员编号固定5位,CHAR定长存储更省IO(虽然现代Oracle差异极小,但2009年这是常识);
  • student.stu_class char(13)外键引用classes.class_no,但classes表中class_no char(10)——长度不一致!这是典型疏漏,实际建表会报错。正确做法是统一为char(10)或改用VARCHAR2;
  • kaoqin_record.sk_time datetime:Oracle无DATETIME类型,此处应为DATE(含时分秒)或TIMESTAMP。这是报告硬伤,但恰好提醒你:课程设计必须验证每个数据类型是否Oracle原生支持。

3. 表空间与建表脚本:手敲DDL前必须搞清的四个物理层陷阱

3.1 表空间创建:linpeng_data不只是名字,它定义了数据文件生命周期

报告6.1节的建表空间语句:

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

这段代码藏着Oracle DBA的底层逻辑:

  • datafile路径/u01/oracle/oradata/表明这是Linux环境,且Oracle安装在标准路径,意味着你能直接用ls -l /u01/oracle/oradata/确认文件是否存在;
  • size 100M是初始大小,但maxextents 999限制了最大扩展次数,若数据暴增会报ORA-01653(表无法扩展)。课程设计虽数据量小,但你要养成习惯:建表空间必配AUTOEXTEND ON NEXT 10M MAXSIZE UNLIMITED;
  • pctincrease 0关闭百分比增长,强制每次扩展next 128K,这是为避免碎片化——对考勤系统这种写多读少的场景很关键。

3.2 建表语句里的外键陷阱:报告中3处致命错误必须手动修复

报告6.2节的建表脚本有硬伤,不修复根本跑不起来:

  1. 学生表外键引用错误:

    stu_class char(5) foreign key references classes(class_no)

    问题:classes表名在报告中是classes(第11张表),但前面E-R图和数据字典都叫班级,且classes表定义中class_no char(10),而此处stu_class char(5)长度不匹配。
    修复:统一为char(10),并确认classes表存在。

  2. 请假表字段拼写错误:

    day_number nubmer not null -- 注意:nubmer 是 typo!

    问题:nubmer应为number,Oracle会直接报错ORA-00907: missing right parenthesis。
    修复:day_number number not null

  3. 考勤表时间字段类型错误:

    sk_time datetime not null

    问题:Oracle无datetime类型,必须改为DATE。
    修复:sk_time DATE not null

注意:这些错误不是“报告不严谨”,而是课程设计的真实场景——你永远在和手误、版本差异、文档滞后搏斗。我的习惯是:建表前先用DESCRIBE查目标表结构,再逐字段比对。

3.3 外键约束的隐性成本:级联删除该开还是关?

所有外键如stu_major number foreign key references major(major_id)都没加ON DELETE CASCADE。这是刻意为之:

  • 学生删了,专业不能删(major表是静态基础数据);
  • 但请假记录删了,学生记录不该删(student是主实体)。 所以报告选择不启用级联,靠应用层保证数据完整性。但你要知道:如果真要加级联,语法是:
FOREIGN KEY (stu_major) REFERENCES major(major_id) ON DELETE CASCADE

不过考勤系统里,更稳妥的做法是用ON DELETE SET NULL(如删除专业时,学生专业字段置空),避免误删。

3.4 字符集与NLS设置:为什么中文能存进去却查不出来?

报告所有char/varchar2字段都存中文(如admin_name char(10)),但没提数据库字符集。2009年Oracle 10g默认可能是ZHS16GBK,若你用AL32UTF8建库,char(10)只能存5个中文(UTF8下中文占3字节)。
验证方法:连接后执行

SELECT * FROM NLS_DATABASE_PARAMETERS WHERE PARAMETER IN ('NLS_CHARACTERSET', 'NLS_NCHAR_CHARACTERSET');

若字符集不兼容,建表时必须用NVARCHAR2存中文,例如:

admin_name NVARCHAR2(10) NOT NULL

否则插入'张三'会报ORA-01401: inserted value too large for column。

4. 数据库对象实战:存储过程、视图、触发器如何协同完成业务闭环

4.1 存储过程getMessage:不是炫技,是为解决“统计性能瓶颈”

报告6.3.1节的存储过程:

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;

表面看只是查个数量,但深挖三层价值:

  • 参数化封装:学生查自己某门课缺勤,不用每次拼SQL,调用CALL getMessage('0820980113', 'ORCL101', ?)即可;
  • 性能预编译:PL/SQL块在数据库端编译,比应用层拼SQL少一次网络传输和SQL解析;
  • 可扩展性埋点:现在只返回总数,但absence_times变量可扩展为计算“缺勤率=总数/总课时”,只需加一行SELECT COUNT(*) FROM course_schedule WHERE course_no=:course_no。

提示:课程设计常犯的错是把存储过程写成“SQL搬运工”。真正的好过程,要像这个一样——输入明确、输出单一、逻辑内聚。

4.2 视图rjxy:用WHERE student.stu_faculty=5实现数据权限的物理隔离

报告6.3.2节的视图:

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

这是典型的行级安全(RLS)雏形。院系领导登录后查SELECT * FROM rjxy,自动过滤出软件学院(ID=5)学生数据。但要注意两个坑:

  • 性能隐患:视图里用FROM kaoqin_record, student是旧式逗号JOIN,易漏WHERE条件导致笛卡尔积。应改写为显式INNER JOIN:
    CREATE VIEW rjxy AS SELECT k.kaoqin_id, k.sk_time, k.stu_number, k.stu_status, ... FROM kaoqin_record k INNER JOIN student s ON k.stu_number = s.stu_no WHERE s.stu_faculty = 5;
  • 权限绕过风险:视图只限制查询,但院系领导若直接查kaoqin_record表仍能看到全部数据。必须配合GRANT SELECT ON rjxy TO collegeleader_role,并REVOKE SELECT ON kaoqin_record FROM collegeleader_role。

4.3 触发器alertMessage:用AFTER INSERT实现业务规则的强一致性

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

这是课程设计里最“硬核”的部分——它把业务规则(缺勤3次禁考)固化在数据库层,任何应用(Web/APP/命令行)插入考勤记录都会触发。但必须处理三个现实问题:

  • 并发安全:若两个老师同时给同一学生录缺勤,COUNT(*)可能查到旧值。应改用SELECT COUNT(*) INTO current_times FROM kaoqin_record WHERE ... FOR UPDATE加行锁;
  • 输出不可见:DBMS_OUTPUT.PUT_LINE在SQL*Plus里需先SET SERVEROUTPUT ON,Web应用根本看不到。生产环境应写入日志表alert_log;
  • 状态持久化:触发器只提示,没更新学生状态。应追加UPDATE student SET exam_status='DISABLED' WHERE stu_no=:new.stu_number。

5. 避坑指南:运行这份2009年课设时,90%的人会栽在这5个坑里

5.1 现象:建表时报ORA-00907: missing right parenthesis

原因:报告中qingjia表的day_number nubmer not null拼写错误,nubmer不是合法类型;另外kaoqin_record.sk_time datetime中datetime非Oracle类型。
解决:全文搜索替换nubmer为number,datetime为DATE;用DESCRIBE确认所有引用表(如classes,student)已存在且字段名匹配。

5.2 现象:插入学生数据时报ORA-02291: integrity constraint violated - parent key not found

原因:外键约束要求父表记录必须存在。例如插入student时stu_major=101,但major表里没有major_id=101的记录。
解决:按依赖顺序建表——先建faculty(院系),再建major(专业),再建classes(班级),最后建student;插入数据也按此顺序,或用INSERT /*+ APPEND */批量导入时加ENABLE NOVALIDATE跳过约束检查(仅测试用)。

5.3 现象:执行SELECT * FROM rjxy返回空结果,但kaoqin_record里有数据

原因:视图rjxy的WHERE student.stu_faculty=5条件严格,而student表中stu_faculty存的是院系名称(如'软件学院')而非ID(5)。报告数据字典说stu_faculty是“所属学院”,但表结构定义为number类型,矛盾!
解决:统一数据类型——要么stu_faculty改为VARCHAR2(20)存名称,视图条件改为s.stu_faculty='软件学院';要么建faculty表,student.stu_faculty存faculty_id,确保faculty表中有faculty_id=5的记录。

5.4 现象:存储过程getMessage编译成功,但调用时报PLS-00306: wrong number or types of arguments

原因:过程有3个参数(2个IN,1个OUT),但调用时没声明OUT变量。例如EXEC getMessage('0820980113', 'ORCL101');缺少接收total_times的变量。
解决:用匿名块调用:

DECLARE v_total NUMBER; BEGIN getMessage('0820980113', 'ORCL101', v_total); DBMS_OUTPUT.PUT_LINE('缺勤次数:' || v_total); END;

5.5 现象:触发器alertMessage不触发,或触发后报ORA-04091: table is mutating

原因:触发器里SELECT COUNT(*) FROM kaoqin_record试图查询正在被INSERT的表,Oracle禁止这种“变异表”查询。
解决:改用复合触发器(Oracle 11g+)或用PRAGMA AUTONOMOUS_TRANSACTION新建事务:

CREATE OR REPLACE TRIGGER alertMessage AFTER INSERT ON kaoqin_record FOR EACH ROW DECLARE PRAGMA AUTONOMOUS_TRANSACTION; 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 INSERT INTO alert_log VALUES (:NEW.stu_number, :NEW.course_no, SYSDATE, '取消考试资格'); COMMIT; END IF; END;

6. 进阶验证:用3个SQL证明你的考勤系统真的跑通了业务流

6.1 验证请假审批流:从提交到生效的全链路追踪

考勤系统的核心是“请假影响考勤结果”。我们用一条SQL验证:学生A请假3天,任课老师录课时,系统是否自动判为“请假”而非“旷课”?
首先,插入一条请假记录(假设学生学号'0820980113',班级'SJ08-1',请假3天):

INSERT INTO qingjia(id, class_id, stu_no, leave_reason, start_time, end_time, day_number, qingjia_time, class_tea_id, class_tea_sp_status, class_tea_sp_time, coll_leader_sp_status, coll_leader_id, coll_leader_sp_time) VALUES(1, 'SJ08-1', '0820980113', '感冒', SYSDATE, SYSDATE+3, 3, SYSDATE, 'CT001', '1', SYSDATE, '1', 'CL001', SYSDATE);

然后,模拟任课老师录入考勤(stu_status应为'请假'而非'旷课'):

INSERT INTO kaoqin_record(kaoqin_id, sk_time, stu_number, stu_status, teacher_no, course_no) VALUES('KQ2024001', SYSDATE, '0820980113', '请假', 'T001', 'ORCL101');

最后,用以下SQL验证逻辑是否自洽:

-- 查看该学生当天考勤,确认状态为'请假' SELECT k.stu_number, k.stu_status, q.leave_reason, q.day_number FROM kaoqin_record k LEFT JOIN qingjia q ON k.stu_number = q.stu_no AND SYSDATE BETWEEN q.start_time AND q.end_time WHERE k.stu_number = '0820980113' AND k.sk_time >= TRUNC(SYSDATE); -- 查看该学生本学期所有考勤,统计'请假'次数 SELECT COUNT(CASE WHEN stu_status = '请假' THEN 1 END) AS 请假次数, COUNT(CASE WHEN stu_status = '旷课' THEN 1 END) AS 旷课次数, COUNT(*) AS 总考勤次数 FROM kaoqin_record WHERE stu_number = '0820980113' AND sk_time >= ADD_MONTHS(SYSDATE, -6);

若第一条SQL返回leave_reason='感冒',第二条返回请假次数 > 0,说明请假与考勤已打通。

6.2 验证权限隔离:用同一套数据,不同角色看到不同视图

创建三个测试用户,赋予不同权限:

-- 创建院系领导用户 CREATE USER college_leader IDENTIFIED BY leader123; GRANT CONNECT, SELECT ON rjxy TO college_leader; -- 创建学校领导用户(可查所有) CREATE USER school_leader IDENTIFIED BY leader456; GRANT CONNECT, SELECT ON kaoqin_record TO school_leader; GRANT SELECT ON student TO school_leader; -- 创建学生用户 CREATE USER student_user IDENTIFIED BY stu123; GRANT CONNECT TO student_user; -- 授予只查自己考勤的权限(通过视图) CREATE VIEW my_kaoqin AS SELECT * FROM kaoqin_record WHERE stu_number = '0820980113'; GRANT SELECT ON my_kaoqin TO student_user;

然后分别用各用户登录,执行:

-- college_leader用户执行 SELECT COUNT(*) FROM rjxy; -- 应只返回软件学院学生考勤数 -- school_leader用户执行 SELECT COUNT(*) FROM kaoqin_record; -- 应返回全部考勤数 -- student_user用户执行 SELECT COUNT(*) FROM my_kaoqin; -- 应只返回该学生自己的考勤数

若三者结果符合预期(如rjxy返回120行,kaoqin_record返回500行,my_kaoqin返回8行),证明权限模型落地成功。

6.3 验证触发器业务规则:缺勤3次自动禁考的实时性

这是最考验数据库功底的环节。我们需要制造“第3次缺勤”,观察触发器是否立即响应:

-- 先查该学生当前缺勤次数 SELECT COUNT(*) FROM kaoqin_record WHERE stu_number = '0820980113' AND stu_status = '旷课'; -- 插入第1次旷课 INSERT INTO kaoqin_record VALUES('KQ2024002', SYSDATE, '0820980113', '旷课', 'T001', 'ORCL101'); -- 插入第2次旷课 INSERT INTO kaoqin_record VALUES('KQ2024003', SYSDATE, '0820980113', '旷课', 'T001', 'ORCL101'); -- 插入第3次旷课(此时触发器应报警) INSERT INTO kaoqin_record VALUES('KQ2024004', SYSDATE, '0820980113', '旷课', 'T001', 'ORCL101');

在SQL*Plus中,执行最后一条INSERT后,若看到:

学号为:0820980113的学生该门课程被取消考试资格!

则触发器生效。但生产环境需将DBMS_OUTPUT改为写表,因此建议同步创建日志表:

CREATE TABLE alert_log ( stu_no VARCHAR2(10), course_no VARCHAR2(13), alert_time DATE, message VARCHAR2(200) );

并在触发器中改为:

INSERT INTO alert_log VALUES (:NEW.stu_number, :NEW.course_no, SYSDATE, '取消考试资格');

从那以后我每次部署课程设计,都强制走一遍这三步验证:先跑通业务流(请假→考勤),再验权限隔离(不同角色查不同数据),最后压测触发器(临界点行为)。不是为了交作业,而是训练一种肌肉记忆——当未来面对ERP、CRM等复杂系统时,你能一眼看出:哪个表是主实体,哪个视图是权限出口,哪个触发器在守业务红线。这份2009年的课设,没有高大上的架构图,只有扎进泥土的SQL和PL/SQL,但它教会我的,是数据库工程师最朴素的信仰:数据在哪里,规则就在哪里;表建好了,系统就活了。希望帮到你。

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

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

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

立即咨询