简介:一份面向医院信息科、系统集成商与医疗信息化技术人员的电子病历表结构文档,完整覆盖卫宁EMR中800多张数据表的定义,包括职工代码库、医疗项目库、药品分类库、科室/病区代码库、收费项目库、医保分类库、诊断代码库等关键数据域。每张表均列出字段名、类型、长度、注释和值域,便于快速掌握数据库设计规范;内容预览中可见从系统框架到财务收费、医疗信息、标准化互操作与安全保密等章节结构。整个资源包仅含1个doc文档,压缩包大小约13.92MB,打开后即可直接查阅全部表结构说明。当前已有1547人学习/下载,适合在EMR二次开发、接口联调、数据迁移和统计分析等工作中作为权威参考手册使用,也可帮助新成员理解卫宁临床信息系统的底层数据模型。
1. 卫宁电子病历表结构:一份让接口从黑匣子变白盒子的数据地图
接手过医疗信息化项目的工程师大多有过这种经历:拿到一份「卫宁电子病历表结构.doc」,打开一看几十张表、上千个字段,不知道从哪下手;真到写接口查数据时,又发现医嘱表、病历文书表、质控表之间关系盘根错节,稍不留神就查出重复或漏数。这份 doc 本质上是一张数据地图——它不负责解释业务,但把患者、就诊、医嘱、病历文书、质控这些核心域落成了具体表名、字段名和主外键关系。读懂了它,你能独立完成三类事:给第三方系统写数据查询接口、给科室做运营报表统计、给集成平台做数据对照和迁移校验。适合人群是医院信息科工程师、集成商实施人员和做二次开发的厂商研发。本文不假设你手上有完整源码,只按这份文档最常见的组织方式,把表结构设计逻辑和可复用的 SQL 套路拆开讲。
2. 看懂卫宁表结构的前提:电子病历系统的三层数据模型
2.1 患者、就诊、病历文书:为什么表要分三层
电子病历系统不管哪家厂商,底层数据模型几乎都逃不开三层:患者主索引层、就诊层、业务文书层。卫宁的表结构文档翻开后,第一眼看到的往往不是病历表,而是患者主索引表和就诊表,原因就在这里。患者主索引表解决「这个人是谁」——姓名、性别、出生日期、证件号、院内唯一患者 ID;就诊表解决「这次住院或门诊是什么」——入院时间、出院时间、科室、病区、床号、就诊类型。病历文书表才真正承载「这次就诊期间写了哪些记录」。
三层分开设计的直接好处是:一次就诊可以挂多份文书,一份文书只属于一次就诊,患者可以反复住院而产生多条就诊记录。写 SQL 时最容易犯的错是拿患者 ID 直接去关联文书表,结果把该患者历次住院的病历全捞出来。正确路径是先通过就诊表锁定本次就诊,再向下关联文书。我在项目里见过不止一次统计口径错误,根因都是跳过就诊层直接做患者级关联,查出来的「某患者住院期间文书数」其实是多年的累计值。
表结构里这三层的主键关系通常是:患者主索引表主键被就诊表引用,就诊表主键被文书表引用,形成一条完整的引用链。这份 doc 的目录编排也基本按这个顺序走,所以读文档时不要跳着看,先找到三层的主表和主键字段名,后面所有查询都围绕这条链展开。
2.2 字段命名的规律:看清前缀和状态位,半小时就能定位表
卫宁的表结构文档阅读门槛不高,但前提是掌握命名规律。我习惯先做一件事:把 doc 里所有表名导出来扫一遍,按前缀归类。常见做法是业务表带业务域前缀,比如患者相关、就诊相关、医嘱相关、病历相关、质控相关,每个前缀对应一个业务域。辅助表则常见字典、参数、人员、科室这类基础数据表。这样归类后,你不需要逐张表去读,按业务查询需求反向定位即可。
表内字段也有明显套路。几乎每张核心业务表都带几个固定字段:主键字段(往往叫流水号或内部编号)、业务编号(如就诊号、医嘱号)、有效标志、创建时间、创建人、最后修改时间。有效标志这个字段特别容易被忽略——它不是业务数据,但直接影响查询结果。很多表做的是逻辑删除而不是物理删除,一条医嘱作废了不会真删行,而是把这个标志从有效置为无效。如果写 SQL 时不带这个过滤条件,统计医嘱数、费用明细时会把已作废数据也数进去,报表对不上账。
字段值域方面,表结构文档里通常有字典表说明,比如性别、婚姻状况、科室代码的取值来源。实际工作中,这些字典值在不同院区的历史数据里可能不一致,所以我建议把字典表挨个过一遍,顺便记下代码值和显示名的对应关系,写查询条件时直接用代码值过滤,显示名只用于结果输出。
2.3 状态机字段:比数据本身更值钱的元数据
电子病历表结构里最容易低估的是状态字段。一份病历文书从创建到归档会经历编辑、提交、审签、质控等多个环节,表里就对应一组状态值。状态字段查询的价值在于:统计「已归档文书数」「待审签文书数」这类管理指标时,靠的就是对状态值做条件过滤,而不是逐行读正文内容。质控表则单独记录质控扣分、质控状态,它和文书表之间通过文书记录关联。
经验是拿到表结构先把每条状态字段的取值范围确认清楚,有条件的话直接用一条 distinct 语句看真实数据分布。某开发者曾写过一个统计报表,把状态过滤条件写反了,导致「质控通过率」和实际完全背离——后来发现是状态字段有多个取值各代表一个环节,他只过滤了其中一个。我的习惯是,凡是含「状态」二字的字段,在第一周内逐一验证取值范围,这个动作能挡住绝大多数统计口径错误。
3. 核心表逐个拆解:从患者主索引到病历文书
3.1 患者主索引表和就诊表:先定位人,再定位事
患者主索引表是整套系统最基础的表,字段基本覆盖身份信息,但要注意:身份证号不一定唯一,历史上可能出现过手工建档的重复档案,所以真实项目里存在「同一患者多条主索引记录」的情况。做主数据对照时要格外谨慎,不能只拿身份证号去匹配,最好联合姓名、出生日期、性别一起核对。
就诊表在住院场景下通常叫住院就诊记录表,核心字段包括就诊流水号、患者 ID、入院科室、入院病区、入院时间、出院时间、就诊状态。就诊状态常见取值有在院、出院、归档等,统计在院患者数就是对这个字段做条件计数。这里有个容易踩的坑:出院时间和就诊状态可能不同步,偶发情况是患者已临床出院几天,状态还没翻掉。做实时在院统计时尽量以状态为准,做历史分析时再回看时间字段。
我在实际对接中一般先把这两张表的所有字段列一遍,标记出哪些是物理主键、哪些是业务主键,再画一条「患者 → 就诊 → 文书」的关联示意图。示意图贴在工位旁边,后面写再多查询也不会迷路。
3.2 就医嘱表:长期医嘱、临时医嘱怎么区分
医嘱表是电子病历系统里数据量最大、查询最频繁的表之一。表结构里通常有一个字段区分医嘱类型,常见取值区分长期医嘱和临时医嘱,还可能细分出退费、作废等业务类型。长期医嘱和临时医嘱在业务上的差异非常大:长期医嘱每天自动滚动执行,临时医嘱只执行一次。做统计分析时,如果混在一起数,得到的指标既不是长期也不是临时的口径,完全没有业务解释力。
医嘱表还有两个关键时间字段:开嘱时间和停止时间。长期医嘱会有对应的停止时间,临时医嘱这条字段往往为空。查「当前有效医嘱」时,条件要写成开嘱时间早于当前时间、停止时间晚于当前时间(或为空)、有效标志为有效。三条件缺一个都不严谨。药品医嘱还常带药品编码、剂量、频次,这些字段一般以编码形式存储,输出时要关联药品字典翻译成名称和规格。
给外部系统提供接口时,医嘱表也是重点关注对象。比如集成平台要同步用药信息,通常会以医嘱表为数据源,按时间增量抽取。增量抽取的游标字段一般用创建时间或最后修改时间,但这两者如果没建索引,大表查询会非常慢,这点到后面避坑章节展开。
3.3 病历文书表:正文内容存在哪,签名状态怎么标
病历文书在表结构上一般拆成两到三张表:文书主表、文书正文表、文书签名表。主表存的是文书的元数据——文书类型、所属就诊、创建人、创建时间、最后修改时间、文书状态;正文表存的是文书内容。正文内容常见两种存储方式:一种直接存在大字段里,比如 CLOB 类型;另一种存的是模板数据和渲染参数,页面展示时动态拼接。判断属于哪种方式,看正文字段类型即可。CLOB 字段可以直接通过数据库客户端查看内容;如果是模板拼接方式,正文表里可能只是一个关联模板的标识和一组 XML 或 JSON 格式的参数。
文书类型是另一个必须摸清的字段。入院记录、病程记录、出院小结、手术记录……每种类型一条记录。统计报表里最常见的需求是「某科室本月出院小结完成率」,这就要在文书主表上按就诊关联、按文书类型过滤、按完成状态判断。文书状态在表结构里常见取值包括编辑中、已提交、已审签、已归档,但不同厂商或不同项目可能在取值上存在差异,最稳妥的方式是直接查一条真实数据看状态值。
签名表和文书主表一般是一对多的关系:一份文书可能经历住院医师、主治医师、主任医师多级签名,每个签名一条记录。表结构里通常有签名人、签名时间、签名级别、签名结果。查「某份文书是否完成审签」,不能只看主表状态,更严谨的做法是从签名表里查是否存在对应级别的签名记录。这两个口径在个别项目里出现过不一致,我一般以签名表为准。
3.4 质控表:医务科最关心的指标其实在这
质控信息在卫宁电子病历里常见独立表存放,和文书主表通过文书标识关联。质控表记录每条文书的质控状态、质控人、质控时间和质控结果,有的项目还带扣分明细表,每条扣分记录一个原因、一个分值。医务科报表里的「病历质控合格率」「平均扣分」都是从这组表算出来的。
扣分明细表往往比质控主表更重要。主表只告诉你这份文书有没有问题,明细表才告诉你问题出在哪个环节。做指标分析时,既可以对主表做计数统计合格率,也可以对明细表做分组汇总看高频缺陷类型。缺陷类型一般以编码形式存储,需要关联缺陷字典翻译。这组字典在表结构文档里一般放在基础数据部分,建议提前导出来维护成一份本地码表。
写质控相关 SQL 时要特别注意状态字段的过滤顺序:先按时间范围收紧,再做状态过滤,最后关联字典翻译。质控表的记录不是一次性写入的,一份文书可能被复核多次,取最新记录时要按质控时间倒序取第一条,或者按质控批次标识过滤。常见实现方式是使用窗口函数或子查询取最大时间行,这点在后面查询案例里会给出 SQL。
4. 表间关联与常用 SQL:三个拿来就能改的查询模板
4.1 查询某患者本次住院期间的全部病历文书
场景很典型:外部系统需要拉取某患者某次住院的病历文书列表。正确关联路径是:患者主索引 → 就诊表 → 文书主表。先在患者表里按证件号定位患者内部 ID,再到就诊表里按内部 ID 和入院时间锁定本次住院的就诊号,最后从文书主表取文书列表。SQL 如下:
SELECT v.visit_id, v.in_time, p.patient_name, m.doc_type, m.doc_status, m.create_time FROM pat_master p JOIN visit_info v ON p.patient_id = v.patient_id JOIN emr_doc_master m ON v.visit_id = m.visit_id WHERE p.id_card_no = '110101199001011234' AND v.in_time >= DATE '2025-01-01' AND v.in_time < DATE '2025-04-01' AND m.effective_flag = '1' ORDER BY m.create_time;逻辑说明:第一层用证件号过滤患者,第二层用入院时间范围锁定本次住院(实际项目中这里通常还会加就诊状态过滤),第三层关联文书主表时过滤有效标志,避免把已作废记录带出来。文末按创建时间排序,保证多份文书按书写顺序展示。
参数说明:pat_master、visit_info、emr_doc_master 是常见的通用命名,真实表名以 doc 中标注为准;effective_flag 是有效标志位,在卫宁表结构里常以 0/1 表示无效/有效,个别项目也用 Y/N,取值标准要先查字典;id_card_no 换成实际证件号码。如果就诊表里存在多条同时段的就诊记录(极少数转科造成的记录拆分),建议先按主键去重或按住院次数取最大值。
4.2 统计某科室最近 30 天的医嘱类型分布
统计科室工作量、药品消耗这类报表需求,几乎都落在医嘱表上。查询逻辑是先限定科室、再限定时间窗口、按医嘱类型分组计数。需要注意两点:科室字段要确认是入院科室还是当前科室,历史数据场景下一般以医嘱开立时的科室为准;时间字段要用开嘱时间,不能用创建时间,两者偶发不一致。
SELECT o.order_type, COUNT(*) AS order_cnt, COUNT(DISTINCT o.visit_id) AS visit_cnt FROM doctor_orders o WHERE o.order_dept = '0102' AND o.create_time >= SYSDATE - 30 AND o.effective_flag = '1' GROUP BY o.order_type ORDER BY order_cnt DESC;逻辑说明:按医嘱类型分组计数,同时统计涉及的就诊数(去重)。order_cnt 反映医嘱条数,visit_cnt 反映覆盖多少患者,两者结合能看出该科室开嘱密度。若只统计有效医嘱,过滤条件为 effective_flag 有效值即可。
参数说明:SYSDATE - 30 是 Oracle 写法,换成 MySQL 用 DATE_SUB(NOW(), INTERVAL 30 DAY)。order_type 的编码值需要关联字典翻译,常见取值如 L 代表长期医嘱、T 代表临时医嘱,但以实际项目字典配置为准。如果统计结果和科室手工台账对不上,优先排查是否有退费医嘱没过滤——退费医嘱通常以负数金额或作废标志存在,不影响 count,但影响金额汇总。
4.3 查询未完成质控的病历文书清单
质控管理员最常跑的一条查询是「哪些文书还没质控」。核心是先找出所有已归档(或已提交)的文书主表记录,再左连接质控表,连接不上的就是未质控记录。这里必须用左连接而非内连接,因为未质控的文书在质控表里压根没有行。
SELECT m.visit_id, m.doc_id, m.doc_type, m.submit_time FROM emr_doc_master m LEFT JOIN emr_quality_control q ON m.doc_id = q.doc_id WHERE m.doc_status = '30' AND m.effective_flag = '1' AND q.qc_id IS NULL;逻辑说明:LEFT JOIN 后通过 q.qc_id IS NULL 过滤出质控表无关联记录的行,即为未质控文书。doc_status 的取值 30 在本示例里代表已归档,真实项目的状态值需查询确认。若同一文书存在多次质控记录,则 qc_id 不唯一,这个写法会把已质控过的文书也当成未质控——更稳妥的写法是关联质控表的最新一条记录(取最大质控批次或最大质控时间),或先用子查询按 doc_id 去重后再关联。
SELECT m.doc_id, m.doc_type, m.submit_time FROM emr_doc_master m LEFT JOIN ( SELECT doc_id, MAX(qc_time) AS qc_time FROM emr_quality_control GROUP BY doc_id ) q ON m.doc_id = q.doc_id WHERE m.doc_status = '30' AND m.effective_flag = '1' AND q.qc_time IS NULL;逻辑说明:子查询先按 doc_id 分组取最大质控时间,再与主表做连接,避免因多次质控记录导致误判。这套写法同样适用于签名表、审签表的关联查询。参数说明:qc_time 为质控时间字段名,真实表结构里也可能是 create_time 或 operate_time,确认后替换即可。
5. 卫宁表结构避坑指南:空值、编码不一致和连表陷阱
5.1 查询结果比预期多:把业务编号当成了物理主键
现象:关联两张表后,查询返回的行数明显多于预期,明细对不上总数。原因:业务编号(如就诊号、医嘱号)在表结构里并非唯一约束,历史数据可能出现重建、合并或重复录入。解决:先查业务编号是否有重复,再决定用物理主键(通常是内部流水号)做关联。判断 SQL 用一条分组统计即可——按业务编号分组且计数大于 1 的组即为重复数据。
经验是任何新接入的表,第一周都跑一遍唯一性校验,把该表所有标注为主键或业务唯一键的字段逐一带 distinct 验证。这个过程不复杂,但能避免之后所有查询建立在错误关联上。
5.2 退费医嘱导致金额汇总为负数
现象:统计科室一段时间内药品费用,出现负数或明显偏小的金额。原因:医嘱表里退费记录仍保留在原表,只是加了作废标志或反向金额。解决:做金额汇总时必须判断金额字段的符号和作废标志,将退费记录排除或单独归集。正确过滤条件通常是金额大于 0 且有效标志为有效,若是退费另表记录,则从汇总表排除退费表的关键字段。
这类问题最隐蔽的变体是「冲正」记录——金额为负的抵消记录,单看数据行完全正常,但汇总后会把真实费用拉低。我踩过一回后,凡是涉及金额的统计都会多问一句:退费怎么记录?表的标志位是什么?确认清楚了再写 SQL。
5.3 直接查大字段内容导致接口响应超时
现象:从文书正文表取数据时,SQL 本身执行很快,但接口返回很慢,甚至卡死。原因:正文内容存在大对象字段里,直接 SELECT 会把完整大字段读出,传输耗时巨大。解决:查询时避免直接 SELECT 大字段,只查元数据;确需正文内容时使用数据库大对象函数截取前段,或拆成两条 SQL ——先取元数据,再按需拉正文。Oracle 常用 DBMS_LOB.SUBSTR,MySQL 用 SUBSTRING 但注意大字段类型限制。
这个坑在提供接口给第三方时尤其明显。某次对接体检系统,对方要取入院记录的既往史文本,直接查询大字段导致接口平均响应 8 秒,后来改成只取前 2000 字符才达标。
5.4 编码字典两张皮:业务表存代码,字典表有另一套值
现象:统计报表里的科室名称、药品名称显示为代码,关联字典表后仍有一批数据关联不上。原因:业务表的编码值来自旧字典或历史自定义值,而当前字典表已改版,旧编码未做映射。解决:先查业务表 distinct 的编码值,和字典表全量编码做差集,把缺失值单独确认含义后手工映射。
更常见的是同一个编码在不同表里含义不同。某个项目的性别字段,患者表用 0/1/2 表示未知/男/女,医嘱表里却用 F/M/U。这两套必须分开维护映射,不能一套字典通用。我的习惯是建一张自维护的编码对照表,把每次排查出来的差异手工登记,越攒越全,后面写任何查询都先查这张表。
5.5 在大表上对时间字段做函数处理导致全表扫描
现象:按时间范围统计医嘱量,数据量只有几十万行却跑了近一分钟。原因:对时间字段使用了函数包裹,如 TRUNC(create_time) = TRUNC(SYSDATE),导致索引失效。解决:改成范围条件,写成 create_time >= TRUNC(SYSDATE) AND create_time < TRUNC(SYSDATE + 1)。范围条件能走索引,函数条件走不了。
排查方法很简单:执行计划里看到 TABLE ACCESS FULL 就是没走索引。养成写完 SQL 先看执行计划的习惯,比出问题后加索引更省时间。
6. 进阶:把表结构文档变成接口交付物的三个习惯
6.1 先做字段级映射,再审阅数据样例
拿到 doc 的第一周先不要写业务代码,做三件事:把核心表字段导出成清单,按业务域分组标注主键、外键、状态位、字典表来源;对每张核心表做一次数据抽查,看字段非空率和值域分布;把抽查中发现的问题登记到字段清单备注列。这套准备工作做完后,写接口时基本不需要翻原版 doc,直接看这份标注后的清单即可。我见过不少同事直接拿原版 doc 当字典用,翻页找字段的时间比写 SQL 还长。
6.2 用视图封装核心查询,隔离底层表结构调整
如果项目里多个接口都用到同一套查询逻辑,建议在数据库里建视图或公共查询层。比如上面「某患者本次住院全部文书」的关联逻辑,建成视图后所有接口统一查询视图,一旦底层表结构调整或字段改名,只改视图一处即可。实际项目中电子病历表结构偶尔会因为版本升级新增字段或调整状态值含义,视图封装能把影响范围控制在最小。
但要注意:视图内不要写死具体业务参数,像科室编码、证件号码应作为查询条件传入,视图只做表关联和基础过滤。参数写在外部 SQL 里,既能复用又保灵活。
6.3 建一张自己的编码对照表,告别口头确认
第三步是一个长期受益的习惯:在本地维护一张编码对照表,记录每个编码字段的代码值、显示名、所属表、来源代码、确认日期、确认人。每次发现字典差异或编码缺漏,就往这张表里补一行。连续维护两三个项目后,这张表的价值会超过官方字典本身——它记录的是真实数据里的分布情况,而不是理想的设计值。
某次上线前数据迁移,迁移脚本里所有编码转换都直接查这张对照表生成,省掉了大量逐字段核对的时间。对照表本身不需要多复杂,字段名、代码值、显示名、来源表、备注,五行足够。
说到底,表结构文档只是起点。真正让人放心的不是背下字段名,而是建立一套自己的验证流程:字段唯一性校验、状态值分布探查、编码字典核对、执行计划阅读。这些习惯能挡住绝大多数接口事故。做医疗数据对接这几年,我养成的最大教训是:永远不要相信字段名,永远要相信查询出来的数据——字段名叫 patient_name 的不一定是患者姓名,也许是亲属联系人。拿着 doc 做参照,用真实数据做验证,两条腿走路才稳当。希望这篇表结构拆解对你有帮助。
本文还有配套的精品资源,点击获取