eICU没有ventilation_event表?用respCare等表构建机械通气事件
2026/9/16 10:24:49 网站建设 项目流程

先提醒一句:这不是你权限有问题,也不是版本下载错了。你在 eICU 数据库里找不到 ventilation_event 表,是正常现象,因为 eICU 和 MIMIC 两套数据库的 schema 设计根本就不是一回事。我在第一次接触 eICU 的时候就掉进过这个坑里,拿着 MIMIC 的经验去查表,结果卡了整整一个下午。

这篇文章要解决的就是这个问题。我会把 eICU 里和机械通气相关的表挨个说清楚,告诉你没有 ventilation_event 表时,怎么自己动手构建一个“通气事件表”,还会把我实际操作中遇到的各种边界情况、脏数据问题一起交代出来。无论你是做重症医学研究、做临床数据挖掘,还是刚接触 eICU 正在摸索阶段,这篇文章都能帮你少走弯路。

1. 为什么会习惯性找 ventilation_event 表?先搞清楚两套数据库的设计差异

1.1 MIMIC 和 eICU,底层逻辑完全不一样

很多人第一次接触公开重症数据库,基本都是先从 MIMIC 入门的。MIMIC-III 里有 d_ventilation_event 相关的时间分段思路,MIMIC-IV 在重症监护模块里也直接给出了 ventilation 相关的视图。这套体系给人的感觉是:数据库里就应该有一张现成的表,记录着患者从“上呼吸机”到“脱机”的完整时间段。

但 eICU Collaborative Research Database 不是这个思路。eICU 由飞利浦健康(Philips Healthcare)提供数据,采集的是美国多家 ICU 的真实临床信息系统数据。它的表结构更贴近临床护理流程,而不是把某一个临床事件整理成一张独立的明细表。换句话说,MIMIC 更像“已经帮你整理好的研究型数据”,eICU 则更像“病历系统的原始分表拆解”,很多事件需要你自己拼接。

就机械通气而言,eICU 里没有 ventilation_event 这张表,但它有 respCare、respSupport、respCharting 这几个表,把呼吸机相关的状态、参数、护理记录分开放。你要的“通气事件”,实际上是散落在这些表中的,需要你按照自己的研究定义去重新组织。

1.2 eICU 的核心组织逻辑:按护理维度拆表,而不是按事件聚合

eICU 数据库的基础单位是 patientunitstayid,即一次 ICU 住院记录。围绕一次住 ICU 的过程,eICU 把数据拆成很多张表,比如 lab 是检验结果、infusionDrug 是静脉给药、intakeOutput 是出入量、respCare 是呼吸护理状态、respSupport 是呼吸支持参数、respCharting 是呼吸护理观察记录。

这种设计的好处是贴近原始系统的采集方式,缺点是研究者需要自己做大量的数据清洗和事件构建工作。比如你要判断“患者这三天是不是一直带着呼吸机”,MIMIC 里可能查一张表就行,eICU 里则需要先看 respCare 的状态段,再用 respSupport 的参数去验证,必要时还得借助 respCharting 里的值班记录做人工核对。

在动手之前,先说一个最容易踩的坑:不要拿着 MIMIC 的 SQL 直接跑到 eICU 上执行。两个库不仅表名不同,字段命名规则也不同,甚至时间字段的表达方式都不一样,盲目套用只会越查越乱。

2. 拆解 eICU 里真正和呼吸机相关的几张表:respCare、respSupport、respCharting

2.1 respCare 表:呼吸支持状态的“大段记录”

先看 respCare。这张表的核心作用是记录患者在某一段时间内处于什么呼吸支持状态。比如这个患者是“自主呼吸”,还是“机械通气”,还是“无创通气”,每条记录都有一个开始偏移量和结束偏移量,单位是分钟。

常见字段包括:

  • patientunitstayid:患者 ICU 住院的唯一标识
  • respCareStatus:呼吸护理状态,比如 Mechanical Ventilation、Continuous Ventilation、Spontaneous Respiration 等
  • ventilatorMode:通气模式,比如 SIMV、PRVC、PSV 等
  • airwayType:气道类型,常见值有 Endotracheal Tube、Tracheostomy Tube 等
  • respCareStatusStartOffset:这条状态从记录起点的多少分钟开始
  • respCareStatusEndOffset:这条状态到多少分钟结束

你可以把 respCare 理解成一个大时间轴,它会告诉你某个患者什么时候处于机械通气状态区间。虽然不是 ventilation_event 表那种干净、规整的事件表,但它是构建通气事件最核心的起点。

这里必须强调一点:respCare 表里不一定每个患者的记录都连续。临床上有些 ICU 值班人员不会频繁更新状态,可能导致某段区间缺记录,或者上一个状态的结束时间直接等于下一个状态的开始时间,这都很正常。

2.2 respSupport 表:呼吸机参数的“高频明细”

如果说 respCare 是大状态,那 respSupport 就是细粒度的呼吸机参数采集。临床系统中,呼吸机会定期自动记录一些数值,比如吸氧浓度 FiO2、潮气量 Tidal Volume、PEEP 等。这些记录会落到 respSupport 表里。

respSupport 的关键字段大致如下:

  • patientunitstayid
  • respSupportTimeOffset:参数记录的时间点
  • respSupportType:参数类型,常见有 FiO2、Tidal Volume、PEEP、Pressure Support 等
  • respSupportValue:对应的数值

这张表的数据量通常很大,因为呼吸机会持续记录。你在构建通气事件时,respSupport 不能单独用来判断患者是否在呼吸机上,但如果结合 respCare 的状态区间,你就可以提取到“机械通气期间的平均 PEEP”等具体参数,这正好是很多临床研究需要的指标。

2.3 respCharting 表:护士记录的“观察流水账”

respCharting 可以理解为护理人员在呼吸治疗这块的手工记录。临床上护士隔一段时间就会记录一次患者的呼吸情况,比如呼吸频率、吸氧浓度、气道压力、呼吸音评估等。这些记录通常不是机器自动生成的,而是人工录入的,所以时间频率不稳定,字段也比较杂。

respCharting 常用字段包括:

  • patientunitstayid
  • respChartTimeOffset
  • respChartEntry:记录项的名称
  • respChartValue:记录的数值或文本

如果你的研究不只看“呼吸机参数”,还关心护理层面的人工评估,那么这张表是很好的补充。但要注意,因为它由人工录入,容易出现拼写不一致、单位不一致、偶发缺失等问题,清洗时要特别小心。

从整体来看,respCare 解决“时间段”问题,respSupport 解决“参数值”问题,respCharting 解决“人工观测记录”问题。三者合在一起,才能拼出你真正需要的机械通气研究数据集。

3. 没有 ventilation_event 表,用 SQL 自己构建通气事件表

3.1 先明确你要的“通气事件”到底怎么定义

在写 SQL 之前,必须先给自己定一个明确的事件定义。不同研究对“机械通气事件”的定义可能完全不同。有些研究要求患者插管后使用有创通气才算,有些研究把无创通气也算进去,有些研究只关心通气时长,有些研究则关注通气过程中是否发生了参数调整。

没有通行标准时,我用得最多的是这套判断逻辑:

  • 以 respCare 表中 respCareStatus 包含 Mechanical Ventilation、Continuous Ventilation、Invasive Ventilation 等有创机械通气标识的记录为主
  • 或 ventilatorMode 字段不仅限于空值、None、Spontaneous 等非机械通气模式
  • 一个通气事件由连续的状态段合并而成,如果两个状态段之间的间隔不超过 10 分钟到 15 分钟,视为同一段事件

定义一旦明确,后面的 SQL 就好写了。这里没有唯一正确答案,你需要根据自己的研究目的灵活调整。比如你把无创通气也算进去,判断条件就需要放宽。

3.2 第一步:从 respCare 提取机械通气状态片段

首先写一条简单的查询,先看看单个患者的原始数据长什么样:

SELECT patientunitstayid, respCareStatus, ventilatorMode, airwayType, respCareStatusStartOffset AS start_min, respCareStatusEndOffset AS end_min FROM respCare WHERE patientunitstayid = 123456 ORDER BY start_min;

拿到原始数据后,你会发现同一个患者有多条记录,有些是连续的,有些中间有断档。这时候就需要做一次筛选,只保留机械通气相关的状态段:

SELECT patientunitstayid, respCareStatus, ventilatorMode, respCareStatusStartOffset AS start_min, respCareStatusEndOffset AS end_min FROM respCare WHERE (respCareStatus IN ('Mechanical Ventilation', 'Continuous Ventilation', 'Invasive Ventilation') OR ventilatorMode NOT IN ('', 'None', 'Spontaneous')) ORDER BY patientunitstayid, start_min;

注意一点:respCareStatus 的具体枚举值在不同版本的数据集中可能略有差异。我第一次跑的时候,发现有的版本写的是 Mechanical Ventilation,有的版本写的是 Continuous Mechanical Ventilation。最稳妥的办法是先跑一次 distinct 查看全部取值,再确定过滤条件。

3.3 第二步:合并相邻状态段,处理断档

筛选出通气状态片段后,下一步就是把同一患者、时间相邻但中间有短暂断档的片段合并成一个事件。这是整个过程中最关键的一步,处理不好会让事件数量虚高,通气时长也被低估。

我常用的合并逻辑是:先按患者分组,按开始时间排序,用窗口函数取上一条记录的结束时间,如果当前记录的开始时间与上一条结束时间之间的间隔不超过 15 分钟,就认为是同一个通气事件,否则另起一个新事件。

WITH vent_filtered AS ( SELECT patientunitstayid, respCareStatusStartOffset AS start_min, respCareStatusEndOffset AS end_min FROM respCare WHERE (respCareStatus IN ('Mechanical Ventilation', 'Continuous Ventilation', 'Invasive Ventilation') OR ventilatorMode NOT IN ('', 'None', 'Spontaneous')) ), with_prev AS ( SELECT patientunitstayid, start_min, end_min, LAG(end_min) OVER (PARTITION BY patientunitstayid ORDER BY start_min) AS prev_end_min FROM vent_filtered ), new_event_flag AS ( SELECT patientunitstayid, start_min, end_min, prev_end_min, CASE WHEN prev_end_min IS NULL THEN 1 WHEN start_min - prev_end_min <= 15 THEN 0 ELSE 1 END AS is_new_event FROM with_prev ), event_group AS ( SELECT patientunitstayid, start_min, end_min, SUM(is_new_event) OVER (PARTITION BY patientunitstayid ORDER BY start_min) AS event_id FROM new_event_flag ) SELECT patientunitstayid, MIN(start_min) AS vent_start_min, MAX(end_min) AS vent_end_min, MAX(end_min) - MIN(start_min) AS vent_duration_min FROM event_group GROUP BY patientunitstayid, event_id ORDER BY patientunitstayid, vent_start_min;

这套 SQL 的思想很简单:用 LAG 拿到上一个区间的结束点,再用窗口累加生成事件编号。如果两个相邻区间的间隔很小,就把它们归到同一个事件里。15 分钟这个阈值是我根据临床记录频率定的,你可以改成 5 分钟或 30 分钟,主要看你对“连续性”的要求有多严格。

3.4 第三步:把事件表和研究队列关联起来

事件表构建好以后,通常还要和患者基本信息表关联。eICU 里患者基本信息在 patient 表中,patientunitstayid 是关联键。你可以把患者性别、年龄、入院诊断、住院时长等字段拉到一起,形成最终的机械通气事件队列。

WITH vent_events AS ( -- 上面构建的事件表,这里简写为完整查询后的结果 ), patient_basic AS ( SELECT patientunitstayid, uniquepid, gender, age, unitdischargestatus FROM patient ) SELECT p.patientunitstayid, p.gender, p.age, p.unitdischargestatus, v.vent_start_min, v.vent_end_min, v.vent_duration_min FROM vent_events v LEFT JOIN patient_basic p ON v.patientunitstayid = p.patientunitstayid ORDER BY p.patientunitstayid, v.vent_start_min;

age 字段在 eICU 里有个细节:超过 89 岁的患者,记录值可能是 89 以上,实际数据库里会用一些特殊处理,常见做法是把大于 89 的值统一视为 89 岁以上,或者直接过滤掉年龄异常值,取决于你的研究方案。

3.5 第四步:用 respSupport 验证事件边界

事件表构建好之后,不要急着直接用于分析,我强烈建议用 respSupport 表做一次交叉验证。呼吸机参数记录通常比较密集,如果某个事件区间内完全没有任何呼吸机参数记录,可能说明这段事件提取有误,或者数据本身存在缺口。

SELECT r.patientunitstayid, COUNT(*) AS support_record_count, MIN(r.respSupportTimeOffset) AS first_support_min, MAX(r.respSupportTimeOffset) AS last_support_min FROM respSupport r WHERE r.respSupportType IN ('FiO2', 'Tidal Volume', 'PEEP') GROUP BY r.patientunitstayid LIMIT 100;

通过对比 event 表中的 vent_start_min 和 respSupport 表的最小时间偏移,你可以检查事件边界是否合理。如果 respSupport 表的第一条 FiO2 记录时间明显晚于通气事件开始时间,那就要看看是不是 respCare 状态判定太宽了。反过来,如果 respSupport 在事件结束后还有大量参数记录,也可能说明事件结束时间定早了,需要考虑是否把相邻的通气片段合并进去。

4. 实际操作中绕不开的坑:时间偏移、ID 选择、重复计数、状态断档

4.1 时间字段不是时间戳,是分钟偏移量

这是刚上手 eICU 时最容易犯的错误。MIMIC 里有很多 timestamp 类型的字段,可以直接比较时间大小。eICU 里的大部分时间字段,尤其是 respCare、respSupport、respCharting 里的时间字段,表达方式是从某个零点开始算的分钟偏移量。常见偏移零点一般对应 ICU 入院时间,但不同表可能存在细微差异。

所以你在做时间过滤时,一定要搞清楚“偏移量零点”。以 ICU 入院为 0 点,则偏移量 120 表示入院后 2 小时。如果你不习惯这种方式,可以先把偏移量转换成绝对时间,方法是从患者表里找到 ICU 入院时间,再在分钟偏移量上做加法转换。这种转换在做跨表关联时尤其重要,否则你 join 出来的时间永远对不上。

4.2 patientunitstayid 才是核心主键,别拿 uniquepid 当唯一键

eICU 的层级结构是:uniquepid 是患者唯一标识,一个人可能在研究周期内多次住进 ICU;而 patientunitstayid 是某一次 ICU 住院记录的标识。做机械通气事件分析时,几乎所有的表都使用 patientunitstayid 作为关联键。

我见过有人拿 uniquepid 去关联 respCare 表,结果把同一个患者两次入 ICU 的呼吸机记录全混在一起,分析结果自然完全乱掉。如果你关心的是“每一次住院期间的通气情况”,就以 patientunitstayid 为维度;如果你关心的是“患者个体层面的长期预后”,才需要先用 patientunitstayid 汇总后,再映射到 uniquepid 上。

4.3 respCare 状态段的断档和重叠问题

respCare 表不一定保证每个患者的状态段首尾相接。常见情况有两种:一是中间缺了一段记录,比如上一段结束时间是 300,下一段开始时间直接跳到 400,中间一大段时间没有任何状态;二是多个状态段重叠,比如 Mechanical Ventilation 和 Spontaneous Respiration 同时存在。

遇到重叠时,需要先确定你的优先级。一般建议以“更积极的呼吸支持”为准,也就是如果同时存在机械通气和自主呼吸记录,优先采用机械通气状态来判断患者处于通气状态。遇到断档时,则要结合 respSupport 或 respCharting 里的参数记录来手动补充判断。

4.4 多表 join 时不要直接 count,小心中招“重复计数”

respSupport 表是参数级的明细数据,同一个时间点可能有五六条不同类型的参数记录。如果你把 respSupport 和 events 表直接 join 后 count 行数,很容易把一次通气天数算成好几倍。

正确做法是:先对 respSupport 做聚合去重,比如按 patientunitstayid、通气事件编号、时间阈值做窗口去重,再计算时长指标。任何时候,先算行数再加总前,都要回头想想这个表里每一行的粒度是什么。

这里整理了一份我在实际排查中用到的速查表,遇到问题可以直接对着排查:

常见现象可能原因解决办法
找不到 ventilation_event 表eICU 的物理表设计里就没有这张表改用 respCare、respSupport、respCharting 组合构建
用 MIMIC 的代码直接在 eICU 跑,报错表名、字段名完全不同重新核对 eICU 表结构,不要直接套用
respCare 中某个患者没有任何机械通气状态该患者可能全程未上机结合 respSupport 和 respCharting 判断
事件数量比实际患者数多很多状态断档被切分成了多个事件调整合并阈值,检查 gap 设置
同一患者时间区间重叠临床记录存在重叠状态定义支持优先级,保留更高级的支持状态
时间偏移量和预期时间对不上偏移零点理解错误查看对应表的数据字典,统一转换口径
和呼吸机参数对比时事件边界对不上只看 respCare 不够稳增加 respSupport 交叉验证逻辑

5. 更进一步:没有 ventilation_event 表,反而给了你更大的灵活度

一开始我也觉得 eICU 很不方便,缺了现成的事件表,什么都要自己拼。但用久了以后,我的看法变了。没有 ventilation_event 表,其实意味着没有一个人替你做死的事件定义,你可以完全按照自己的研究需要定义机械通气事件。

比如你想研究的不是“是否上机”这种二分类,而是“上机阶段内呼吸机参数变化轨迹”,那你可以把事件表中某个通气事件区间内的所有 FiO2、PEEP、潮气量记录全部提取出来,做时间序列分析。这在 MIMIC 的固定事件表下反而要手动处理边界,在 eICU 里拆开结构反而更容易。

再比如你想分析“从有创通气切换到无创通气的中间过程”,eICU 的 respCare 里有 airwayType 和 ventilatorMode,可以更细致地识别切换时间点。只要你愿意花时间把多张表组合起来,得到的研究数据通常比固定事件表更精准。

我在做队列划分时,最后的流程基本稳定为四步。第一步,确定通气状态筛选条件,多看 distinct 值保证过滤条件无遗漏。第二步,提取状态段并合并相邻区间,形成候选事件。第三步,与呼吸机参数记录做交叉验证,修正左右边界。第四步,加入基线特征、结局指标,形成可用于分析的宽表。

6. 写到最后,分享一点个人的数据处理体会

说实话,eICU 的表结构对新手并不友好,尤其当你习惯了 MIMIC 那种“数据已经按研究思路整理好”的模式时,第一次接触 eICU 很容易产生挫败感。我自己的调整方式是:不再试图把 eICU 硬套成 MIMIC,而是接受它的原始性,把 eICU 当成一份更贴近真实临床记录的数据源来对待。

真实世界里的记录就是不完美的。respCare 会有断档,respSupport 会有冗余,respCharting 里会有人工录入的错误。做机械通气研究时,不要追求“绝对完美的通气事件表”,只要你的筛选条件、合并规则、边界处理逻辑有明确的临床依据,并且重复可复现,那结果就是可信的。

如果后面你再遇到类似“为什么 eICU 没有 XX 表”的问题,先别急着查代码,先去看看这个库的设计文档和表关系图。绝大多数时候,不是数据库缺了东西,而是我们自己还没换过思路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询