☰
联众电子病历表结构文档实战:从ER图到SQL联表查询避坑指南
2026/10/12 8:17:53 网站建设 项目流程

简介:浙江联众卫生信息科技有限公司出品的电子病历(EMR)表结构说明文档,面向医院信息化管理员、系统运维与二次开发人员,旨在帮助读者理解并高效管理电子病历相关数据库结构。文档由占贵顺、葛向东多次修订,内容覆盖ZJHIS、EMR3、EMR及EMRJK等应用场景,从基础用户信息到临床数据的关键表均有涉及。压缩包内为1个doc格式文档,大小6.07MB。文档详细介绍了Zjhis用户、系统信息、公用医院标识、公用代码、病人类别/性质、科室/病区代码、系统参数、职工与用户信息等表的字段定义与用途,还包含住院病人信息、婴儿登记等扩展内容,可作为电子病历数据库设计、数据治理和日常维护的重要参考。已有349人下载学习,是医疗信息化项目实施中实用的工具性资料。

1. 联众电子病历表结构文档:一份让 EMR 联表查询少走弯路的底图

接了三个月联众 EMR 的接口开发,最难受的不是写业务代码,而是每次想查“某个患者最近一次病历记录”都得靠猜:病历表的主键到底是谁?模板表和记录表是直接关联还是中间还有一张映射表?状态字段里的数字含义文档里有没有写?这份联众电子病历表结构文档,解决的正是这些问题。它把二十来张核心表的字段、主外键关系、索引和常用查询路径一次讲清,适合两类人:一是刚接手联众 EMR 做二次开发或数据对接的工程师,二是要写报表、做质控统计的医院信息科人员。文档是 doc 格式,结构完整,能直接作为建表评审、接口设计的参照底图。

2. 先读懂这份 doc:ER 图、表清单与字段命名的门道

拿到一份表结构文档,大部分人习惯直接翻字段列表,我建议反过来:先花半小时把 ER 图和外键关系捋顺,再回头查字段。联众电子病历这套表结构,核心表之间不是简单的父子关系,而是围绕“患者、就诊、病历、模板”四条线交织在一起的,没看懂关系就写 SQL,后面基本要返工。

2.1 文档内通常包含哪些内容

这份 doc 文档虽然只有十几页,但信息密度不低,一般会按目录分成几个固定板块:实体关系图(ER 图)在前,然后是表清单,再往后是逐表字段明细,最后是索引和常用查询示例。我在实际拆分时,建议你按下面这张表去定位内容:

板块内容使用场景
ER 图表与表之间的关联关系设计联表查询、确认外键方向
表清单表名、中文注释、用途分类快速判断业务功能对应哪张表
字段明细字段名、类型、长度、是否为空、默认值、说明写 SQL、核对接口返回数据
索引说明主键索引、普通索引、联合索引优化查询性能、避免全表扫描
查询示例典型业务场景的简易 SQL复用到自己的报表或接口里

doc 文档和在线文档不同的地方在于,它没有超链接跳转,所以我的习惯是先给每个板块做一个书签,或者把 ER 图单独截图放在手边,查表的时候反复对照。特别提醒一下,doc 里的字段明细部分,我遇到过中文字段注释和实际数据库注释对不上的情况,这种情况以数据库实库为准,doc 只作参照。

2.2 ER 图先看主表,优先抓三张核心表

联众 EMR 的 ER 图我拆过多次,画得还算规范,但表一多就乱。我的方法是先找三类表:病人主索引表、病历记录表、病历模板表。病人主索引表通常是整个 ER 图的起点,几乎所有业务表都直接或间接引用它的主键;病历记录表是收治过程中的事实表,记录一次入院或一次门诊产生的病历内容;病历模板表提供结构化定义,记录表引用模板表的模板 ID 来约束内容格式。

抓这三张表就够了,其他辅助表比如科室表、医生表、字典表,本质上是维度表,只在需要展示中文名称时才关联。多数联众表结构文档里,这三张表会出现在 ER 图的中心位置,外键线也最密。如果你发现文档里的 ER 图没有直接画出外键线,只是用字段名暗示关联,那就要特别小心:这种文档通常需要你自己拿 SQL 去验一遍实际外键关系。

2.3 字段命名规则:前缀表名、后缀表类型

联众的表字段命名有一个容易上手的规律:字段前缀通常与所属表名相关,比如病历记录表里的字段多以“BR_”或“BL_”开头,别名字段常以“BH”结尾(编号)。字段类型方面,主键几乎都是整型自增,业务编号用 VARCHAR,时间字段用 DATETIME,内容大文本用 TEXT 或 LONGTEXT。由于具体命名在各版本里可能有差异,我一般会先看 ER 图旁边是否有“字段命名规范”说明,有就以说明为准。

这套命名规律的价值在于:拿到新字段不用每次都翻表,基本能猜个八九不离十。比如某表有字段 EMP_ID 和 DEPT_ID,那大概率是关联员工表和部门表的维度字段。猜归猜,最后建查询前仍然建议用DESC语句确认实际字段,避免文档和实库不一致带来的“玄学报错”。

3. 核心表拆解:病人主索引、模板库与病历记录怎么连

表结构文档最有价值的章节,永远是核心表的字段明细。拆联众这套表结构时,我的建议是先建一张核心表速查表,把自己关心的表名、主键、关键业务字段集中列出来,后面写 SQL 时不需要反复翻文档。下面把最常用的四张表过一遍,字段模型按通用口径描述,实际以文档和实库为准。

3.1 病人主索引表:整个 EMR 的入口

病人主索引表承担的是患者身份唯一性职责,联众 EMR 里通常叫 PATIENT_INDEX 或类似命名,主键是 PID,业务上可能还有门诊号、住院号、身份证号等统筹字段。这个表的字段设计有几个值得注意的地方:

CREATE TABLE patient_index ( pid BIGINT PRIMARY KEY COMMENT '病人主索引ID,全局唯一', patient_no VARCHAR(32) NOT NULL COMMENT '院内患者编号,跨就诊唯一', id_card VARCHAR(18) COMMENT '身份证号,脱敏后存储', name VARCHAR(64) NOT NULL COMMENT '患者姓名', birth_date DATE COMMENT '出生日期', gender CHAR(1) COMMENT '性别,1男 2女 9未知', create_time DATETIME COMMENT '建档时间', update_time DATETIME COMMENT '最后更新时间' );

逻辑说明:这张表最重要的约束是patient_no唯一,它是业务层面识别患者的依据。id_card在很多医院场景里出于隐私保护会做加密或脱敏,所以文档里如果写了明文存储,你在做数据迁移时就要额外加一道脱敏处理。pid是全局主键,所有子表的外键都用它。

参数说明:gender用单个字符存枚举值,比用 VARCHAR 存“男/女”更省空间,但可读性差,查询时要做字典翻译。birth_date用 DATE 类型而不是 DATETIME,是为了避免时间部分对统计造成干扰。

3.2 病历模板表:结构化的元数据中枢

病历模板表保存的是病历的“格式定义”,比如入院记录模板、出院小结模板、手术记录模板。联众 EMR 里模板表通常包含模板编码、模板名称、模板内容(往往是 XML 或 JSON 结构)、版本号以及生效状态。模板是病历记录表引用的核心维度,版本管理做得不好会导致历史病历渲染错乱。

CREATE TABLE emr_template ( template_id BIGINT PRIMARY KEY COMMENT '模板ID', template_code VARCHAR(32) NOT NULL COMMENT '模板编码,业务唯一', template_name VARCHAR(128) NOT NULL COMMENT '模板名称', template_type CHAR(2) COMMENT '模板分类:01入院 02出院 03手术 04病程', content_format VARCHAR(16) COMMENT '内容格式:XML/JSON/TEXT', content_structure LONGTEXT COMMENT '模板结构定义,XML或JSON', version_no INT DEFAULT 1 COMMENT '版本号,每次修改自增', status TINYINT DEFAULT 1 COMMENT '状态:0停用 1启用' );

逻辑说明:version_no字段是这套设计的灵魂。模板发生调整时,不是直接更新记录行,而是插入一条新模板并把版本号递增,这样历史病历通过快照引用固定版本,不会被后续模板调整影响。content_structure存的是结构定义,不是实例内容,所以用 LONGTEXT 完全够用。

参数说明:template_type用两位字符编码,扩展性比 TINYINT 数字更直观。status是软删除思路,停用的模板在业务查询里默认要加status = 1过滤条件,否则历史数据会被重复带出。

3.3 病历记录表:事实数据的主战场

病历记录表是联众 EMR 里数据量增长最快的表,每一次保存操作都可能落一条记录。它除了记录模板 ID、患者 ID、医生 ID,还会保存病历内容快照以及状态字段。状态字段是最容易踩坑的,因为不同版本的取值含义不一致,有的用 0/1 表示草稿和完成,有的用 1/2/3 表示暂存、提交、归档。

CREATE TABLE emr_record ( record_id BIGINT PRIMARY KEY COMMENT '病历记录ID', pid BIGINT NOT NULL COMMENT '病人主索引ID,关联patient_index', encounter_id BIGINT NOT NULL COMMENT '就诊ID,一次住院或门诊对应一个', template_id BIGINT NOT NULL COMMENT '模板ID,关联emr_template', record_content LONGTEXT COMMENT '病历内容快照,XML或JSON', record_status TINYINT COMMENT '状态:1暂存 2提交 3归档', doctor_id BIGINT COMMENT '经治医生ID', create_time DATETIME COMMENT '创建时间', submit_time DATETIME COMMENT '提交时间' );

逻辑说明:record_content是这份文档里最值钱的字段,它存的是整个病历的结构化内容,通常包含患者主诉、现病史、诊断等子项,用 JSON 存储就是一套完整的病历数据。encounter_id把一次就诊下的多份病历串起来,是统计“某次住院写了多少份病历”的关键关联键。

参数说明:查询时优先通过encounter_id过滤而不是pid,因为一次住院会有多次病程记录,按就诊维度查才能拿到完整链路。record_status的取值一定要在写 SQL 前用文档核对,常见版本是 1 暂存、2 提交、3 归档,但有些版本会用 A/B/C 存字符。

3.4 三张表的串联路径

现在把三张表放在一起看完整的关联关系:从 patient_index 拿到患者,通过 encounter 表(就诊表)找到一次具体的住院或门诊,再通过 encounter_id 查到该次就诊下所有病历记录,最后用 template_id 关联模板表获得病历的格式定义。这个链路的顺序是固定的,中间不能跳步。

我在拆文档时习惯把这条链路写成固定模板:

SELECT pi.name, r.record_id, t.template_name, r.record_status, r.create_time FROM emr_record r JOIN patient_index pi ON r.pid = pi.pid JOIN emr_template t ON r.template_id = t.template_id WHERE r.encounter_id = 12345 ORDER BY r.create_time;

逻辑说明:这条 SQL 展示了三表关联的基本姿势。JOIN的方向是从事实表 emr_record 出发,左连维度表,这样查询基数最小。如果反过来从 patient_index 出发,结果集会因为一对多关系被放大,统计数字就会翻车。

参数说明:WHERE r.encounter_id = 12345是核心过滤,实际业务里 encounter_id 来自就诊表查询结果。ORDER BY r.create_time按时间排序,保证病历按书写先后顺序展示。这里没有关联 encounter 表,因为本次查询只需要一次就诊的数据,直接指定 encounter_id 即可;如果需要展示就诊类型和入出院时间,再补 JOIN encounter。

4. 从表结构到业务查询:把文档翻译成可跑的 SQL

表结构文档的价值不在“看”,在“用”。下面拿三个真实场景来演示:按就诊查全量病历、按模板统计科室完成率、跨表筛选患者。这三个查询覆盖了一大半日常开发需求,你可以直接把 SQL 改改表名拿去用。

4.1 场景一:查某次就诊下所有病历及内容摘要

临床科室最常问的问题是“这个患者这次住院都写了哪些病历”。按第 3 章的串联路径,查询核心是 emr_record 表,再把模板名称带出来:

SELECT r.record_id, t.template_name, r.record_status, CASE r.record_status WHEN 1 THEN '暂存' WHEN 2 THEN '提交' WHEN 3 THEN '归档' END AS status_text, LEFT(r.record_content, 80) AS content_preview, r.create_time FROM emr_record r LEFT JOIN emr_template t ON r.template_id = t.template_id WHERE r.encounter_id = 20240315001 ORDER BY r.create_time DESC;

逻辑说明:LEFT(r.record_content, 80)是对大字段做截断预览,避免把整篇病历内容一次性拉出来导致网络传输过大。CASE WHEN把状态码翻译成人话,这是接联众 EMR 报表时最常用的技巧。

参数说明:20240315001是就诊号,实际场景中来自就诊查询。ORDER BY ... DESC把最新写的病历排最前。如果病历数量非常大,建议在 emr_record 表上建(encounter_id, create_time)联合索引,这条 SQL 的响应时间能从秒级降到毫秒级。

4.2 场景二:按模板维度统计各科室病历完成情况

这个场景多用于质控统计,核心是“提交并归档的病历算完成,暂存的不算”。需要联合科室表和医生表:

SELECT d.dept_name, t.template_name, COUNT(r.record_id) AS total_cnt, SUM(CASE WHEN r.record_status = 3 THEN 1 ELSE 0 END) AS finished_cnt, ROUND(SUM(CASE WHEN r.record_status = 3 THEN 1 ELSE 0 END) / COUNT(r.record_id) * 100, 2) AS finish_rate FROM emr_record r JOIN emr_doctor doc ON r.doctor_id = doc.doctor_id JOIN emr_dept d ON doc.dept_id = d.dept_id JOIN emr_template t ON r.template_id = t.template_id WHERE r.create_time >= '2025-01-01 00:00:00' AND r.create_time < '2025-02-01 00:00:00' GROUP BY d.dept_name, t.template_name ORDER BY finish_rate ASC;

逻辑说明:这里把状态过滤放在SUM(CASE WHEN ...)内部而不是WHERE,目的是保留总记录数,再做条件统计。如果直接在 WHERE 里过滤状态,就看不到分母了,完成率会失真。

参数说明:时间范围用左闭右开写法>= and <,可以规避月末最后几秒的数据漏查。ROUND(x, 2)控制百分比小数位。ORDER BY finish_rate ASC升序是把完成率低的科室排前面,方便质控重点关注。

4.3 场景三:跨表筛选含主诉关键词的患者

病历内容存在 LONGTEXT 里,联众常用 XML 或 JSON 格式保存。想查“主诉包含胸痛的患者”,如果直接LIKE '%胸痛%'会误匹配到现病史、诊断等其他子项,常见做法是先把 JSON 里的主诉字段提取出来:

SELECT pi.patient_no, pi.name, r.encounter_id, JSON_EXTRACT(r.record_content, '$.chief_complaint') AS chief_complaint FROM emr_record r JOIN patient_index pi ON r.pid = pi.pid WHERE r.template_id = 1001 AND JSON_EXTRACT(r.record_content, '$.chief_complaint') LIKE '%胸痛%' AND r.record_status = 3;

逻辑说明:JSON_EXTRACT只取出 JSON 内容里的主诉字段,避免 LIKE 误伤其他字段。template_id = 1001限定在入院记录模板内,因为只有入院记录才规范填写主诉,病程记录里即使有主诉数据也不该参与检索。

参数说明:这段 SQL 依赖库版本对 MySQL 5.7+ JSON 函数的支持。如果现场是 MySQL 5.5,JSON_EXTRACT 用不了,退路是取出 content 后在应用层解析,或者查文档确认是否用 XML 格式存储,XML 场景需要改用EXTRACTVALUE。条件里强制record_status = 3,避免把草稿里的模拟数据统计进临床数据。

4.4 查询前必须确认的四个参数

查联众 EMR 数据前,我一般会先确认清楚:状态字段的取值含义是否和文档一致、时间字段是DATETIME还是VARCHAR、内容字段是TEXT还是LONGTEXT、主键是BIGINT还是字符串,这四点不确认就开写 SQL,大概率要返工。实际经验是时间字段最容易出问题,有些历史版本会用 VARCHAR 存'2025-01-01 10:30:00',导致范围查询走不上索引。确认手段就是DESC emr_record;看字段类型,再SELECT COUNT(*) FROM emr_record WHERE create_time IS NULL;看脏数据量。

5. 避坑:字段文档与实库不一致的五个典型雷区

联众电子病历的表结构文档我用下来,最深的感受是:文档能做向导,不能做圣旨。下面这五个坑是在多个现场反复踩出来的,每个都是“现象 → 原因 → 解决”的完整路径,你对照着排雷即可。

5.1 doc 里的字段名在实库里根本不存在

现象:按文档写SELECT emr_id FROM emr_record,数据库直接报“Unknown column”。

原因:文档版本和现场部署版本不一致,现场做过二次开发或补丁升级,字段被改名或删除。

解决:先用DESC emr_record;拉出实库字段清单,和 doc 做字段级比对。我一般会把实库字段导成 Excel,和 doc 手工比对两遍,重点看业务编号和时间字段。比对时注意大小写敏感性,MySQL 在 Linux 下字段名区分大小写,Windows 下不区分,同一个库迁移到 Linux 上也可能踩这个坑。

5.2 ER 图里画了外键,实际 SQL 却跑不通关联

现象:ER 图上 emr_record 和 emr_doctor 明显连线,JOIN 时却查不到数据。

原因:表间关联字段虽然存在,但值域不匹配。常见情况是 emr_record.doctor_id 存的是工号字符串,emr_doctor.doctor_id 是自增整型,两边编码体系不同。

解决:不要轻信关联字段名相同就一定能 JOIN。先用两秒钟做值域验证:SELECT doctor_id FROM emr_record LIMIT 5;和SELECT doctor_id FROM emr_doctor LIMIT 5;对比数据类型和样例值。如果一边是工号一边是自增 ID,需要通过员工对照表转换,或者直接用工号字段关联。这种问题在文档上看不出来,只有跑数据才能发现。

5.3 时间字段用 VARCHAR 存,范围查询慢到怀疑人生

现象:WHERE create_time >= '2025-01-01'执行了三十秒,就差没超时。

原因:老版本表结构把时间存成 VARCHAR,索引失效;或者字段名为create_time实际是字符类型。

解决:如果字段确实是 VARCHAR 类型,优先做数据治理把存量数据迁成 DATETIME 或 TIMESTAMP,新写入由程序统一用参数化时间格式。迁移前先确认应用代码的读写逻辑是否依赖字符串格式,比如DATE_FORMAT函数嵌套,迁移后要同步改掉。日常查询可以临时用STR_TO_DATE(create_time, '%Y-%m-%d %H:%i:%s')转换再比较,但这种方式不会走索引,大数据量下依然慢,只能作为过渡方案。我的习惯是一旦确认时间字段是 VARCHAR,就推动下个版本直接改表结构,不要给历史包袱留情面。

5.4 病历内容取出来是乱码,或者是一大段转义字符

现象:record_content查出来是{\"chief_complaint\":\"...\"},前端直接展示会出现斜杠和引号。

原因:内容字段可能存储了双重转义的数据,或者程序写入时做了addslashes处理,取出时没做反转义。

解决:先用SELECT LEFT(record_content, 200) FROM emr_record LIMIT 1;观察原始存储格式。如果是 JSON 带反斜杠,用REPLACE(record_content, '\\"', '"')逐层清理;如果是 PHP 老系统写入的,可能还需要去掉\\r\\n。更稳妥的方案是写一个数据清洗脚本,把历史数据统一转成标准 JSON 再覆盖回去,但覆盖前必须备份表,谨防误操作导致临床数据丢失。这类“内容扭曲”不是表结构问题,是历史代码问题,doc 文档里不会有任何说明。

5.5 状态字段的魔法值:文档里没写的取值范围

现象:按文档写的状态 3 过滤归档病历,统计结果比科室手工数少了 20%;单独查状态字段,发现还有 4、5、9 等文档没提到的值。

原因:现场二次开发扩展了状态枚举,比如新增 4 表示“已打印”,5 表示“作废”,或者 9 表示“异常关闭”,字段注释没同步更新。

解决:凡是碰到状态字段,必须先跑一遍全量枚举:

SELECT record_status, COUNT(*) FROM emr_record GROUP BY record_status;

逻辑说明:这条 SQL 能把状态分布一次性摊开,哪些值有数据、哪些值文档没说明,一目了然。看到文档没写的枚举值不要慌,找到使用该状态的业务代码或问现场运维确认语义,确认后把新枚举值补充到自己的状态字典表里。

参数说明:如果结果是 0 也算一种状态,说明库里存在状态初始化的脏数据,需要排除。统计时建议把枚举映射放在 SQL 外层或者视图层,不要散落在各个报表 SQL 里,以方便后续维护状态语义。

6. 进阶:用 information_schema 反向核验文档与实库的一致性

表结构文档最怕的不是字段多,而是文档和实库悄悄分家。我见过某医院联众 EMR 做完一次版本升级,文档还是旧的,结果报表系统跑错了一周才发现。从那以后我每次接手联众 EMR 相关项目,都强制走一遍“文档 ⇄ 实库”字段比对,再累也值。

这一步可以直接用 SQL 把实库中的表结构拉出来,再和 doc 字段明细逐条核对:

SELECT TABLE_NAME, COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_COMMENT FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = 'emr_db' AND TABLE_NAME IN ('emr_record', 'emr_template', 'patient_index') ORDER BY TABLE_NAME, ORDINAL_POSITION;

逻辑说明:information_schema.COLUMNS是 MySQL 自带的元数据表,查出来的结果就是各表的真实字段定义。把这份结果导出成 Excel,和 doc 字段明细逐列比对,差异部分就是文档失真点。核对时重点看四列:字段名、字段类型、是否可空、字段注释。

参数说明:TABLE_SCHEMA = 'emr_db'换成实际库名。比对时留意COLUMN_TYPE里的长度信息,比如varchar(50)和varchar(255)在文档里可能都写作“字符型”,但实际容量天差地别,字符串长度不够会导致后续写入截断。

如果现场环境是 Oracle 或 SQL Server,对应查询换成ALL_TAB_COLUMNS或INFORMATION_SCHEMA.COLUMNS,思路完全一致。手动比对费时,但准确性最高;如果你有 Python 环境,也可以写个十行脚本自动做差集比对,把差值输出成报告。我一般会把比对报告存档,当作交接文档的一部分,后续任何人接手都不用再从头猜。

这套核验方法配合前面四章的拆解路径,基本能把联众电子病历表结构文档的价值完整榨出来。核心就一句话:先看 ER 图定链路,再查字段理枚举,最后用实库校验兜底。希望帮你少走两天弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询