☰
数据库课程设计健康档案管理系统:MySQL建表与统计查询实战
2026/10/12 1:03:26 网站建设 项目流程

简介:这份《数据库课程设计——健康档案管理系统》文档,面向高校计算机相关专业学生及数据库初学者,用于完成课程设计或数据库建模练习。内容围绕健康档案管理场景,完整覆盖需求分析、数据流图与数据字典、概要结构设计、逻辑结构设计等环节,并涉及用户管理、档案录入、查询检索、统计分析等模块划分,以及ER图、关系模式、规范化、索引与安全备份等知识点,还延伸讨论了报销、工资、资金计划等财务流程的数据处理。资源包共1个doc文件,约863KB,为纯文档资料,便于直接阅读、打印或作为报告模板参考。目前已有128人学习下载。读者可借此获得一套从需求到实现的完整设计思路,理解实体关系建模与SQL建表过程,并参考其中的目录结构与章节组织方式,快速搭建自己的课程设计框架,适合需要系统梳理数据库设计流程、查漏补缺的读者。

1. 一份数据库课程设计文档,为什么值得翻出来重做一遍

如果你正在搜「数据库课程设计 健康档案管理系统」,大概率是两种情况:要么课设选题刚定下来,脑子里只有「增删改查」四个字,不知道从哪下手;要么文档已经写了一半,需求分析、数据流图、数据字典堆了一堆,但一到建表和写 SQL 就卡住。这份《数据库课程设计——健康档案管理系统》文档,恰好卡在中间——它给了一套完整的分析框架,从课程设计目的、需求分析、数据流图、数据字典,一路铺到概要结构、逻辑结构、物理结构,但真正落到 MySQL 建表、约束、查询和统计时,需要你自己补上。

我翻完这份文档后最大的感受是:它的价值不在「代码写得多漂亮」,而在于把「健康档案」这个场景拆得足够细——体检文件和病历文件分开建模,学生基本信息、体征数据、诊断记录、住院标记各自独立,这种拆分思路放到今天做医疗类数据库设计依然成立。适合谁?数据库课程设计选健康/医疗方向的学生、需要一份可复现建表脚本的开发者、以及想拿一个真实场景练手 SQL 增删改查和统计查询的人。接下来我按「文档给了什么 → 怎么落成表 → 查询统计怎么写 → 坑在哪」的顺序,把它拆成能直接抄作业的版本。

2. 从数据流图到三张核心表:健康档案的建模逻辑

2.1 文档里的需求分析到底说了什么

文档在需求分析部分明确了两类健康文件:体检文件和病历文件。体检文件的字段是学号、姓名、性别、系别、年龄、身高、体重、胸围、日期;病历文件的字段是学号、姓名、性别、系别、诊断、日期。功能要求五项:登记、修改、删除、查询、统计。统计又分一般统计(计数、求平均值)和动态分析(由健康历史求平均年增长值和年增长率)。

这里有个容易被忽略的设计点:文档提到「病历表的属性不止包括学号、姓名、性别、系别、诊断、日期,还有医疗记录和是否住院等,体检表中又包含体检项目,而身高、体重、胸围等均包含在项目名称中」。这句话其实暗示了一个更规范的建模方向——把「体检项目」抽象出来,而不是把身高、体重、胸围硬编码成列。但课程设计阶段,为了查询和统计方便,直接建宽表反而更实际。我一般会折中:核心体征字段保留在体检表里,额外项目用扩展表存。

数据字典部分给出了数据流条目,比如「原始数据」由医务室流向系统,组成包括学号、姓名、性别、系别、年龄、身高、体重、胸围、日期、诊断结果、联系方式、医疗记录、是否住院、其他。注意这里出现了「联系方式」和「其他」,说明文档作者已经考虑到字段扩展,但正文的体检/病历字段清单里没列全。落地时我会把联系方式放进学生基本信息表,避免每张表都冗余。

2.2 三张表怎么拆:学生、体检、病历

按文档的实体划分,最稳妥的拆法是三张表:学生基本信息表(student)、体检记录表(physical_exam)、病历记录表(medical_record)。学生表存学号、姓名、性别、系别、联系方式等相对稳定的属性;体检表和病历表通过学号外键关联,各自存多次记录。这样拆的好处是:一个学生可以有多条体检记录和多条病历记录,符合「不同时期的病历表和体检表」这个描述。

为什么不把体检和病历合成一张表?因为两者的字段差异大,病历有诊断、医疗记录、是否住院,体检有身高、体重、胸围、年龄,硬合会导致大量 NULL,查询统计时还要额外过滤。分开建表后,统计平均身高只查体检表,统计某诊断出现次数只查病历表,逻辑清晰。

下面是建表脚本,我按 MySQL 8 的语法写,字段类型和约束都给了注释:

-- 学生基本信息表:一个学生一条记录 CREATE TABLE student ( student_id VARCHAR(20) PRIMARY KEY COMMENT '学号,主键', name VARCHAR(50) NOT NULL COMMENT '姓名', gender CHAR(1) NOT NULL COMMENT '性别:M男 F女', department VARCHAR(50) NOT NULL COMMENT '系别', phone VARCHAR(20) DEFAULT NULL COMMENT '联系方式', created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '建档时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生基本信息'; -- 体检记录表:一个学生多次体检,每次一条 CREATE TABLE physical_exam ( exam_id INT AUTO_INCREMENT PRIMARY KEY COMMENT '体检记录ID', student_id VARCHAR(20) NOT NULL COMMENT '学号,外键', age TINYINT DEFAULT NULL COMMENT '年龄', height DECIMAL(5,1) DEFAULT NULL COMMENT '身高cm', weight DECIMAL(5,1) DEFAULT NULL COMMENT '体重kg', chest DECIMAL(5,1) DEFAULT NULL COMMENT '胸围cm', exam_date DATE NOT NULL COMMENT '体检日期', CONSTRAINT fk_exam_student FOREIGN KEY (student_id) REFERENCES student(student_id) ON DELETE CASCADE, INDEX idx_exam_student_date (student_id, exam_date) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='体检记录'; -- 病历记录表:一个学生多次就诊,每次一条 CREATE TABLE medical_record ( record_id INT AUTO_INCREMENT PRIMARY KEY COMMENT '病历记录ID', student_id VARCHAR(20) NOT NULL COMMENT '学号,外键', diagnosis VARCHAR(200) NOT NULL COMMENT '诊断结果', treatment TEXT DEFAULT NULL COMMENT '医疗记录', hospitalized TINYINT(1) DEFAULT 0 COMMENT '是否住院:0否 1是', visit_date DATE NOT NULL COMMENT '就诊日期', CONSTRAINT fk_record_student FOREIGN KEY (student_id) REFERENCES student(student_id) ON DELETE CASCADE, INDEX idx_record_student_date (student_id, visit_date) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='病历记录';

逻辑说明:student 表用学号做主键,因为学号天然唯一且业务上稳定;physical_exam 和 medical_record 都用自增 ID 做主键,学号做外键,这样同一学生可以插入多条记录。外键的 ON DELETE CASCADE 表示删除学生时自动清理其体检和病历记录,避免孤儿数据。两个索引都建在 (student_id, 日期) 上,因为最常见的查询是「查某学生某段时间的记录」。

参数说明:height、weight、chest 用 DECIMAL(5,1) 而不是 FLOAT,因为体征数据不需要浮点精度,DECIMAL 避免 170.1 存成 170.09999 这种玄学问题。hospitalized 用 TINYINT(1) 存 0/1,比 CHAR(1) 存 Y/N 更省空间且便于统计。exam_date 和 visit_date 用 DATE 而不是 DATETIME,因为文档里只要求日期,不要求时分秒。

2.3 插入测试数据:把文档里的字段填进去

建完表后先插几条数据验证结构。注意插入顺序:先 student,再 physical_exam 和 medical_record,否则外键约束会报错。

-- 先插学生 INSERT INTO student (student_id, name, gender, department, phone) VALUES ('2024001', '张三', 'M', '计算机系', '13800000001'), ('2024002', '李四', 'F', '计算机系', '13800000002'), ('2024003', '王五', 'M', '医学系', '13800000003'); -- 再插体检记录,张三有两次体检 INSERT INTO physical_exam (student_id, age, height, weight, chest, exam_date) VALUES ('2024001', 20, 175.0, 68.5, 90.0, '2024-09-01'), ('2024001', 21, 175.5, 70.0, 91.0, '2025-09-01'), ('2024002', 20, 162.0, 52.0, 84.0, '2024-09-01'); -- 插病历记录 INSERT INTO medical_record (student_id, diagnosis, treatment, hospitalized, visit_date) VALUES ('2024001', '上呼吸道感染', '口服抗生素三天', 0, '2024-10-12'), ('2024002', '急性阑尾炎', '手术切除', 1, '2024-11-03');

逻辑说明:张三插了两条体检记录,日期跨一年,正好用来演示文档要求的「动态分析」——求平均年增长值。李四只有一条体检记录,统计时要注意单条记录不能算增长率,否则会除零。病历里王五没插,查询时用 LEFT JOIN 才能看出「无病历学生」的差异。

参数说明:插入时字符串用单引号,日期用 'YYYY-MM-DD' 格式,MySQL 会自动转成 DATE。如果 sql_mode 开了 STRICT,插入超长字符串会直接报错而不是截断,这是好事,能提前发现字段长度不够。

3. 增删改查与统计查询:把文档的五项功能写成 SQL

3.1 登记、修改、删除的标准写法

文档要求的第一项功能是「登记」,即把学生健康信息插入健康文件。对应到 SQL 就是 INSERT,上面已经演示。实际使用时,登记通常分两步:先确认学生是否已存在,不存在则插 student,存在则只插体检或病历。常见做法是用 INSERT ... ON DUPLICATE KEY UPDATE 做 upsert,但要注意它依赖主键或唯一索引。

-- 登记学生,若学号已存在则更新姓名和系别 INSERT INTO student (student_id, name, gender, department, phone) VALUES ('2024004', '赵六', 'F', '护理系', '13800000004') ON DUPLICATE KEY UPDATE name = VALUES(name), department = VALUES(department), phone = VALUES(phone);

逻辑说明:ON DUPLICATE KEY UPDATE 在 MySQL 里当主键冲突时执行更新,VALUES(col) 取的是本次 INSERT 想插入的值。这样一条语句同时覆盖「新增」和「修改」两个场景,适合登记入口。参数说明:如果不想更新 phone,把它从 UPDATE 列表里去掉即可,但要注意别把主键也更新了。

修改功能对应 UPDATE,删除对应 DELETE。文档要求「修改一个学生的健康档案记录」,通常指改体检或病历的具体记录,而不是改学号。下面按记录 ID 改:

-- 修改体检记录:把 exam_id=1 的体重改成 69.0 UPDATE physical_exam SET weight = 69.0 WHERE exam_id = 1; -- 删除病历记录:删掉 record_id=2 的记录 DELETE FROM medical_record WHERE record_id = 2;

逻辑说明:UPDATE 和 DELETE 的 WHERE 条件必须走索引或主键,否则会全表扫描甚至误删全表。exam_id 和 record_id 都是主键,命中索引,安全。参数说明:生产环境执行 DELETE 前先用 SELECT 把同样的 WHERE 跑一遍,确认影响行数,这是血泪经验——我见过有人 WHERE 写漏了,一条 DELETE 清空整张表。

3.2 组合条件查询与报表输出

文档要求「可以组合各种条件进行查询,显示学生健康信息并打印健康文件报表」。组合条件查询的核心是动态拼 WHERE,但课程设计阶段用固定几种组合即可。下面这条 SQL 查「某系某段时间内体检过的学生,显示姓名、身高、体重、体检日期」:

SELECT s.student_id, s.name, s.department, p.height, p.weight, p.exam_date FROM student s JOIN physical_exam p ON s.student_id = p.student_id WHERE s.department = '计算机系' AND p.exam_date BETWEEN '2024-01-01' AND '2025-12-31' ORDER BY p.exam_date DESC;

逻辑说明:JOIN 把学生和体检记录关联,WHERE 里系别和日期范围组合过滤,ORDER BY 让最近的体检排前面。参数说明:BETWEEN 是闭区间,包含两端日期;如果只要某天之后,用 >= 更直观。department 字段如果区分大小写,取决于列的 collation,utf8mb4_general_ci 是不区分大小写的,'计算机系' 和 '计算机系' 一样。

报表输出通常还要查「有体检但无病历」或「有病历但无体检」的学生,用 LEFT JOIN + IS NULL:

-- 查从未体检过的学生 SELECT s.student_id, s.name FROM student s LEFT JOIN physical_exam p ON s.student_id = p.student_id WHERE p.exam_id IS NULL;

逻辑说明:LEFT JOIN 保留左表所有学生,右表没匹配到的行 exam_id 为 NULL,WHERE 过滤出这些行。参数说明:判断 NULL 必须用 IS NULL,不能用 = NULL,后者永远返回空结果,这是新手最常见的翻车点之一。

3.3 一般统计与动态分析:计数、平均值、年增长率

文档把统计分成一般统计和动态分析。一般统计包括计数和求平均值,动态分析由健康历史求平均年增长值和年增长率。先看一般统计:

-- 一般统计:各系体检人数、平均身高、平均体重 SELECT s.department, COUNT(DISTINCT s.student_id) AS student_count, ROUND(AVG(p.height), 1) AS avg_height, ROUND(AVG(p.weight), 1) AS avg_weight FROM student s JOIN physical_exam p ON s.student_id = p.student_id GROUP BY s.department;

逻辑说明:COUNT(DISTINCT student_id) 避免一个学生多次体检被重复计数,AVG 自动忽略 NULL,ROUND 保留一位小数。参数说明:GROUP BY 的列必须在 SELECT 里出现或作为聚合函数的参数,否则 MySQL 8 在 ONLY_FULL_GROUP_BY 模式下会报错。

动态分析稍复杂,要按学生算身高或体重的年增长。文档说「由健康历史求出平均年增长值和年增长率」,我一般用窗口函数 LAG 取上一次记录,再算差值:

-- 动态分析:每个学生相邻两次体检的体重增长值和年增长率 SELECT student_id, exam_date, weight, weight - LAG(weight) OVER (PARTITION BY student_id ORDER BY exam_date) AS weight_gain, ROUND( (weight - LAG(weight) OVER (PARTITION BY student_id ORDER BY exam_date)) / LAG(weight) OVER (PARTITION BY student_id ORDER BY exam_date) * 100, 2 ) AS growth_rate_pct FROM physical_exam ORDER BY student_id, exam_date;

逻辑说明:LAG(weight) OVER (PARTITION BY student_id ORDER BY exam_date) 取同一学生按日期排序的上一条体重,第一条记录没有上一条,返回 NULL,所以 weight_gain 和 growth_rate_pct 也是 NULL,这是正常的。参数说明:PARTITION BY 按学生分组,ORDER BY 决定「上一条」是谁;如果同一学生同一天有两条体检,排序会不稳定,建议加 exam_id 作为第二排序键。增长率算出来是百分比,乘以 100 后保留两位。

提示:窗口函数需要 MySQL 8.0 及以上,5.7 不支持。如果课程设计环境是 5.7,得用自连接或变量模拟,麻烦很多,建议直接上 8.0。

4. 避坑与排查:健康档案建表和查询里最容易翻车的五件事

4.1 外键约束导致插入顺序报错

现象:先插 physical_exam 再插 student,报错「Cannot add or update a child row: a foreign key constraint fails」。原因:外键要求被引用的学生必须先存在。解决:调整插入顺序,先 student 后子表;或者临时 SET FOREIGN_KEY_CHECKS=0,但课程设计里不建议关外键,关了等于没建。

4.2 中文乱码:库、表、连接三处字符集不一致

现象:插入「张三」后查出来是「??」或乱码。原因:建库时没指定 utf8mb4,或连接串没设 characterEncoding。解决:建库用 CREATE DATABASE health DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;建表也带 utf8mb4;JDBC 连接串加 ?useUnicode=true&characterEncoding=utf8。三处一致才不会乱码。

4.3 AVG 算出来和手算不一样:NULL 和重复行在作怪

现象:平均身高手算 170,SQL 算出来 168。原因:AVG 忽略 NULL,如果某些记录 height 为空,分母变小;或者 JOIN 后一个学生多条体检被重复计入。解决:先 SELECT COUNT(*) 和 COUNT(height) 对比,看有多少 NULL;统计人数用 COUNT(DISTINCT student_id),统计平均值前确认是否需要按学生去重。

4.4 增长率算出除零或 NULL

现象:growth_rate_pct 全是 NULL,或者报「Division by 0」。原因:LAG 对第一条记录返回 NULL,NULL 参与除法结果还是 NULL;如果上一条体重为 0,直接除零。解决:用 NULLIF(LAG(weight), 0) 把 0 转成 NULL,避免除零;第一条记录的 NULL 是预期行为,展示时用 IFNULL 显示「首次体检」即可。

4.5 DELETE 忘写 WHERE 或 WHERE 条件不走索引

现象:执行 DELETE FROM medical_record WHERE visit_date = '2024-10-12' 删掉了所有该日期的记录,但本意是删某学生的。原因:WHERE 条件不够精确,且 visit_date 没索引,全表扫描。解决:删除前先用 SELECT 验证条件,删除时尽量用主键;如果必须按日期删,给 visit_date 建索引,并在 WHERE 里加上 student_id。

5. 把文档变成可交付课设:ER 图补全与物理设计检查清单

文档在概要结构设计和逻辑结构设计部分给了框架,但没给完整的 ER 图和物理设计参数。我一般会补一张 ER 图(用 draw.io 或 PowerDesigner 画),实体是 student、physical_exam、medical_record,关系是 1:N。物理设计阶段要确认三件事:字段类型是否够用、索引是否覆盖高频查询、字符集是否统一。下面这张检查表是我每次交课设前必过的:

检查项合格标准常见问题
主键每张表都有主键子表用学号做主键导致一人只能一条记录
外键子表引用父表主键忘建外键,删学生后留孤儿记录
字符集库、表、连接三处 utf8mb4中文乱码
索引高频查询字段有索引WHERE 走全表扫描
字段类型体征用 DECIMAL,日期用 DATE身高用 VARCHAR 导致无法比较大小
约束性别、是否住院有 CHECK 或枚举插入「未知」性别

补 ER 图时注意:文档里「体检项目」如果展开成独立实体,会变成 student - exam - exam_item 三张表,查询要再 JOIN 一层。课程设计如果时间紧,保持宽表更稳;如果要求规范化到 3NF,就把身高、体重、胸围抽到 exam_item 表,用 item_name 和 item_value 存。两种方案都能过,关键是在文档里写清楚选型理由。

物理设计还有一个容易忽略的点:备份恢复策略。文档摘要里提到「数据的安全性、备份恢复策略」,但正文没展开。课程设计至少写一句:用 mysqldump 每周全量备份,命令是 mysqldump -u root -p health > health_backup.sql,恢复用 mysql -u root -p health < health_backup.sql。不用真跑,但写进文档能加分。

从那以后我每次做数据库课设,都强制走一遍「建表 → 插测试数据 → 跑五类查询 → 过检查表」的流程,不先插数据就写查询,十有八九要返工。希望帮到你。

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

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

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

立即咨询