EMR表结构设计:字典、映射与主从表的工程取舍
简介这份《卫宁电子病历表结构》面向医院信息科、EMR实施顾问及医疗信息化研发人员系统整理卫宁临床信息系统5.0的数据库结构用于理解医疗业务数据模型、表间关系与字段规范。文档收录800余张数据表的说明、字段定义、类型、长度、备注和值域覆盖职工、科室、病区等代码库药品与收费项目分类、医保分类、诊断代码ICD及地区区县等基础库并涉及权限管理与数据标准化设计可作为接口开发、数据交换、报表取数和系统运维的查阅手册。资源包共1个doc文档约13.92MB目录按系统框架、财务收费、医疗信息等模块组织便于按主题检索已有1554人学习下载。读者可据此快速定位目标表与字段、核对编码规则和值域约束减少实施对接中的字段猜测也为医疗大数据分析与系统优化提供结构化参考。1. 一套 EMR 表结构里真正难存的不是“病历”先说一个反直觉的结论把这几百张电子病历表结构翻完你会发现真正用来存病历正文的表其实没几张绝大多数是字典、对应关系、模板和配置。EMR 表结构的复杂度根本不在数据量上而在于同一件事有多个身份一个药品既是药理实体又是收费项目还是医保目录条目一个科室既是行政单元又是药房归属方还是收费执行科室。这套临床信息系统数据库结构用“代码库”承载身份用“对应库”缝合身份再用“设置库”约束行为。把这三层的作用和关系弄清楚比记住任何一张表的字段都管用。2. 命名就是架构从 SYS_ / PUB_ / CPOE_ 前缀读出依赖方向这套表结构最值钱的信息量藏在表名的前缀里。它并不是随手起的名字而是一份写在字符串里的分层契约谁在底层、谁能引用谁、哪张表改了会牵连一片。看懂命名等于拿到了一张不必画出来的架构图。2.1 三段式表名前缀定层、中段定域、后缀定角色拿PUB_KSYFDYK拆开看PUB是公共层KSYF是“科室—药房”这个业务域DYK是“对应库”。同一个KSYF语义如果出现在CPOE_前缀下那它一定是医生工作站侧的业务数据而不是全系统共享的基础字典。后缀的角色划分更关键它直接决定了这张表是“被引用的”还是“引用别人的”后缀含义角色典型表K代码库、字典库被引用只增不改SYS_ZGDMK、PUB_YPFLK、CPOE_YZLXKMXK明细库从属依附主表CPOE_XCFMXK、CPOE_XKSHMXKDYK对应库、映射库缝合两方PUB_LCSFXMDYK、CPOE_YBYPDYKMBK模板库被实例化引用CPOE_RIS_SQDMBK、CPOE_SSJLMBSZ / _SZ设置表存规则而非事实CPOE_XCF_SZ、CPOE_SSYZK_SZJLK记录库只写不读CPOE_SHSFJLK这张表不是我从文档里抄来的分类法而是把几十个表名摆在一起之后自然浮现的规律。CPOE_SXBLFYK是住院输血不良反应CPOE_YWBLFYK是药物不良反应两个不同的业务域用了同一个BLFY词根说明命名首先保证的是语义一致其次才轮到简洁。这种一致性在后期维护中是保命的你要找“不良反应”相关的东西脑子里只需要过一个词根不用记六七个不同的叫法。2.2 字典外置的理由不是规范化洁癖是变更成本SYS_KSDMK科室代码库、SYS_BQDMK病区代码库、PUB_YFDMK药房代码库、PUB_ZCBMK职称编码库这一批表在任何一个业务表里都只以“代码”形式出现名称永远靠回查。新手容易觉得这是过度设计直接在主表里存一个科室名字不是更省事问题在于医院里科室名称的变更频率远超想象科室合并、更名、拆分、临时启用停用一年下来几十上百次很正常。如果业务表存的是名称快照那么每次改名字都意味着一次全库范围的数据订正而且订正完历史数据里当年的名字就永久丢失了。反过来字典表只保留“代码—名称”的一行改名只是更新一行所有历史业务数据因为只存代码天然跟着变。更实际的一点是检索。这类字典表几乎都会带拼音码、五笔码一类的辅助字段录入时敲几个首字母就能把科室、药品、诊断调出来。举个典型结构-- 科室等级字典的典型形态示意非原始 DDL CREATE TABLE SYS_KSDMK ( KSDM VARCHAR(10) NOT NULL, -- 科室代码业务表引用的就是这个 KSMC VARCHAR(60), -- 科室名称仅用于展示 PYM VARCHAR(20), -- 拼音码录入时做前缀匹配 SJKSDM VARCHAR(10), -- 上级科室代码自关联成树 TYBZ CHAR(1) DEFAULT 0 -- 停用标志0 启用 1 停用 );SJKSDM这个自关联字段值得单独说明。PUB_YJKSK一级科室库和PUB_EJKSK二级科室库的存在说明系统里既有“扁平两级表”的方案也有“单表自关联”的方案两套并存。常见的实现思路是老模块按两级表取数新模块走自关联树展开中间靠一个视图或者转换层保持一致。这种“新旧并存”在长生命周期的医疗系统里是常态不是设计失误是迁移成本压出来的妥协。TYBZ停用标志则是另一个高频踩点。字典表几乎从不做物理删除因为一旦删除所有引用了该代码的历史业务数据就变成了悬空引用。停用只影响“能不能被新单据选中”不影响“历史数据能不能显示”。这条约束如果一开始没立住后期一定会有人为了图省事直接 DELETE然后某天翻旧病历时发现科室永远是空白的。2.3 依赖方向单向引用绝不反向把前缀按依赖关系排一下顺序是清楚的SYS_在最底下PUB_建在它上面CPOE_和OUTP_再建在PUB_上面。SYS_层放的是全院级别的组织与人员职工、科室、病区PUB_层放的是跨业务共享的临床与财务字典药品分类、收费项目、诊断、手术、医保CPOE_层是医生工作站的具体业务OUTP_层是门诊侧的对应关系。这条单向依赖的实践意义是改SYS_ZGDMK的字段理论上不该影响任何CPOE_表的写入逻辑但改PUB_LCSFXMDYK的映射规则收费和医嘱两个方向都会抖。所以运维上有个朴素的判断法——按表名前缀估影响面前缀越靠底层改动越要慎重尤其别在业务高峰期动。反过来一旦你发现有PUB_表里存了某个具体患者的信息或者SYS_表里出现了业务状态的字段那基本可以判定是历史遗留的越界大概率伴随一次“这张表当年为了图快临时用了”的注释。这类越界是排查数据不一致时首先要怀疑的地方。3. 对应关系表医疗系统的多对多为什么用表解决而不靠中间件医疗信息化的核心矛盾很少是“数据存不下”而是“一件事在不同部门眼里是不同东西”。对应库DYK正是为这个矛盾准备的它在不合并两边实体的前提下让两边能够对话。这套表结构里 DYK 后缀出现的密度明显高于普通业务系统这就是行业特征的直接体现。3.1 科室的三条链药房、病区、收费执行同样是“科室”这个实体在医院里有三种完全不同的用途所以文档里出现了三张独立的对应表PUB_KSYFDYK科室药房对应、PUB_KSBQDYK科室病区对应、PUB_MZSFZXKSK门诊收费执行科室对应与PUB_ZYSFZXKSK住院收费执行科室对应。为什么不合并成一张“科室关系表”加个类型字段因为三者的基数、生命周期和维护方都不同。科室到药房通常是多对一或一对一由药剂科维护变动很少科室到病区在医院里经常是多对多一个科室管几个病区一个病区收几个科室的病人由护理部维护换季调整床位时改得频繁收费执行科室的对应关系则由财务和物价部门掌握和医保政策调整同步。三种关系由三个部门、三种节奏维护硬塞进一张表只会让并发修改互相踩。这里的取舍很典型表的物理分离服务的是组织和流程的分离。如果只从数据库范式角度评价合并成一张带类型列的表完全成立甚至更“干净”但工程上的干净不等于运维上的干净。3.2 临床项目与收费项目的双轨制一次开单两条账PUB_SFXXMK导入收费小项目库、PUB_LCSFXMK临床收费项目库、PUB_LCSFXMDYK临床收费项目对应库这三张表摆在一起就是这套系统最重要的一条设计主线。它承认了一个现实临床和财务看同一件事的角度不同。医生开的是“血常规”这是一个临床项目有标本类型、有采集要求、有危急值阈值财务收的是“血常规五分类”这个收费项有物价编码、有金额、有医保类别。两边由不同部门维护物价调价的时候临床项目的定义不应该跟着改反过来临床新增一个检验组合也不该逼着物价部门当天出新条目。所以系统里保留两套实体用PUB_LCSFXMDYK做映射一张临床项目可以映射到一个或多个收费小项目组合项目拆分明细映射关系本身带生效时间。这条链一旦断了最直接的表现就是医嘱能开出来但费用落不了账或者反过来账上有钱但病历里找不到对应的医嘱依据。CPOE_SFXMJLPCDYK收费项目剂量频次对应库是一层更细的补充同一个收费项目在不同剂量和频次下可能对应不同的计费单位这个信息必须挂在映射上而不是项目上否则一次开单的金额算不准。这类“映射之上再加维度”的表是医疗计费复杂度的真实写照。3.3 医嘱频次、用法、剂型的三级联动门诊和住院各有一套频次、用法字典OUTP_MZYZPCK、CPOE_YZYPPCK、OUTP_MZYZYFK、CPOE_YZYPYFK再加上CPOE_YZPCYFDYK医嘱频次用法对应库和PUB_JXYFDYK剂型用法对应库构成一个三级联动选剂型 → 带出可用用法 → 选用法 → 带出可用频次。这套联动并不是为了好看。给药途径和频次之间存在硬性的医学约束某些剂型只能静脉给某些频次在特定用法下没有临床意义。把这些约束放在代码里每加一个药品就要改一次程序放在对应表里药剂科自己就能维护。-- 按剂型带出可用用法示意逻辑 SELECT y.YFDM, y.YFMC FROM CPOE_YZYPYFK y WHERE EXISTS ( SELECT 1 FROM PUB_JXYFDYK d WHERE d.JXDM :剂型代码 -- 传入当前选中的剂型 AND d.YFDM y.YFDM -- 剂型与用法的允许组合 ) AND y.TYBZ 0; -- 只用启用状态的用法这段查询的关键在PUB_JXYFDYK充当了过滤条件而不是结果集。CPOE_YZYPYFK是全集剂型用法对应表是允许清单两者取交集才得到界面上能选的项。如果把这一步省略、界面上直接列全集那么录入效率的下降还是小事选错给药途径才是真正会出问题的环节。CPOE_YPDYLDSZ剂型对应药品联动设置则把粒度再往下推到单个药品某一种药即使剂型相同也可能因为厂家说明书的差异只允许特定的用法。这张设置表的定位不是字典而是“例外规则的存放处”它的行数增长很慢但每一条都对应一次真实的临床反馈。4. 一条医嘱的字段级拆解主表、明细、设置表的三件套医嘱是整套系统的核心实体围绕它的表最多也最密。把这些表按职责归拢会发现一个反复出现的三件套结构主表记事实、明细表记组成、设置表记规则。这个结构在文档里至少出现了三轮只是业务域不同。4.1 为什么“设置”必须单独成表小处方这一组最清楚CPOE_XCFK小处方库、CPOE_XCFMXK小处方明细、CPOE_XCF_SZ小处方设置。手术医嘱也是同一套CPOE_SSYZK手术医嘱库、CPOE_SSYZK_SZ手术医嘱设置。区别在于数据的性质。主表和明细表存的是“已经发生的事实”某个患者在某天被开了一组小处方处方里有三个药品。设置表存的是“允许怎么开”这个科室的小处方最多几个药品、哪些药品可以放进同一个处方、剂量上限是多少。事实会无限增长规则是有限且低频变更的。把两者混在一张表里最直接的后果是每次校验规则都要扫一遍全量业务数据而且是并发写的热表。-- 小处方主从结构示意字段为典型形态非原始 DDL CREATE TABLE CPOE_XCFK ( XCFID VARCHAR(32) NOT NULL, -- 处方主键 BRID VARCHAR(32), -- 患者标识 KSDM VARCHAR(10), -- 开单科室回查 SYS_KSDMK KDRQ DATETIME, -- 开单时间 ZT CHAR(1) -- 状态草稿/已提交/已作废 ); CREATE TABLE CPOE_XCFMXK ( XCFID VARCHAR(32) NOT NULL, -- 外键回指主表删主表必须级联 XH INT NOT NULL, -- 明细序号 YPDM VARCHAR(20), -- 药品代码回查药品字典 YL DECIMAL(12,4), -- 用量 PC VARCHAR(10), -- 频次回查 CPOE_YZYPPCK YF VARCHAR(10) -- 用法回查 CPOE_YZYPYFK );XCFID这个主键在两表间的传递决定了主从一致性的边界。它必须是同一次事务内生成的不能依赖数据库自增后再回查——中间件的连接池、分库分表的路由都可能让“插入主表后立刻取 ID”这一步在并发下拿到意外的值。常见的实现思路是在应用层生成主键比如业务编号加序列一次生成、两表共用这样主从插入可以在同一个事务里完成回滚也干净。ZT状态字段是另一个隐性契约明细表的行在什么状态下允许增删改完全由主表状态决定而不是由明细表自己判断。这条规则如果写成触发器会让批量的状态流转变得极慢写在应用层则要求所有入口都走同一个服务方法否则一定会有人绕过校验直接写明细。4.2 文字医嘱结构化之前的过渡形态CPOE_TEXTORDER文字医嘱、CPOE_WZYZK文字医嘱库、CPOE_YZLXK医嘱类型库这三张表的存在说明了一个事实临床上总有大量内容无法完全结构化。自由文本、护理嘱托、特殊说明这些必须留出通道否则医生会绕着系统走。这里的设计取舍点在于文字医嘱要不要走和结构化医嘱同一套状态机和同一条计费链路。文档里同时保留了CPOE_YZDJK医嘱单据和CPOE_YZDJFLK医嘱单据分类、CPOE_YZZXDJFLK医嘱执行单据分类说明单据是按类型分流的而不是所有医嘱共用一张大表。分流的好处是各类医嘱的字段可以独占手术医嘱有手术相关字段文字医嘱没有坏处是统计口径要做联合查询。CPOE_YZYLKZXXK医嘱用量扩展信息库和CPOE_YZSXTJDYK医嘱时限条件对应库是结构化程度的渐进式体现。前者把“用量”这个简单字段扩展成一组信息单次量、总量、单位换算、限量标志后者把“长期/临时”这类时限属性和条件绑定。这两个表的行数不会爆炸但字段的语义密度很高改动它们之前必须先弄清楚有哪些下游单据在引用。4.3 申请单的主从 附属信息模式CPOE_RIS_BRJCSQD病人检查申请单、CPOE_RIS_BRJCSQDMX检查申请单明细、CPOE_RIS_BRJCSQDFSXXK申请单附属信息这套三表结构解决的是典型的稀疏字段问题。一张检查申请单的必填字段就那么几个患者、申请科室、申请医生、检查项目、临床诊断。但不同类型的检查会带出一堆特有的附加信息CT 要造影剂过敏史超声要空腹标志内镜要既往史。这些字段如果全部拍进主表主表会变得极宽而绝大多数行是空值如果按检查类型建一堆子表表数量会失控。附属信息表用的是第三种解法一张窄表存“申请单 ID 信息键 信息值”。代价是查询要转置收益是加一种检查类型不需要改表结构。这种键值表在医疗系统里很常见因为检查、检验项目的种类在持续增加而且不同医院的配置差异极大。真正的坑在于值的类型——数字和文本混在一列里存做区间查询时排序会变成字典序。常见的实现思路是加一列类型标记或者把可查询的数值单独提一列。4.4 模板与实例分离模板库怎么被引用CPOE_RIS_SQDMBK检验申请单模板库、CPOE_RIS_SQDWZMBK申请单文字模板库、CPOE_SSJLMB手术记录模板、CPOE_SSXYMB手术协议模板这一批表全部以MBK结尾共同特征是“被复制不被引用”。这个区别很关键。字典表是被引用的业务表存代码改字典影响所有历史数据模板表是被复制的医生套用模板生成一份病历时模板内容被复制成实例数据此后模板怎么改都不影响那份已完成的病历。这个设计直接决定了模板可以放心地反复优化措辞而不用担心篡改历史记录。CPOE_SSXY手术协议和CPOE_SSXYMB手术协议模板的关系就是这条规则的实例协议是患者签过字的法律文书模板只是医生起草时的起点。如果实现时图省事让协议表只存一个模板 ID 加几个覆盖字段那么模板一改所有历史协议的展示内容都会跟着变——这在医疗纠纷场景里是致命的。5. 排查与避坑这几百张表最容易出问题的五个位置表结构文档写得再全也挡不住落地时踩坑。下面这五处是我在处理类似结构的系统时反复见到的每一条都按“现象 → 原因 → 解决”拆开写。5.1 坑一把字典表当快照用历史病历显示混乱现象某天有医生反馈翻看几年前的手术记录切口等级显示的是现在的名称和当时打印出来的纸质记录不一致。原因手术记录表里存的是切口等级代码展示时实时回查PUB_QKDJK。字典表的名称被更新过一字之差历史病历的展示内容随之改变。同样的坑还出现在科室更名、药品规格调整上。解决区分“需要跟随时点”和“需要跟随当前”的字段。诊断名称、科室名称这类展示层面跟当前版本通常可以接受但涉及等级评定、剂量规格、协议条款的必须在业务表里冗余一份名称快照或者给字典表加版本号让业务表引用“代码 版本”而非光秃秃的代码。判断标准很简单这个字段如果变了会不会影响对历史记录的解释会就必须存快照。5.2 坑二对应表缺有效区间月底对账对不上现象月中调整过一次医保项目对应关系月底财务对账时发现某几天的费用按新规则重算了总额和科室台账差了几十块钱。原因CPOE_YBXMDYK医保项目对应库这类表如果只存“项目 → 医保条目”的当前关系、没有生效和失效时间那么历史费用在补打单据或重算时会用上新规则。医疗计费的本质是“按业务发生时点的规则计算”缺了时间维度等于放弃了这条原则。解决所有涉及金额、比例、报销类别的对应表一律带QSRQ起始日期和ZZRQ终止日期查询时按业务发生时间落在区间内取唯一一条。CPOE_YBYPBXBLDYK医保药品报销比例对应库尤其要注意——比例调整是常态而且调整前后同一天的两次开单可能适用不同规则必须精确到时间点而非日期。区间重叠的脏数据要靠唯一约束或定期校验来兜底。5.3 坑三ICD9 与 ICD10 并存统计口径打架现象同一份月度手术统计从不同模块导出的数字对不上差额不大但每次都有。原因PUB_ICD9和PUB_ICD10两套诊断代码同时在用部分老模块还在写 ICD9 编码新模块写 ICD10统计时只查了其中一套。这种双轨并行在系统升级期是必然的因为历史数据的编码没法一次性全转。解决明确一条边界——新产生的业务数据只写新编码老编码只用于历史数据展示两者之间用转换表桥接不靠代码里的 if-else 判断。统计查询一律走转换后的统一口径并在报表上标注数据来源和转换比例。千万别指望“两套都能用所以无所谓”只要存在两条写入路径早晚会出一个漏改的模块。5.4 坑四主从表没有事务边界申请单出现无主明细现象检查申请单列表里主表记录被作废了但明细还在关联查询时出现一堆孤儿行。原因主从表的删除没有放在同一个事务里或者用ON DELETE CASCADE之外的方式手工删——先删主表再删明细中间一旦失败就留下残局。另一种更隐蔽的情况是应用层做了软删除主表标了作废标志明细表却没有同步。解决先做一次全库体检把孤儿明细捞出来-- 找出没有主表对应的明细行以小处方为例 SELECT m.* FROM CPOE_XCFMXK m LEFT JOIN CPOE_XCFK f ON f.XCFID m.XCFID WHERE f.XCFID IS NULL; -- 主表不存在即为孤儿 -- 软删除场景主表已作废但明细未同步 SELECT m.XCFID, COUNT(*) AS 残留明细数 FROM CPOE_XCFMXK m JOIN CPOE_XCFK f ON f.XCFID m.XCFID WHERE f.ZT 9 -- 9 表示已作废 AND m.ZT 9 GROUP BY m.XCFID;体检只是止血。真正的解法是把主从写操作收敛到单一服务入口事务里要么全成要么全败对已经存在的脏数据按业务时间点做一次对账修正而不是简单地批量删除——有些孤儿明细背后可能对应着真实的费用记录。5.5 坑五审核流程被硬编码改一个步骤要停机现象血库想加一级审核结果发现流程写死在代码里改完要发版、要停机还得等窗口期。原因CPOE_XKSHBZK审核步骤库、CPOE_XKSHJSK审核角色库、CPOE_XKSHLCK审核流程库、CPOE_XKSHMXK审核明细库这四张表本来就是为配置化准备的但落地时只用了其中一两张其余靠代码里的固定顺序。解决这四张表的分工要理清——流程库定“有哪几个流程”步骤库定“每个流程有几步、顺序如何”角色库定“每一步谁有权审”明细库记“每张单子走到了哪一步”。四张表齐全的时候加一级审核就是插一行步骤数据缺了任何一张配置能力就残缺一块。注意流程表最容易被绕过的地方是“特批”入口。只要系统里存在一个跳过审核的直接写入路径流程配置就形同虚设而且出问题时很难从日志里发现——因为从数据表面看单据状态是合法的。6. 设计取舍这套表结构为什么十年后还值得翻把这几百张表按主题过一遍会发现它在几个关键点上选了同一个方向宁可多几张表也要把“变化的”和“不变的”隔开。这个选择有代价值得说清楚。取舍点这套结构的选法代价收益字典 vs 冗余名称业务表存代码名称回查每次列表查询多一次关联或缓存维护字典改一次全库生效单表 vs 多表按业务域拆前缀分类表数量膨胀跨域查询要联表局部改动不扩散外键约束多数表靠应用层保证不建物理外键数据一致性依赖代码纪律批量导入、数据迁移不被锁死结构化 vs 文本两套并存按需分流统计口径要合并医生不会被系统逼着绕路映射 vs 合并保留双实体加对应表多一层间接排查链路变长两个部门各自维护互不阻塞外键那一行值得多解释两句。医疗系统里的批量操作极多夜间同步、报表抽取、历史数据迁移、跨院区数据交换。物理外键在这些场景下会带来额外的锁和校验开销所以很多实现选择“逻辑外键”——约束写在应用层数据库层只建索引。这个选择的代价是所有写入路径都必须守规矩一旦有人绕过服务层直接写库脏数据就进来了。这也是为什么前面那个孤儿明细的坑几乎无法根除。另一个取舍是表数量。几百张表听着吓人但真正需要日常关注的其实集中在少数几张热表上医嘱主表、申请单主表、费用明细。其余绝大多数是低频配置表一季度可能都不动一次。所以“表多”并不等于“复杂”复杂度取决于高频表的关联深度。如果要把这套结构迁移到新系统我的建议是先做一件事把每张表按“事实/字典/映射/模板/设置”五类贴好标签然后只盯“事实表”和“映射表”这两类的依赖链。事实表决定数据量映射表决定业务正确性其余三类基本上是配套设施迁移时可以整体平移不必逐字段推敲。说个我自己的教训。早年接手一次字典表清理看着PUB_YPCDMLK产地目录库里有几百条重复的产地名称觉得合并一下无非是改名就批量做了去重和规范化。改完第二天药品入库模块报错——产地代码被采购和库存两个模块同时引用我合并的是名称动的却是主键映射。从那以后我每次碰任何以K结尾的表都强制走一遍“先出引用清单、再评估影响、最后在测试库演练”的流程一次都没省过。字典表看起来最无害实际上是整套系统里牵连最广的一层这份表结构文档里那几十张K表每一张背后都有一串依赖它的业务表值得慢慢翻。希望帮到你。本文还有配套的精品资源点击获取