3. 医疗数据原始层(ODS)建模
原始层(Operational Data Store,ODS)在医疗数据分层架构中承担“第一落点”的角色。它的核心职责不是清洗、不是标准化,而是尽可能原样接住源系统的数据,为后续明细层、汇总层和指标层保留可追溯、可重放、可审计的原始证据。医院信息系统(HIS)、电子病历(EMR)、检验系统(LIS)、影像系统(PACS)、收费与医保结算系统往往由不同厂商建设,接口形态、字段命名、编码版本、更新节奏差异极大。如果 ODS 阶段过早做统一转换,一旦发现映射错误或口径争议,原始信息已经丢失,回溯成本极高。因此 ODS 建模的第一原则是贴源存储、原始保真。
贴源存储:结构上“像源”,语义上“不解释”
贴源存储要求 ODS 表结构与源系统保持高度一致:字段名可做规范化命名,但字段含义、粒度、主键、时间戳、编码值域应尽量保留源系统原貌。常见做法是为每张源表建立一张对应的 ODS 表,并附加少量技术字段,例如 ods_load_time(入库时间)、ods_source_system(来源系统)、ods_batch_id(批次号)、ods_record_hash(记录指纹)。这些技术字段只用于治理和追踪,不改变业务语义。
以 HIS 的住院患者表为例,源表可能包含 PAT_ID、VISIT_ID、DEPT_CODE、ADM_DATE、DISCHARGE_DATE、PAT_STATUS 等字段。ODS 层应保留这些字段的原始类型与编码,例如 PAT_STATUS 在源系统中可能是 1/2/3 表示在院、出院、转科,ODS 不急于翻译成中文,而是保留代码并另建码表映射。这样,当临床或医保口径发生变化时,仍能回到原始代码重新解释。
历史拉链:让每一次变化都有时间坐标
医疗数据中大量实体是缓慢变化维,例如患者基本信息、科室归属、医师职称、床位状态。若 ODS 只保留最新快照,就无法回答“患者出院时属于哪个科室”“医师开立医嘱时是什么职称”。历史拉链通过为每条记录增加生效时间与失效时间,把变化过程显式保存下来。常用字段包括 start_date、end_date、is_current,其中 end_date 可用 9999-12-31 表示当前有效。
拉链更新时,先比较源记录与 ODS 当前有效记录的 ods_record_hash。若哈希一致,说明业务字段未变化,仅更新 ods_load_time;若哈希不一致,则关闭旧记录(写入 end_date、is_current=0),再插入一条新记录(start_date 取本次业务时间或入库时间,end_date 取 9999-12-31,is_current=1)。这样,同一患者在不同时间点的科室、状态、联系方式都能被准确还原。
增量捕获:从“全量重刷”走向“变化感知”
ODS 的增量捕获方式取决于源系统能力。常见策略有三类:基于时间戳的增量、基于数据库日志的 CDC、基于接口消息的流式捕获。时间戳增量实现简单,但要求源表有可靠的 update_time,且不能遗漏物理删除;CDC 通过解析数据库日志捕获插入、更新、删除,实时性和完整性更好,但需要源库开启相应日志并处理日志解析链路;接口消息方式适合 HL7、FHIR 或集成平台推送场景,需处理消息乱序与重复。
无论采用哪种方式,ODS 都应记录 ods_op_type(I/U/D)和 ods_batch_id,使每条记录的变化类型可识别。对于删除操作,建议采用软删除标记而非物理删除,保留 is_deleted 与删除时间,确保历史可追溯。
数据缓冲与原始数据保真策略
ODS 同时承担缓冲职责:当上游源系统批量推送、下游加工任务尚未就绪时,ODS 先接住数据,避免源系统阻塞。缓冲设计要关注三点:一是幂等写入,同一批次重复到达不产生重复记录;二是批次隔离,不同批次数据可独立回滚;三是原始报文留存,对于接口推送的 JSON、XML、HL7 消息,建议将原始报文完整存入 ODS 的 raw_payload 字段或对象存储,并记录 payload_hash,以便后续解析规则变更时重新解析。
原始数据保真还包括字符集、精度和空值语义。例如检验结果 RESULT_VALUE 在源系统中可能是字符串 "<0.05"、"阴性" 或数值 0.05,ODS 不应强制转为数值而丢失定性结果;金额字段应保留源系统精度,避免过早四舍五入;空字符串与 NULL 在源系统中可能含义不同,ODS 应分别保留。
一个可参考的 ODS 表设计示例
以住院患者贴源表 ods_his_patient_visit 为例,可设计如下字段结构(示例,非实测):
CREATE TABLE ods_his_patient_visit (
pat_id VARCHAR(32) COMMENT '患者ID,源系统原值',
visit_id VARCHAR(32) COMMENT '住院就诊ID,源系统原值',
dept_code VARCHAR(16) COMMENT '科室代码,源系统原值',
adm_date DATETIME COMMENT '入院时间,源系统原值',
discharge_date DATETIME COMMENT '出院时间,源系统原值',
pat_status VARCHAR(8) COMMENT '患者状态代码,源系统原值',
ods_source_system VARCHAR(32) COMMENT '来源系统标识',
ods_batch_id VARCHAR(64) COMMENT '入库批次号',
ods_load_time DATETIME COMMENT '入库时间',
ods_op_type CHAR(1) COMMENT 'I/U/D 操作类型',
ods_record_hash VARCHAR(64) COMMENT '业务字段指纹',
start_date DATETIME COMMENT '拉链生效时间',
end_date DATETIME COMMENT '拉链失效时间',
is_current TINYINT COMMENT '是否当前有效',
is_deleted TINYINT COMMENT '软删除标记',
raw_payload TEXT COMMENT '原始报文(接口场景)'
);
该结构同时承载贴源、拉链、增量、缓冲与保真五类信息。实际落地时,还需配套建立元数据登记表,记录每个字段的来源系统、源字段名、是否参与哈希、是否允许为空、编码表引用关系。只有元数据与数据同步建设,ODS 才能真正成为可治理的原始层,而不是一堆难以解释的“数据垃圾场”。