2. 关联分析工程目标定义
在多表临床数据关联分析中,最容易出问题的地方往往不是 SQL 写不出来,而是目标没有定义清楚。临床医生提出的是“我想看看用了某药的患者后来肾功能怎么样”,而数据工程需要的是“以谁为一行、取哪段时间、关联哪些表、输出哪些字段、用什么口径判断异常”。这两者之间隔着一层必须显式完成的翻译工作。本节要解决的就是这层翻译:把模糊的临床问题,拆解为可执行、可验证、可复现的数据关联任务。
一、目标定义要回答的四个核心问题
一个可执行的关联任务,必须能明确回答以下四件事,缺一件都会在实现阶段返工:
- 分析粒度(Grain):结果表的“一行”代表什么?是一次就诊、一位患者、一次用药、还是一天?粒度决定了主键、去重逻辑和聚合方式。
- 时间窗口(Time Window):围绕哪个时间锚点取数?锚点前多久、后多久?窗口是闭区间还是开区间?
- 关联路径(Join Path):从主表出发,通过哪些键、以什么方向关联到哪些表?是一对一、一对多还是多对多?
- 输出指标(Output Metrics):最终要产出哪些字段?每个字段的取值口径、单位、缺失处理规则是什么?
这四者不是并列关系,而是有依赖顺序的:粒度决定主键,主键决定关联路径,时间窗口决定取数范围,输出指标决定聚合与筛选。下面这张图展示了从临床问题到可执行任务的完整翻译链路,以及四个核心问题在链路中的位置。
二、分析粒度:先定“一行是什么”
粒度是关联分析的基石。以“某药用药后肾功能变化”为例,至少有三种常见粒度选择:
- 患者级:一位患者一行,取该患者首次用药后的肾功能指标。适合做组间比较、生存分析。
- 用药事件级:一次用药记录一行。适合分析不同用药方案、剂量与肾功能的关系。
- 时间点级:一次检验结果一行。适合做纵向趋势分析,但关联时容易产生笛卡尔积。
粒度一旦确定,主键就确定了。例如选择“用药事件级”,主键可能是 (patient_id, drug_code, start_date)。后续所有关联都必须保证不破坏这个粒度——如果检验表一个用药事件对应多条检验记录,就需要先聚合再关联,而不是直接 JOIN 导致行数膨胀。
三、时间窗口:锚点、方向与边界
时间窗口的定义必须包含三个要素:锚点、方向、长度。以用药后肾功能监测为例:
- 锚点:首次用药日期
first_drug_date。 - 方向:向后(用药后),即
lab_date >= first_drug_date。 - 长度:7 天到 90 天,即
lab_date BETWEEN first_drug_date + 7 AND first_drug_date + 90。
这里有两个容易踩的坑。第一,边界开闭:BETWEEN 是闭区间,如果业务上要求“用药后第 7 天开始算”,就要确认第 7 天当天是否纳入。第二,基线窗口:很多指标需要“用药前基线值”,这就要定义另一个反向窗口,例如用药前 30 天内最近一次检验值。两个窗口的锚点相同、方向相反,必须在任务定义中分别写清楚。
下面给出一个时间窗口定义的示例表格,用于在任务文档中固化口径:
| 窗口名称 | 锚点 | 方向 | 范围 | 用途 |
|---|---|---|---|---|
| 基线窗口 | 首次用药日 | 向前 | [-30天, 0天) | 取用药前最近一次肌酐值 |
| 随访窗口 | 首次用药日 | 向后 | [7天, 90天] | 取用药后肌酐峰值 |
| 排除窗口 | 首次用药日 | 向前 | [-365天, 0天) | 排除既往肾功能异常患者 |
四、关联路径:从主表到目标表的“路线图”
关联路径要明确三件事:起点表、终点表、中间经过哪些表以及用什么键连接。以“用药事件 → 患者 → 检验结果”为例,典型路径是:
drug_event (patient_id, drug_code, start_date)
→ patient (patient_id) -- 补充人口学信息
→ lab_result (patient_id, lab_date, lab_item, value)
ON lab_result.patient_id = drug_event.patient_id
AND lab_result.lab_date BETWEEN drug_event.start_date + 7
AND drug_event.start_date + 90
AND lab_result.lab_item = 'CREA'
这条路径中,patient_id 是连接键,lab_date 是时间约束键。注意关联条件里同时出现了等值连接和时间范围连接,这是临床数据关联的典型特征。如果检验表在窗口内有多条记录,就需要在关联前或关联后做聚合(取最大值、最小值或最近值),具体取决于输出指标的定义。
SELECT patient_id, COUNT(*) FROM lab_result GROUP BY patient_id HAVING COUNT(*) > 1 检查一对多关系,确认聚合策略后再关联。五、输出指标:口径、单位与缺失处理
输出指标是任务的最终交付物,必须逐字段定义。以肌酐变化分析为例,输出字段可以包括:
baseline_crea:基线肌酐值,单位 μmol/L,取基线窗口内最近一次检验结果。peak_crea:随访窗口内肌酐峰值,单位 μmol/L,取最大值。crea_change:变化幅度,peak_crea - baseline_crea,单位 μmol/L。aki_flag:是否满足急性肾损伤判断,依据 KDIGO 标准,crea_change >= 26.5或peak_crea / baseline_crea >= 1.5。
每个字段都要明确缺失处理规则:如果基线窗口内没有检验记录,baseline_crea 是置空、排除该患者,还是用其他方式填补?这些规则必须在任务定义阶段写清楚,而不是留到实现时临时决定。输出指标的定义越具体,后续的 SQL 实现和结果验证就越顺畅。
六、从定义到可执行任务的检查清单
在把任务交给实现之前,可以用下面这份清单做一次自检:
- 结果表的主键是否明确?一行代表什么业务对象?
- 时间锚点、方向、窗口长度、边界开闭是否全部写清楚?
- 关联路径中每一跳的连接键和时间约束是否明确?
- 是否存在一对多或多对多关系?聚合策略是什么?
- 每个输出字段的口径、单位、缺失处理规则是否定义?
- 预期结果行数量级是否可估算?用于验证关联是否膨胀或丢失。