资讯详情

联众电子病历表结构文档解析:从数据字典到SQL避坑实战

📅 2026/10/9 16:42:20 | 华诺云谱 👁 阅读
联众电子病历表结构文档解析:从数据字典到SQL避坑实战
简介《联众电子病历表结构文档》面向ZJHIS、EMR3、EMR及EMRJK等系统的开发、运维与数据管理人员系统梳理电子病历核心数据表的字段定义与设计逻辑帮助读者理解并高效管理病历数据。文档由占贵顺、葛向东多次修订完善内容覆盖用户信息、系统配置、医院标识、公用代码、病人类别与性质、科室与病区代码、系统参数、职工信息、医生分组、医疗收费及套餐、费用接口、项目审批、住院病人信息、婴儿登记等模块并延伸至病案系统相关表结构。资源包共1个doc文件约6.07MB以表格与目录形式组织便于按模块检索字段含义与关联关系。目前已有349人学习下载适合需要快速掌握联众EMR数据模型、开展接口对接或二次开发的技术人员参考。1. 联众电子病历表结构文档从一份“天书”到可落地的数据字典第一次拿到联众电子病历的表结构文档很多人以为它只是一份“字段清单”翻两页就扔到一边。真正踩过坑的人才知道这份文档决定了你后面能不能把病历数据查出来、对得上、跑得通。它描述的是电子病历系统底层数据库里每一张表、每一个字段、每一个主外键的含义与约束是数据抽取、接口对接、报表开发、质控分析的起点。你如果是医院信息科工程师、医疗数据开发、或者做病历数据治理的从业者这份文档就是你的地图。地图读不懂后面全是玄学。我见过太多人跳过文档直接连库结果在“病人主索引”和“就诊记录”之间反复翻车查出来的数据对不上号。这一章先把这份文档到底管什么、为什么值得花时间啃清楚讲明白。2. 联众电子病历表结构文档里到底有什么核心表与字段关系拆解2.1 从病人主索引到病历文书四层数据模型联众电子病历的表结构按我的经验大体分成四层。第一层是病人主索引通常叫PATIENT_MASTER或类似名字存的是患者基本信息比如病案号、姓名、性别、出生日期、身份证号。这一层的关键是病案号它是整个系统里唯一标识一个患者的业务主键。第二层是就诊记录常见表名VISIT或ENCOUNTER一次住院或一次门诊就是一条记录关联病案号同时带就诊类型、入院时间、出院时间、科室。第三层是病历文书比如入院记录、病程记录、手术记录、出院小结这些通常存在MEDICAL_DOCUMENT或按文书类型分表。第四层是具体内容比如诊断、医嘱、检验结果、影像报告这些表通过就诊号或文书号关联。这四层的关系是一个病案号对应多次就诊一次就诊对应多份文书一份文书对应多条内容。你写 SQL 的时候如果跳过就诊层直接从病案号去关联文书就会把不同次住院的数据混在一起。这是最常见的翻车点。2.2 关键字段的命名规律与含义联众的表结构文档里字段命名有比较明显的规律。主键通常叫ID业务主键叫PATIENT_NO、VISIT_NO、DOC_NO。时间字段喜欢用_TIME或_DATE结尾比如ADMIT_TIME、DISCHARGE_TIME、CREATE_TIME。状态字段常用STATUS或FLAG值一般是数字或单字符。文本内容字段常用CONTENT、TEXT、REMARK。文档里会标注每个字段的类型、长度、是否可空、默认值。你要特别关注的是可空字段和默认值。比如DISCHARGE_TIME在患者还没出院时是空的你如果直接拿它做时间差计算就会得到空值。再比如STATUS默认值是0但0到底代表“未提交”还是“已作废”文档里不一定写清楚得结合业务代码或实际数据去验证。2.3 用一条 SQL 把四层关系串起来下面这条 SQL 是我常用的模板用来验证表结构文档里的关联关系是否和实际数据一致。假设数据库是 Oracle 或 SQL Server语法略有差异但思路一样。-- 查询某患者所有就诊记录及其病历文书数量 SELECT p.PATIENT_NO, -- 病案号 p.PATIENT_NAME, -- 患者姓名 v.VISIT_NO, -- 就诊号 v.VISIT_TYPE, -- 就诊类型1住院 2门诊 v.ADMIT_TIME, -- 入院时间 v.DISCHARGE_TIME, -- 出院时间未出院为空 COUNT(d.DOC_NO) AS DOC_COUNT -- 该次就诊的病历文书数量 FROM PATIENT_MASTER p JOIN VISIT v ON p.PATIENT_NO v.PATIENT_NO LEFT JOIN MEDICAL_DOCUMENT d ON v.VISIT_NO d.VISIT_NO WHERE p.PATIENT_NO 你的测试病案号 GROUP BY p.PATIENT_NO, p.PATIENT_NAME, v.VISIT_NO, v.VISIT_TYPE, v.ADMIT_TIME, v.DISCHARGE_TIME ORDER BY v.ADMIT_TIME DESC;这段 SQL 的逻辑是从病人主索引出发关联就诊记录再左关联病历文书。用LEFT JOIN是为了保证即使某次就诊没有文书也能查出来。COUNT(d.DOC_NO)统计文书数量。参数说明PATIENT_NO是业务主键不是自增 IDVISIT_TYPE的值需要查文档或问业务方确认DISCHARGE_TIME为空表示在院。跑完这条 SQL你就能验证文档里写的关联字段是否真的能对上。如果查出来文书数量全是 0要么是关联字段写错了要么是文书表里没有数据。2.4 文档里没写但你必须知道的三个隐含约束表结构文档通常只写字段定义不写业务规则。但下面这三个隐含约束你不搞清楚就会踩坑。第一病案号可能重复。有些系统里同一个患者多次住院会生成新的病案号而不是复用同一个。这时候病人主索引表里会有多条记录你需要用身份证号或其他唯一标识去合并。第二文书表可能分表。入院记录、病程记录、手术记录可能不在同一张表里而是按类型分表。文档里如果只写了一张MEDICAL_DOCUMENT实际库里可能有MEDICAL_DOCUMENT_IN、MEDICAL_DOCUMENT_COURSE等多张表。第三时间字段的时区。有些系统存的是 UTC 时间有些存的是本地时间。文档里一般不写。你如果做时间统计先拿一条已知时间的数据去验证。3. 把表结构文档变成可查询的数据字典实操步骤与参数配置3.1 从文档到 ER 图先画关联再写 SQL拿到文档后不要急着写 SQL。先花半小时把核心表的关联关系画成 ER 图。不用很漂亮纸上画就行。重点标出主键、外键、关联方向、一对多还是多对多。比如病人主索引和就诊记录是一对多就诊记录和病历文书是一对多病历文书和诊断内容是一对多。画完 ER 图你会发现有些表是“孤立”的文档里没写它和谁关联。这时候去问业务方或者去查系统里已有的视图和存储过程。视图里通常藏着关联逻辑。3.2 用 Python 读取文档并生成字段清单如果文档是 Word 或 PDF手动抄字段太慢。我一般用 Python 把文档里的表格提取出来生成 CSV 或 JSON方便后续查询。下面是一个用python-docx读取 Word 表格的示例。from docx import Document import csv doc Document(联众电子病历表结构文档.docx) rows [] for table in doc.tables: for row in table.rows: cells [cell.text.strip() for cell in row.cells] rows.append(cells) # 写入 CSV方便用 Excel 或 pandas 查看 with open(table_structure.csv, w, newline, encodingutf-8-sig) as f: writer csv.writer(f) writer.writerows(rows) print(f共提取 {len(rows)} 行)这段代码的逻辑是遍历文档里所有表格把每一行单元格的文本提取出来写入 CSV。参数说明encodingutf-8-sig是为了 Excel 打开不乱码。newline避免空行。跑完之后你会得到一个字段清单可以用 pandas 做筛选比如查所有包含“时间”的字段。3.3 建立本地数据字典表字段名、类型、含义、关联表提取出来的字段清单最好导入数据库建一张本地数据字典表。这样你写 SQL 的时候可以随时查字段含义。表结构可以这样设计CREATE TABLE LOCAL_DATA_DICT ( TABLE_NAME VARCHAR(100), -- 表名 COLUMN_NAME VARCHAR(100), -- 字段名 DATA_TYPE VARCHAR(50), -- 数据类型 DATA_LENGTH INT, -- 长度 IS_NULLABLE CHAR(1), -- 是否可空 Y/N DEFAULT_VALUE VARCHAR(200), -- 默认值 COLUMN_COMMENT VARCHAR(500), -- 字段含义 RELATED_TABLE VARCHAR(100), -- 关联表 RELATED_COLUMN VARCHAR(100) -- 关联字段 );建好之后把 CSV 里的数据导入。之后你遇到不认识的字段直接SELECT COLUMN_COMMENT FROM LOCAL_DATA_DICT WHERE COLUMN_NAME XXX就能查到。这个习惯能省很多时间。3.4 验证关联关系用 COUNT 和 DISTINCT 做数据探查文档里写的关联关系不一定和实际数据一致。我一般用下面这组 SQL 做验证。-- 验证病人主索引和就诊记录是否一对多 SELECT PATIENT_NO, COUNT(*) AS VISIT_COUNT FROM VISIT GROUP BY PATIENT_NO HAVING COUNT(*) 1 ORDER BY VISIT_COUNT DESC; -- 验证就诊记录和病历文书是否一对多 SELECT VISIT_NO, COUNT(*) AS DOC_COUNT FROM MEDICAL_DOCUMENT GROUP BY VISIT_NO HAVING COUNT(*) 1 ORDER BY DOC_COUNT DESC; -- 检查关联字段是否有空值 SELECT COUNT(*) FROM VISIT WHERE PATIENT_NO IS NULL; SELECT COUNT(*) FROM MEDICAL_DOCUMENT WHERE VISIT_NO IS NULL;这几条 SQL 的逻辑是先看一对多关系是否成立再看关联字段有没有空值。如果VISIT表里PATIENT_NO有空值说明文档里写的“非空”约束在实际数据里不成立你写 JOIN 的时候就要用LEFT JOIN或者先过滤空值。参数说明HAVING COUNT(*) 1是筛选出有多条记录的主键。ORDER BY ... DESC是让最多的排前面方便看极端情况。4. 联众电子病历表结构文档的避坑与排查五个血泪教训4.1 坑一病案号不是唯一标识直接 JOIN 导致数据翻倍现象写了一条 SQL从病人主索引 JOIN 就诊记录结果查出来的就诊记录数量比实际多了一倍。原因病人主索引表里同一个病案号有多条记录可能是历史数据迁移导致的重复也可能是不同院区的数据合并。文档里写病案号是主键但实际数据里不是。解决先用SELECT PATIENT_NO, COUNT(*) FROM PATIENT_MASTER GROUP BY PATIENT_NO HAVING COUNT(*) 1查重复。如果有重复用ROW_NUMBER() OVER (PARTITION BY PATIENT_NO ORDER BY CREATE_TIME DESC)取最新一条或者用身份证号去重。4.2 坑二文书内容存在大字段里直接查会拖垮数据库现象查一份病历文书的内容SQL 跑了十几秒还没出来数据库 CPU 飙升。原因病历文书的正文通常存在CLOB或TEXT大字段里直接SELECT CONTENT会把整个大字段读出来。如果表里数据量大全表扫描会非常慢。解决先查DOC_NO和元数据需要看内容时再按主键查单条。比如SELECT DOC_NO, DOC_TITLE, CREATE_TIME FROM MEDICAL_DOCUMENT WHERE VISIT_NO XXX然后再SELECT CONTENT FROM MEDICAL_DOCUMENT WHERE DOC_NO XXX。另外给VISIT_NO和CREATE_TIME建索引。4.3 坑三时间字段格式不统一统计结果对不上现象统计某个月入院人数用ADMIT_TIME过滤结果比报表系统少了几十条。原因ADMIT_TIME存的是字符串格式有YYYY-MM-DD HH24:MI:SS和YYYY/MM/DD两种。文档里写的是DATE类型但实际是VARCHAR。解决先用SELECT DISTINCT LENGTH(ADMIT_TIME) FROM VISIT看长度分布。如果长度不一致用TO_DATE或STR_TO_DATE转换。转换前先过滤掉异常数据比如WHERE LENGTH(ADMIT_TIME) 10。4.4 坑四状态字段的枚举值文档没写全过滤条件漏数据现象查“已提交”的病历文书用STATUS 1过滤结果漏掉了部分已提交的文书。原因文档里写STATUS有0和1实际数据里还有2、3、9。2可能代表“已审核”9可能代表“已归档”。这些在文档里没写。解决先SELECT STATUS, COUNT(*) FROM MEDICAL_DOCUMENT GROUP BY STATUS看实际有哪些值。然后去问业务方每个值的含义或者查系统里的字典表。不要只按文档写过滤条件。4.5 坑五关联字段类型不一致JOIN 时隐式转换导致索引失效现象VISIT表的PATIENT_NO是VARCHAR2(20)PATIENT_MASTER表的PATIENT_NO是NUMBER。JOIN 的时候数据库做了隐式转换索引失效查询变慢。原因文档里两个表的字段类型写的不一样但实际数据里能关联上因为数据库自动转换了。但转换会导致索引失效。解决用CAST显式转换或者改表结构让类型一致。如果改不了至少在 JOIN 条件里用TO_CHAR或TO_NUMBER统一类型。比如ON p.PATIENT_NO TO_NUMBER(v.PATIENT_NO)。5. 进阶用数据字典反向生成表结构文档与自动化校验5.1 从数据库反向生成 Markdown 表结构文档如果你手里的文档版本旧了或者你想给团队维护一份最新的可以从数据库反向生成。下面这段 Python 用sqlalchemy和pandas读取表结构生成 Markdown 表格。from sqlalchemy import create_engine, inspect import pandas as pd # 替换为你的数据库连接串 engine create_engine(oraclecx_oracle://user:passhost:1521/?service_nameORCL) inspector inspect(engine) lines [] for table_name in inspector.get_table_names(): lines.append(f### {table_name}\n) lines.append(| 字段名 | 类型 | 可空 | 默认值 | 注释 |) lines.append(|--------|------|------|--------|------|) columns inspector.get_columns(table_name) for col in columns: lines.append(f| {col[name]} | {col[type]} | {col[nullable]} | {col.get(default, )} | {col.get(comment, )} |) lines.append() with open(generated_structure.md, w, encodingutf-8) as f: f.write(\n.join(lines)) print(生成完成)这段代码的逻辑是用inspect获取所有表名再获取每张表的字段信息拼成 Markdown 表格。参数说明create_engine的连接串需要根据实际数据库改Oracle、MySQL、SQL Server 的驱动不一样。col.get(comment, )取字段注释有些数据库不返回注释就是空字符串。生成之后你可以和原文档对比看哪些字段变了。5.2 自动化校验文档与实际库的差异比对反向生成之后用 pandas 做差异比对。假设你之前已经把原文档提取成了table_structure.csv现在把数据库里的字段也导出成 CSV然后比对。import pandas as pd doc_df pd.read_csv(table_structure.csv) db_df pd.read_csv(db_structure.csv) # 按表名和字段名合并找出文档有但库里没有的以及库里有但文档没有的 merged doc_df.merge(db_df, on[TABLE_NAME, COLUMN_NAME], howouter, indicatorTrue) only_in_doc merged[merged[_merge] left_only] only_in_db merged[merged[_merge] right_only] print(文档有但库里没有的字段) print(only_in_doc[[TABLE_NAME, COLUMN_NAME]]) print(库里有但文档没有的字段) print(only_in_db[[TABLE_NAME, COLUMN_NAME]])这段代码的逻辑是用merge的outer模式合并两个 DataFrameindicatorTrue会标记每行来自哪边。left_only是只在文档里出现的right_only是只在库里出现的。参数说明on[TABLE_NAME, COLUMN_NAME]是比对键需要两个 CSV 里都有这两列。跑完之后你就能知道文档哪里过时了。5.3 一个具体技巧用注释字段做数据血缘追踪联众的表结构文档里字段注释通常写的是业务含义。但有些系统会在注释里写数据来源比如“来自 HIS 系统”或“由接口同步”。你可以用正则把注释里的来源提取出来建一张数据血缘表。import re def extract_source(comment): # 匹配“来自XXX”或“由XXX同步” match re.search(r(来自|由)(.?)(系统|同步|接口), comment) if match: return match.group(2) return 未知 # 假设 db_df 里有 COLUMN_COMMENT 列 db_df[SOURCE] db_df[COLUMN_COMMENT].apply(extract_source) print(db_df[db_df[SOURCE] ! 未知][[TABLE_NAME, COLUMN_NAME, SOURCE]])这个技巧的价值在于当你发现某个字段的数据不对时能快速知道它是从哪个系统来的该找谁排查。我一般会把这张血缘表存下来每次数据出问题先查它。参数说明正则(来自|由)(.?)(系统|同步|接口)里的.?是非贪婪匹配避免匹配到太长的内容。如果你的注释格式不一样改正则就行。5.4 我踩过的一个坑别在业务高峰期跑全表校验最后说一个我自己的教训。有一次我在上午门诊高峰期跑了一个全表扫描的字段校验 SQL结果把数据库 IO 打满了门诊系统卡了十几分钟。后来我学乖了所有校验 SQL 都加WHERE CREATE_TIME SYSDATE - 1或者WHERE ROWNUM 1000先小范围验证。另外能走从库就走从库别在主库上折腾。这个习惯帮我省了很多后悔药。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑