
简介医院门诊管理系统数据库设计课程设计文档适合软件工程或数据库课程设计的学生参考用于完成医院门诊业务从需求梳理到数据库落地的完整设计过程。文档基于结构化分析方法围绕病人信息、医生信息、药品信息、诊断信息等核心数据详细展开数据流程图、数据字典、E-R图设计并覆盖概念设计、逻辑设计、物理设计以及SQL Server 2008环境下的数据库实施与测试可直接借鉴其模式构建同类管理系统。压缩包内共有1个doc文件大小731KB以课程论文形式呈现结构包含目录、需求分析、数据库结构设计、物理设计与实施测试等章节便于按章节查阅和修改。已有2831人学习浏览对于正在撰写数据库课程设计报告、需要规范图表和数据库建表思路的读者是一份实用且具参考价值的范例。1. 门诊系统的需求边界挂号、诊断、收费三条主数据流门诊管理系统是典型的OLTP场景看起来只是“病人挂号、医生开单、药房发药”但真正把数据模型拆开时要面对的坑不少同一张收费单会被挂号、药品、检查三个环节引用同一个病人可能先欠费后补缴医生和科室之间还存在排班带来的动态关系。这份课程设计选的是最稳妥的结构化路线——先画数据流程图DFD再写数据字典最后用E-R图收敛成关系模式。整个过程没有引入过于花哨的设计反而更适合拿来当数据库课程的完整范本。对还在做课程设计、或者想快速搭一套门诊业务演示库的读者来说这份文档的价值在于它把数据流、数据字典、概念模型到物理实施的完整链路都走了一遍SQL Server和Oracle两套建库脚本也齐备直接照着改改就能用。2. E-R图设计8个实体如何收敛成8个关系模式2.1 分E-R图与全局E-R图的合并流程需求分析阶段产出的数据字典里定义了35个数据项、8个数据结构、11个数据流和6个处理逻辑。在此基础上画分E-R图时论文按照挂号收费、诊断、取药三张第二层数据流图分别建立局部模型。挂号收费这组包含病人、医生、科室、挂号单、收费单五个实体诊断这组增加了处方、诊断结果取药这组则把药品实体与处方关联起来。关键的动作是合并分E-R图时消除冗余。常见的冗余有两种一种是属性冗余比如挂号单里已经通过病人编号关联到病人姓名那挂号单实体里就没必要再存一遍“病人姓名”另一种是关系冗余比如病人和医生之间既有“挂号”联系又存在“诊断”联系如果分图里重复画了合并时就要按实际业务保留一条路径。文档里的全局E-R图最终保留了病人-医生、病人-挂号单、科室-医生、医生-诊断结果、诊断结果-处方单、处方单-药品-收费单这六组主要联系其中处方单、收费单和药品构成三元联系这是后续逻辑设计里最值得留意的地方。2.2 实体转关系模式的三种合并策略逻辑设计阶段要把E-R图映射到关系模型。这里遵循的标准做法是一个实体转一张表1:1联系可以并入任意一端1:n联系并入n端m:n联系必须独立建表。本系统的八张表里科室与医生是1:n所以把科室号并入医生表医生与病人是1:n所以医生号放进病人表病人与挂号单是1:1挂号单并入病人端。最终形成下面这套关系模式关系模式主键外键说明科室 DepartmentDp_no-科室基本信息医生 DoctorDnoDp_no医生归属科室病人 PatientPnoDno病人登记及主治医生药品 MedicineMno-药品价格与库存收费单 BillBno-收费记录属性含金额和方式处方单 PrescriptionPr_noMno, Bno处方关联药品与收费单诊断结果 Diagnose(Dno, Pno)Pr_no复合主键记录确诊信息挂号单 RegisterRnoPno, Bno挂号记录这里有个容易被课程设计毙掉的地方诊断结果表把(Dno, Pno)作为复合主键隐含了一个假设——同一个医生对一个病人只会确诊一次。真实门诊里复诊场景很常见所以更稳妥的做法是引入独立的诊断流水号。但如果是教学演示复合主键能体现“医生-病人-病名-处方”的完整关联答辩时能说出取舍理由即可。逻辑结构定义时文档用表格列出了每个属性的类型、长度、是否主外键、约束条件这一步在课程设计里属于“用户子模式建立”的范畴用来证明你考虑了不同角色的数据访问视角。实际工程中这也对应数据库设计文档里的字段说明书。2.3 三范式检查与常见误用关系模式到了三范式核心是消除传递依赖。以病人表为例Pno决定DnoDno决定Dname那么如果把医生姓名放进病人表就产生了传递依赖病人表就不再满足3NF。本系统的做法是把医生姓名留在医生表病人表只保留Dno作为外键查询时通过JOIN取名字代价是多一次关联换来的是一致性——医生改名后只需更新一处。非主属性对码的部分依赖同样要清理。例如挂号单表如果同时放了病人姓名和病人编号而病人姓名可由病人编号推导就会造成冗余。检查时可以按规范化的定义逐表过一遍也可以直接写SQL查依赖我个人会在PowerDesigner这类工具里画好模型后用它的检查功能跑一遍比人工核对快得多。另外一个常见误用是过度规范化——为了消除冗余把1:1关系拆成两张表比如把挂号单拆成“挂号基本信息表”和“挂号扩展信息表”。门诊这类场景查询频率远高于写入频率表拆得太碎反而让每次挂号查询都要做三次JOIN。这套设计的分寸掌握得比较合适挂号单保持单表收费单也保持单表只在必要的地方用外键关联。3. SQL Server 2008落地建表、视图、索引与存储过程3.1 物理设计字段类型与约束的选择进入物理设计阶段数据库名为Hospital八张基本表的建表语句在课程设计文档里完整给出。类型选择上全部用varchar(20)做主键这个粒度对课程设计演示够用但生产环境里我一般会改用自增int或bigint或者用业务编码如“P20260601-0001”这类定长字符串。varchar(20)做主键的问题在于一旦门诊量上来索引页分裂和存储开销都会被放大而且外键引用时每次关联都要做字符串比较。建表时值得注意的约束有三个。一是病人表的Page字段带check(Page0 and Page150)把年龄约束落到数据库层防止应用程序端漏校验二是医生表的Dp_no通过references Department(Dp_no)实现外键确保医生不会挂到不存在的科室三是处方单表里同时引用了Medicine和Bill两张表表达“一次处方对应一种药、一次缴费”的业务逻辑。外键要不要建课程设计里一般都会建但实际生产里很多团队会故意去掉外键把一致性交给应用层通过事务保证理由是避免锁竞争和死锁。站在教学角度保留外键是对的能体现参照完整性概念。3.2 视图层的查询路径设计六个视图对应六类高频查询场景-- 收费细则视图通过多表关联把诊断、处方、收费单据联成一条明细 create view BillDetail as select distinct Diagnose.pno, Bill.Bno, Bdate, Bmoney, Bway from Prescription, Bill, Diagnose, Register where Register.pno Diagnose.pno and (Diagnose.Pr_no Prescription.Pr_no and Prescription.bno Bill.bno or Register.bno Bill.bno)这段SQL拼接了四条表的笛卡尔积distinct用来去重。逻辑上是把一条挂号链路里可能出现的两条收费路径——挂号费和药费——都合并进同一个视图。注意到or条件的存在一条挂号单可能先产生挂号收费后续诊断才产生药品收费两个都满足时就会查出两条记录所以必须distinct。这里的隐式JOIN写法在SQL Server 2008里没问题但不推荐在更长查询里继续用可读性差且容易漏条件产生笛卡尔积。换成显式JOIN写法会更清晰create view BillDetail as select distinct r.pno, b.Bno, b.Bdate, b.Bmoney, b.Bway from Register r join Bill b on r.bno b.bno left join Diagnose d on d.pno r.pno left join Prescription p on p.Pr_no d.Pr_no and p.bno b.bno视图层的价值在于把复杂关联封装成“虚拟表”应用层只需select * from BillDetail就能拿到收费明细。剩下的病人-药品视图、诊断结果视图、医生病人视图、科室医生视图、病人挂号视图本质上都是两张表按外键关联后投影部分列代码模式一致。课程设计里用视图主要是为了体现数据库编程能力答辩时能说清“为什么用视图”——通常是复用查询、权限控制只暴露指定列、逻辑屏蔽应用层不直接感知表结构变更。3.3 索引的设计粒度索引部分文档给出三例Medicine(Mname)上的unique索引、Patient(pname)上的unique索引以及Bill(bno)上的普通升序索引。unique索引在这里承担的是唯一性约束职责防止药品名和病人名重复入库。从查询路径看真正高频的过滤条件是Rno挂号单号、Dno医生号、Pno病人编号这三者都是主键默认已有聚集索引不需要额外建索引。可能欠缺的是外键字段的索引Prescription表的Mno经常作为查询条件关联Medicine如果Mno上没有索引每次关联都要全表扫描。课程设计里没有覆盖这一点但实际演示时插入几千条数据后就能感觉到差异。建议在做性能测试前对外键列统一补上非聚集索引。3.4 存储过程把业务流程封装进数据库存储过程是这份设计最有含金量的部分。它把“添加病人-生成收费单-创建挂号记录”三步操作封装成一个原子流程对应挂号业务的addpatient存储过程create proc addpatient Rno varchar(20), Rway varchar(20), Pno varchar(20), Bno varchar(20), Pname varchar(20), Psex varchar(20), Page int, Dno varchar(20), Bmoney float as begin insert into Patient values(Pno, Pname, Psex, Page, Dno) insert into Bill values(Bno, GETDATE(), Bmoney, 挂号收费) insert into Register values(Rno, Rway, GETDATE(), Pno, Bno) end三个insert分表写入了病人、收费单、挂号单参数的顺序与三张表的字段顺序一一对应。这里有个明显的隐患如果第二个insert失败第一个insert已经提交会造成“有病人无挂号单”的脏数据。严格来说应该用begin tran/commit tran包一层事务或者直接声明为with execute as调用方并在过程内部管理事务。课程设计正文没提事务处理但答辩时几乎必问能主动补上会加分。addDiagnose存储过程逻辑同理完成“确诊开处方录入药费”三连操作。问题比addpatient更复杂因为它同时写了Bill、Prescription、Diagnose三张表还涉及收费金额的累计。这里我会补充一个参数说明Bmoney如果传的是药品总价Bill表会新增一条独立收费记录如果业务要求合并到挂号产生的Bill上就要在前面用update而不是insert。这个细节看你定义的收费粒度是“一次就诊一条收费单”还是“一个项目一张收费单”。另有两个存储过程值得注意change_tel用于更新科室电话change_med用于更新药品剩余量这类“单表更新”封装成存储过程意义不大更像是凑功能点。实际工程里我更倾向于让应用层直接执行update省一层数据库调用。但课程设计需要展示存储过程覆盖增改查多类操作所以保留可以理解。3.5 分组统计与条件查询存储过程末尾还藏着两个查询型过程Dept_Doc按科室统计医生人数Diag_p按病名筛选病人。按科室统计的实现用group by Dp_name加count(dno)结果集是科室名加人数两列。这类聚合查询放存储过程里确实比视图灵活可以带参数做动态筛选。但注意Diag_p统计感冒病人的过程是写死的条件where iname感冒这在演示时没问题实际使用时应改成iname参数否则每增加一种病就要新建一个过程。4. Oracle移植同一套模型的差异点与数据入库4.1 类型与语法的关键差异同一个设计从SQL Server移植到Oracle不是简单把脚本跑一遍就行至少有四处要改。日期类型SQL Server的date在Oracle里通常换成DATE带时间部分字符串长度语义不同varchar2(20)的单位是字节还是字符取决于字符集设置中文库下建议用varchar2(20 char)避免入库报超长。空字符串Oracle把空字符串当成NULLSQL Server允许这会导致插入空值时报约束错误。主键自增SQL Server可以用identity列Oracle 11g只能用序列加触发器或者直接在插入时用序列的nextval。语法兼容create proc在Oracle里是create or replace procedure参数前必须带方向修饰符比如pname改成p_name in varchar2。对应下面的建表对比-- SQL Server: identity自增主键 create table Bill ( Bno int identity(1,1) primary key, Bdate date, Bmoney float, Bway varchar(20) ); -- Oracle 11g: 序列 插入时显式取值 create sequence seq_bill start with 1 increment by 1; create table Bill ( Bno number(10) primary key, Bdate DATE, Bmoney number(10,2), Bway varchar2(20 char) ); -- 插入时: values(seq_bill.nextval, sysdate, 50.00, 挂号收费)这里int identity改成number(10)后主键生成方式从数据库自动变为主键值需外部供给。float改number(10,2)顺便解决了浮点金额精度问题这是Oracle方案里比原设计合理的地方金额用float会有二进制误差累计报表时容易对不上。4.2 数据入库的三条路线文档里的Oracle实施演示了数据库对象建立和数据入库两部分。实际生产或课程演示中往里灌数据常见有三条路线按效率从低到高排列第一条是逐条insert适合验证触发器、外键约束几十条数据手工造没问题。第二条是SQL*Loader适合从文本文件批量导入语法简单映射关系写在控制文件里几千条药品数据几秒就能入库。第三条是用存储过程循环造数适合生成压测数据比如往Register表插入10万条挂号记录。课程设计里一般数据量不大其实第一条就够。要点是注意外键插入顺序先科室再医生接着病人、药品、收费单最后才是处方和诊断结果否则外键约束会直接报ORA-02291。4.3 移植后的验证清单数据入库后要做的第一件事不是select *而是按外键顺序逐表核对行数-- 核对各表记录数与关联完整性 select (select count(*) from Register) as reg_cnt, (select count(*) from Bill) as bill_cnt, (select count(*) from Patient) as pat_cnt; -- 找出孤儿记录挂号单引用了不存在的病人 select r.Rno, r.Pno from Register r left join Patient p on r.Pno p.Pno where p.Pno is null;第二条SQL的left join写法能把孤儿记录揪出来。如果结果不为空说明入库顺序有误或外键约束被延迟检查了。这一步在答辩时非常实用能直观展示你对自己的数据心里有数。另外要注意的是Oracle的varchar2不能直接存储超过4000字节的字符串如果扩展字段描述应换成CLOB或拆表。这份设计里的字段全在20字符内不存在这个问题但如果有人想把检查结果、病史描述塞进病人表就要提前规划大字段策略。5. 答辩现场视图依赖、防重复与存储过程的事务边界课程设计做到能运行只是及格答辩时考官更关心边界条件。三个必考点视图更新问题、唯一索引防重复、存储过程事务边界。视图更新是高频提问点。基于多表join的视图如BillDetail默认不可更新因为SQL Server无法确定一行改动应该映射到哪张基表。考官问“这个视图能不能update”时正确回答是单表视图可以多表视图要看是否满足键保留表条件——即视图中是否有一张表的主键能唯一定位视图中每行。BillDetail因为有distinct和or逻辑肯定不可更新应该直接说明这类视图只读写操作走后端存储过程。防重复方面unique索引的唯一约束要防的是重复挂号。但不要把唯一性全部压在数据库层例如同一病人上午挂内科下午挂外科是合法业务用(Pno, Rdate, Dno)做唯一索引会误伤正常流程。正确粒度是按业务维度区分Rno本身是主键能防重复Pname上的唯一索引反而会让同名病人无法建档——文档里给Pname建unique_pname索引这个选择在真实环境里会踩坑。答辩时可以主动提起这个权衡说明如果业务上允许同名应改成非唯一索引或加身份证号字段。存储过程的事务边界是最后一个深渊。addpatient的三个insert没有包事务在快速连续插入时如果中途失败会出现部分写入。补上事务只需要包一层create proc addpatient Rno varchar(20), Rway varchar(20), Pno varchar(20), Bno varchar(20), Pname varchar(20), Psex varchar(20), Page int, Dno varchar(20), Bmoney float as begin begin try begin tran insert into Patient values(Pno, Pname, Psex, Page, Dno) insert into Bill values(Bno, GETDATE(), Bmoney, 挂号收费) insert into Register values(Rno, Rway, GETDATE(), Pno, Bno) commit tran end try begin catch rollback tran raiserror(挂号流程写入失败请检查参数, 16, 1) end catch endbegin try/begin catch在SQL Server 2008开始支持原设计漏掉这个防御是唯一需要改动才能过严苛评审的点。验证方法很直接故意把Pno传成一个已有主键触发器或约束会报错看前两张表是否留下脏数据。如果事务包装正确三条insert会一并回滚Register表行数保持不变。另一个值得展示的小技巧是用系统视图查依赖关系确认表的关联设计没有悬空引用select fk.name as 约束名, tp.name as 父表, ref.name as 引用表 from sys.foreign_keys fk join sys.tables tp on fk.parent_object_id tp.object_id join sys.tables ref on fk.referenced_object_id ref.object_id order by 父表;这条查询把库内所有外键约束和对应主从表关系一次性列出来可以用来核对逻辑设计文档里画的关联图和实际建库结果是否一致。课程设计里画了E-R图但建库时漏建外键的情况很常见跑一遍这条SQL就能在答辩前发现问题。整个项目最有复用价值的就是这套“数据流分析-关系模式-建库脚本-验证SQL”的链路下次换一个业务域比如图书馆借阅或在线考试直接把实体替换掉工作流完全不需要变。本文还有配套的精品资源点击获取