简介天健医院信息系统数据结构手册是天健医疗HIS系统的数据库表结构说明文档面向医院信息化工程师、数据库运维及二次开发人员帮助快速掌握各业务模块的数据字典与表关联关系。资源包共1个doc文件压缩包大小为7.19MB以Word文档呈现完整手册内容按公共部分、人员属性、国家地区单位及其属性、科室字典、医疗工作、疾病诊断与医疗操作等模块系统展开涵盖性别、婚姻状况、民族、血型、职业分类等基础字典以及工作人员、技术职务、科室配置、临床科室与病房对照等核心表结构。文档不仅详细说明各表用途还以红、蓝、黑字标识区分不同使用状态辅助读者快速定位在用与未用字段提升数据查询与系统对接效率。依托从1996年延续至2006年的多次修订手册积累了长期工程实践经验。目前已有852人学习下载是HIS系统运维、报表开发和二次开发中不可多得的参考资料。1. 天健医院信息系统数据结构手册先认清它是一张“铁轨图”而不是“地图”接手过医院信息系统HIS项目的工程师十有八九被一件事卡过脖子甲方说“数据都在库里你自己拉”但登上去以后面对的是几百张表外键关系全靠猜字段含义全靠试。这时候一份《数据结构手册》就像救命稻草。天健这套手册我第一次见到是在某医院的数据上报项目里信息科老师给了个.doc老文件封面写着“天健医院信息系统数据结构手册”打开以后是几页业务说明加厚厚一沓表结构。当时我跟很多同行一样第一反应是“这年代怎么还有 Word 版数据字典”但项目做完之后我彻底改了看法这本手册解决的不是“库里有哪些表”而是告诉你每张表在门诊、住院、结算、药库这条业务链上处在什么位置字段靠什么关联状态位怎么过滤。它更像铁轨图不是地图地图告诉你地形铁轨图告诉你列车必须走哪条路。对做报表、做集成、做数据迁移的我来说拿到手册的第一天就该把它当成系统数据层的唯一可靠参照物。2. 先读业务模块再读字段把数据手册拆成三条主线数据结构手册最容易让人犯的错是一上来就翻到“表结构”章节对着ALTER TABLE一样的字段清单背。但医院信息系统的数据字典有个特点表名和字段名大量使用英文缩写而业务状态几乎全部靠状态位表达。如果只盯字段你会在REG_STATUS、ORDER_STATUS、FEE_STATUS里绕晕。所以我拿到手册的第一件事是把整本手册按业务线拆开先建立“模块 → 表 → 关键字段”的树状结构再回到表里去查细节。2.1 业务域到表名的映射从挂号到结算的连接链医院信息系统的核心数据通常绕不开四条线患者主索引线、门急诊就诊线、住院医嘱执行线、费用结算线。天健手册里表名的命名规律和很多 HIS 厂商一样是“业务域前缀 主表/明细表后缀”。比如患者域的表常以PAT开头挂号和分诊常用REG医嘱执行常用ORD药房药库常用DRUG计费和结算常用FEE或INV。读手册时我会先把这些前缀抄在一张白纸上再去翻“业务模块说明”里的实体关系描述。以门诊患者为例连接链一般是PAT_MASTER患者主索引→REG_MASTER挂号主记录→CLINIC_VISIT门诊接诊记录→FEE_DETAIL费用明细。这条链路里PAT_MASTER与REG_MASTER靠PATIENT_ID关联REG_MASTER与CLINIC_VISIT靠VISIT_ID关联CLINIC_VISIT又通过VISIT_ID找到FEE_DETAIL。手册字段清单里会注明外键关系但很多.doc版本只写在字段备注里例如“关联 PAT_MASTER.PATIENT_ID”。这是我建议先读业务说明的原因只有先知道主路径字段关系才不是一串无序外键。反过来如果只按表结构读很容易把VISIT_ID当成“一次挂号记录的唯一号”但实际上在住院场景里VISIT_ID可能是“一次住院记录的标识”在门诊场景里又是“一次门诊就诊的标识”。同一个字段名在不同表里含义可以完全不同。手册里的业务说明部分往往在实际案例中藏了这些差异。2.2 字段类型、精度和值域别把 CHAR 当 VARCHAR 用字段清单最核心的内容不只是“字段名 类型 空值”而是“值域说明”。天健手册里常见写法是PATIENT_IDVARCHAR2(32)患者全局唯一标识ID_NOVARCHAR2(18)身份证号可为空VISIT_TIMEDATE挂号/入院时间FEE_TYPE_CODEVARCHAR2(10)费别编码值域见FEE_TYPE_DICTREG_STATUSCHAR(1)状态位0 预约1 报到2 接诊9 作废。注意CHAR(1)这种类型在 Oracle 里如果字段定义了CHAR(1)查询时用1可能因为隐式类型转换或尾随空格导致结果和预期不同。真实的血泪经验是向导出的 CSV 里明明是1但WHERE REG_STATUS 1查不到后来发现线上表里实际存储的是1 带一个空格因为老代码写入时用了固定长度。所以读手册时看到CHAR类型的状态位我会顺手记一笔这个字段在写 SQL 时建议用TRIM()包裹或者在手册的“取值范围说明”里确认有没有默认带空格的坑。类型和精度还直接影响连接字段的选择。比如DEPT_CODE在挂号表和科室字典表里都定义成VARCHAR2(20)但手册可能漏写了一个关键信息挂号表里DEPT_CODE实际存的是“末级科室编码”而科室字典表里DEPT_CODE才是科室唯一编码。如果直接把两张表JOIN ON DEPT_CODE会因为匹配到父级科室而产生重复。正确做法是先看手册中的“科室字典表”说明确认编码层级再用DEPT_CODE加DEPT_TYPE的过滤条件去做关联。2.3 状态位和机构隔离列报表过滤条件里的隐藏逻辑HIS 数据字典最容易被忽略的部分是“业务状态定义”和“机构隔离字段”。天健手册里几乎每张业务主表都有一个状态位比如REG_STATUS0 预约、1 报到、2 就诊、9 作废ORDER_STATUS0 新开、1 已执行、2 取消、3 作废FEE_STATUS0 未结算、1 已结算、2 已退费。报表统计时最常见的翻车是拿“当天挂号的记录”去算门诊量而没有过滤REG_STATUS 9的作废记录结果门诊量虚高。另一个隐蔽坑是“机构隔离列”很多 HIS 在集团化部署后会加一个HOS_ID或ORG_ID字段用来区分院区但手册版本如果不更新字段清单里根本没有这一列。你在查询时如果把两个院区的数据混在一起算财务对账会平白多出一大块差异。还有一个容易忽略的是“数据有效性标志”。天健手册里有些表不是物理删除而是用CANCEL_FLAG或DEL_FLAG做逻辑删除。这个字段通常在字段清单的倒数几行如果读手册时只看前二十个字段极容易漏掉。我的做法是每读一张表先把“状态位、逻辑删除位、机构隔离位”三个字段圈出来写在一个“待验证清单”里等到数据库里抽样验证。这一步能避免大部分报表数据对不上的问题。3. 把 .doc 手册变成可检索的数据字典提取脚本与首次清洗工具链的第一步是把.doc格式转成能程序化读取的格式。.doc是 Word 97-2003 二进制格式不能直接交给python-docx解析。建议先转成.docx或.html再用脚本提取。下面是我常用的做法。3.1 从 .doc 转成 .docx 或 .html用 LibreOffice 命令行批量转换手工打开 Word 另存为太慢而且容易破坏表格结构。我一般用 LibreOffice 的 headless 模式做批量转换。命令如下soffice --headless --convert-to docx 天健医院信息系统数据结构手册.doc --outdir ./output如果想走 HTML 管道也可以改成--convert-to html。转换后python-docx或者BeautifulSoup都能继续解析。参数含义--headless表示不启动界面--convert-to docx指定输出格式--outdir指定输出目录。需要注意路径和文件名尽量不要带空格否则某些版本的 LibreOffice 会因为命令行解析问题失败。转换完成后先别急着写脚本打开生成的.docx人工翻一遍。我看到很多次这样的情况原.doc里的表格是用“文本框”或“手工制表符”伪装的转换后表格会变成一堆段落根本没变成真正的 Word 表格。此时再用python-docx读表格就什么都拿不到。如果遇到这种情况我建议改用 HTML 管道用BeautifulSoup去抓table标签兼容性会好很多。3.2 用 python-docx 解析表格按标题分组输出 Markdown假设手册转换后是规规矩矩的.docx可以用python-docx把“标题段落”和“后续表格”一一对应地抓出来。下面这个脚本会把一级标题、二级标题作为分组上下文把每个标题下的表格输出成 Markdown 表格保存到文件。from docx import Document import re doc Document(天健医院信息系统数据结构手册.docx) current_h1 current_h2 records [] for block in doc.element.body: # 判断当前元素是否是段落 if block.tag.endswith(}p): p doc.paragraphs[len(records)] # 占位下面有更稳妥取法这个写法有问题因为python-docx的遍历不能直接按 XML 节点映射到 paragraphs 索引。我平时会用更直接的做法先遍历doc.paragraphs和doc.tables按照它们在文档流中的顺序无法天然对应需要更复杂处理。下面是我验证过的写法按iter_inner_content思路借助python-docx的低层接口from docx import Document from docx.table import Table from docx.text.paragraph import Paragraph def iter_block_items(parent): from docx.oxml.ns import qn for child in parent.element.body.iterchildren(): if child.tag qn(w:p): yield Paragraph(child, parent) elif child.tag qn(w:tbl): yield Table(child, parent) doc Document(天健医院信息系统数据结构手册.docx) out_lines [] current_h1 current_h2 for block in iter_block_items(doc): if isinstance(block, Paragraph): style block.style.name if block.style else text block.text.strip() if not text: continue if block.style.name.startswith(Heading 1): current_h1 text current_h2 out_lines.append(f\n## {current_h1}\n) elif block.style.name.startswith(Heading 2): current_h2 text out_lines.append(f\n### {current_h2}\n) else: # 正文段落按需保留 pass elif isinstance(block, Table): out_lines.append(f\n!-- 分组: {current_h1} / {current_h2} --\n) for row in block.rows: cells [c.text.replace(\n, ).strip() for c in row.cells] out_lines.append(| | .join(cells) |) out_lines.append() # 空行 with open(data_dict.md, w, encodingutf-8) as f: f.write(\n.join(out_lines))这个脚本的要点有三处。第一iter_block_items把 Word 文档的段落和表格按实际文档顺序合并成一个生成器这样“标题 1 → 标题 2 → 表格 → 标题 2 → 表格”的排版才能原样保留。第二block.style.name用来判断Heading 1 / Heading 2但不同厂商生成的.docx可能把标题样式命名为标题 1所以在脚本里最好先打印所有样式名看一眼。第三row.cells直接取值但如果表里有合并单元格row.cells返回的每个单元格对象会被重复引用导致同一段文本出现多次这个问题留到下一步清洗时处理。3.3 对提取结果做二次清洗合并单元格、续表、空行和编码提取出 Markdown 不等于拿到干净的数据字典至少还有四个坑要过。第一个是合并单元格前面的脚本取cell.text合并单元格在每一行都会返回合并区域的内容导致重复值。我一般会做一个去重逻辑如果某行的单元格文本和上一行相邻单元格文本完全相同就只保留第一次出现的值。第二个是“续表”问题Word 打印长表时会在下一页自动拆表但标记可能是“续表”或者空行解析后数据中断需要在分组时把属于同一张表的续表块拼回去。我的做法是在文本中预标记所有包含“续”的行手动核对后再合并。第三个坑是空行和“数据字典说明”混在一起比如表格后面跟了一大段“注中文字段名仅供参考”这些内容不能简单丢弃要作为字段备注保留。第四个坑是编码.doc转换时如果源文件不是 UTF-8输出的.docx可能出现中文乱码。我习惯在转换命令前先执行file命令检查原文件编码再决定要不要加--infilter参数。遇到乱码时最保险的路线是把soffice的 HTML 转换结果用chardet侦测编码再读入。清洗完之后我通常会再生成两个文件一个是data_dict.csv字段名、类型、长度、说明、分组信息都拉平另一个是schema_mapping.sql把表名和字段名整理成“注释模板”。这两个文件是后续写报表查询和核对线上表结构的基线。4. 用数据手册反推业务查询从字典到第一张可看的报表 SQL数据手册的价值不是放在书架上的而是用来把“业务问题”翻译成“SQL 问题”。这一章我用两个常见的报表场景演示怎么从手册里的字段反推查询语句。所有表名和字段名都以天健手册中常见的命名为例你拿到自己手册后做映射即可。4.1 门诊收入汇总从挂号主表到费用明细的关联路径需求是“统计某天门诊各科室的实收金额”。拿到手册后我会做三步第一找“门诊费用”相关的表第二找连接键第三找过滤状态位。对应到天健手册大概是REG_MASTER挂号主表、FEE_MASTER结算主表、FEE_DETAIL收费明细。关联逻辑是REG_MASTER.VISIT_ID对应FEE_DETAIL.VISIT_IDFEE_MASTER.INVOICE_NO对应FEE_DETAIL.INVOICE_NO。关键过滤条件是FEE_MASTER.FEE_STATUS 1已结算和FEE_DETAIL.CANCEL_FLAG 0未作废。一个基础查询如下SELECT r.DEPT_CODE, SUM(fd.AMOUNT) AS total_amount, COUNT(DISTINCT r.VISIT_ID) AS visit_cnt FROM REG_MASTER r JOIN FEE_DETAIL fd ON r.VISIT_ID fd.VISIT_ID JOIN FEE_MASTER fm ON fd.INVOICE_NO fm.INVOICE_NO WHERE r.REG_TIME TO_DATE(2024-05-20, YYYY-MM-DD) AND r.REG_TIME TO_DATE(2024-05-21, YYYY-MM-DD) AND fm.FEE_STATUS 1 AND fd.CANCEL_FLAG 0 GROUP BY r.DEPT_CODE这段 SQL 里特意用了和来限定日期区间而不是TRUNC(r.REG_TIME) TO_DATE(...)原因是要让REG_TIME字段上的索引生效。很多手册不会提醒你这一点但值班报表最怕的就是全表扫描。COUNT(DISTINCT r.VISIT_ID)用来统计就诊人次因为一张挂号单可能有多条费用明细不按VISIT_ID去重会把人数算成费用条数。如果对不上财务口径优先检查FEE_STATUS是否把“已结算”用0表示而不是1——不同版本的 HIS 定义可能相反。4.2 药品消耗量与库存周转把字典表翻译成可读名称第二个场景是药库管理员要的“某药品在月初到月末的出库量”。这里的问题是药品消耗记录只存DRUG_CODE而药名、规格、厂商都在DRUG_DICT表里。数据手册的价值就是帮你找到这两张表的关联字段以及“出库类型”的值域。SELECT d.DRUG_NAME, d.SPEC, SUM(s.OUT_QTY) AS out_qty FROM DRUG_STOCK_OUT s JOIN DRUG_DICT d ON s.DRUG_CODE d.DRUG_CODE WHERE s.OUT_TIME TO_DATE(2024-05-01, YYYY-MM-DD) AND s.OUT_TIME TO_DATE(2024-06-01, YYYY-MM-DD) AND s.STOCK_TYPE 0 -- 0: 药房出库 1: 药库出库 2: 报损 GROUP BY d.DRUG_NAME, d.SPEC ORDER BY out_qty DESC注意STOCK_TYPE的具体含义不同医院会在不同值域。手册里的“值域说明”通常会给出 0/1/2/9 的含义但实际库里可能混入历史遗留值比如STOCK_TYPE 。这类脏数据会在GROUP BY中形成一行空字符串单独把分布查出来看一眼。另一个常见坑是OUT_QTY可能存在负数退药这会让出库量虚高或虚低需要跟业务确认是否要把负数冲减回来。4.3 手册里没有的字段用命名规则反推但要有验证底线再完整的手册也难免缺字段。天健手册某个版本里我做一次住院结算统计时遇到一个CHARGE_TIME字段手册里没写但表结构里实际存在。靠命名规则能大概率猜出它是“计费时间”HIS 表里TIME结尾的字段通常是操作时间DATE结尾的字段通常是业务日期。但猜出来的字段不能直接上线必须先验证。验证方法很简单跑一条SELECT COUNT(*) FROM 表 WHERE CHARGE_TIME IS NOT NULL再看表里的历史数据分布。如果验证结果和猜测的“计费时间”语义矛盾就要回到业务侧找人确认找信息科老师或者厂商的实施顾问问一句。这个环节最怕的是不问就硬编码等报表上线后被财务指出“这个时间点不对”返工成本很高。我的底线是字典里有明确说明的信 7 成字典里没有说明但命名规则明显可推断的信 3 成不影响正确性的情况下先做出来影响正确性时必须求证。宁可多问一句也不要让报表在月底对账时给业务添乱。5. 用数据手册踩过的坑现象、原因、解决一条条捋手册读再多遍落不了地还是会翻车。这一章我把自己和同行实际踩过的高频坑整理成五条每一条都按“现象 → 原因 → 解决”来写方便你对照排查。5.1 手册取值范围和数据库实际数据对不上现象写好的报表某天突然某个月份的数据参与汇总后总和差了一大截用GROUP BY看字段分布发现有个FEE_STATUS 3手册里只写了 0、1、2没写 3。原因新版本系统上线后加了“医保结算失败”的状态手册没有同步更新。解决每个状态字段都要先跑一次SELECT status, COUNT(*) FROM table GROUP BY status和手册对一遍把不在手册里的值单独列出来问医院信息科。这个动作应该成为写任何报表之前的固定前奏。5.2.doc手册里的表和线上库表不一致现象跑一个字段清单里的表名数据库报ORA-00942: table or view does not exist。原因文档是两年前的版本线上库经历过一次迁移业务表改过名。比如旧版PATIENT_DETAIL被拆成了PATIENT_INFO和PATIENT_CONTACT手册没跟上。解决用数据字典视图直接核对元数据。Oracle 可以查ALL_TABLES和ALL_TAB_COLUMNSSQL Server 查INFORMATION_SCHEMA.TABLES。我一般会写一个对比脚本把手册里所有表名拉出来和线上视图里的表名做FULL OUTER JOIN这样能一次性把“多表、少表、字段多、字段少”全部暴露出来。5.3 全角空格和半角空格导致 JOIN 匹配不上现象DEPT_CODE明明有值做JOIN时却匹配不到科室字典报表里科室显示为 NULL。原因数据录入界面用了全角空格或者从旧系统迁移时带了不可见字符。文字上看起来都是101但LENGTHB不一样。解决关联字段统一用TRIM()必要时再REPLACE(字段, , )替换全角空格。注意不要在手册本身没提到的情况下给所有字段加 TRIM否则会牺牲索引影响大表性能。只需要对从手工录入界面来的编码类字段做这种处理。5.4 按患者数去重统计结果比实际门诊量少现象统计“某天来院就诊患者数”用COUNT(DISTINCT PATIENT_ID)结果比窗口挂号人次少很多。原因同一患者多次建档形成多条PAT_MASTER记录但挂号时有的挂了旧档案、有的挂了对新档案PATIENT_ID不唯一。手册里的PATIENT_ID虽然是主键但业务实际上靠“姓名 身份证号”判断同一人。解决找手册里有没有“主索引合并记录表”或者“患者索引关系表”如果有就用它合并后再去重如果没有按身份证号去重但要注意患者隐私保护只能在合规环境下做。不要直接按姓名去重重名会加剧误差。5.5 “自然日”和“会计日”的边界差异现象财务说某天收入是 10 万你按自然日BETWEEN 20240520 00:00:00 AND 20240520 23:59:59算出来是 10.5 万。原因医院收费日结和医保业务可能按“会计日”走也就是当天 6:00 到次日 6:00或凌晨 2 点扎账挂号发生在 24:00 前但结算在次日 0:00 后会被算到第二天。解决检查手册里是否有BUSINESS_DATE、SETTLE_DATE或ACCOUNT_DATE字段用这些业务日期字段代替CREATE_TIME做日切。如果没有问信息科确认夜班节次然后在查询里把时间范围整体偏移。这个问题几乎每个 HIS 项目都会遇到最好一开始就确认不要等对账时才发现。6. 让数据手册真正成为团队资产自动化校验与增量维护的小习惯读到这里你可能已经把一本旧手册变成了能用的数据字典。但更值钱的一步是让这本手册不再腐烂。我的做法是把它变成一个“活文档”先用手册解析出data_dict.csv然后把线上数据字典导出成online_dict.csv每天跑一次差异对比。对比的规则很简单表名是否一致字段名是否一致类型长度是否一致。只要发现差异就自动生成一条xxx表_新增字段的记录。做完对比后我会维护一张“手册-代码-现场”的三列映射清单手册里叫PATIENT_ID的字段在报表代码里叫patient_id在现场数据库里的备注是“患者内码”。这张清单的意义是下次接手的人不需要重新踩一遍“字段名猜谜”的过程。最后一个技巧是版本号管理。.doc文件本来很难做 diff我会在解析清洗后把它提交到代码仓库每次从业务方拿到新版.doc就重新解析一遍然后看仓库里的 diff一眼就知道这次升级改了哪些表。这个习惯帮我避过一次大坑有一次升级手册里把FEE_STATUS的 1 和 2 语义对调了要是不看 diff我还在按老逻辑跑数直到日终不平才发现。我做数据抽取这几年最后悔的一次就是因为没先看手册版本就参考旧文档写接口上线后数据对不上。现在我的习惯是拿到任何 HIS 文档第一步不是打开而是先确认版本日期再去查核心表的字段分布最后才写 SQL。希望这个三步走也能帮到你。本文还有配套的精品资源点击获取