简介这份资源是面向软件工程、计算机及相关专业学生的数据库课程设计参考文档聚焦医院门诊管理系统的数据库设计适合正在完成课程设计或需要数据库建模实战案例的学习者。压缩包内共1个doc文件约731KB内容为完整的课程设计论文涵盖需求分析、数据流程图、数据字典、数据项与数据结构定义、数据流与处理逻辑描述以及概念设计、E-R图、逻辑设计、物理设计和数据库实施与测试等章节并给出SQL Server 2008环境下的建库建表与数据入库思路。读者可借此掌握从需求梳理到E-R建模、关系模式规范化、物理存储设计的完整流程理解挂号、诊断、收费等门诊业务的数据组织方式也可作为撰写课程设计论文的结构与内容参考。目前已有2832人学习下载具备一定的参考热度。1. 医院门诊管理系统数据库设计从课程设计到能跑起来的 MySQL 库医院门诊管理系统这个题目几乎每年都会出现在数据库课程设计的选题清单里。很多人第一反应是去搜一份现成的.doc文档把表结构抄下来交差。但真正做过一遍的人都知道这个题目的价值不在文档本身而在于你能不能把「挂号—就诊—开方—收费—发药」这条业务链在 MySQL 里完整地建模出来并且让 SQL 跑得通、查得对。我见过太多同学的库建到一半就卡住挂号表和就诊表的关系理不清处方明细和药品库存对不上收费记录重复插入。这篇笔记就按一线做项目的思路把医院门诊管理系统的数据库设计从需求拆解、表结构落地、约束与索引、到常见翻车点讲透。适合正在做数据库课程设计、需要交一份能演示的 MySQL 库、或者想拿这个题目练手的同学。读完你应该能自己从零建出一个能插入数据、能联表查询、能解释每张表为什么这么设计的门诊库。2. 门诊业务到底要落哪几张表先画业务流再定实体2.1 从挂号到发药把业务流拆成实体数据库设计最容易犯的错是打开 Navicat 就开始建表。我一般会先在纸上把门诊一天的业务流走一遍患者到院 → 挂号选科室、选医生、付挂号费→ 候诊 → 医生接诊写病历、开处方、开检查→ 患者缴费 → 药房发药 / 检查科室执行。这条链上每一个「动作」和「对象」都是潜在的实体。拆实体有个笨办法但很管用把业务流里的名词圈出来。患者、科室、医生、排班、挂号单、病历、处方、处方明细、药品、收费单、收费明细。这些名词里有的是独立存在的对象患者、医生、药品有的是两个对象之间的关系排班是医生和科室、时间的组合挂号单是患者和排班的组合。区分「实体」和「关系」直接决定了你后面是建一张表还是建两张表加外键。以挂号为例。很多课程设计文档里把挂号做成患者表的一个字段这是典型的翻车起点。一个患者今天挂内科、下周挂外科挂号记录会有多条塞进患者表就没法存。正确做法是单独建registration表用patient_id和schedule_id两个外键指向患者和排班。同理处方和处方明细必须拆开处方是「一次开方行为」明细是「这次开了哪几种药、各多少」。拆开之后一张处方对应多条明细查询和统计才有得做。这里给一个我常用的实体清单你可以对照自己的需求增删实体/关系对应表名说明患者patient独立实体存基本信息科室department独立实体医生doctor独立实体关联科室排班schedule关系医生科室时段挂号单registration关系患者排班病历medical_record关系挂号单诊断处方prescription关系病历药品集合处方明细prescription_item关系处方药品数量药品drug独立实体含库存收费单payment关系挂号单金额2.2 主键、外键、业务主键怎么选实体定完接下来是主键。课程设计里最常见的两种做法用自增整数做主键或者用业务编号比如挂号单号REG20240501001做主键。我的建议是自增整数做主键业务编号单独加一个唯一索引字段。原因很实际业务编号会变规则今天REG开头明天可能加个院区前缀而且业务编号是字符串做外键关联时索引体积大、联表慢。自增主键稳定、紧凑业务编号只用来给用户看和做查询条件。外键要不要加是个有争议的点。教学场景我建议加因为外键能帮你自动挡住脏数据比如往挂号表插一个不存在的patient_id数据库直接报错省得你写代码去校验。生产环境很多团队会去掉外键改由应用层保证那是为了分库分表和写入性能课程设计阶段没必要。加外键时注意ON DELETE和ON UPDATE的行为患者被删除时他的挂号记录怎么办一般设RESTRICT不允许删有挂号记录的患者避免历史数据丢失。业务主键的唯一约束别忘了。挂号单号、处方号、收费单号这些字段要加UNIQUE否则并发插入时可能生成重复编号。虽然课程设计里并发场景少但养成习惯没坏处。2.3 用一张 ER 关系表把外键方向定死外键方向搞反是新手高频错误。比如把doctor_id放在科室表里意思是「一个科室只有一个医生」这显然不对。判断方向的方法看「多」的一方。一个科室有多个医生所以doctor表里放department_id一个医生有多个排班所以schedule表里放doctor_id。我把核心表的外键方向列成一张表建表时照着对表外键字段指向关系doctordepartment_iddepartment.id多对一scheduledoctor_iddoctor.id多对一registrationpatient_idpatient.id多对一registrationschedule_idschedule.id多对一medical_recordregistration_idregistration.id一对一prescriptionrecord_idmedical_record.id多对一prescription_itemprescription_idprescription.id多对一prescription_itemdrug_iddrug.id多对一paymentregistration_idregistration.id一对一这张表定完建表顺序也就出来了先建没有外键的department、patient、drug再建doctor然后schedule最后建带多个外键的registration、prescription_item。顺序错了会因为外键指向的表不存在而建表失败。3. 建表 SQL 逐张写字段类型和约束怎么定3.1 基础表患者、科室、医生、药品先建四张不依赖别人的表。字段类型的选择原则能用定长就别用变长能用整数就别用字符串。手机号用CHAR(11)而不是VARCHAR因为长度固定金额用DECIMAL(10,2)而不是FLOAT浮点数算钱会出现0.10.20.30000000000000004这种玄学问题。-- 科室表最基础无外键 CREATE TABLE department ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 科室主键, name VARCHAR(50) NOT NULL UNIQUE COMMENT 科室名称, location VARCHAR(100) COMMENT 科室位置, phone VARCHAR(20) COMMENT 科室电话 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT科室表; -- 患者表身份证号做唯一约束避免重复建档 CREATE TABLE patient ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 患者主键, name VARCHAR(50) NOT NULL COMMENT 姓名, gender CHAR(1) NOT NULL DEFAULT U COMMENT 性别 M/F/U, birth_date DATE COMMENT 出生日期, id_card CHAR(18) NOT NULL UNIQUE COMMENT 身份证号, phone CHAR(11) COMMENT 手机号, address VARCHAR(200) COMMENT 联系地址, created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 建档时间 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT患者表; -- 医生表关联科室 CREATE TABLE doctor ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 医生主键, name VARCHAR(50) NOT NULL COMMENT 姓名, department_id INT NOT NULL COMMENT 所属科室, title VARCHAR(20) COMMENT 职称, phone CHAR(11) COMMENT 手机号, CONSTRAINT fk_doctor_dept FOREIGN KEY (department_id) REFERENCES department(id) ON DELETE RESTRICT ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT医生表; -- 药品表库存字段单独放方便扣减 CREATE TABLE drug ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 药品主键, name VARCHAR(100) NOT NULL COMMENT 药品名称, spec VARCHAR(50) COMMENT 规格, unit VARCHAR(10) COMMENT 单位, price DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT 单价, stock INT NOT NULL DEFAULT 0 COMMENT 库存数量, UNIQUE KEY uk_drug_name_spec (name, spec) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT药品表;逻辑说明department.name加UNIQUE防止建出两个「内科」patient.id_card加UNIQUE是门诊建档的核心约束同一个人不能建两次档drug用(name, spec)联合唯一因为同名不同规格的药是两种药。参数上utf8mb4是为了存生僻字姓名InnoDB是为了支持外键和事务这两个别改。3.2 业务表排班、挂号、病历、处方这四张表是门诊系统的核心外键多、约束多建的时候要小心顺序。-- 排班表医生在某天某个时段出诊 CREATE TABLE schedule ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 排班主键, doctor_id INT NOT NULL COMMENT 医生, work_date DATE NOT NULL COMMENT 出诊日期, time_slot VARCHAR(20) NOT NULL COMMENT 时段 上午/下午, total_slots INT NOT NULL DEFAULT 0 COMMENT 总号源, left_slots INT NOT NULL DEFAULT 0 COMMENT 剩余号源, CONSTRAINT fk_schedule_doctor FOREIGN KEY (doctor_id) REFERENCES doctor(id) ON DELETE RESTRICT, UNIQUE KEY uk_doctor_date_slot (doctor_id, work_date, time_slot) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT排班表; -- 挂号表患者挂某个排班的号 CREATE TABLE registration ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 挂号主键, reg_no VARCHAR(30) NOT NULL UNIQUE COMMENT 挂号单号, patient_id INT NOT NULL COMMENT 患者, schedule_id INT NOT NULL COMMENT 排班, reg_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 挂号时间, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待诊 1已诊 2退号, fee DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT 挂号费, CONSTRAINT fk_reg_patient FOREIGN KEY (patient_id) REFERENCES patient(id) ON DELETE RESTRICT, CONSTRAINT fk_reg_schedule FOREIGN KEY (schedule_id) REFERENCES schedule(id) ON DELETE RESTRICT ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT挂号表; -- 病历表一次挂号对应一份病历 CREATE TABLE medical_record ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 病历主键, registration_id INT NOT NULL UNIQUE COMMENT 挂号单, chief_complaint VARCHAR(500) COMMENT 主诉, diagnosis VARCHAR(500) COMMENT 诊断, created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 就诊时间, CONSTRAINT fk_record_reg FOREIGN KEY (registration_id) REFERENCES registration(id) ON DELETE RESTRICT ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT病历表; -- 处方表一份病历可开多张处方 CREATE TABLE prescription ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 处方主键, presc_no VARCHAR(30) NOT NULL UNIQUE COMMENT 处方号, record_id INT NOT NULL COMMENT 病历, created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 开方时间, CONSTRAINT fk_presc_record FOREIGN KEY (record_id) REFERENCES medical_record(id) ON DELETE RESTRICT ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT处方表;逻辑说明schedule的uk_doctor_date_slot保证同一个医生同一天同一时段只有一条排班避免重复放号registration.reg_no唯一防止单号重复medical_record.registration_id加UNIQUE是因为一次挂号只对应一份病历这是一对一关系用唯一约束强制。status用TINYINT存状态码而不是字符串查询快、占空间小但要在注释里写清楚每个数字的含义否则过两周自己都看不懂。3.3 明细表处方明细和收费明细明细表是「一对多」里「多」的那一端字段少但插入频繁。-- 处方明细一张处方多种药 CREATE TABLE prescription_item ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 明细主键, prescription_id INT NOT NULL COMMENT 处方, drug_id INT NOT NULL COMMENT 药品, quantity INT NOT NULL DEFAULT 1 COMMENT 数量, usage_desc VARCHAR(200) COMMENT 用法用量, CONSTRAINT fk_item_presc FOREIGN KEY (prescription_id) REFERENCES prescription(id) ON DELETE CASCADE, CONSTRAINT fk_item_drug FOREIGN KEY (drug_id) REFERENCES drug(id) ON DELETE RESTRICT ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT处方明细表; -- 收费单一次挂号对应一张收费单 CREATE TABLE payment ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 收费主键, pay_no VARCHAR(30) NOT NULL UNIQUE COMMENT 收费单号, registration_id INT NOT NULL UNIQUE COMMENT 挂号单, total_amount DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT 总金额, pay_method VARCHAR(20) COMMENT 支付方式, pay_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 收费时间, CONSTRAINT fk_pay_reg FOREIGN KEY (registration_id) REFERENCES registration(id) ON DELETE RESTRICT ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT收费单表;逻辑说明prescription_item的外键用ON DELETE CASCADE因为明细脱离处方没有意义删处方时明细跟着删是合理的而drug_id用RESTRICT药品被引用时不允许删。payment.registration_id加UNIQUE保证一次挂号只收一次费防止重复收费。金额字段统一DECIMAL(10,2)别用FLOAT。提示建表顺序必须是 department → patient → drug → doctor → schedule → registration → medical_record → prescription → prescription_item → payment。顺序错了会报errno: 150外键错误。4. 索引、约束和事务让库不只是能建还要能用4.1 该加索引的字段和不该加的字段索引不是越多越好。每加一个索引插入和更新都要多维护一棵 B 树。门诊系统里真正需要索引的是高频查询条件患者按手机号查、挂号按日期查、处方按病历查。-- 患者按手机号查询 CREATE INDEX idx_patient_phone ON patient(phone); -- 挂号按日期和状态查比如查今天所有待诊 CREATE INDEX idx_reg_time_status ON registration(reg_time, status); -- 处方明细按药品统计用量 CREATE INDEX idx_item_drug ON prescription_item(drug_id);逻辑说明idx_reg_time_status是联合索引把reg_time放前面是因为日期范围查询更常见status放后面用于过滤。联合索引有最左前缀原则WHERE reg_time ... AND status ...能命中单独WHERE status ...命中不了。参数上区分度低的字段比如性别只有 M/F/U不要单独建索引建了也用不上。不该加索引的patient.address这种长文本、medical_record.chief_complaint这种大字段建索引体积大且查询少。外键字段 MySQL 会自动建索引不用手动再加。4.2 用事务保证挂号扣号和收费一致门诊系统里有两个操作必须用事务包起来否则数据会不一致。第一个是挂号插入挂号记录的同时要扣减排班的剩余号源。如果插入成功但扣号失败号源就超卖了。START TRANSACTION; -- 扣号源同时检查是否还有号 UPDATE schedule SET left_slots left_slots - 1 WHERE id 1 AND left_slots 0; -- 检查影响行数如果为 0 说明没号了回滚 -- 应用层判断 affected_rows 1 才继续 INSERT INTO registration (reg_no, patient_id, schedule_id, fee) VALUES (REG20240501001, 1, 1, 10.00); COMMIT;逻辑说明UPDATE ... WHERE left_slots 0这个条件很关键它把「检查」和「扣减」合成一个原子操作避免先查后扣之间的并发问题。应用层拿到affected_rows如果是 0 就ROLLBACK并提示无号。第二个事务是收费插入收费单的同时更新挂号状态为已收费两个操作要么都成功要么都失败。4.3 视图和存储过程课程设计里的加分项课程设计如果只建表分数一般。加一两个视图和存储过程能体现你理解「数据库不只是存数据」。视图适合把常用的联表查询封装起来比如「今日待诊患者列表」CREATE VIEW v_today_waiting AS SELECT r.reg_no, p.name AS patient_name, d.name AS doctor_name, dep.name AS dept_name, r.reg_time FROM registration r JOIN patient p ON r.patient_id p.id JOIN schedule s ON r.schedule_id s.id JOIN doctor d ON s.doctor_id d.id JOIN department dep ON d.department_id dep.id WHERE DATE(r.reg_time) CURDATE() AND r.status 0;逻辑说明视图把五张表的联表逻辑封装成一个「虚拟表」查询时直接SELECT * FROM v_today_waiting就行。注意视图不存数据每次查都实时算所以底层表的索引要建好。存储过程可以封装「开处方并扣库存」这种多步操作但课程设计里视图的性价比更高先把这个做扎实。5. 避坑与排查课程设计里最容易翻车的 5 个点5.1 外键报错 errno 150建表顺序和字段类型不一致现象建registration表时报ERROR 1005 (HY000): Cant create table ... (errno: 150)。原因有两个一是被引用的表还没建二是外键字段和主表主键的类型不完全一致比如主键是INT UNSIGNED而外键写成了INT。解决先按第 3 章的建表顺序建表再用SHOW CREATE TABLE department看主键的完整类型外键字段照抄包括UNSIGNED和字符集。5.2 中文乱码字符集没统一成 utf8mb4现象插入患者姓名「张伟」后查出来是问号或乱码。原因建表时用了默认字符集latin1或者连接串没指定字符集。解决建表语句统一加DEFAULT CHARSETutf8mb4连接时用jdbc:mysql://localhost:3306/clinic?useUnicodetruecharacterEncodingutf8。已经建好的表用ALTER TABLE patient CONVERT TO CHARACTER SET utf8mb4;转换。5.3 金额算错用了 FLOAT 导致精度丢失现象挂号费 10.00 加药费 23.50算出来是 33.499999。原因FLOAT和DOUBLE是二进制浮点存不了精确的十进制小数。解决所有金额字段用DECIMAL(10,2)Java 里对应BigDecimalPython 里用decimal.Decimal。已经建的表用ALTER TABLE payment MODIFY total_amount DECIMAL(10,2);改。5.4 重复挂号业务编号没加唯一约束现象同一患者同一时段出现两条挂号记录单号还一样。原因reg_no只设了NOT NULL没设UNIQUE应用层生成单号时并发插入。解决给reg_no加UNIQUE KEY插入时捕获唯一键冲突异常并重试。更稳的做法是用数据库序列或UUID生成单号但课程设计里加唯一约束就够了。5.5 删数据删不掉外键 RESTRICT 挡住了现象想删一个测试患者报Cannot delete or update a parent row。原因该患者有挂号记录外键设了ON DELETE RESTRICT。解决这是正常保护不是 bug。测试时先删挂号记录再删患者或者临时SET FOREIGN_KEY_CHECKS0;关掉检查仅限测试环境别在生产用。设计时想清楚哪些关系允许级联删除明细表可以CASCADE主数据表一律RESTRICT。6. 用 SQL 验证设计是否站得住三个必跑的查询设计完不验证等于没设计。我一般会跑三个查询来检验表结构和索引是否合理。第一个是「某患者完整就诊记录」把患者、挂号、病历、处方、明细全串起来能跑通说明外键方向没错SELECT p.name, r.reg_no, mr.diagnosis, d.name AS drug_name, pi.quantity FROM patient p JOIN registration r ON p.id r.patient_id JOIN medical_record mr ON r.id mr.registration_id JOIN prescription pr ON mr.id pr.record_id JOIN prescription_item pi ON pr.id pi.prescription_id JOIN drug d ON pi.drug_id d.id WHERE p.id_card 110101199001011234;第二个是「某天各科室挂号量统计」检验联合索引和分组查询SELECT dep.name, COUNT(*) AS reg_count FROM registration r JOIN schedule s ON r.schedule_id s.id JOIN doctor doc ON s.doctor_id doc.id JOIN department dep ON doc.department_id dep.id WHERE DATE(r.reg_time) 2024-05-01 GROUP BY dep.name ORDER BY reg_count DESC;第三个是「药品用量排行」检验明细表的聚合性能SELECT d.name, SUM(pi.quantity) AS total_used FROM prescription_item pi JOIN drug d ON pi.drug_id d.id GROUP BY d.id, d.name ORDER BY total_used DESC LIMIT 10;这三个查询跑通基本能证明你的库不是「只能建不能查」的花架子。跑的时候用EXPLAIN看执行计划重点看type是不是ref或rangekey有没有命中你建的索引。如果出现ALL全表扫描回去检查索引。最后说个我自己的习惯每次改完表结构我都会把建表 SQL 全部导出成一个schema.sql再写一个seed.sql塞几条测试数据然后在一个空库上从头跑一遍。这个动作救过我很多次——本地改着改着忘了同步换台机器就建不起来。课程设计答辩前老师大概率会让你当场演示建库能一条命令跑通整个 schema比什么都稳。希望帮到你。本文还有配套的精品资源点击获取
