2. 临床数据模型与查询对象

临床数据复杂查询的第一步,不是写 SQL,而是把“临床世界里真实存在的东西”翻译成“数据库里可被连接、过滤、聚合的对象”。如果对象边界没划清,后面无论写多复杂的 JOIN,都会出现重复计数、时间错配、口径不一致的问题。本节围绕患者、就诊、诊断、用药、检验、检查六类核心实体,讲清它们各自代表什么、靠什么字段发生关系,以及查询时最容易踩的坑。

一、六个核心实体分别承载什么信息

患者(Patient)是身份主体,回答“这是谁”。关键字段通常包括患者主索引(patient_id)、院内病案号(mrn)、姓名、性别、出生日期、身份证号等。患者表是全局唯一的身份锚点,一次住院、多次门诊都挂在同一个 patient_id 下。查询“某患者全部诊疗记录”时,患者表是起点。

就诊(Encounter/Visit)是一次具体的医疗接触,回答“哪一次来院”。关键字段包括就诊号(visit_id/encounter_id)、患者ID、就诊类型(门诊/急诊/住院/体检)、入院时间、出院时间、科室、医生。就诊是临床数据的时间容器——诊断、用药、检验、检查几乎都发生在某一次就诊之内。区分“患者级”和“就诊级”是临床查询最核心的口径问题。

诊断(Diagnosis)回答“得了什么病”。字段包括诊断ID、就诊ID、诊断编码(ICD-10/ICD-9-CM)、诊断名称、诊断类型(入院诊断/出院诊断/主诊断/次诊断)、诊断时间。同一就诊可有多条诊断,主诊断与次诊断的区分直接影响统计口径。

用药(Medication Order)回答“用了什么药”。字段包括医嘱ID、就诊ID、药品编码、药品名称、剂量、给药途径、频次、开始时间、停止时间、医嘱状态。用药是典型的“时间区间型”数据,判断“某时刻是否在用药”必须做时间区间重叠计算,不能只看是否有记录。

检验(Lab Test)回答“化验结果是什么”。字段包括检验申请ID、就诊ID、检验项目编码、结果值、单位、参考范围、采样时间、报告时间。检验是“项目—结果”结构,一个申请单可含多个项目,查询时通常要按项目编码过滤并做数值比较。

检查(Examination/Procedure)回答“做了什么影像或功能检查”。字段包括检查ID、就诊ID、检查项目编码、检查名称、检查时间、报告结论。检查与检验的区别在于:检验产出数值型结果,检查产出描述型报告,二者查询方式不同。

二、实体之间的关系建模

六类实体构成一棵以患者为根、以就诊为枢纽的关系树:患者 1:N 就诊;就诊 1:N 诊断、1:N 用药、1:N 检验、1:N 检查。所有临床事件表都通过 visit_id 与就诊关联,再通过就诊的 patient_id 回溯到患者。这种“患者—就诊—事件”的三层结构,是临床数据仓库最通用的建模方式。

关系建模要明确三种连接路径:身份路径(patient_id 连接患者与就诊)、事件路径(visit_id 连接就诊与各事件表)、时间路径(事件时间落在就诊时间窗内)。前两条是显式外键,第三条是隐式约束,也是查询中最容易出错的地方。

临床核心实体关系模型(患者—就诊—事件三层结构) 患者 Patient patient_id · mrn · 性别 · 出生日期 就诊 Encounter visit_id · 类型 · 入/出院时间 · 科室 1 : N 诊断 Diagnosis ICD编码 · 诊断类型 主/次诊断 · 诊断时间 用药 Medication 药品编码 · 剂量 · 途径 开始/停止时间 · 状态 检验 Lab Test 项目编码 · 结果值 · 单位 参考范围 · 采样时间 检查 Examination 检查项目 · 检查时间 报告结论 1 : N 1 : N 1 : N 1 : N 时间约束: 事件时间应落在就诊时间窗内(入院时间 ≤ 事件时间 ≤ 出院时间),否则视为跨就诊或数据异常。 连接键: patient_id 连接患者与就诊;visit_id 连接就诊与诊断/用药/检验/检查。

三、查询口径:患者级、就诊级与事件级

同一份数据,按不同层级聚合会得到完全不同的结果。例如“糖尿病患者人数”,若按患者级去重统计 patient_id,得到的是人数;若按就诊级统计 visit_id,得到的是就诊人次;若按诊断记录条数统计,则同一患者多次就诊会被重复计数。写查询前必须先明确口径,并在 SQL 中体现:患者级用 COUNT(DISTINCT patient_id),就诊级用 COUNT(DISTINCT visit_id)。

另一个高频问题是“一次就诊内多个事件的时间对齐”。比如要查“入院 48 小时内使用某抗生素且该次住院诊断为肺炎”的患者,需要同时满足:诊断表存在肺炎 ICD 编码、用药表存在目标药品、用药开始时间在入院后 48 小时内。三个条件分别落在三张表,必须通过 visit_id 关联,并用时间条件约束,不能简单用 IN 子查询拼接。

四、一个可复用的查询对象清单

把上述实体整理成查询前必须确认的对象清单,可以直接作为建模与写查询的检查表:

实体 主键 关联键 时间字段 查询口径要点
患者patient_id—出生日期身份去重,患者级统计基准
就诊visit_idpatient_id入院/出院时间就诊级统计基准,时间窗容器
诊断diag_idvisit_id诊断时间区分主/次诊断,按 ICD 编码过滤
用药order_idvisit_id开始/停止时间时间区间重叠判断,注意医嘱状态
检验lab_idvisit_id采样/报告时间按项目编码过滤,数值比较需带单位
检查exam_idvisit_id检查时间报告为文本,需关键词或编码匹配
建模时建议为每张事件表保留 visit_id 和 patient_id 两个冗余字段。虽然 patient_id 可由就诊表推导,但在大表关联时冗余字段能显著减少 JOIN 层数,也便于做患者级快速过滤。
常见错误:把检验结果直接与诊断表按 patient_id 关联,忽略 visit_id 和时间窗,导致“用 A 次住院的检验结果去匹配 B 次住院的诊断”。正确做法是始终以 visit_id 为事件对齐的主键。