数据库设计_冯火:HIT数据库表设计实践

本文作者冯火分享了关于HIT数据库表设计的实践经验,强调了合理命名、注释重要性、关键字段索引以及范式的合理使用。建议业务表尽可能接近1NF,基础表接近3NF,减少多表查询并冗余存储不常变动的基础数据,以提高效率和系统稳定性。同时,指出外键和中间表在关系表示中的作用,并提醒注意业务理解和数据库专业知识的结合。
摘要由CSDN通过智能技术生成

c9716338da536294107a85d9b42d8ea8.png

文章来源:CHIMA   作者:冯火  广东省罗定市人民医院信息科

开始今天文章之前,请允许我先回顾前段时间在CHIMA公众号写了一篇文章 “ 冯火:如何设计可以运行20年的新一代HIS系统” , 我看到有一条留言说我的文章观点独到,你可能会好奇我是怎么想到的?“携号转网”是我在别人文章里看到的,本质是解决“用户被运营商绑架”的问题,当时只是觉得这个了不起,没有想到它跟我从事的HIT有什么关系。那天听HIT专家网组织了“新一代医院信息系统”网课,提到“如何打造20年不做颠覆性改变”,其中又提到厂商需要开放,我突然想到HIT最大问题是“绑架”,本质上“携号转网”解决一样的问题,两件事情在我脑子里瞬间完成了碰撞,怎么可以做到“携号转网”,带着这个疑问,继续深思,运营商绑架的是什么,是手机号码,HIT厂商绑架的是什么,是“医疗数据”,再逐步展开分析,就有了你们看到的文章! 言归正传,既然上文中提到了如何设计“数据库”是关键。数据库设计特指关系型数据库设计,nosql不是本文所要讨论的内容。我们经常遇到HIT表结构设计的乱糟糟(也有少数公司设计精良的),可以用一个词来形容“随心所欲”,有命名像天书的、没有注释字段都是靠猜、不加相应索引导致系统跑段时间突然变“龟速”、有照搬教科书设计范式等等,数据库设计好与坏,你我心里都有一杆“秤”。 在今天大数据、人工智能非常火爆的情况下,对数据的质量非常重视,即当下有一个火热的词汇“数据治理”,我写了一篇文章“浅谈数据治理”(https://www.jianshu.com/p/ee4850a9a1be),大家有兴趣可以去看看。既然数据的源头基本来自“数据库”。怎么样设计出一个合理的数据库?这是一个很重要的问题,以下是根据我多年经验试着回答这个问题。 ec56b0334280f4ae399545ace6eb1fc1.png

不合理的数据库设计

天书般的命名。你应该见过这样的命名(AKA01、AKA02、BAA、P1、P2等),软件开发有句俗话“代码即文档”,什么意思,看你的代码就像看你写的文档一样的易懂。这样的命名无疑增加大家的使用成本,有人说这样做是为了数据库安全,数据库的安全不是靠用不规范的命名就可以解决?再强调一次,安全问题本质非法访问未授权的资源!这是人为造成不便利,公司开发效率低、运维效率低。

表结构与注释分离。 字段命名采用拼音缩写的,字段的注释没有在一起描述,很多软件公司不在数据库表直接写注释,而是单独用一个txt或者doc来记录,我个人认为这是一个不好的习惯,我们经常有这样的困惑,写sql的时候,突然想知道这个表这个字段是什么意思,第一种方法,靠猜,看它的记录是什么数据;第二种方法,打开公司提供的数据字典,ctrl+f查找这个字段是什么意思。或许你说这是为了技术保密,保密不是靠不写注释就可以保障的! 这是人为造成不便利,用户、公司的开发、运维、实施也不方便,不经意间降低工作效率!

7144ce88cef7f2d2f818a061a60776bd.png注释那一列是空白

范式使用场景不对。 首先了解范式的是什么,范式是用来解决数据冗余存储的问题的,范式别级越高,冗余度越小。由于经验缺乏的关系,业务数据表经常被设计成3NF,导致数据完整性受影响、增加写sql复杂程度问题。为什么会有范式使用场景不对的,会有什么影响,后面我将举例说明。 基础表与业务表设计没有充分解耦。 “解耦”这个词,大家并不陌生,在数据库这个层面,个人认为“基础表与业务表”需要充分解耦。这个怎么理解,表之间关系通常是一对一、一对多、多对多,问题主要出在“一对多”这种关系,我们一般在维护了“外键”来表达这种关系。举例说明,“医嘱表”设计一个来自于“医生表”外键,设计人员本意要表达一种“一对多”关联关系。在实际使用过程当中会产生什么问题,第一个问题,业务表数据量通常是非常大,势必增加系统开销;第二个问题,假设删除某个医生,数据库就会关联业务表的外键报错,业务表一般认为不可变的数据(人为后台修改除外),影响用户体验。有经验设计者,一般去掉这个外键,然后在注释当中写明“外键关系”,降低基础表与业务表耦合。

ef943074ca1371cf9da026161aac3d78.png业务表设计一个来自于基础表的外键

我总结的经验:1、业务数据表,一般设计冗余存储,可以不加外键,在注释当中说明这种“一对多”关系;2、基础数据表,一般设计成3NF,基础数据表之间可以加“外键”,遵循最小代价修改原则,后面我还会提到这个问题,因为十分重要。

关键字段没有索引。这个很好理解,系统跑了一段时间以后,由于没有对关键字段维护索引,系统突然变“龟速”。

我认为合理的设计

合理的命名。表、字段的命名,可以采用简写拼音、英语单词、英语单词缩写。比方说“患者姓名”字段,可以是xm(简写拼音)、patient_name(英语单词)、pat_name(英语单词缩写)。对于英文水平很高优先采用英语单词、英语单词缩写,对于英文差的,可以简写拼音,总之不要用“六亲不认”的命名。

表结构与注释放在一起。如果时间一长,公司开发人员经常变动,做不到严格的文档管理,到时候谁都搞不清楚这个字段是什么意思,对于公司来说也是一个风险。现在数据库管理工具都是图形化的,要做这件事情不难,我建议表“外键”的说明写入到注释当中,以便以后分析表与表之间的关系。

35a595ac382b00b2ba14800971f0bf01.png

表结构与注释一起

b33f73cf8b85726b785e9f281b6ec985.png

mysql表,外键描述

范式合理使用场。 首先分析HIT数据库的构成,我通常分为2个部分,基础表(职工基础表、医生基础表、科室基础表、项目基础表)、业务表(挂号业务表、发票业务表、医嘱业务表)。基础表的特点,记录少,经常增、删、改、查操作,业务表的特点,记录多,查询操作占到90%以上。

对于“业务数据”表结构设计,尽可能接近1NF

对于一些不经常变动基础的数据进行冗余保存到业务表,比如,保存“发票类别代码+发票类别名称”、“医生工号+医生姓名”、“科室代码+科室名称”、“结算方式+结算方式名称”等。这样做的目的,1、尽可能避免多表查询,考虑效率,因为业务数据是要被经常访问的,能访问一张表搞定,就不要访问两张表;2、保持历史数据独立完整性,比如,某位员工离职了,就把操作权限连同基础数据一起删除了,依然不影响历史数据查询。我们医院就有“结算方式”被禁用,导致有些系统无法读不出来历史数据了,原因是原来在开发软件时候,由于没有冗余存储,直接关联了基础表,导致读不出来。

对于“基础字典数据”表结构设计,尽可能接近3NF(尽量最小的修改代价原则)

范式级别越高,信息冗余就越小,但一般到3NF就可以,尽可能符合“尽量最小的修改代价原则”。基础数据由于记录少,多表查询一般不会有执行效率问题,数据冗余越小我们维护越方便,出错概率就越小,系统稳定性高。

基础表与业务表设计之间耦合问题,上文已经提及解决方案,在此不再赘述。关键字段没有索引问题,有时候需要专业的DBA来维护,在此不再赘述。

回顾理论知识

为了考虑广大读者的实际情况,更好理解文中所描述的内容,我还是把重要的理论知识跟大家简单回顾一下。

第一范式(1NF)指字段是否为复合字段,不可拆分,这个要看业务情况,比如姓名字段,你要不要拆分成“姓”、“名”2个字段。“地址”要不要拆分省市、县、街道。

第二范式(2NF)在1NF基础上,非主属性字段之间消除部分或完全依赖(另一种说法非主属性完成依赖于主键)。举例说明,非主属性数量、单价、金额之间存在依赖关系。

63d8414138ad62d625cd667b142bc30c.png

部分依赖

第三范式(3NF)在2NF基础上,非主属性字段之间消除传递依赖。举例说明,出生日期、年龄、是否成人,存在传递依赖。

91f5f14c1f870ed44f162b8790ac666b.png

传递依赖

"一对多"关系,一般在“多”的一方,维护一个“外键”表达这种关系。

537a6fb163586342817ac5986d8a6bbc.png

一对多

"多对多"关系,使用中间表表达这种关系。

b89dd0648bf66c196734ef853b85852b.png

多对多四

患者主索引设计实战(含视频)

设计好数据库表应该具备2种能力,第一,对业务的理解程度;第二,数据库专业知识与实际设计经验。我曾经写过一篇文章,“如何全面构建患者主索引新思路”(https://www.jianshu.com/p/af0b84a46fd8),大家有兴趣可以去看看,可能对你有所帮助。假设现在要你设计一个“患者主索引”满足新时代各种要求,你会怎么做?可能文字表达不清楚,我录了一个小视频来表达这个意思。你可以带着这个问题,来看我的视频。

视频地址如下:https://www.iqiyi.com/v_19ryciu2y4.html 密码:chima

eac7542392cb85e92d9c75f0dfdc01ec.png

一、数据库的背景及功能需求 进入21世纪以来,计算机的普及应用和信息技术、网络技术的发展给人们的工作和生活带来了极大的便利和高效,信息化、电子化已经成为节约运营成本,提高工作效率的首选。 相比之下,国内的相当数量的中小型医院的病人资料工作流程还采用相对保守的人工工作方式,数据信息的查询和存储的成本较高,而且效率还很低下。所以需要一种对于医院的病人资料管理系统来高效、低成本、便捷的进行医院病人信息数据的查询和存储。 1. 病人的相关信息应该由医院数据库管理员进行添加、删除、修改、查询等维护操作。 2. 需通过病人所患的疾病来确定病人的治疗 3. 对病人的编号、出生日期、性别、工作、住址信息进行查询。 4. 对病人的治疗进程,缴费情况等进行及时的更新与统一管理。 5. 医院需通过治疗结果来查看病人是否结束治疗,以进行必要的及时续约等行为。 6. 病人可通过数据库进行对所交费用,治疗情况的相关信息进行查看。 7. 病人信息的更新等等由数据库管理员进行维护。 系统功能的基本功能: 1病人信息包含编号,姓名,性别,出生日期,工作单位及地址,住址,工作,保险,医保号,电话,邮箱,死亡日期。 2交费项目信息包含项目序号,项目类型,缴费金额等 3回访记录包含住院号,病人编号,回访日期,回访人,记录,生命体征。 4 可通过对数据库的查询了解病人的相关信息,以及病情,并确定治疗方案。 所有关系模式都属于BC范式 (1)在关系模式patient中patientID是主键,所以在包含属性patientID的函数依赖是一个superkey。 (2)在关系模式中inpatient中number为主键,所以在所包含属性number的函数依赖是一个superkey。 (3)在关系模式outpatient中patientID为主键,所以包含patientID的函数依赖是一个superkey。 (4)在关系模式bed中number是主键,所以包含patientID的函数依赖是一个superkey。 (5)在关系模式department中depname是主键,所以在包含属性depname的函数依赖是一个superkey。 (6)在关系模式中re_call中number为主键,所以在所包含属性number的函数依赖是一个superkey。 (7)在关系模式case中caseID为主键,所以包含caseID的函数依赖是一个superkey。 (8)在关系模式charge1中chargeID是主键,所以包含chargeID的函数依赖是一个superkey。 (9)在关系模式charge2中chargeID是主键,所以在包含属性chargeID的函数依赖是一个superkey。 (10)在关系模式中inotice中inoticeID为主键,所以在所包含属性inoticeID的函数依赖是一个superkey。所以一定属于BCNF。 二、数据库的概念结构设计 病人资料管理系统的E-R模型 三、数据库的物理结构设计 住院病人inpatient 列名 数据类型 字段长度 字段描述 备注 Number Bigint 住院号 主键 patientID bigint 病人编号 非空,外码 Name Bigint 姓名 Inday Datetime 入院时间 Bedroom char 4 床号 Sort varchar 20 入院科别 病人信息patient 列名 数据类型 长度 字段描述 备注 patientID bigint 病人编号 主键 Name varchar 20 姓名 不能为空 Sex char 4 性别 Birth datetime 出生日期 Dep varchar 40 单位 Depadd varchar 60 单位地址 address varchar 60 住址 Work varchar 10 工作 在职、离休、退休 Insure char 4 医保 insnumb varchar 30 医保号 有、无,不为空 Tel varchar 10 电话 Email varchar 50 E-mail deathday varchar 20 死亡日期 门诊病人outpatient 列名 数据类型 字段长度 字段描述 备注 patientID Bigint 病人编号 主键 Jz_date Varchar 20 就诊时间 Pay varchar 10 缴费情况 病人就诊花费 Name varchar 20 姓名 非空 Sex char 10 性别 Age Int 年龄 床位bed 列名 数据类型 字段长度 字段描述 备注 Number Bigint 住院号 主键 Name Varchar( 20 姓名 非空 Bedroom bigint 床号 Doctor Varchar 20 主治医生 主管本床位的医生姓名 Result Varchar 20 治疗结果 Department varchar 20 所属病区 病区department 列名 数据类型 字段长度 字段描述 备注 Depname varchar 10 病区名称 主键 Bedamount bigint 病床数 Responsor varchar 20 负责人姓名 非空 Inpeople bigint 入住人数 Wellpor real 好转率 Death real 、 死亡率 回访re_call 列名 数据类型 长度 字段描述 备注 Number bigint 序号 主键,自动产生 patientID bigint 病人编号 不为空 Callday varchar 20 回访时间 Callbody varchar 20 回访人 Record varchar 500 回访记录 Life char 8 生命特征 死亡 门诊病历ccase 列名 数据类型 字段长度 字段描述 备注 caesID Bigint 病历号 主键 Name Varchar 20 姓名 非空 Context Varchar 60 病例内容 Diadate datetime 诊断时间 Doctor Varchar 20 主治医生 联系 支付2pay2 列名 数据类型 字段长度 字段描述 备注 PatientID bigint 病人编号 主键 chargeID Bigint 支付项目号 主键 Amount varchar 20 payDate datetime 支付时间 收费项目2charge2 列名 数据类型 字段长度 字段描述 备注 chargeID bigint 项目序号 主键 Chargesort Varchar 20 项目类型 Amount Varchar 10 收费金额 Name Varchar 20 病人姓名 Operator Varchar 20 收款员 收费项目1charge1 列名 数据类型 字段长度 字段描述 备注 chargeID bigint 项目序号 主键 Name Varchar 20 病人姓名 number bigint 住院号 外码 category Varchar 10 收费类型 chargeday datetime 收费日期 opertor varchar 20 收款人 Amount Varchar 10 应收金额 payamount Char 10 交费金额 入院通知单inotice 列名 数据类型 字段长度 字段描述 备注 inoticeID bigint 通知单号 主键 doctor varchar 20 医师姓名 非空 Name varchar 20 病人姓名 非空 PatientID bigint 病人编号 外码 Diagadvice varchar 40 诊断建议 Pay varchar 20 收费情况 Pass Char 4 是否批准 是 否。非空 关系及各属性 Patient(patientID,name,sex,birth,dep,depadd,address,work,insure,insnumb,tel,email,deathday) Inpatient(number,patientID,name,inday,sort,bedroom) Outpatient(patientID,name ,sex,age,jz_date,pay) Bed(number,name,bedroom,doctor,result,department,empty) Department(depname,bedamount,responsor,inpeople,wellpor,,deathpor) Re_call(number,patientID,callday,life,record,callbody) Case(caseID,name,context,diadate,doctor) Charge2(chargeID,chargesort,amount,name,operator) Charge1(chargeID,category,name ,number,chargeday,payamount,amount) inotice(inoticeID,doctor,name,patientID,diaadvice,pay,pass) 联系 Pay2(patientID,amount,paydate,chargeID) 触发器 1提醒触发器 create trigger reminder on patient after insert,update as raiserror('你在插入或修改病人的数据',16,10); 2.更新操作的触发器 create trigger hehe on outpatient for update as begin update outpatient set pay=pay*0.9 end 存储过程 1病人信息插入的存储过程 create procedure patientInsert ( @patientID bigint, @name varchar(20), @sex char(4), @birth datetime, @dep varchar(40), @depadd varchar(60), @address varchar(60), @work varchar(10), @insure char(4), @insnumb varchar(30), @tel varchar(15), @email varchar(50), @deathday datetime ) as insert into patient( name,sex ,birth ,dep,depadd ,address ,work ,insure,insnumb ,tel ,email ,deathday ) values(@name,@sex ,@birth ,@dep,@depadd ,@address ,@work ,@insure,@insnumb ,@tel ,@email ,@deathday ) 2 住院病人信息插入的存储过程 create procedure inpatientInsert ( @number bigint, @patientID bigint, @name varchar(20), @inday datetime, @sort char(20), @bedroom char(4) ) as insert into inpatient( number,patientID ,name ,inday ,sort ,bedroom ) values( @number ,@patientID ,@name ,@inday ,@sort ,@bedroom ) 索引 1 use hospital_patient create unique nonclustered index inpatient_name on inpatient ( name ) 2 use hospital_patient create nonclustered index charge_amount on charge1 ( amount desc )
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值