☰
医院管理系统数据库设计文档解读:从业务流到SQL建表实战
2026/10/27 11:14:21 网站建设 项目流程

简介:这份文档面向计算机专业学生、数据库课程设计者及医院信息化初学者,围绕医院管理系统数据库设计展开,帮助读者理解从需求分析到关系模型落地的完整思路。内容结合门诊与住院两大部门的业务流程,梳理挂号、收费、药房、检查、病案等科室的协作关系,并给出数据完整性、一致性、安全性、性能与可扩展性等设计要点,涉及ER图、实体表、外键关联与索引优化等知识点。资源包共1个docx文件,约398KB,以文字与结构图形式呈现需求分析与机构组织说明,便于对照课程作业或毕业设计参考。已有174人学习下载,适合需要一份完整数据库设计文档框架、用于模仿章节组织与需求描述写法的读者。

1. 拿到一份医院管理系统数据库设计文档,先看什么

一份 20 页的《医院管理系统数据库设计.docx》摆在面前,多数人的第一反应是翻到 ER 图或者建表语句,结果翻完发现通篇是需求描述和数据字典,连一行 SQL 都没有。这不是文档写砸了,而是它本来就不是代码包——它是一份从需求分析推到数据字典的完整设计底稿,价值在于把门诊和住院两条业务线的数据流、单据流转、字段定义全摊开了。你要拿它做课程设计、做毕设开题、或者给一个小型 HIS 原型搭库,这份文档能省掉大量业务调研时间。适合谁:数据库课程设计的学生、需要快速理解医疗业务实体关系的初级开发、以及要给医院类项目做数据建模的工程师。不适合谁:想直接复制建表脚本跑起来的人,得自己补 DDL。

2. 从业务流到实体:把文档里的文字拆成表结构

2.1 先分清门诊流和住院流,别一上来就画全局 ER 图

文档里把医院拆成门诊部门和住院部门两条主线,这个划分不是随便写的,它直接决定了你建表时的聚合边界。门诊流的骨架是:挂号单 → 门诊病历 → 门诊处方 / 检查单 → 收费 → 药房发药。住院流的骨架是:入院登记 → 病区床位分配 → 医嘱 → 药品请领 / 检查申请 / 手术申请 → 出院结算。两条流共享病人、医生、药品、收费项目这几个基础实体,但单据表几乎不重叠。

我一般会先把两条流各自画一张局部 ER 图,再合并。合并时重点看共享实体怎么挂外键:病人表被挂号单、病历、处方、住院记录同时引用,医生表被挂号单、病历、处方、医嘱、手术同时引用。如果你一上来画全局图,很容易把挂号单和住院记录硬塞进一张“就诊记录”表,后面发现门诊没有床位、住院没有挂号费,字段一半是空的。文档里挂号单和病案是分开定义的两个数据结构,这就是信号——别合并。

具体操作上,从文档的“数据结构”小节逐条抄出实体名和组成字段,先落一张实体清单:

实体来源章节关键字段归属业务流
挂号单数据结构挂号号、挂号类别、主治医师、挂号科室、挂号日期门诊
门诊病历数据结构病历号、病人姓名、主治医师、诊断时间、病历内容门诊
门诊处方数据结构处方号、主治医师、病人姓名、处方内容门诊
检查项目数据结构检查序号、检查医师、检查内容、检查结果、检查收费共用
医嘱数据结构诊断单序号、诊断类别、医嘱内容、主治医师、病人住院
手术数据结构手术序号、主刀医师、住院号、手术名称、手术日期、手术室住院
病案数据结构病案号、入院科室、入院日期、出院日期、出院诊断住院
药品数据结构药品编号、药品名、规格、单位、数量、单价、保质期共用
供应商数据结构供应商号、地址、电话共用
收费项目数据结构收费项目号、挂号费、药品费、检查费共用

这张表就是你后续写 DDL 的底稿。文档里数据项的类型长度也给了,比如挂号号 char(8)、订单号 char(20)、病区号 char(10),照抄就行,别自己改成 int 自增——医疗单据号往往有业务含义(比如含日期段或科室段),用自增会丢掉这层信息。

2.2 主键和外键怎么定:从数据字典的别名反推

文档的数据字典部分给了每个数据项的别名,比如挂号号 Gh_no、病历号 Bl_no、处方号 Cf_no、医生号 doctor_no。这些别名不是装饰,它们就是字段命名规范。建表时直接用别名做列名,后面写查询和对接前端都省事。

主键选择上,单据类实体用业务单号做主键(挂号号、处方号、检查序号),基础实体用编码做主键(病人号、医生号、药品编号)。外键关系从数据流的“数据来源 / 数据流向”里读:处方信息的数据来源是门诊医生和收费处、流向药房,说明处方表要挂医生号外键,同时收费记录要能回查处方号。医嘱的数据来源是住院医生、流向病人和病区科室,说明医嘱表挂医生号和病人号,病区科室通过住院记录关联。

一个容易翻车的地方:文档里“病人”这个数据结构同时出现在门诊和住院场景,但字段组成是一样的(病人号、姓名、性别、年龄、身份证号、出生日期、住址、联系方式)。这意味着病人表只建一张,门诊和住院都通过病人号引用,不要建两张病人表。同理医生表也只有一张,门诊医生和住院医生的差异(挂号费、出诊时间 vs 所在病区、负责病人)放到扩展表里,别在主表里堆可空字段。

2.3 用 SQL 把核心表落下来

下面这段 DDL 是按文档字段定义整理的,覆盖病人、医生、挂号单、门诊病历、门诊处方五张核心表,MySQL 语法,其他库改一下类型即可:

-- 病人表:门诊住院共用,主键病人号 CREATE TABLE patient ( people_no CHAR(8) NOT NULL COMMENT '病人号', name VARCHAR(32) NOT NULL COMMENT '姓名', sex CHAR(1) NOT NULL COMMENT '性别 M/F', age INT COMMENT '年龄', id_card VARCHAR(18) COMMENT '身份证号', birth_date DATE COMMENT '出生日期', address VARCHAR(128) COMMENT '住址', phone VARCHAR(20) COMMENT '联系方式', PRIMARY KEY (people_no) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 医生表:基础信息,职称区分主治/副主任/普通 CREATE TABLE doctor ( doctor_no CHAR(8) NOT NULL COMMENT '医生号', name VARCHAR(32) NOT NULL COMMENT '姓名', sex CHAR(1) NOT NULL, title VARCHAR(16) COMMENT '职称', dept_no CHAR(8) COMMENT '所在科室', phone VARCHAR(20), PRIMARY KEY (doctor_no) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 挂号单:门诊入口单据,挂病人和医生 CREATE TABLE registration ( gh_no CHAR(8) NOT NULL COMMENT '挂号号', gh_type VARCHAR(16) COMMENT '挂号类别', doctor_no CHAR(8) NOT NULL COMMENT '主治医师', patient_no CHAR(8) NOT NULL COMMENT '挂号人', dept_no CHAR(8) COMMENT '挂号科室', gh_date DATE NOT NULL COMMENT '挂号日期', PRIMARY KEY (gh_no), KEY idx_patient (patient_no), KEY idx_doctor (doctor_no), CONSTRAINT fk_reg_patient FOREIGN KEY (patient_no) REFERENCES patient(people_no), CONSTRAINT fk_reg_doctor FOREIGN KEY (doctor_no) REFERENCES doctor(doctor_no) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 门诊病历:一次就诊一份,挂病人和医生 CREATE TABLE medical_record ( bl_no CHAR(8) NOT NULL COMMENT '病历号', patient_no CHAR(8) NOT NULL, doctor_no CHAR(8) NOT NULL, diag_date DATETIME NOT NULL COMMENT '诊断时间', content TEXT COMMENT '病历内容', PRIMARY KEY (bl_no), KEY idx_patient (patient_no), CONSTRAINT fk_mr_patient FOREIGN KEY (patient_no) REFERENCES patient(people_no) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 门诊处方:挂病历和医生,处方内容存文本 CREATE TABLE prescription ( cf_no CHAR(8) NOT NULL COMMENT '处方号', bl_no CHAR(8) COMMENT '关联病历号', doctor_no CHAR(8) NOT NULL, patient_no CHAR(8) NOT NULL, content TEXT COMMENT '处方内容', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (cf_no), KEY idx_bl (bl_no), CONSTRAINT fk_cf_doctor FOREIGN KEY (doctor_no) REFERENCES doctor(doctor_no) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

逻辑说明:病人表和医生表是基础维表,先建;挂号单、病历、处方是事实表,通过外键挂上去。挂号单同时挂病人和医生,是因为文档里挂号单的组成字段明确有“主治医师”和“挂号人姓名”。处方表额外挂了 bl_no,这是从数据流“处方信息由门诊医生开出处方”推出来的——处方通常对应一次就诊,关联病历号能让“按病历查所有处方”这条查询走索引而不是全表扫。

参数说明:CHAR(8) 严格照文档的数据项长度,别改成 VARCHAR 图省事,医疗单据号定长是行业习惯,定长在索引里也更紧凑。ENGINE=InnoDB 是为了外键约束,如果你们课程要求用 MyISAM 或者要分库分表,外键得去掉改应用层保证。utf8mb4 是给病人姓名和病历内容留的,病历里可能有生僻字。索引只建了外键列和常用查询列,别一上来给每个字段都加索引,挂号单表每年万级数据量,索引多了写入反而慢。

3. 数据字典里的类型长度不是随便写的:字段设计避坑

3.1 char 和 varchar 的选择要看单据号语义

文档里几乎所有单据号都是 char(8),订单号是 char(20),病区号是 char(10)。有人觉得 char 浪费空间,全改成 varchar,结果发现按挂号号做前缀查询时性能没差别,但存储引擎对定长列的处理更稳定。更关键的是,单据号定长意味着业务上不允许你塞超长字符串,这是一种隐式约束。我一般照抄 char,只在备注类字段(病历内容、处方内容、检查分析)用 TEXT 或 VARCHAR(500) 以上。

年龄字段文档写的是 Int 3,这个要留意:年龄是随时间变的,存死值会导致病人跨年后年龄不对。常见做法是存出生日期,年龄在查询时算,或者存一个“建档时年龄”加“建档日期”,展示时动态推算。文档里病人表同时有年龄和出生日期,说明设计者已经意识到这个问题,你建表时把 age 保留为冗余展示字段,以 birth_date 为准。

3.2 金额字段别用 float

收费项目里的挂号费、药品费、检查费,文档标的是 Int 10。如果单位是分,用 INT 没问题;如果单位是元,用 INT 会丢小数。我见过有人直接 float 存金额,对账时 0.1+0.2 算出 0.30000000000000004,财务那边直接打回来。稳妥做法是 DECIMAL(10,2),或者按文档思路用 INT 存分。药品单价同理,别用 float。

3.3 库存字段的并发更新要加版本或行锁

文档在需求里明确写了“当库存少到一定程度,系统应提出警告”,还写了药房向药库领药、药库向供应商采购的流程。这意味着药品库存是个高频更新的字段。如果你按最朴素的UPDATE drug SET qty = qty - 1 WHERE drug_no = ?写,并发发药时会出现超卖。常见做法是加一个 version 列做乐观锁,或者发药时用UPDATE ... SET qty = qty - ? WHERE drug_no = ? AND qty >= ?并在应用层检查影响行数。文档没写这块,但这是落地时绕不开的。

4. 避坑:这份设计文档落地时的五个常见问题

现象一:照文档建了“处理方案”表,发现没有地方引用它。原因:文档的数据结构里有“处理方案”(处理方案号、医师号、开出时间、处理方案内容),但数据流和处理过程里没有明确它和处方、检查单的关系。解决:把处理方案理解成“医生对挂号病人的处置决策”,它应该是处方和检查单的上游。落地时要么把它做成处方表的一个 type 字段,要么单独建表并在处方表加 plan_no 外键,别让它悬空。

现象二:医嘱表同时挂了病人和主治医师,但住院号没地方放。原因:文档里医嘱的组成是“诊断单序号、诊断类别、启动日期、处理日期、遗嘱内容、主治医师、病人”,而手术表里明确有住院号。住院场景下医嘱必须能追溯到一次住院。解决:医嘱表加 zy_no 外键挂住院记录表,病人号通过住院记录间接关联,避免病人号和住院号双挂导致的不一致。

现象三:检查项目和检查单混为一张表,门诊住院查询互相干扰。原因:文档里“检查项目”数据结构既被门诊检查单引用,也被住院检查申请引用,字段完全一样。解决:检查项目表只存检查定义(项目名、收费、所属科室),检查记录表存每次检查的实例(检查序号、病人号、申请来源、结果)。文档把两者揉在一个数据结构里,落地时必须拆开,否则门诊查“我的检查结果”会扫到住院数据。

现象四:供应商号在数据项里出现两次,别名不一致。原因:文档数据项部分“供应商号”先写别名 Gys_no,后面又写 Trader_no,数据结构里订单用的是 Trader_no。解决:统一用 Trader_no,Gys_no 视为笔误。建表时供应商表主键叫 trader_no,订单表外键也叫 trader_no,别两个都建。

现象五:数据存储描述里“数据量每年 10000 张”被当成精确值去做分表。原因:文档写的是估算量,挂号单、病历、检查单、医嘱都是每年万级。解决:万级数据量单表完全扛得住,别上来就分表分库。先把索引建对,挂号单按 gh_date 建索引支持按日统计,病历按 patient_no 建索引支持按人查历史。等真到百万级再考虑按月分区。

5. 从文档到可运行库:补 DDL、造测试数据、验证一致性

文档给的是设计,不是脚本,所以最后一步是自己把剩下的表补全并跑通。我一般按这个顺序收尾:先补药品、供应商、收费项目、住院记录、医嘱、手术、病案这几张表,再写一段造数脚本灌几百条测试数据,最后用几条联表查询验证业务流是否走得通。

补表时重点处理住院流:住院记录表(zy_no、patient_no、dept_no、bed_no、in_date、out_date)是住院业务的中枢,医嘱、手术、病案都挂它。病案表文档里字段是“病案号、病人、入院科室、入院日期、入院诊断、出院日期、出院诊断”,它和住院记录有重叠,落地时病案可以做成住院记录的扩展表,主键共用 zy_no,避免两份入院日期打架。

造数脚本用 Python 加 Faker 最快,但医疗数据别用真名生成器,用“病人+编号”的假名即可:

import random from datetime import date, timedelta # 造 200 个病人,编号 P0000001 起 for i in range(1, 201): pid = f"P{i:07d}" print(f"INSERT INTO patient VALUES ('{pid}','病人{i}','{'M' if i%2 else 'F'}'," f"{random.randint(1,90)},NULL,'{date(1950,1,1)+timedelta(days=random.randint(0,25000))}'," f"'某市某区','1380000{i:04d}');") # 造挂号单:每个病人随机挂 1-3 次,日期落在近一年 for i in range(1, 201): pid = f"P{i:07d}" for j in range(random.randint(1, 3)): gh = f"G{i:04d}{j}" d = date.today() - timedelta(days=random.randint(0, 365)) print(f"INSERT INTO registration VALUES ('{gh}','普通','D{random.randint(1,50):07d}'," f"'{pid}','K{random.randint(1,10):02d}','{d}');")

逻辑说明:病人号用 P 前缀加 7 位数字凑满 8 位,和文档 char(8) 对齐;挂号号用 G 加病人序号加次数,保证唯一且可读。日期用近一年随机,方便后面验证“按月统计挂号量”这类查询。参数上,病人年龄随机 1 到 90,出生日期从 1950 年起随机偏移,两者不严格对应没关系,测试数据不用较真,但正式环境必须由出生日期算年龄。

灌完数据跑三条验证查询:按病人查挂号历史、按科室统计当日挂号量、按处方号联查病人和医生。如果三条都走索引且结果对得上,说明主外键和索引设计没问题。最后一条我每次都会跑,因为它同时验证了处方、病历、病人、医生四张表的关联链:

SELECT p.name AS 病人, d.name AS 医生, cf.cf_no, cf.content FROM prescription cf JOIN medical_record mr ON cf.bl_no = mr.bl_no JOIN patient p ON mr.patient_no = p.people_no JOIN doctor d ON cf.doctor_no = d.doctor_no WHERE cf.cf_no = 'C0001001';

这条查询如果返回空但处方表里确实有这条记录,八成是 bl_no 没对上——造数时处方和病历的关联字段容易随手填。血泪经验是:造数脚本里所有外键值都从已插入的主表里随机取,别自己编,否则联表验证时全是孤儿记录,排查半天以为是建表错了。

从那以后我每次拿到这类设计文档,都强制先跑一遍“实体清单 → 主外键 → 造数 → 联表验证”这条链,再回头补文档里没写清的边界。希望帮到你。

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

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

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

立即咨询