ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

医院管理系统数据库设计:业务流程驱动建表,避开五大常见坑

医院管理系统数据库设计:业务流程驱动建表,避开五大常见坑 简介围绕医院管理系统数据库设计的一份完整课程设计文档适合计算机相关专业学生、数据库初学者及医院信息系统开发人员参考。文档从门诊与住院部门的组织架构入手梳理挂号、收费、取药、检查、住院、手术等核心业务流程并围绕数据完整性、一致性、安全性、系统性能、可扩展性与法规遵从性展开需求分析配有ER图设计思路、表结构与外键关联方式、索引优化等数据库设计要点。包内为1个docx文件约398KB涵盖需求分析、业务流程图示、数据库结构规划、关键设计策略等章节可作为课程设计、毕业设计或医院信息系统项目初期的参考模板。已有173人学习对理解医院业务信息化、数据建模规范及数据库落地实践具有实际参考价值。1. 医院管理系统数据库设计别急着建表先把看病流程走一遍很多刚接触医院管理系统的人一上来就盯着病人表、医生表、药品表使劲建结果做到门诊收费和药库盘点时发现表和表之间根本对不上账。这份医院管理系统数据库设计文档好就好在它把门诊、住院两条主线的业务流程和数据字典都梳理清楚了挂号、处方、检查、医嘱、手术、药库出入库全都有对应的数据结构定义。它不是一份纯理论文档而是可以直接照着画ER图、写建表SQL的底稿适合做课程设计、毕设开题或者刚接手HIS项目的开发人员参考。我拆完这份文档最大的感受是医院信息系统的难点不在单张表而在业务流程怎么映射成外键关系和数据流。2. 先从业务实体拆起门诊、住院、药房和库房到底有哪几张核心表2.1 门诊那条线挂号单、病历、处方、检查单是一串连续事件门诊业务的核心不是“病人”这个实体而是“一次就诊过程”。文档里定义的数据字典很清楚地说明了这一点挂号号Gh_no、病历号Bl_no、处方号Cf_no、检查序号Jc_no都是8位char类型编码每个编号代表一次独立的业务事件。设计表的时候不能把挂号信息和病历信息全塞进病人表里而是要让病人表和这些事件表分别建立一对多关系。门诊这条线至少要有这几张表病人信息表People、挂号单表Gh、门诊病历表Bl、门诊处方表Cf、检查项目表Jc。挂号单表记录挂号类别、挂号科室、主治医师、挂号日期病历表记录诊断时间、病历内容处方表记录处方内容和收费状态检查表记录检查医师、检查时间、检查结果和收费。文档里还强调了一个容易被忽略的细节复诊病人不用重新登记基本信息挂号处直接处理病历管理就行。这意味着在设计时病人表要有唯一标识比如身份证号挂号单表通过病人号关联这样复诊时只需要新建挂号单不需要重复插入病人资料。2.2 住院那条线病区、床位、医嘱、手术四个实体要分开建模住院业务和门诊最大的区别是引入“病区”和“床位”概念。文档里列出了住院号Zy_no、病区号Bq_no、病床号Bc_no、医嘱号Yiz_no、手术序号Ssx_no这样一组编码。病区科室要根据医生开的医嘱来执行医嘱内容包括用药和检查申请药品要分类汇总成药品请领单检查申请单要发给检查科室病人需要手术时病区科室提交手术申请手术室安排日程并生成手术通知单。所以住院这条线的核心表至少包括住院登记表Zy、病区表Bq、病床表Bc、医嘱表Yiz、手术表Ssx、药品请领单表B。其中病区表和病床表不是简单的从属关系——一个病区有多个床位一个床位同一时间只归属一个住院病人所以病床表里要放病区号和当前住院号两个外键。手术表需要关联住院号、主刀医师号、手术室、手术日期和手术结果。文档里特别提到转科业务病人从一个病区转到另一个病区要办理转科手续并重新安排床位。这里不能直接改病床表里的病区号而是需要设计一张转科记录表否则转科前后状态查不清审计时很容易翻车。2.3 数据字典里的字段命名规律8位char编码和日期字段别乱改类型这份文档的数据字典有一个明显特征几乎所有编号字段都是char(8)比如Gh_no、Bl_no、Cf_no、Jc_no、doctor_no、People_no。这种设计出自早期流程式系统的习惯好处是编码可以包含字母和数字比如“GH000001”“00120020”坏处是如果直接用char(8)存纯数字排序和范围查询都会出问题。我在实际项目里一般会建议主键改用int自增把业务编码单独留一个字段存原样的8位码前端展示用业务编码表关联用自增ID。日期字段在文档里定义得很规整挂号日期Gh_date、诊断时间Zd_date、检查时间Jc_date都是date类型。注意文档里对“检查时间安排”和“检查收费情况”是分开定义的这说明检查项目表需要同时具备计划时间和执行时间两个概念而不是只放一个DateTime。如果你把检查时间做成单一的datetime后面统计“今日检查完成量”和“预约未检查”就比较痛苦。数据字典里还有一类字段容易看走眼收费项目号Sfxm_no它是由挂号费、药品费、检查费组合出来的汇总概念不是一张具体业务表而更像一张收费项目目录表。我在建表时一般会把收费项目表做成静态字典表用外键关联到挂号单和处方单这样月底对账时才能把每一笔收入拆分到对应来源。3. 从数据字典到可执行建表SQL把三大模块落到数据库里3.1 用户信息表与人员表区分系统账号、病人、医生三种身份文档里的数据字典分别定义了病人、医生、门诊医师、门诊病人的不同字段但真正的用户信息表在文档中没有单独出现这是我建议补的第一张表。医院管理系统里“人”的模型其实三层系统登录账号、医护人员档案、病人档案。不能混成一张表因为病人会出院医生会离职账号需要停用三种身份的生命周期完全不同。下面是我常用的建表方式CREATE TABLE sys_user ( user_id INT AUTO_INCREMENT PRIMARY KEY COMMENT 用户ID, login_name VARCHAR(32) NOT NULL UNIQUE COMMENT 登录名, password_hash VARCHAR(64) NOT NULL COMMENT 密码哈希, user_type TINYINT NOT NULL COMMENT 1-管理员, 2-医生, 3-护士, 4-收费员, doctor_id INT NULL COMMENT 关联医生表, status TINYINT NOT NULL DEFAULT 1 COMMENT 1-启用, 0-停用, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT系统用户表; CREATE TABLE patient ( patient_id INT AUTO_INCREMENT PRIMARY KEY COMMENT 病人ID, people_no CHAR(8) NOT NULL UNIQUE COMMENT 病人编号, name VARCHAR(32) NOT NULL COMMENT 姓名, sex CHAR(1) NOT NULL COMMENT 性别, age INT NOT NULL COMMENT 年龄, id_card VARCHAR(18) NOT NULL UNIQUE COMMENT 身份证号, birth_date DATE NULL COMMENT 出生日期, address VARCHAR(128) NULL COMMENT 住址, phone VARCHAR(15) NOT NULL COMMENT 联系方式, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT病人信息表;sys_user表和patient表分开建sys_user里的doctor_id是可选外键只有user_type为2和3时才关联到对应的医生表或护士表。登录名用login_name做唯一约束password_hash不要存明文。病人表用id_card做唯一约束这是挂号时避免重复建档的关键。people_no保持char(8)是为了兼容文档里的旧编号但主键用自增ID这样关联挂号单、处方单时不会因为业务编号修改而被迫级联更新。有一个细节age字段在数据字典里是int但如果病人过生日年龄需要重新计算所以我一般会存birth_date查询时用TIMESTAMPDIFF计算年龄而不是把年龄落库。3.2 药品与药库药房库存表拆出库、入库、请领三张流水文档里用很大的篇幅描述药库和药房的流程药库负责药品贮存、发放和采购药房根据处方生成领药单向药库领药并管理库存。这里最容易踩的设计误区是把“当前库存量”直接作为药品表的一个字段每次发药就减一每次入库就加一。这样看起来直观但只要有一笔退药或者录入错误库存就彻底对不上而且查不到差异发生在哪一天。正确的做法是设计一张药品表专门存静态信息库存余额通过流水表动态计算。CREATE TABLE drug_info ( drug_id INT AUTO_INCREMENT PRIMARY KEY, kind_no CHAR(8) NOT NULL UNIQUE COMMENT 药品编号, drug_name VARCHAR(64) NOT NULL COMMENT 药品名, spec VARCHAR(32) NULL COMMENT 规格, unit VARCHAR(8) NOT NULL COMMENT 单位, price DECIMAL(10,2) NOT NULL COMMENT 单价, production_date DATE NULL COMMENT 生产日期, expire_date DATE NULL COMMENT 保质期, supplier_id INT NOT NULL COMMENT 供应商外键 ) ENGINEInnoDB COMMENT药品信息表; CREATE TABLE drug_stock_flow ( flow_id BIGINT AUTO_INCREMENT PRIMARY KEY, drug_id INT NOT NULL, flow_type TINYINT NOT NULL COMMENT 1-入库, 2-出库, 3-请领, 4-退药, quantity INT NOT NULL COMMENT 数量正数增加负数减少, ref_no VARCHAR(20) NULL COMMENT 关联处方号/请领单号/采购单号, operator_id INT NOT NULL COMMENT 操作人用户ID, flow_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, INDEX idx_drug_flow (drug_id, flow_time) ) ENGINEInnoDB COMMENT药品库存流水表;药品的当前库存量用SUM(quantity)按drug_id聚合得到这样做月末盘点时只要看流水表的明细每一笔增减都有ref_no追根溯源。请领单处理流程在文档里是这样的药房根据处方汇总形成药品请领单然后到药库领药。所以请领单本身也是一张主表加明细表的结构请领单主表记录请领号、请领的药房、请领日期请领单明细表记录每种药品的请领数量。药库发货时在入库流水和出库流水各写一笔数量一正一负两个药房的库存就都平了。这里千万注意事务药库出库和药房入库必须放在同一个数据库事务里提交否则会出现药库已扣库存、药房还没收到货的状态盘点永远对不上。3.3 门诊处方与住院医嘱两张表结构类似但语义完全不同文档里门诊处方和医嘱是两个独立的数据结构。门诊处方号Cf_no由医生开给门诊病人病人拿处方去收费处交费再取药住院医嘱Yiz_no由住院医生开出病区科室根据医嘱执行用药分类汇总成药品请领单。很多初学者会把这两张表合并成一张“开药表”这是大坑因为两者的生命周期完全不同门诊处方在收费后基本就结束了住院医嘱是一直持续到病人出院中间可能每天都会追加新医嘱需要和病区执行记录、药品请领记录关联。CREATE TABLE outpatient_prescription ( cf_no CHAR(8) NOT NULL PRIMARY KEY COMMENT 处方号, patient_id INT NOT NULL COMMENT 病人ID, doctor_id INT NOT NULL COMMENT 医生ID, charge_status TINYINT NOT NULL DEFAULT 0 COMMENT 0-未收费, 1-已收费, cf_content VARCHAR(100) NULL COMMENT 处方内容, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, INDEX idx_prescription_patient (patient_id, create_time) ) ENGINEInnoDB COMMENT门诊处方表; CREATE TABLE inpatient_medical_order ( yiz_no CHAR(8) NOT NULL PRIMARY KEY COMMENT 医嘱号, patient_id INT NOT NULL COMMENT 病人ID, doctor_id INT NOT NULL COMMENT 医生ID, zy_no CHAR(8) NOT NULL COMMENT 住院号, order_type TINYINT NOT NULL COMMENT 1-用药, 2-检查, 3-护理, order_content VARCHAR(100) NOT NULL COMMENT 医嘱内容, start_date DATE NOT NULL COMMENT 启动日期, handle_date DATE NULL COMMENT 处理日期, status TINYINT NOT NULL DEFAULT 0 COMMENT 0-待执行, 1-已执行, 2-已停止, INDEX idx_order_zy (zy_no, start_date) ) ENGINEInnoDB COMMENT住院医嘱表;两张表都用医生ID和病人ID做外键但住院医嘱表额外多了住院号、医嘱类型、状态字段。处方表的charge_status是收费处回写的标志位事务上要确保收费成功后才能让药房看到需发药的数据。可以做一个发药状态位但别直接删处方记录。另外注意文档里医嘱数据字典有个手误“遗嘱内容”应该是“医嘱内容”但表结构设计上不影响。处理日期handle_date允许为空表示这条医嘱尚未执行启动日期start_date是必填的用来区分长期医嘱和临时医嘱。3.4 挂号单与操作日志并发场景下的唯一约束和幂等设计挂号是医院系统并发压力最高的入口。初诊病人在挂号处登记复诊病人直接挂号和就诊排号。如果挂号单表用业务号做主键两个挂号窗口同时操作时容易冲突。我的建议是挂号单表也拆成主表和日志表挂号单主表保存当前状态操作日志表记录每一次挂号和退号动作。CREATE TABLE registration ( reg_id INT AUTO_INCREMENT PRIMARY KEY, gh_no CHAR(8) NOT NULL UNIQUE COMMENT 挂号号, patient_id INT NOT NULL COMMENT 病人ID, dept_id INT NOT NULL COMMENT 科室ID, doctor_id INT NOT NULL COMMENT 主治医师ID, gh_category VARCHAR(8) NOT NULL COMMENT 挂号类别, reg_fee DECIMAL(10,2) NOT NULL COMMENT 挂号费, reg_date DATE NOT NULL COMMENT 挂号日期, status TINYINT NOT NULL DEFAULT 0 COMMENT 0-已挂号, 1-已就诊, 2-已退号, UNIQUE KEY uk_reg_date_gh (reg_date, gh_no) ) ENGINEInnoDB COMMENT挂号单表;挂号号gh_no必须唯一但不要依赖数据库主键自增直接当成挂号号因为医院打印的小票上的号码要连续、可读、跨天不重复。常见做法是用一张计数器表每天按日期拼接流水号比如“20241208 00123”。文档里的数据字典要求挂号号char(8)如果你要兼容这个格式可以考虑“日期后三位序号”像“12080123”每天最多9999个号基本够用。挂号费金额要单独存字段不能去关联收费项目表临时查因为收费标准可能调整历史挂号记录必须留存当时的费用快照。4. 医院管理系统数据库设计避坑五个反复出现的设计问题4.1 编号用char(8)存数字排序和关联越来越慢现象用Gh_no、People_no这类char(8)字段直接做表关联和ORDER BY数据量到十几万后挂号记录排序错乱关联查询明显变慢。原因char类型按字典序排序而不是按数值大小比如“1000002”会排在“989999”之前而且char(8)存储固定占8字节索引效率低于int。解决我一般把业务编号保留为展示字段新建自增int主键做关联。如果必须要用char(8)做主键至少保证编码规则统一不要混用数字和字母同时给常用查询字段加索引。4.2 药品库存直接减字段月底对账全靠猜现象药房发药后药品表stock字段减一但盘点时发现库存和实物差几十盒不知道是录入错还是退药没处理。原因没有库存流水表每一次增减操作没有留下可审计的记录数据库里只有最终值追溯不了变化过程。解决把drug_stock_flow这样的流水表作为强制要求所有库存变动只能通过插入流水记录来实现当前库存用视图SELECT SUM(quantity) FROM drug_stock_flow GROUP BY drug_id动态计算。一旦对不上按操作员和ref_no逐笔核对就行。4.3 医生和科室直接做成从属字段排班和转科全乱现象医生表里放一个dept_id字段结果医生调到另一个科室后历史的门诊记录和当前科室混在一起统计科室工作量完全不准。原因医生和科室是多对多关系而且一个医生可能同时在门诊和住院部有排班。解决建doctor_dept_rel中间表记录医生在某个科室的任职开始时间和结束时间。查询“某科室某天的门诊量”时用挂号单上的doctor_id反查医生当时的科室而不是看医生表的当前dept_id。4.4 门诊处方和住院医嘱混在一张表退药逻辑抄来抄去现象项目里只有一张“用药表”门诊和住院都往里插数据后来发现住院的长期医嘱每天要执行多次门诊处方只有一次性付费发药两种逻辑根本没法统一代码里到处是if判断。原因设计阶段没有区分业务流程把两种生命周期的单据揉在一起。解决严格按文档拆成outpatient_prescription和inpatient_medical_order两张表哪怕字段大部分相似。住院医嘱还要再拆明细表因为一条医嘱可能对应多种药品而且执行状态要逐项追踪。4.5 日期字段没建索引查“今天的挂号量”全表扫描现象每早上班查当天挂号记录数据库CPU直接拉满查询耗时好几秒。原因挂号日期、检查日期等字段没有索引SQL里对日期列用了函数或者范围查询MySQL没法走索引。解决在reg_date、flow_time这类经常按日期范围查询的字段上加普通索引SQL写法上避免WHERE DATE(reg_date) CURDATE()改成WHERE reg_date CURDATE() AND reg_date CURDATE() INTERVAL 1 DAY。护理记录、医嘱执行记录这类只增不改的表还可以按月份做分区。5. 把统计查询固化成视图和存储过程对账与报表不再天天改SQL到了这一章表结构基本稳定接下来要解决的是“每天都要看的数据”。门诊收入、住院收入、药品收支文档需求分析里明确提出要对这些原始数据和统计数据进行相关分析。与其每天手工写联表查询我会把统计逻辑做成视图和存储过程让收费员和护士只需要按一个按钮就能拿到结果。CREATE VIEW v_daily_outpatient_income AS SELECT DATE(r.reg_date) AS biz_date, d.dept_name, COUNT(r.reg_id) AS reg_count, SUM(r.reg_fee) AS reg_fee_total, SUM(CASE WHEN c.charge_status 1 THEN c.cf_fee ELSE 0 END) AS drug_fee_total FROM registration r JOIN department d ON r.dept_id d.dept_id LEFT JOIN ( SELECT cf_no, charge_status, SUM(price * quantity) AS cf_fee, reg_id FROM outpatient_prescription_detail GROUP BY cf_no, charge_status, reg_id ) c ON r.reg_id c.reg_id GROUP BY DATE(r.reg_date), d.dept_name;这个视图把挂号费收入和处方药品收入按科室聚合到同一天月底财务科只需要对这个视图做范围查询不需要关心底层表结构。视图的缺点是MySQL对视图的优化有限如果数据量特别大我更建议在每天凌晨用定时任务把统计结果刷进一张报表汇总表保留历史快照因为医院报表需要的是“截至某天的数据”而不是实时变动数。存储过程更适合做“某月药品出入库汇总”这种参数化统计。验证数据正确性有一个笨但有效的办法拿一天的真实门诊数据人工数一遍挂号单、处方单、收费记录的条数再和视图结果对比。我第一次做这个项目时视图统计的药品费比收费处少了几百块原因是有两张处方没有关联到挂号单因为收费流程允许先开药后补挂号。从那以后我每次设计统计逻辑都强制走一遍“业务单号全链路校验”从挂号单到处方、检查、发药、收费逐级对比数量保证任何一张单子在统计视图里都能溯源到位。医院管理系统数据库设计归根结底是对业务流程的忠实映射表结构错了后面的报表、对账、绩效全是错的希望这份文档里的数据字典和业务流程梳理能帮你在起步阶段少踩几个坑。本文还有配套的精品资源点击获取
返回列表