2. 医疗数据模型与表结构

医疗数据查询与分析的第一道门槛,不是 SQL 语法,而是“数据到底长什么样”。同一家医院里,患者信息、就诊记录、诊断结论、医嘱明细、检验结果、影像报告和费用流水往往分散在不同业务系统中,各自有独立的表结构和主键体系。如果直接凭字段名猜关系,很容易把“一次就诊”和“一个患者”混为一谈,导致统计口径错误。本节围绕核心实体及其关系建模展开,目标是建立一张可落地的“医疗数据地图”,为后续查询、聚合和指标计算提供稳定基础。

一、核心实体与主键设计

医疗数据模型通常以患者(Patient)为最上层实体,以就诊(Encounter/Visit)为业务主线,向下挂接诊断、医嘱、检验、影像和费用。每个实体必须有唯一标识,且标识的粒度决定了后续关联的准确性。

  • 患者表(patient):主键为 patient_id,存储姓名、性别、出生日期、身份证号、联系方式等相对稳定的属性。一个患者可以多次来院,因此患者与就诊是一对多关系。
  • 就诊表(encounter):主键为 encounter_id,外键 patient_id,记录就诊类型(门诊/急诊/住院)、科室、接诊医生、入院时间、出院时间、就诊状态。一次就诊是诊断、医嘱、检验、影像和费用的共同归属单元。
  • 诊断表(diagnosis):主键 diagnosis_id,外键 encounter_id,字段包括诊断编码(ICD-10)、诊断名称、诊断类型(主诊断/次诊断)、诊断时间。一次就诊可有多条诊断,主诊断通常唯一。
  • 医嘱表(order):主键 order_id,外键 encounter_id,记录医嘱类型(药品/检查/检验/护理)、项目编码、项目名称、开立时间、执行状态、剂量与频次。
  • 检验表(lab_result):主键 lab_id,外键 encounter_id,字段包括检验项目、结果值、单位、参考范围、异常标志、报告时间。
  • 影像表(imaging):主键 imaging_id,外键 encounter_id,记录检查部位、检查方法、影像号、报告结论、报告医生。
  • 费用表(charge):主键 charge_id,外键 encounter_id,记录收费项目、数量、单价、金额、费用类别、结算时间。

这里的关键判断是:诊断、医嘱、检验、影像、费用都直接挂在就诊上,而不是直接挂在患者上。因为一次就诊才对应一次完整的诊疗过程,患者可能在不同时间、不同科室产生多组数据。若把费用直接关联到患者,跨就诊汇总时就会丢失“哪一次就诊花了多少钱”的语义。

二、实体关系与关联路径

从查询视角看,最常用的关联路径是:patient → encounter → 各业务明细表。患者表与就诊表通过 patient_id 关联;就诊表与诊断、医嘱、检验、影像、费用表通过 encounter_id 关联。这种“星型”结构让一次就诊成为分析的基本单元,也便于按患者、科室、时间等维度聚合。

需要特别注意的是一对多关系带来的行数膨胀。例如一次就诊有 3 条诊断、5 条医嘱、10 条检验结果,若直接多表连接后统计费用,费用金额会被重复计算。正确做法是先按业务粒度聚合,再与费用表关联,或使用子查询分别汇总。下面这张关系图展示了核心实体、主键外键以及连接方向,可作为建表和写查询时的参照。

患者 patient PK patient_id 姓名 / 性别 / 出生日期 身份证号 / 联系方式 就诊 encounter PK encounter_id FK patient_id 类型 / 科室 / 时间 / 状态 诊断 diagnosis PK diagnosis_id FK encounter_id ICD-10 / 主次诊断 医嘱 order PK order_id FK encounter_id 类型 / 项目 / 状态 检验 lab_result PK lab_id FK encounter_id 结果 / 单位 / 异常标志 影像 imaging PK imaging_id FK encounter_id 部位 / 方法 / 结论 费用 charge PK charge_id FK encounter_id 项目 / 数量 / 金额 1 : N 1 : N 1 : N 1 : N 1 : N 1 : N PK = 主键 FK = 外键 1 : N = 一对多关系

三、建模中的常见陷阱与处理

第一,患者与就诊混淆。统计“糖尿病患者人数”时,应按 patient_id 去重;统计“糖尿病就诊人次”时,应按 encounter_id 计数。两者口径不同,结果可能相差数倍。

第二,主诊断与次诊断混用。诊断表中一次就诊可能有多条记录,若直接按诊断编码分组统计,会把次诊断也计入。通常需要加 diagnosis_type = '主诊断' 条件,或明确说明统计口径。

第三,时间字段语义不清。就诊表有入院时间、出院时间,医嘱表有开立时间、执行时间,检验表有采样时间、报告时间。做时间趋势分析时,必须明确使用哪个时间字段,并在查询中统一口径。

第四,空值与状态过滤。就诊状态可能包含“已取消”“作废”等,费用表可能包含退费记录。建模时应明确哪些状态纳入统计,避免把无效数据计入指标。

建议在数据字典中为每张表标注:主键、外键、业务粒度(一行代表什么)、常用过滤条件。例如“就诊表一行代表一次就诊,过滤条件为 status != '已取消'”。这比记住字段名更重要。

四、一个可执行的关联查询示例

假设要统计某科室 2024 年 1 月每位患者的主诊断分布,并附带该次就诊的总费用。可以先按就诊聚合费用,再与诊断、患者关联,避免多表连接导致金额重复。示例 SQL 如下(表名与字段为教学假设):

SELECT
    p.patient_id,
    p.name,
    d.diagnosis_name,
    e.encounter_id,
    COALESCE(c.total_amount, 0) AS total_amount
FROM encounter e
JOIN patient p ON p.patient_id = e.patient_id
JOIN diagnosis d ON d.encounter_id = e.encounter_id
    AND d.diagnosis_type = '主诊断'
LEFT JOIN (
    SELECT encounter_id, SUM(amount) AS total_amount
    FROM charge
    WHERE status = '已结算'
    GROUP BY encounter_id
) c ON c.encounter_id = e.encounter_id
WHERE e.dept_name = '内分泌科'
  AND e.visit_time >= '2024-01-01'
  AND e.visit_time < '2024-02-01'
  AND e.status != '已取消'
ORDER BY p.patient_id, e.encounter_id;

这段查询体现了三个建模要点:主诊断过滤、费用子查询预聚合、就诊状态过滤。执行后每一行代表“一位患者的一次就诊及其主诊断和总费用”,粒度清晰,可直接用于后续分组统计。若把费用表直接 JOIN 进来而不做子查询,当一次就诊有多条费用记录时,诊断行会被放大,总费用也会重复累加。

五、从模型到分析:口径先行