简介《卫宁电子病历表结构》面向医院信息科、系统集成与医疗数据分析人员聚焦卫宁EMR数据库表结构的查阅与理解。文档系统梳理800余张表的说明、字段定义、类型、长度、备注及值域覆盖职工、科室、病区、药品、收费、医保、诊断等基础代码库与业务数据模型可用于系统对接、报表开发、数据查询和接口字段核对。压缩包内为1个doc文件约13.92MB适合按目录检索并作为日常数据字典参考。已有1554人学习下载说明其在医疗信息化从业者中有一定参考价值。通过厘清表间关系与编码规则读者能更快定位字段含义、减少沟通成本并为数据标准化、互操作及权限管理提供依据。1. 字典表、业务表与映射表800 张表里真正装的是什么第一次翻电子病历表结构文档的人几乎都会先去找「病历正文存在哪张表」然后在 800 多张表里翻半天最后发现真正承载病历内容的表可能连十分之一都不到。剩下的大头全是字典以及把字典两两对起来的映射表。原因不难理解。临床信息系统 EMR 里最难缠的部分从来不是文本存储而是编码体系一条诊断要落到 ICD-10 上一个收费项目要和医保目录对得上一种剂型只允许配特定的医嘱频次与用法一个科室要挂到某个药房下面。这些「谁和谁能配上」的规则每一条都得有张表来承接。所以看这份数据库结构设计说明书的目录前五十项几乎清一色是PUB_、SYS_开头职工代码库、科室代码库、药品分类库、收费大项目库、医保分类库、职称编码库、诊断代码库。这套设计真正解决的问题是把「医疗业务规则的高频变化」和「数据库表结构的低频变化」隔开。规则变了改数据结构尽量不动。值不值得搞懂取决于你信不信一句话——医院的流程天天在变但变的方式是稳定的。2. 命名即契约前缀和后缀在说什么一套 800 多张表的 EMR 库如果表名不带约定三个月后连原厂的人都读不懂。这份文档最值钱的隐性资产其实是命名规则本身。它用前缀划边界、用后缀标角色两层叠加之后光看表名就能猜出这张表在系统中扮演什么角色、大概长什么样。2.1 四个前缀划出的系统边界SYS_是系统级基础职工代码库、科室代码库、病区代码库都在这里。它存储的是「全院唯一」的组织实体任何模块引用科室最终都指回SYS_KSDMK。PUB_是公共字典覆盖医疗项目、药品分类、凭证类型、收费大项目、医保分类、药房代码、职称编码、诊断代码、地区/区县/国家代码以及 ICD9、ICD10。这一层的特点是跨模块复用门诊要用、住院要用、医生站要用、护士站也要用。文档里把 ICD9 与 ICD10 并排放成两张独立表PUB_ICD9、PUB_ICD10而不是一张表加版本字段这个取舍后面还会说到。CPOE_是医生站侧医嘱、申请单、手术、模板、打印、排班、临床路径相关的东西基本都在这个前缀下。CPOE 是 Computerized Physician Order Entry 的缩写直译是「计算机化医嘱录入」但这份文档里的 CPOE 前缀覆盖面明显更宽连血库审核流程、小处方、术后随访都挂在这里说明它在实际实现里代表的是「医生工作站的整套业务域」而不只是医嘱录入。OUTP_是门诊侧里面有门诊医嘱频次、门诊医嘱用法、皮试药品对应、导入院感诊断。这里有个值得注意的信号CPOE_YZYPPCK是住院医嘱药品频次OUTP_MZYZPCK是门诊医嘱频次两条平行车道。同样叫「频次」门诊和住院各存一份而不是统一到一张全局字典里。这不是设计失误而是被业务节奏逼出来的。门诊频次是「每日三次、隔日一次」这种以天为单位的粗粒度住院频次则要精确到「q8h」「q12h」这种以小时为单位的执行节拍还要和护士执行单绑定。强行合并成一张表字段会膨胀成一张谁都看不懂的宽表查询时还得处处带就诊类型过滤条件。分开存虽然冗余但每张表都干净。CPOE_RIS_这种二级前缀也值得留意。RIS 是放射信息系统检查申请单相关的一组表都挂在它下面主表、明细、附属信息、模板库、数据分类库。二级前缀的作用是把一个子系统圈起来避免它污染整个 CPOE 命名空间。做数据库分层的时候这个手法可以直接借鉴。2.2 后缀才是信息密度最高的部分前缀回答「这张表属于谁」后缀回答「这张表怎么用」。六个常见后缀值得单独拎出来后缀含义典型例子判断依据K字典库存编码与名称PUB_YPFLK药品分类库有*DM主键和*MC名称MXK明细库主表的下级行项目CPOE_RIS_BRJCSQDMX检查申请单明细有指向主表的*IDDYK对应库两张表之间的多对多映射PUB_KSYFDYK科室药房对应至少两个*DM维度字段SZ设置参数、开关、联动规则CPOE_XCF_SZ小处方设置无业务主键多为一行为一条规则MB/MBK模板CPOE_SSJLMB手术记录模板带模板名称与启用标志BFK备份CPOE_ZXZTDSQBFK中心制团队申请备份库结构与主表同构这套后缀约定至少在两个地方救过命。一是做数据迁移时*DYK结尾的表可以先跳过它们都是纯映射关系重置成本极低二是排查线上问题时如果怀疑某条医嘱的频次不对视线要落到*PCK和*PCYFDYK上而不是去翻病历正文表。但约定只是约定它依赖每个开发者自觉遵守没有任何机制强制。写个脚本把表名扫一遍、推断角色是读这套文档最快的切入点import re # 后缀必须按长度降序匹配 # 如果 K 排在 DYK 前面所有对应表都会被误判成字典表 SUFFIX_RULES [ (DYK, mapping), # 对应库两个维度以上的多对多关系 (MXK, detail), # 明细库主表的行项目 (MBK, template), # 模板库 (BFK, backup), # 备份库 (_SZ, setting), # 设置项参数、开关、联动规则 (MB, template), # 无 K 的模板如 CPOE_SSJLMB (K, dict), # 字典库 ] def classify(table_name: str) - str: upper table_name.upper() for suffix, role in SUFFIX_RULES: if upper.endswith(suffix): return role return biz # 无后缀约定的纯业务流水表 for t in [PUB_KSYFDYK, CPOE_RIS_BRJCSQDMX, CPOE_XCF_SZ, CPOE_SSJLMB, PUB_YPFLK]: print(f{t:24s} - {classify(t)})这段逻辑只有三十来行但它把「匹配顺序就是正确性」这件事讲透了。DYK天然以K结尾MXK也一样顺序一错整批表的角色判定全崩后面所有依赖这个分类做的迁移脚本、脱敏脚本、索引建议都会跟着跑偏。这类问题不靠测试很难发现因为脚本本身不会报错它只是安静地给你一份错的答案。另一个坑是格式不统一。CPOE_XCF_SZ用的是下划线后缀CPOE_SSTSGR干脆没有后缀CPOE_YZDYSZ_EX还带了个_EX。这说明命名约定在长期迭代中已经被侵蚀了一部分。真正写解析逻辑时规则表得留一个兜底分支把认不出来的表登记出来人工过一遍而不是硬塞进某个分类。3. 映射表为什么占了大半多对多关系的隔离带翻目录时最容易忽略、也最容易在维护时踩坑的是那一大批*DYK结尾的表科室药房对应、门诊收费执行科室对应、住院收费执行科室对应、科室病区对应、剂型用法对应、医嘱频次用法对应、医保项目对应、医保药品对应、诊断药品对应、职务药品对应。数量上看它们在这份文档里占据了相当可观的篇幅而每一张都可能直接关系到线上业务能不能正常跑通。3.1 用对应表代替外键的真实动机如果按教科书的做法科室和药房的关系应该在科室表里放一个药房代码外键。这套结构没有这么做它把关系抽成独立的行放进PUB_KSYFDYK。动机是组织结构的不稳定性。医院里科室合并、药房拆分、收费执行科室调整、医保目录年度切换这些事件的发生频率远高于「数据库表结构变更」的容忍度。关系抽成数据行之后一次调整就是一次INSERT加一次UPDATE不动 DDL不用停业务也不用担心历史数据被牵连。更关键的是对应表一般都带有效标志和生效时间而不是直接把旧行删掉。这一点对医嘱系统尤其重要三年前开的一条医嘱回看时得按「当时」的对应关系还原出它到底执行到哪个科室、走了哪个药房。如果只保留当前最新的对应关系历史医嘱的责任归属就彻底丢了。常见做法是在对应表上加三列YXBZ有效标志、QSRQ生效日期、JSRQ结束日期查询时用QSRQ 业务日期 AND (JSRQ IS NULL OR JSRQ 业务日期)来定位。代价是每次关联都要多一个时间条件索引要建在(维度A, 维度B, QSRQ)这种组合上否则历史区间的过滤会退化成全表扫。3.2 一条医嘱落下去到底碰了几张表拿一条住院西药医嘱举例药品本身的属性在PUB_YPLXK剂型在PUB_YPJXK频次和用法分别走CPOE_YZYPPCK与CPOE_YZYPYFK但这两个不是自由填写的——它们要经过CPOE_YZPCYFDYK和CPOE_YZYPYFDYK的合法性校验。也就是说一种剂型允许哪些频次、哪些用法是配置出来的不是硬编码在代码里的。-- 还原一条住院西药医嘱的字典侧完整依赖链 -- 目的确认频次/用法是否在该剂型的合法组合内以及医保报销比例 SELECT yz.YZID AS 医嘱号, yz.YZMC AS 医嘱内容, yplx.YPMC AS 药品名称, jx.JXMC AS 剂型名称, pc.PCMC AS 频次, yf.YFMC AS 用法, dyk_pc.YXBZ AS 频次对该剂型可用, dyk_yf.YXBZ AS 用法对该剂型可用, yb.YBBZ AS 医保标志, bl.BL AS 报销比例 FROM CPOE_YZK yz LEFT JOIN PUB_YPLXK yplx ON yz.YPDM yplx.YPDM LEFT JOIN PUB_YPJXK jx ON yplx.JXDM jx.JXDM LEFT JOIN CPOE_YZYPPCK pc ON yz.YZID pc.YZID LEFT JOIN CPOE_YZYPYFK yf ON yz.YZID yf.YZID LEFT JOIN CPOE_YZPCYFDYK dyk_pc ON pc.PCDM dyk_pc.PCDM AND yplx.JXDM dyk_pc.JXDM LEFT JOIN CPOE_YZYPYFDYK dyk_yf ON yf.YFDM dyk_yf.YFDM AND yplx.JXDM dyk_yf.JXDM LEFT JOIN CPOE_YBYPDYK yb ON yz.YPDM yb.YPDM LEFT JOIN CPOE_YBYPBXBLDYK bl ON yb.YBPZDM bl.YBPZDM;这几张表里JXDM是最不显眼、作用却最大的一个字段。它在频次对应和用法对应里充当维度把「剂型」变成了一条隐形的约束边界。口服液的频次选项和注射剂的频次选项本来就该不一样这个差异不是写死在界面上的而是从CPOE_YZPCYFDYK里查出来的。代价是查询深度。单是一条医嘱的完整展示就要七到八次关联。真实系统不会每次都这么查它会在医嘱主表里冗余一份频次名称和用法名称的文本快照。字典改了历史医嘱显示不变这是有意为之并非偷懒。快照字段的存在等于给每条医嘱留了一颗后悔药。这也解释了一个现象为什么*DYK表数量这么多但真正参与高频查询的只有那么几张。大部分对应表只在开单那一刻被读一次用来生成候选列表之后就被快照替代了。理解这一点才不会误以为所有 DYK 都需要建重量级索引。4. 从医嘱到执行主表、明细、设置这套组合怎么配合如果说字典层解决的是「数据从哪来」那 CPOE 业务层解决的就是「一次临床动作怎么被完整记录下来」。这套文档里反复出现一种三段式结构主表管生命周期、明细表管行项目、设置表管规则。看懂了这个模式剩下几百张表基本都是它的变体。4.1 申请单的三段式主表、明细与附属信息以检查申请单为例CPOE_RIS_BRJCSQD存一次开单行为本身——开单医生、开单时间、申请科室、执行科室、单据状态CPOE_RIS_BRJCSQDMX存具体检查项目一次可以开多个所以是一对多CPOE_RIS_BRJCSQDFSXXK存附属信息比如临床诊断、症状描述、结构化字段这些。把附属信息单独拆出来是这套结构里比较讲究的一手。主表的字段集合相对稳定几年下来就那么几十个但附属信息的结构随检查类型变化——CT 要问造影剂过敏史超声要问空腹状态内镜要问既往病史。如果全塞进主表主表会被撑成一张几百列、处处可空的宽表任何一次SELECT *都是灾难。拆成附属信息表之后它变成了一个纵向扩展的结构检查类型增加只需要增加行不需要增加列。代价是读一次申请单要做两次查询但在申请单这种低频操作上这个代价完全可以接受。模板库CPOE_RIS_SQDMBK和文字模板库CPOE_RIS_SQDWZMBK的存在进一步说明这套设计对「开单效率」的重视。模板不是给医生看的参考文档它是可复用的行集选中一个模板等于一次性往明细表里插好几行。这是把医生的重复劳动压缩成一次点击。4.2 小处方与血库审核流程同构模式在别处的体现小处方用的是完全相同的三段式CPOE_XCFK是处方头CPOE_XCFMXK是处方明细CPOE_XCF_SZ是设置。设置表在这里承担的是「某类处方默认包含哪些药品、默认用法用量是什么」的规则存储。血库审核那组表则更值得单独看因为它其实是套微缩的工作流引擎表名角色存什么CPOE_XKSHBZK步骤库定义审核有哪些步骤、步骤顺序、是否终态CPOE_XKSHJSK角色库定义每个步骤允许哪些角色操作CPOE_XKSHLCK流程库定义某类业务如全血、成分血走哪条步骤链CPOE_XKSHMXK明细库记录实际发生的每一步审核动作运行时序大致是这样开单时按输血类型去CPOE_XKSHLCK拿到流程编号再顺着流程编号去CPOE_XKSHBZK取出这条流程的步骤序列用户打开待办列表时用当前用户的角色去CPOE_XKSHJSK过滤出他可执行的步骤审核完成时往CPOE_XKSHMXK写一条记录带上步骤号、操作人、时间最后判断当前步骤是否为终态决定单据能不能发血。这套结构的价值在于「换流程不改代码」。某个医院想把「主治审核后再由血库复核」改成「血库直接复核」改的是CPOE_XKSHLCK里的几行数据不是重新发版。输血本身是高风险操作能不动物流逻辑只调配置对实施团队来说省了很多事。顺带一提输血相关的表不止这四个还有CPOE_SXBLFYK输血不良反应库和CPOE_SXZLTYS输血治疗同意书。前者说明系统把「事后」也纳入了数据模型——不良反应是独立实体不是主记录上的一个标志位这样才能支持一条输血记录对应多种不良反应。4.3 手术域字典先行业务后置手术相关的表几乎把「字典先行」这个思路演示了一遍。手术等级、切口等级、手术诊断、手术部位、手术麻醉、手术类别、手术科室七张字典表打底然后才是CPOE_SSYZK手术医嘱库和CPOE_SSYZK_SZ手术医嘱设置。VVZYK_SZ这个后缀在这里的语义很清楚它存的是「某类手术默认要下哪些医嘱」的规则。开一台胆囊切除术系统自动带出术前抗生素、术后镇痛、留置引流管观察这几条医嘱医生逐条确认或删掉。规则从代码里搬到表里术式新增时实施人员自己就能配不用排开发资源。CPOE_ZXZTDK中心制团队库和它的备份库CPOE_ZXZTDSQBFK则是另一种模式主表和备份表同构备份表承担的是「申请变更时先存一份原样」的职责。这是在没有完整审计日志体系时常用的补偿手段简单、直接、不依赖任何中间件。它的短板也很明显——只能存一个版本第二次变更就覆盖了第一次的备份。要真正追溯还是得上独立的变更流水表。5. 避坑清单拿到这套表结构后最容易翻车的五处文档读得再顺落地时照样会踩坑。下面这几条是从实际对接中攒下来的按现象、原因、解决三段写每条都能对上号。5.1 文档层面的坑同名表、重复条目与命名不统一现象按文档建表时CPOE_TEXTORDER出现了两次——一次在系统框架章节1.36 文字医嘱一次在医生工作站章节2.24 文字医嘱库。照单全收会直接撞名建表脚本在第二处报错退出。原因这份文档是按模块分区组织的每个模块把自己用到的表都列一遍模块之间没有做全局去重。CPOE_PSYPDYK皮试药品对应库也是同样情况在OUTP_和CPOE_两个章节里都出现过。这反映的是文档组织方式不是数据库里真有两张同名表。解决建表前先做一次全量去重按表名分组同名表只保留一份 DDL其余位置只写引用注释。去重时以业务归属为准——CPOE_TEXTORDER归医生站OUTP_PSYPDYK归门诊CPOE_PSYPDYK归住院两边字段如果有差异要人工比对不能想当然认为完全一致。5.2 建表层面的坑主键、类型与值域三件事都没给全现象文档给的是表说明、字段名、字段类型、字段长度、备注和值域看起来齐了。但照着建出来的表一张主键都没有也没有索引定义。上线跑两周一条关联查询要几秒。原因结构设计说明书描述的是逻辑模型不是物理模型。主键、索引、约束这些属于物理实现文档里天然缺失。另外值域那一列很多时候写的是中文描述比如「0 表示未审核1 表示已审核」不是可执行的枚举直接拿去生成CHECK约束会失败。解决主键靠命名推断加人工确认两步走——*DM、*ID、*BH结尾的字段优先怀疑是主键*MXK结尾的表还要再看有没有联合主键通常是主表 ID 加行号。索引先按外键列和日期列建别一上来就建一堆复合索引。值域那一列要反查对应的字典表PUB_*K里基本都能找到找不到的才考虑用注释里的描述生成枚举并且要在代码里注明来源。5.3 运行期的坑字典变更把历史数据带偏现象某科室调整后三个月前开的历史医嘱在报表里全部归到了新科室名下财务对账对不上。原因*DYK对应表如果是直接UPDATE覆盖而不是插入新行并结束旧行历史业务日期就失去了当时的映射关系。再加上医嘱主表里的科室字段如果存的是代码而不是快照文本回看时必然跟着字典一起漂移。解决对应表一律用「新增行 结束日期」的方式维护禁止原地改主键维度。查询侧统一带业务日期条件。医嘱、申请单这类核心业务表科室、药品、频次这些关键属性除存代码外再冗余一份名称快照历史展示只读快照不读字典。5.4 口径层面的坑门诊与住院的平行表被当成一张用现象做统一报表时把CPOE_YZYPPCK和OUTP_MZYZPCK用UNION ALL合并结果频次去重后数量对不上同一个频次名称在两边的主键不一样。原因这两张表本来就是两条独立车道主键生成规则不同编码体系也没有强制对齐。名字撞了不代表语义一致。解决跨门诊住院的统计一律先建立一张内部对照表把两边的频次、用法、科室代码手工归一再做聚合。别指望UNION能自动对齐。5.5 扩展层面的坑把设置表当业务表改现象CPOE_SSYZK_SZ手术医嘱设置被业务人员直接在数据库里改了几行第二天开单时模板全乱了而且没人记得改之前是什么。原因_SZ后缀意味着规则存储。它没有版本、没有审计、没有回滚改错了只能靠记忆复原。而它影响的却是所有新开的单据。解决所有_SZ结尾的表在权限上单独收口只允许通过配置界面修改禁止直连写。上线前做一次全量导出做基线每次变更自动留一份带时间戳的备份。这类表数量不多但每张都能直接决定业务能不能跑。6. 设计取舍为什么它宁愿宽表加编码也不做强约束把这套结构从头看到尾会发现一个很一致的选择数据库层面几乎见不到外键约束几乎所有关系都靠应用层加对应表来维持。这不是技术能力问题是刻意的取舍。强约束换来的是一致性代价是变更成本。医院的字典不是静态的医保目录一年一调科室随组织架构变动收费项目按物价文件更新。如果医嘱表上挂了外键指向药品字典字典里一条记录想停用都得先处理完所有引用它的历史医嘱要么级联、要么置空两种做法都会污染历史数据。把约束上移到应用层数据库层面就获得了随时增加新维度的自由——代价是每个写入入口都得自己记得校验漏一处就产生一条脏数据。第二层取舍在查询性能上。七到八张表的关联在开单那一刻可以接受在报表和列表页就不能接受。所以它选择在业务表里冗余快照字段用空间换响应时间同时顺带解决了历史数据漂移的问题。这算是一份投入拿了两份收益。第三层取舍在扩展性上。_SZ设置表和*DYK对应表把「规则」从代码里搬到了数据里实施人员能自己配不用等发版。代价是规则的执行逻辑散落在各处没有统一入口排查问题时得一层层往下找。判断这套结构合不合理有个很实在的验证方法把所有*DYK结尾的表列出来统计每张表的维度字段数量。如果某张表出现了三个以上的*DM字段它其实已经在扮演规则引擎的角色而不是简单的两两映射。-- 统计对应表的维度字段数超过 3 个的要重点看 SELECT TABLE_NAME, COUNT(*) AS 维度字段数, GROUP_CONCAT(COLUMN_NAME) AS 维度字段 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME LIKE %DYK AND COLUMN_NAME LIKE %DM GROUP BY TABLE_NAME HAVING COUNT(*) 3 ORDER BY 维度字段数 DESC;跑出来的结果基本就是这套系统里最复杂的几处业务规则所在。拿到这几张表再去对照文档里的备注比通读 800 张表效率高得多。这套结构的适用边界也很清楚它适合业务规则变化快、历史数据必须可回溯、实施团队有能力做配置的场景不适合对强一致性要求极高、写入路径单一且稳定的系统。看清楚这个边界才不会把「宽表加编码」这套手法无差别搬到自己项目里。我第一次接手这类系统时吃过一个亏以为文档写的就是数据库真实结构直接照建结果主键、索引全靠猜第一次压测就翻车。从那以后我拿到任何一份表结构文档都先跑三件事——全量去重、后缀分类、维度字段统计三份结果摆在面前再动手写第一行 DDL。希望帮到你。本文还有配套的精品资源点击获取