简介:本资源是一份完整的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节的建表脚本有硬伤,不修复根本跑不起来:
学生表外键引用错误:
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表存在。请假表字段拼写错误:
day_number nubmer not null -- 注意:nubmer 是 typo!问题:
nubmer应为number,Oracle会直接报错ORA-00907: missing right parenthesis。
修复:day_number number not null考勤表时间字段类型错误:
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,但它教会我的,是数据库工程师最朴素的信仰:数据在哪里,规则就在哪里;表建好了,系统就活了。希望帮到你。
本文还有配套的精品资源,点击获取