简介:这份资源是北邮研一数据库课程大作业的完整详解文档,面向计算机相关专业研究生及需要完成数据库课程设计的学习者,围绕学生成绩管理系统的设计与实现展开。内容从需求分析入手,梳理成绩维护、教师信息管理、不及格学生名单统计及无教学任务教师查询等功能,并给出Course、Student、Sc、Teacher四张表的数据字典、主外键约束与一对多关系设计,同时包含局部与全局ER图、逻辑结构设计及建表SQL语句,可帮助读者理清从概念模型到关系模式的完整推导过程。资源包共1个docx文件,约503KB,以图文与代码结合的方式呈现,便于对照复现建库建表流程。目前已有607人学习,适合作为数据库大作业的参考范本与查漏补缺材料。
1. 学生成绩管理系统:从教务科三句话需求到可验收的数据库方案
教务科提需求往往就三句话:老师能录成绩、学生能查成绩、教务处能拉出不及格名单和没课的老师名单。听着简单,真动手做数据库大作业时,很多人第一版就翻车——表建完了,发现「没有教学任务的老师」这个查询写不出来,因为课程表里塞的是教师号,教师和课程的关系没拆干净。这篇就按一份完整的数据库课程设计来拆:需求怎么落到四张表、ER 图怎么画才不返工、SQL 怎么写才经得起答辩追问、最后怎么自测验收。适合正在做数据库大作业、课程设计,或者要交一份能跑能讲的关系型数据库方案的人直接照着改。
2. 需求与数据建模:四张表怎么撑起三类角色的查询
2.1 先把三类角色的动作翻译成数据操作
需求文档里写的是「维护和管理」「按条件查询」,这种话不能直接进设计。我一般会把每个角色拆成具体动作,再对应到表和字段:
| 角色 | 动作 | 落到哪张表 | 关键字段 |
|---|---|---|---|
| 授课教师 | 录入、修改成绩 | sc | sno、cno、degree |
| 学生 | 查询本人成绩 | sc 关联 course | sno、cname、degree |
| 教务处 | 统计某课不及格名单 | sc 关联 student | cno、degree<60 |
| 教务处 | 查无教学任务的教师 | teacher 左连接 course | tno |
这张表看着朴素,但它决定了后面所有设计。比如「无教学任务教师」这条,本质是 teacher 表里存在、但 course 表里没有对应 tno 的记录,属于典型的反连接查询。如果建表时把教师信息冗余进 course 表,这条查询就得靠字符串比对,既不规范也容易出错。所以四张表的分工必须在需求阶段就定死:teacher 存人,course 存课,student 存学生,sc 存「谁选了什么课考了多少分」这个多对多关系。
2.2 四张表的字段与约束设计
按项目给出的数据字典,四张表的结构如下,这里把类型、长度、可空性和约束一次列清,建表时直接照抄:
| 表 | 字段 | 类型 | 长度 | 可空 | 约束 |
|---|---|---|---|---|---|
| course | cno | char | 5 | 否 | 主键 |
| course | cname | varchar | 20 | 否 | — |
| course | tno | char | 3 | 否 | 关联 teacher |
| student | sno | char | 9 | 否 | 主键 |
| student | sname | char | 8 | 是 | — |
| student | ssex | char | 2 | 是 | 男/女 |
| student | smajor | char | 20 | 是 | — |
| student | sclass | char | 10 | 是 | — |
| sc | sno | char | 10 | 否 | 组合主键、外键 |
| sc | cno | char | 5 | 否 | 组合主键、外键 |
| sc | degree | decimal | 4,1 | 是 | 0–100 |
| teacher | tno | char | 3 | 否 | 主键 |
| teacher | tname | varchar | 8 | 是 | — |
| teacher | tsex | char | 2 | 是 | 男/女 |
| teacher | tdept | char | 16 | 是 | — |
有两处细节值得在答辩时主动讲:一是 sc 表的 degree 用 decimal(4,1),能存 100.0 也能存 0.0,配合 CHECK 约束把范围卡死在 0 到 100,比用 int 更贴合成绩场景;二是 sc 的主键是 (sno, cno) 组合主键,这直接保证了「一个学生对一门课只有一条成绩记录」,重复录入在数据库层面就被挡住了,不用靠应用层去查重。
2.3 ER 图与关系模式的对应关系
局部 ER 图里,学生和课程之间是 m:n 的选修关系,教师和课程之间是 1:n 的讲授关系。转成关系模式时,m:n 必须独立成表,这就是 sc 的由来;1:n 则可以把「一」端的主键放到「多」端做外键,所以 course 表里带 tno。全局 ER 图合并后得到四个关系模式:
- course(cno, cname, tno)
- student(sno, sname, ssex, smajor, sclass)
- sc(sno, cno, degree)
- teacher(tno, tname, tsex, tdept)
四个模式的主属性都是候选码,非主属性完全函数依赖于码,满足第三范式。这一点在报告里要写清楚,因为答辩老师十有八九会问「你的表到 3NF 了吗,凭什么」。判断方法很直接:每个非主属性都由整个主键决定、且不传递依赖其他非主属性,逐表过一遍即可。
3. 建库建表与约束落地:把设计图变成能跑的 DDL
3.1 建库与四张表的完整 DDL
设计讲完就得落地。下面这段 DDL 按项目里的建表语句整理,补上了外键约束和字段注释,直接在一个空 MySQL 实例上执行就能得到完整结构:
-- 创建数据库,字符集显式指定,避免中文姓名乱码 CREATE DATABASE score_mgmt DEFAULT CHARACTER SET utf8mb4; USE score_mgmt; -- 教师表先建,因为 course 要引用它 CREATE TABLE teacher ( tno CHAR(3) NOT NULL COMMENT '教师编号', tname VARCHAR(8) NULL COMMENT '教师姓名', tsex CHAR(2) NULL COMMENT '性别:男/女', tdept CHAR(16) NULL COMMENT '所属院系', CONSTRAINT pk_teacher PRIMARY KEY (tno), CONSTRAINT ck_tsex CHECK (tsex IN ('男', '女')) ); -- 课程表,tno 外键指向 teacher CREATE TABLE course ( cno CHAR(5) NOT NULL COMMENT '课程编号', cname VARCHAR(20) NOT NULL COMMENT '课程名称', tno CHAR(3) NOT NULL COMMENT '任课教师编号', CONSTRAINT pk_course PRIMARY KEY (cno), CONSTRAINT fk_course_teacher FOREIGN KEY (tno) REFERENCES teacher(tno) ); -- 学生表 CREATE TABLE student ( sno CHAR(9) NOT NULL COMMENT '学号', sname CHAR(8) NULL COMMENT '姓名', ssex CHAR(2) NULL COMMENT '性别:男/女', smajor CHAR(20) NULL COMMENT '专业', sclass CHAR(10) NULL COMMENT '班级', CONSTRAINT pk_student PRIMARY KEY (sno), CONSTRAINT ck_ssex CHECK (ssex IN ('男', '女')) ); -- 成绩表,组合主键 + 双外键 + 分数范围约束 CREATE TABLE sc ( sno CHAR(10) NOT NULL COMMENT '学号', cno CHAR(5) NOT NULL COMMENT '课程编号', degree DECIMAL(4,1) NULL COMMENT '成绩,0-100', CONSTRAINT pk_sc PRIMARY KEY (sno, cno), CONSTRAINT fk_sc_student FOREIGN KEY (sno) REFERENCES student(sno), CONSTRAINT fk_sc_course FOREIGN KEY (cno) REFERENCES course(cno), CONSTRAINT ck_degree CHECK (degree >= 0 AND degree <= 100) );建表顺序不能乱:teacher 和 student 是「被引用方」,必须先建;course 引用 teacher,sc 同时引用 student 和 course,所以顺序是 teacher → student → course → sc。如果反过来先建 sc,外键会直接报错。另外 CHECK 约束在部分 MySQL 版本里会被静默忽略,答辩前务必用一条越界插入验证它到底生效没有,别在演示时被老师一句话问穿。
3.2 索引设计:哪些查询值得加索引
索引不是越多越好,写多了拖慢插入、占空间,写少了查询全表扫。按这个系统的实际查询模式,我一般只加三类:
-- 按课程统计成绩、查不及格名单,cno 是高频过滤条件 CREATE INDEX idx_sc_cno ON sc(cno); -- 按姓名模糊查学生,前缀匹配能用上索引 CREATE INDEX idx_student_sname ON student(sname); -- 按班级查学生(存储过程里用到) CREATE INDEX idx_student_sclass ON student(sclass);sno 在 sc 里已经是组合主键的左前缀,单独再建 sno 索引属于重复,没必要。degree 这种低选择性的字段也不适合单独建索引,查不及格名单时走 cno 索引再过滤 degree 更划算。这套取舍在报告里写一句「索引围绕高频等值查询和连接字段建立」,比堆一堆索引更有说服力。
3.3 视图:把常用联表查询封装起来
学生查成绩要联三张表,每次都写一遍容易出错,用视图封装是标准做法。项目里定义了 v_student 视图查某门课的选课学生成绩,这里再补一个更通用的学生成绩视图:
-- 学生-课程-成绩三表联查视图,学生端查询直接走它 CREATE VIEW v_score_detail AS SELECT s.sno, s.sname, c.cno, c.cname, sc.degree FROM student s JOIN sc ON s.sno = sc.sno JOIN course c ON sc.cno = c.cno; -- 学生查自己成绩 SELECT * FROM v_score_detail WHERE sno = '120210332';视图的好处不只是少写 SQL,更重要的是把「成绩 = 学生表 + 成绩表 + 课程表」这个业务口径固定下来,前端或报表都从同一个视图取数,不会出现两个地方算法不一致的玄学问题。注意视图默认不可更新,涉及多表连接的视图做 INSERT/UPDATE 会失败,录入成绩还是要直接操作 sc 表。
4. 业务功能实现:存储过程、触发器与核心查询
4.1 用存储过程封装统计逻辑
教务处要「按班级查学生」和「统计某学生选了几门课」,这类固定逻辑适合做成存储过程,调用方传参就行,不用每次拼 SQL:
DELIMITER // -- 按班级查学生,入参为班级关键字 CREATE PROCEDURE proc_stud_by_class(IN p_class VARCHAR(10)) READS SQL DATA BEGIN SELECT sno, sname, smajor FROM student WHERE sclass LIKE CONCAT('%', p_class, '%') ORDER BY sno; END // -- 统计某学生的选课门数,OUT 参数返回结果 CREATE PROCEDURE num_sc(IN p_sno CHAR(9), OUT p_count INT) READS SQL DATA BEGIN SELECT COUNT(*) INTO p_count FROM sc WHERE sno = p_sno; END // DELIMITER ;调用方式:CALL proc_stud_by_class('4412');和CALL num_sc('120210332', @cnt); SELECT @cnt;。这里有两个容易踩的点:一是 DELIMITER 必须改,否则存储过程体内的分号会被客户端当成语句结束;二是声明了 READS SQL DATA 表示只读,能减少不必要的锁,答辩时被问到「为什么加这个」能答上来是加分项。
4.2 触发器:删除学生时留一份归档
学生被删除后成绩记录跟着没了,教务追溯就断了。项目里用触发器把删除的学生学号姓名写进归档表,这个思路很实用:
-- 归档表,结构与 student 的关键字段一致 CREATE TABLE delstudent ( sno CHAR(9), sname CHAR(8), del_time DATETIME DEFAULT CURRENT_TIMESTAMP ); DELIMITER // CREATE TRIGGER trig_student_del AFTER DELETE ON student FOR EACH ROW BEGIN INSERT INTO delstudent(sno, sname) VALUES (OLD.sno, OLD.sname); END // DELIMITER ;注意触发器是 AFTER DELETE,用 OLD 取被删行的值。这里有个血泪经验:如果 sc 表对 student 有外键且没设级联删除,直接删学生会被外键挡住,触发器根本不会触发。所以要么先删 sc 里的成绩,要么把外键设成 ON DELETE CASCADE。我一般选前者,删除操作走一个事务,先删成绩再删学生,归档和清理一起提交,逻辑更可控。
4.3 三类核心查询的写法与验证
需求里点名的几个查询,写法要经得起推敲:
-- 1. 某课程不及格学生名单(教务处高频) SELECT s.sno, s.sname, s.sclass, sc.degree FROM sc JOIN student s ON sc.sno = s.sno WHERE sc.cno = 'C03' AND sc.degree < 60 ORDER BY sc.degree; -- 2. 没有教学任务的教师(反连接,用 LEFT JOIN 判空) SELECT t.tno, t.tname, t.tdept FROM teacher t LEFT JOIN course c ON t.tno = c.tno WHERE c.tno IS NULL; -- 3. 某课程平均分与选课人数 SELECT c.cname, COUNT(*) AS stu_cnt, AVG(sc.degree) AS avg_score FROM sc JOIN course c ON sc.cno = c.cno WHERE sc.cno = 'C01' GROUP BY c.cno, c.cname;第二条是整套方案里最值得讲的一条。用 NOT IN 也能写,但子查询里一旦出现 NULL,NOT IN 会返回空结果集,这是 SQL 里经典的坑。LEFT JOIN + IS NULL 的写法语义清晰、执行计划稳定,答辩时能主动说出「为什么不用 NOT IN」,基本就把这道题拿下了。
5. 避坑与排查:数据库大作业最容易翻车的五个点
5.1 中文乱码:建库没指定字符集
现象是插入「张三」查出来变成问号或乱码。原因通常是数据库、表、连接三处字符集不一致,默认 latin1 在作怪。解决办法是建库时显式DEFAULT CHARACTER SET utf8mb4,建表继承库的字符集,连接串里也带上characterEncoding=utf8。已经建好的库可以用ALTER DATABASE ... CHARACTER SET utf8mb4补救,但已有数据要重新导入才彻底。
5.2 外键报错 1452:插入顺序或数据缺失
现象是往 sc 插成绩时报「Cannot add or update a child row」。原因是 sc.sno 或 sc.cno 在父表里不存在。排查顺序:先查 student 和 course 里有没有这条记录,再确认插入语句的字段顺序和类型是否对得上(char 类型带不带引号差别很大)。解决就是先插父表数据,或者临时SET FOREIGN_KEY_CHECKS=0导入完再打开——但后者只适合批量初始化,日常操作别关。
5.3 CHECK 约束不生效:版本差异
现象是插入 degree = 120 居然成功了。原因是部分 MySQL 版本解析 CHECK 但不执行。解决办法有两个:升级到支持 CHECK 的版本,或者在应用层/触发器里补一道校验。答辩演示前一定用越界数据实测一次,别默认它生效。
5.4 触发器不触发:外键挡住了删除
现象是执行 DELETE 后归档表没数据。原因是 sc 表外键约束阻止了删除,语句直接报错回滚。解决是把删除包装成事务,先删 sc 再删 student,或者给外键加 ON DELETE CASCADE。排查时先单独跑 DELETE 看报什么错,错误信息会直接指向外键。
5.5 存储过程创建失败:分隔符没改
现象是 CREATE PROCEDURE 报语法错误,指向过程体里的分号。原因是客户端把过程体里的分号当成了语句结束。解决就是前后加DELIMITER //和DELIMITER ;,并且确认客户端工具支持这一指令。Navicat 里在查询窗口执行同样有效,但要注意别在已经改了分隔符的状态下继续执行普通语句。
6. 验收与答辩:怎么证明这套库真的能用
验收不是把 SQL 跑一遍给老师看,而是按需求逐条对账。我一般准备一张自测清单,每条需求对应一条可执行的验证语句和预期结果,现场按顺序演示:
| 验收项 | 验证操作 | 预期结果 |
|---|---|---|
| 成绩录入 | 向 sc 插入一条合法成绩 | 插入成功,视图可查 |
| 成绩范围约束 | 插入 degree=120 | 被 CHECK 拒绝 |
| 重复选课约束 | 重复插入同一 (sno,cno) | 主键冲突报错 |
| 不及格名单 | 执行 4.3 第一条查询 | 只返回 degree<60 的记录 |
| 无课教师 | 执行 4.3 第二条查询 | 返回 course 中无记录的教师 |
| 删除归档 | 删除一名学生后查 delstudent | 归档表出现该学生 |
| 存储过程 | CALL num_sc 传入学号 | 返回正确门数 |
演示顺序也有讲究:先跑「正常路径」建立信心,再跑「约束拦截」展示设计严谨性,最后跑触发器和存储过程展示进阶能力。老师追问最多的地方通常是范式判断和外键必要性,提前把「为什么 sc 要组合主键」「为什么 course 的 tno 不做成多值字段」这两个问题的答案背熟,基本不会卡壳。
还有一个容易被忽略的点:报告里的 ER 图和实际建表必须对得上。我见过图里画了「授课」联系、表里却没有对应字段的情况,老师一眼就能看出来。交之前拿建表语句反推一遍关系模式,和图逐条比对,比事后解释省事得多。
从那以后我每次交数据库方案前,都会强制走一遍「需求条目 → 表字段 → 验证语句」的三列对账,任何一条需求找不到对应的验证 SQL,就说明设计有缺口。这套习惯帮我躲过了好几次答辩现场的尴尬。希望帮到你。
本文还有配套的精品资源,点击获取