4. 队列定义的基本要素
在 OMOP CDM 上构建队列,本质上不是写一段 SQL 把病人筛出来,而是把一条临床研究问题翻译成一组可复现、可审计、可版本化的结构化定义。队列定义一旦落到工程层面,就必须回答六个彼此耦合的问题:谁有资格进入(目标人群)、从哪一刻开始算(索引事件)、哪些人必须留下或必须剔除(纳入排除标准)、在什么时间范围内寻找证据(观察窗)、从哪一刻开始观察结局(随访起点)、到哪一刻停止观察(随访终点)。这六个要素不是并列的清单,而是一条有先后依赖的装配链——索引事件决定时间原点,观察窗决定证据是否可用,纳入排除决定人群边界,随访起止决定结局是否被完整捕获。
目标人群与索引事件:先定“人”,再定“时刻”
目标人群(Target Population)描述的是研究想要外推的那一类人,通常用人口学特征、疾病史或用药史来刻画,例如“2018 年至 2022 年间在数据库中至少有 365 天连续观察记录的 2 型糖尿病成人患者”。注意这里已经隐含了观察窗要求,因此目标人群的表述必须与后续要素保持一致,不能只在文字里写“成人”,而代码里忘记年龄条件。
索引事件(Index Event)是队列的时间锚点,它把每个患者“钉”在时间轴上的某一点。索引事件可以是一次诊断、一次处方、一次手术、一次检验结果,也可以是一次住院的开始。工程化表达时,索引事件必须落到具体的 OMOP 表与字段上,例如 drug_exposure.drug_concept_id 属于某个 Ingredient 概念集,且 drug_exposure_start_date 作为索引日期。若一个患者有多次符合条件的索引事件,必须明确取第一次、最后一次还是全部保留——这直接决定队列是“以人为单位”还是“以事件为单位”。
纳入排除标准:布尔条件与时间约束的组合
纳入标准(Inclusion Criteria)回答“必须满足什么”,排除标准(Exclusion Criteria)回答“一旦满足就剔除”。在 OMOP 中,它们通常表现为对 condition_occurrence、drug_exposure、measurement、observation 等表的 EXISTS / NOT EXISTS 子查询。工程化表达的关键在于把每条标准写成“表 + 概念集 + 时间关系 + 逻辑连接”的四元组。例如:
-- 纳入:索引日前 365 天内有 2 型糖尿病诊断
EXISTS (
SELECT 1 FROM condition_occurrence co
JOIN concept_ancestor ca ON co.condition_concept_id = ca.descendant_concept_id
WHERE co.person_id = p.person_id
AND ca.ancestor_concept_id = 201826 -- Type 2 diabetes mellitus
AND co.condition_start_date BETWEEN index_date - 365 AND index_date
)
-- 排除:索引日前 180 天内有胰岛素使用
NOT EXISTS (
SELECT 1 FROM drug_exposure de
JOIN concept_ancestor ca ON de.drug_concept_id = ca.descendant_concept_id
WHERE de.person_id = p.person_id
AND ca.ancestor_concept_id = 21600712 -- Insulin
AND de.drug_exposure_start_date BETWEEN index_date - 180 AND index_date
)
这里的时间关系必须与索引日期绑定,而不是与“今天”或“数据截止日”绑定。很多队列不可复现,就是因为纳入标准里的时间窗口写成了相对当前日期的偏移,导致每次运行结果不同。
观察窗、随访起点与终点:把时间轴切成可计算区间
观察窗(Observation Window)通常指患者在数据库中“可被观察到”的连续时间段,常用 observation_period 表来表达。它决定了一个患者是否有足够长的记录来支持纳入判断和结局捕获。例如要求“索引日前至少有 365 天连续观察记录”,就是要求 observation_period_start_date ≤ index_date - 365 且 observation_period_end_date ≥ index_date。
随访起点(Follow-up Start)不一定等于索引日期。常见做法是设置一段洗脱期(Washout Period),例如索引日后 30 天才开始随访,以排除“新用药者偏倚”或“早期事件堆积”。工程上,随访起点 = 索引日期 + 洗脱天数。随访终点(Follow-up End)则取多个候选日期的最小值:死亡日期、退出观察期日期、数据截止日期、或研究预设的最大随访天数。用公式表达就是:
follow_up_end = LEAST(
death_date,
observation_period_end_date,
study_end_date,
index_date + max_follow_up_days
)
这个 LEAST 逻辑是队列工程中最容易被忽略却最影响结果的部分。如果只取数据截止日,会把已经死亡的患者继续算作“存活随访”,人为拉长随访时间;如果只取死亡日期,又会把失访患者错误地截断。因此,随访终点必须显式地取所有可能终止原因的最小值,并在队列定义中记录每个患者实际终止的原因类别。
从定义到可执行:一个最小队列规格示例
把上述要素组织成一份可执行的队列规格,通常包含以下字段。下表给出一个教学示例,参数为合理假设,用于说明结构而非真实研究结论。
| 要素 | 工程表达 | 示例值 |
|---|---|---|
| 目标人群 | 年龄、性别、疾病史 | ≥18 岁,2 型糖尿病 |
| 索引事件 | 表.字段 + 概念集 + 取第几次 | drug_exposure 首次二甲双胍处方 |
| 观察窗 | observation_period 连续天数 | 索引日前 ≥365 天 |
| 纳入标准 | EXISTS 条件 | 索引日前 365 天内有 T2DM 诊断 |
| 排除标准 | NOT EXISTS 条件 | 索引日前 180 天内有胰岛素使用 |
| 随访起点 | index_date + washout | 索引日后 30 天 |
| 随访终点 | LEAST(多个日期) | min(死亡, 观察期结束, 数据截止, 索引日+730 天) |