多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

SQL Server病房管理系统课程设计:从E-R图到建表避坑指南

SQL Server病房管理系统课程设计:从E-R图到建表避坑指南 简介这份《数据库课程设计》大作业文档面向高校计算机相关专业学生聚焦医院病房管理系统的完整设计与开发适合正在准备数据库课程设计或需要SQL Server实战案例的学习者。文档围绕科室、病房、医生、病人四类实体的业务关系展开涵盖需求分析、概念结构设计、逻辑结构设计、物理实现、功能调试、前台软件设计与设计总结等完整章节并给出数据字典、E-R图、PDM图及一对一、一对多关系的表结构转化方案。资源包共1个docx文件约1.79MB内容为可直接参考的课程设计报告目录结构清晰便于按模块查阅与借鉴。目前已有5902人学习下载读者可从中获取完整的赛题方案、数据库建模思路、SQL Server建表与索引优化方法以及系统测试与排错经验适合作为课程设计模板或数据库综合训练的参考材料。1. 从一份课程设计文档说起病房管理系统到底能跑通什么如果你正在做 SQL Server 数据库课程设计大概率会遇到一个尴尬局面需求分析写得头头是道E-R 图画得密密麻麻真到建表那一刻却卡在外键该加在哪张表上。这份《医院病房管理系统设计与开发》文档就是冲着这个痛点来的——它把需求分析、概念结构、逻辑结构、物理实现、功能调试、前台软件串成了一条完整链路核心业务规则只有四条一个科室有多个病房和多个医生一个病房只属于一个科室一个医生只属于一个科室一个医生负责多个病人但一个病人只有一个主管医生。适合数据库原理刚学完、需要交大作业的在校生也适合想拿一个完整案例练手 SQL Server 建表、视图、存储过程的人。文档里最值钱的部分不是 E-R 图本身而是它把一对多和一对一关系怎么落到物理表上讲清楚了这正是很多人翻车的地方。2. 需求到 E-R 图四个实体和三条关系怎么拆2.1 先锁定实体和属性别急着画图文档里把系统拆成四个实体科室 Office、病房 Room、医生 Doctor、病人 Sick。每个实体的属性在数据字典里列得很细我按建表需要重新整理一遍因为文档里的数据项定义和后面物理实现的字段有出入直接照抄会踩坑。科室实体科室名称 OName、科室地址 OAddr、科室电话 OTele。注意文档里科室表还塞了一个 DNo 外键指向医生表这个设计有问题后面避坑章节会展开。病房实体病房号 RNo、床位号 RBed、所属科室 OName。医生实体工作证号 DNo、姓名 DName、年龄 Dage、职称 DDuty、所属科室 OName。病人实体病历号 SNo、姓名 SName、年龄 Sage、性别 Ssex、主管医生 DNo、病房号 RNo。这里有个容易忽略的点文档在数据字典里给病历号定义的是 varchar(10)取值范围 0001-9999但后面建表又写成 varchar(10)。用 varchar 存编号是常见做法因为编号可能带前导零用 int 会把 0001 变成 1查询和显示都会出问题。我一般建议编号类字段统一用 char 或 varchar长度按实际最大位数留余量。2.2 三条关系的基数判断文档明确写了三条关系科室与病房一对多。一个科室有多个病房一个病房只属于一个科室。外键 OName 放在病房表。科室与医生一对多。一个科室有多个医生一个医生只属于一个科室。外键 OName 放在医生表。医生与病人一对多。一个医生负责多个病人一个病人只有一个主管医生。外键 DNo 放在病人表。病房与病人文档在联系分析里写的是“一个病房可以入住多个病人一个病人只能入住一间病房”这其实也是一对多外键 RNo 放在病人表。所以病人表会同时持有 DNo 和 RNo 两个外键这是整个设计里外键最密集的地方。画 E-R 图时病人实体通过“诊治”联系连医生通过“入住”联系连病房两条联系都是多对一方向。2.3 从 E-R 到逻辑模型的转换规则文档在逻辑结构设计里给了转换方法我把它整理成可操作的判断流程一对一关系任选一端的主键作为另一端的外键或者两端合并。文档里科室和医生的关系其实被写成了“一个医生只能属于一个科室”这是一对多不是一对一。文档标题写“一对一关系的转化”但内容描述的是一对多这里表述有歧义实际建表按一对多处理即可。一对多关系在“多”的一端加“一”的一端的主键作为外键。病房表加 OName 外键指向科室医生表加 OName 外键指向科室病人表加 DNo 外键指向医生、加 RNo 外键指向病房。多对多关系本系统没有多对多不需要中间表。转换完成后得到五张表Office、Room、Doctor、Sick加上文档里科室表多出来的 DNo 字段。这个 DNo 字段是文档的一个设计瑕疵它让科室表和医生表互相引用形成循环外键插入数据时会死锁。正确做法是只保留医生表指向科室表的外键科室表不需要反向引用医生。3. 物理建表SQL Server 里把外键和约束一次做对3.1 建表顺序决定你能不能插进第一条数据有外键约束时插入顺序必须遵循依赖关系先插被引用的表再插引用方。本系统的依赖链是 Office → Room → Doctor → Sick。但文档里科室表又引用了医生表形成 Office ↔ Doctor 循环导致两张表都插不进去。我一般会先把科室表的 DNo 外键去掉按下面的顺序建表。-- 1. 科室表先建不引用任何表 CREATE TABLE Office ( OName VARCHAR(20) NOT NULL PRIMARY KEY, OAddr VARCHAR(20) NOT NULL, OTele VARCHAR(11) NOT NULL ); -- 2. 病房表引用科室 CREATE TABLE Room ( RNo VARCHAR(4) NOT NULL PRIMARY KEY, RBed VARCHAR(4) NOT NULL, OName VARCHAR(20) NOT NULL, CONSTRAINT FK_Room_Office FOREIGN KEY (OName) REFERENCES Office(OName) ); -- 3. 医生表引用科室 CREATE TABLE Doctor ( DNo VARCHAR(12) NOT NULL PRIMARY KEY, DName VARCHAR(20) NOT NULL, Dage INT NOT NULL, DDuty VARCHAR(20) NOT NULL, DSex VARCHAR(2) NOT NULL, OName VARCHAR(20) NOT NULL, CONSTRAINT FK_Doctor_Office FOREIGN KEY (OName) REFERENCES Office(OName) ); -- 4. 病人表引用医生和病房 CREATE TABLE Sick ( SNo VARCHAR(10) NOT NULL PRIMARY KEY, SName VARCHAR(10) NOT NULL, Sage INT NOT NULL, Ssex VARCHAR(2) NOT NULL, DNo VARCHAR(12) NOT NULL, RNo VARCHAR(4) NOT NULL, CONSTRAINT FK_Sick_Doctor FOREIGN KEY (DNo) REFERENCES Doctor(DNo), CONSTRAINT FK_Sick_Room FOREIGN KEY (RNo) REFERENCES Room(RNo) );这段代码和文档里的建表语句有几处不同都是踩过坑之后改的。第一文档把外键用 ALTER TABLE 单独加我直接写在 CREATE TABLE 里好处是建表时就能发现依赖顺序错误不用等到加约束才报错。第二文档里 DDuty 定义成 varchar(2)但职称可能是“主任医师”四个字varchar(2) 存不下我改成 varchar(20)。第三文档里 Ssex 和 DSex 定义成 varchar(10) 和 varchar(2)性别统一用 varchar(2) 就够存“男”或“女”。第四文档里病人表建表时漏了 Ssex 字段后面查询又要用这是明显的笔误。3.2 索引不是越多越好唯一索引要慎用文档里给 Doctor 和 Sick 的年龄字段建了唯一索引CREATE UNIQUE INDEX suoying1 ON Doctor(Dage); CREATE UNIQUE INDEX suoying1 ON Sick(Sage);这里有两个问题。第一两个索引同名 suoying1在同一个数据库里索引名必须唯一第二条会创建失败。第二年龄字段建唯一索引意味着同一年龄只能有一个医生、一个病人这显然不符合现实。唯一索引应该建在真正唯一的字段上比如病历号、工作证号但这些已经是主键SQL Server 会自动建唯一索引不需要重复建。如果确实想练索引我建议改成非唯一索引并且按查询场景来建-- 按年龄查询医生时用 CREATE INDEX IX_Doctor_Dage ON Doctor(Dage); -- 按年龄查询病人时用 CREATE INDEX IX_Sick_Sage ON Sick(Sage); -- 按科室查医生时用 CREATE INDEX IX_Doctor_OName ON Doctor(OName);索引的代价是插入和更新变慢因为要维护索引结构。课程设计数据量小索引效果看不出来但养成按查询条件建索引的习惯比背概念有用。3.3 视图和存储过程把常用查询封装起来文档里建了两个视图和两个存储过程我按它的思路重写一遍修掉语法问题。-- 视图1内科医生信息 CREATE VIEW V_InternalDoctor AS SELECT DNo, DName, Dage, DDuty FROM Doctor WHERE OName 内科; -- 视图2内科病人信息 CREATE VIEW V_InternalSick AS SELECT S.SNo, S.SName, S.Ssex, D.DName FROM Sick S JOIN Doctor D ON S.DNo D.DNo WHERE D.OName 内科;文档里视图 P2 的 SELECT 写了 SNo, SName, SSex但病人表字段是 Ssex 不是 SSex而且没有把 Doctor 表的 DName 选出来视图里看不到医生姓名。我改成 JOIN 写法把医生姓名带出来。存储过程部分文档用 SNo 和 DNo 作为参数语法基本正确但缺少错误处理。我一般会加上存在性判断CREATE PROCEDURE GetSickByNo SNo VARCHAR(10) AS BEGIN IF EXISTS (SELECT 1 FROM Sick WHERE SNo SNo) SELECT * FROM Sick WHERE SNo SNo; ELSE SELECT 未找到该病历号 AS Msg; END;存储过程的好处是把查询逻辑放在数据库端前台只需要传参数。课程设计里前台如果用 C# 或 Java调用存储过程比拼 SQL 字符串更安全能防注入。4. 功能调试增删改查里最容易翻车的四个地方4.1 插入数据时的外键顺序调试阶段第一件事是插测试数据。顺序必须是 Office → Room → Doctor → Sick。如果先插 Sick会因为 DNo 和 RNo 在 Doctor 和 Room 里不存在而报外键冲突。文档里没有明确写插入顺序但这是实际调试时第一个会遇到的报错。-- 先插科室 INSERT INTO Office VALUES (内科, 住院部3楼, 0571-88880001); INSERT INTO Office VALUES (外科, 住院部4楼, 0571-88880002); -- 再插病房 INSERT INTO Room VALUES (3001, 01, 内科); INSERT INTO Room VALUES (3002, 02, 内科); INSERT INTO Room VALUES (4001, 01, 外科); -- 再插医生 INSERT INTO Doctor VALUES (D001, 张医生, 45, 主任医师, 男, 内科); INSERT INTO Doctor VALUES (D002, 李医生, 38, 副主任医师, 女, 内科); INSERT INTO Doctor VALUES (D003, 王医生, 50, 主任医师, 男, 外科); -- 最后插病人 INSERT INTO Sick VALUES (0001, 赵某, 30, 男, D001, 3001); INSERT INTO Sick VALUES (0002, 钱某, 25, 女, D002, 3002);4.2 删除数据时的引用约束删除比插入更容易翻车。如果直接删 Doctor 表里的一条记录而 Sick 表里有病人引用这个 DNo会报外键冲突。解决办法有两种先删引用方数据或者建表时设置级联删除。课程设计里我建议先手动删引用方因为级联删除容易误删。-- 先删该医生负责的病人 DELETE FROM Sick WHERE DNo D001; -- 再删医生 DELETE FROM Doctor WHERE DNo D001;4.3 更新主键的连锁反应文档里没有提更新操作但调试时一定会遇到。如果修改 Doctor 表的 DNoSick 表里引用这个 DNo 的记录不会自动更新会变成孤儿记录。SQL Server 默认阻止更新被引用的主键除非设置了 ON UPDATE CASCADE。我一般不建议更新主键编号一旦分配就固定要改就删了重插。4.4 查询多表关联时的字段歧义文档里查询内科病人信息的 SQL 用了FROM Sick, Doctor WHERE OName内科 AND Sick.DNoDoctor.DNo这是隐式 JOIN。如果两个表都有同名字段比如 DNoSELECT 时不加表前缀会报歧义错误。我改成显式 JOIN 更清晰SELECT S.SNo, S.SName, S.Ssex, D.DName, D.DDuty FROM Sick S INNER JOIN Doctor D ON S.DNo D.DNo WHERE D.OName 内科;显式 JOIN 把连接条件和过滤条件分开可读性更好也不容易漏写连接条件导致笛卡尔积。5. 避坑与排查课程设计里五个血泪教训5.1 科室表和医生表互相引用导致死锁现象建表时先建 Office 再建 DoctorOffice 里有个 DNo 外键指向 DoctorDoctor 里有个 OName 外键指向 Office两张表都建不出来报“引用了无效的表”。原因循环外键。A 表引用 B 表B 表又引用 A 表创建任何一张时另一张还不存在。解决去掉科室表的 DNo 字段。科室和医生的关系是一对多外键只需要放在医生表。如果业务上确实需要科室负责人信息可以加一个“负责人工作证号”字段但不加外键约束或者用触发器维护。5.2 varchar 长度不够导致插入截断现象插入职称“主任医师”时只存进去“主任”后面两个字丢了。原因文档里 DDuty 定义成 varchar(2)一个汉字占两个字节varchar(2) 只能存一个汉字。解决职称字段改成 varchar(20) 或 nvarchar(10)。SQL Server 里如果字段要存中文用 nvarchar 更稳妥因为 nvarchar 按字符计数varchar 按字节计数中文环境下容易算错长度。5.3 唯一索引建在年龄字段上导致第二条数据插不进去现象插入第二个 45 岁的医生时报“违反唯一索引约束”。原因文档里给 Dage 建了唯一索引年龄不唯一。解决删掉唯一索引改成普通索引。唯一索引只能建在业务上真正唯一的字段比如身份证号、病历号。5.4 视图里字段名写错导致查询失败现象创建视图时报“列名 SSex 无效”。原因病人表字段是 Ssex视图里写成了 SSex大小写不一致。SQL Server 默认不区分大小写但字段名拼写错误仍然会报错。解决建视图前先用sp_help Sick查看表结构确认字段名。或者用SELECT * FROM Sick先跑一遍看返回的列名。5.5 存储过程参数长度小于字段长度导致查不到数据现象调用EXEC GetSickByNo 0001返回空结果但表里明明有这条记录。原因存储过程参数定义成SNo VARCHAR(12)字段是VARCHAR(10)传参时如果带了空格或长度不匹配比较会失败。解决参数类型和长度与字段保持一致。调用时用EXEC GetSickByNo SNo 0001明确传参避免位置参数错位。6. 前台软件对接与进阶验证把数据库跑成能演示的系统前台部分文档只给了目录没有具体代码。按课程设计的常见做法用 C# WinForm 或 Java Swing 连 SQL Server 都行。我一般会先写一个连接测试确认数据库能通再往上搭界面。// C# 连接 SQL Server 的测试代码 string connStr Serverlocalhost;DatabaseHospitalDB;Integrated SecurityTrue;; using (SqlConnection conn new SqlConnection(connStr)) { try { conn.Open(); SqlCommand cmd new SqlCommand(SELECT COUNT(*) FROM Sick, conn); int count (int)cmd.ExecuteScalar(); MessageBox.Show($病人记录数{count}); } catch (Exception ex) { MessageBox.Show($连接失败{ex.Message}); } }连接字符串里Integrated SecurityTrue表示用 Windows 身份验证不需要输账号密码。如果 SQL Server 配的是混合验证改成User Idsa;Password你的密码;。数据库名要和建库时一致我一般建库叫 HospitalDB建表前先USE HospitalDB。前台功能按文档的模块划分病人管理模块做查询、插入、删除、修改四个按钮医生管理模块同理管理员模块做病房和科室的维护。每个按钮对应一条 SQL 或一个存储过程。查询用 DataGridView 显示结果插入和修改用文本框收集输入删除前弹确认框。验证系统是否跑通我一般走一遍完整流程新增一个科室 → 新增一个病房 → 新增一个医生 → 新增一个病人 → 查询该病人的主治医生和病房信息 → 修改病人病房号 → 删除该病人 → 删除该医生 → 删除该病房 → 删除该科室。如果每一步都不报错说明外键约束和业务逻辑都对。如果中间某一步报错根据错误信息定位是哪张表的外键或约束问题。进阶一点的做法是加触发器。比如病人出院时自动删除病人记录并释放床位可以用 AFTER DELETE 触发器实现。但触发器调试麻烦课程设计里不是必须学有余力再碰。从那以后我每次做数据库课程设计建表前都强制走一遍依赖顺序检查把有外键关系的表按被引用顺序排好再动手写 CREATE TABLE。这个习惯帮我省掉了大量返工时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表