简介一份面向医疗大数据与ICU临床研究人员的ICU数据库数据表介绍文档基于中国四川省自贡市第四人民医院2019—2020年2790例ICU患者数据整理而成。文档以数据字典形式逐表说明dtBaseline患者基线、dtDrugs药物、dtICD诊断、dtLab实验室、dtTransfer转诊、dtNursingChart护理、dtOutcome结局随访等七类核心表并标注字段含义、参考范围及数据使用注意事项如年龄脱敏规则、事件时间偏移处理、诊断顺序混乱、护理表缺失值多等能帮助研究者快速理解数据口径支撑疾病预后、治疗效果评估及预测模型构建等研究。包体为单个DOCX文档大小约31KB内容简洁清晰目前已有548人浏览学习。适合拟利用该ICU数据库进行二次分析、论文复现或开展临床大数据挖掘的研究人员快速上手。1. 自贡四院ICU数据库一份能直接拿去写SQL的数据字典假如你手里拿到一份重症患者的临床数据集想算一个「ICU死亡率预测模型」第一件事不是调模型而是先打开这份自贡市第四人民医院ICU数据库数据表介绍文档搞清楚里面到底有哪些数据表、每张表的关键字段是什么、表与表之间靠什么关联。这份文档本质上是一本数据字典它不解释算法也不做统计分析只回答「库里有什么、怎么取」。对于做临床科研的医生、写特征工程的数据工程师以及需要定期出重症质控报表的统计人员这份介绍文档能帮你把检索范围从「整个数据库乱翻」缩到「三张核心表加三张事件表」。我拿到这类文档的习惯是先通读一遍字段注释再画一张主键关系草图最后跑三个最简单的SELECT验证表能连上。这套流程走完后面写几十条复杂查询都不会慌。2. 核心表结构拆解从PATIENT到ICUSTAY先搞懂这三张表ICU数据库通常不会只有一张表而是围绕「患者—住院—重症停留」三层结构组织的。自贡四院这份ICU数据库介绍文档也遵循这个惯例最核心的三张基础表是PATIENT、ADMISSION和ICUSTAY。先把这三张表的字段和主键关系摸清后面所有事件表生命体征、检验、医嘱、手术才有挂靠的位置。2.1 PATIENT表性别、年龄、入院次数的正确打开方式PATIENT表存储的是患者静态人口学信息一张表代表一个人。常见字段包括SUBJECT_ID患者唯一标识、GENDER性别、BIRTH_YEAR出生年份、DOD_HOSP院内死亡时间等。在 MySQL 里建表时我建议把字符集统一成utf8mb4因为中文诊断描述会用到生僻字默认utf8可能存不进去。CREATE TABLE PATIENT ( SUBJECT_ID INT NOT NULL COMMENT 患者唯一标识全表主键, GENDER CHAR(4) DEFAULT NULL COMMENT 性别M/F/UNKNOWN, BIRTH_YEAR INT DEFAULT NULL COMMENT 出生年份, DOD_HOSP DATETIME DEFAULT NULL COMMENT 院内死亡时间未死亡则为NULL, PRIMARY KEY (SUBJECT_ID) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT患者基本信息表;逻辑说明SUBJECT_ID是跨所有表的患者维度外键但要特别注意一个人可能多次入院所以PATIENT表里一行代表「一个患者」不区分住院次数。计算年龄时不能直接拿当前年份减BIRTH_YEAR因为数据可能是历史年份采集的正确做法是用入院时间减出生年份。DOD_HOSP只在患者死于院内时有值如果要做结局分析最好结合ADMISSION表中的HOSPITAL_EXPIRE_FLAG一起判断避免只依赖时间字段。2.2 ADMISSION表入院与转科记录时间字段别只看日期ADMISSION表记录每次入院事件一行是一次住院。核心字段是HADM_ID住院号以及入院时间ADMITTIME、出院时间DISCHTIME、死亡时间DEATHTIME、入院类型ADMISSION_TYPE急诊/门诊/转院和HOSPITAL_EXPIRE_FLAG院内死亡标记。这张表的HADM_ID是连接 ICU 停留与所有院内事件的关键桥梁。CREATE TABLE ADMISSION ( HADM_ID INT NOT NULL COMMENT 住院唯一标识, SUBJECT_ID INT NOT NULL COMMENT 外键关联PATIENT, ADMITTIME DATETIME NOT NULL COMMENT 入院时间精确到分钟, DISCHTIME DATETIME DEFAULT NULL COMMENT 出院时间, DEATHTIME DATETIME DEFAULT NULL COMMENT 死亡时间仅当院内死亡时有值, ADMISSION_TYPE VARCHAR(20) DEFAULT NULL COMMENT 急诊/门诊/转院, HOSPITAL_EXPIRE_FLAG TINYINT DEFAULT NULL COMMENT 院内死亡标志1是0否, PRIMARY KEY (HADM_ID), KEY idx_subject (SUBJECT_ID) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT住院事件表;逻辑说明很多新手查「患者住院天数」时直接取DISCHTIME - ADMITTIME但 ICU 数据库里的时间戳往往包含时区偏移而且转科患者可能多次进出ICU所以住院天数必须基于ICUSTAY表的边界重新计算。另外ADMISSION_TYPE在不同医院取值规范不同拿到数据后应先SELECT DISTINCT ADMISSION_TYPE看枚举值不要想当然地按固定值过滤。2.3 ICUSTAY表重症停留期的起止边界与主键票据ICUSTAY表是 ICU 数据库的核心事实表一行是一次 ICU 停留记录。字段包括ICUSTAY_IDICU 停留唯一标识、SUBJECT_ID、HADM_ID、ICU_START入ICU时间、ICU_END出ICU时间、WARDIDICU病区编号。这张表的三件套主键关系——SUBJECT_ID → HADM_ID → ICUSTAY_ID——是理解整个数据库的钥匙。CREATE TABLE ICUSTAY ( ICUSTAY_ID INT NOT NULL COMMENT ICU停留唯一标识, SUBJECT_ID INT NOT NULL COMMENT 患者ID, HADM_ID INT NOT NULL COMMENT 住院ID, ICU_START DATETIME NOT NULL COMMENT 进入ICU的时间, ICU_END DATETIME DEFAULT NULL COMMENT 转出ICU的时间, WARDID TINYINT DEFAULT NULL COMMENT ICU病区编号, PRIMARY KEY (ICUSTAY_ID), KEY idx_hadm (HADM_ID), KEY idx_subject_time (SUBJECT_ID, ICU_START) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENTICU停留事件表;逻辑说明ICU_START和ICU_END是整个数据库事件对齐的锚点。所有临床事件化验、用药、护理记录都带时间戳分析时必须把事件时间限制在[ICU_START, ICU_END]区间内。为什么不用ADMITTIME和DISCHTIME因为患者可能在普通病房住三天后才转进ICU也可能从ICU转到普通病房后继续住院ICU分析必须以ICU停留时间为准。用 MySQL 创建多张数据表的格式时要注意统一ENGINEInnoDB和CHARSETutf8mb4否则中文字段注释和诊断文本会出现乱码。另外外键约束在分析库上不建议启用只建普通索引就够了一是为了导入速度二是因为临床数据质量参差外键约束会导致灌数失败。3. 生命体征与检验表把分钟级监测数据和离散检验值对齐到同一时间轴进入ICU后患者会同时产生两类数值型数据高频生命体征和低频实验室检验结果。自贡四院ICU数据库里一般有两张对应的表VITALS和LABS。这两张表的读取方式完全不同如果按照普通业务表的思路去处理很容易在时间对齐上翻车。3.1 VITALS表每5分钟一条还是按需测量先看DATA_SOURCEVITALS表存储的是床旁监护仪采集的生命体征包括心率、血压、呼吸频率、血氧饱和度等。它通常是长表结构一行一个患者的一个测量项包括ICUSTAY_ID、ITEM_ID测量项编码、VALUE数值、TIME测量时间、DATA_SOURCE数据来源如自动监护仪还是护士手工录入。CREATE TABLE VITALS ( ID BIGINT NOT NULL AUTO_INCREMENT, ICUSTAY_ID INT NOT NULL, ITEM_ID VARCHAR(20) NOT NULL COMMENT 如HR、NIBP_SYS、RR、SPO2, VALUE DECIMAL(10,2) DEFAULT NULL, TIME DATETIME NOT NULL, DATA_SOURCE VARCHAR(10) DEFAULT AUTO COMMENT AUTO监护仪自动采集MANUAL护士录入, PRIMARY KEY (ID), KEY idx_stay_time (ICUSTAY_ID, TIME), KEY idx_item (ITEM_ID) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT生命体征测量表;逻辑说明ITEM_ID不是严格统一的国际编码每家医院都有自己的词典。拿到文档后先看附带的ITEM_DEFINITION表如果文档里有确认HR是心率、NIBP_SYS是无创袖带压收缩压。DATA_SOURCE字段非常关键自动采集的数据通常是每5分钟一条适合做连续趋势分析手工录入的数据可能每小时才一条而且经常漏记。分析时如果混用两种来源样本量分布会很不均匀最好按来源分组分别统计。查询某患者在ICU期间的心率数据标准写法是SELECT v.ICUSTAY_ID, v.TIME, v.VALUE AS heart_rate FROM VITALS v JOIN ICUSTAY i ON v.ICUSTAY_ID i.ICUSTAY_ID WHERE v.ITEM_ID HR AND v.TIME BETWEEN i.ICU_START AND i.ICU_END ORDER BY v.TIME;参数说明BETWEEN是闭区间会包含边界值。如果ICU_START本身就带有多余的秒级精度建议先统一格式化到分钟再比较。实测中发现监护仪的记录时间经常比护士手工录入的时间整点偏移几分钟所以做时间对齐时不要用「精确到秒的等值条件」而要用窗口条件。3.2 LABS表检验项目编码ITEM_ID与单位换算的隐规则LABS表存储抽血化验结果同样采用长表结构。字段包括ICUSTAY_ID、ITEM_ID如GLUCOSE、K、CREATININE、VALUE检验结果、UNIT原始单位、REF_LOW和REF_HIGH参考范围、TIME采血时间。注意同一项检验在不同仪器上单位可能不同比如血钾有mmol/L和mEq/L数值等同但葡萄糖可能是mg/dL和mmol/L不换算直接比较会差18倍。SELECT l.ICUSTAY_ID, l.ITEM_ID, l.VALUE, l.UNIT, l.REF_LOW, l.REF_HIGH, l.TIME FROM LABS l JOIN ICUSTAY i ON l.ICUSTAY_ID i.ICUSTAY_ID WHERE l.ITEM_ID IN (K, NA, CREATININE) AND l.TIME BETWEEN i.ICU_START AND i.ICU_END ORDER BY l.TIME;逻辑说明REF_LOW和REF_HIGH是仪器自带的参考范围不同科室、不同年龄段参考范围不一致。做异常值标记时最好以数据集自带参考范围为准不要用教科书上的固定值。另外检验值中的VALUE字段可能是字符串因为部分检验结果是定性描述如「阳性」「未见」建表时用VARCHAR更稳妥查询数值时再CAST转换。但转换前一定要先看有多少非数值行否则CAST会让整个查询失败。3.3 用时间条件JOIN对齐生命体征和检验结果三种写法的效率差假设你要研究「入ICU后第一次血糖值对应的心率」目标是从LABS取每个患者入ICU后的首次血糖结果再去匹配同时刻的VITALS。常见做法是先用窗口函数取首条WITH first_glucose AS ( SELECT l.ICUSTAY_ID, l.VALUE, l.TIME, l.ITEM_ID, ROW_NUMBER() OVER ( PARTITION BY l.ICUSTAY_ID ORDER BY l.TIME ) AS rn FROM LABS l WHERE l.ITEM_ID GLUCOSE ) SELECT f.ICUSTAY_ID, f.VALUE AS glucose, f.TIME AS glucose_time, v.VALUE AS heart_rate FROM first_glucose f JOIN VITALS v ON v.ICUSTAY_ID f.ICUSTAY_ID AND v.ITEM_ID HR AND v.TIME BETWEEN f.TIME - INTERVAL 5 MINUTE AND f.TIME INTERVAL 5 MINUTE WHERE f.rn 1;参数说明这里用ROW_NUMBER取每个患者第一次血糖然后用 ±5 分钟窗口匹配心率。窗口大小要根据监护仪采样频率调整如果监护仪每5分钟记录一条心率那 ±5 分钟能覆盖到至少一条如果采样频率是每分钟建议收窄到 ±1 分钟避免匹配到变化过大的值。还有一种写法是直接用LATERAL JOIN或相关子查询取最近一条但 MySQL 的LATERAL支持版本受限通用性不如窗口函数。在数据量很大时两种写法都要验索引ICUSTAY_ID TIME的联合索引是必须的。3.4 过滤伪差值的WHERE条件心率0.0、体温42.5、血压负值的合法保单临床数据最大的特点是脏数据多。监护仪电极脱落时心率会跳成0传感器故障时体温可能显示42.5℃甚至血压会出现负值。这些值在数据库里真实存在但不是生理值。分析前必须建立物理合理范围过滤SELECT * FROM VITALS WHERE ITEM_ID HR AND VALUE BETWEEN 30 AND 220 AND TIME IS NOT NULL;逻辑说明心率上限220是极限运动情况ICU患者通常不超过180。但「伪差值」的判定不能一刀切比如起搏器患者的基线心率可能只有20这时下限30会误杀。所以我一般先做分布直方图VALUE BETWEEN 0 AND 300的分桶计数看到明显离群簇后再定过滤边界而不是照搬网上的「正常范围」。体温 42.5℃ 可能是数据录入错误也可能仅仅是华氏度没转换。任何过滤规则都要在文档的字段注释里写明并保留原始值方便复查。4. 诊断、医嘱与手术操作表从ICD编码和剂量文本里挖出可用信息数值型表相对好处理真正让数据工程师头疼的是诊断、医嘱和手术操作这三类带文本和编码的表。自贡四院ICU数据库里这三张表通常命名为DIAGNOSIS、MEDICATIONS、PROCEDURES。它们的共性是编码体系不统一、时间信息粗粒度、剂量单位复杂。这一章讲清怎么读、怎么转、怎么对齐。4.1 DIAGNOSIS表中文诊断描述与ICD编码并存时以哪个为准DIAGNOSIS表存储患者本次住院的诊断记录字段可能包括ICUSTAY_ID、ICD_CODEICD-10编码、ICD_VERSIONICD版本、DIAG_TEXT中文诊断描述、SEQ_NUM诊断序号通常1为主诊断。国内医院普遍同时记录中文文本和国际编码但中文文本是从病历里抽取的存在同义不同形的问题比如「急性心肌梗死」和「急性心肌梗塞」指向同一诊断。SELECT DIAGNOSIS_TYPE, ICUSTAY_ID, ICD_CODE, ICD_VERSION, DIAG_TEXT, SEQ_NUM FROM DIAGNOSIS WHERE ICD_CODE IS NOT NULL ORDER BY ICUSTAY_ID, SEQ_NUM;逻辑说明做统计或建模时优先以ICD_CODE为准因为编码是标准化的。中文描述只能作为补充条件辅助确认编码映射是否准确。需要特别注意ICD_VERSION字段ICD-10和ICD-9的编码规则完全不同比如心绞痛在ICD-9是413.9在ICD-10是I20.9。如果同一患者记录混用了两个版本必须按版本拆开做分层不能直接拼在一起计算「是否冠心病」。还有一种情况是ICD_CODE为空但DIAG_TEXT有值这通常发生在部分未完善编码的急诊记录里此时宁可不纳入也不要用文本猜。4.2 MEDICATIONS表医嘱频率、剂量单位与泵速换算的坑MEDICATIONS表记录用药医嘱字段包括ICUSTAY_ID、DRUG_NAME药品通用名、STARTTIME、ENDTIME、DOSE、DOSE_UNIT、ROUTE给药途径、FREQUENCY频率如 q6h、q8h。最坑的地方在于DOSE字段的语义可能是单次剂量、日总剂量也可能是泵速如mg/h。SELECT DRUG_NAME, STARTTIME, ENDTIME, DOSE, DOSE_UNIT, FREQUENCY, ROUTE, CASE WHEN DOSE_UNIT mg/h THEN DOSE WHEN DOSE_UNIT mg AND FREQUENCY q6h THEN DOSE * 4 WHEN DOSE_UNIT mg AND FREQUENCY q8h THEN DOSE * 3 ELSE NULL END AS daily_dose_mg_h FROM MEDICATIONS WHERE ICUSTAY_ID 12345 ORDER BY STARTTIME;参数说明这段SQL将单次剂量转换为日均剂量前提是FREQUENCY字符串规整。实际数据里可能有q6h PRN按需使用或者Q6H大小写混用所以转换前先SELECT DISTINCT FREQUENCY看全部枚举。更常见的是泵注药如去甲肾上腺素直接在DOSE里写浓度DOSE_UNIT是mg/ml而不是mg/h这时必须结合输液速度表才能算出实际速率。遇到这种文档我一般先找有没有INFUSION子表没有的话就放弃泵速计算不要硬凑。4.3 PROCEDURES表操作时间窗口要放宽到ICU前后还是只取ICU内PROCEDURES表记录手术和操作事件字段包括ICUSTAY_ID、PROC_CODE操作编码、PROC_TEXT操作名称、STARTTIME、ENDTIME。分析时最常遇到的问题气管插管、中心静脉穿刺这些操作可能在转入ICU前就已经在急诊或普通病房完成如果严格限制在ICU_START之后会漏掉患者入ICU时的初始状态。SELECT p.ICUSTAY_ID, p.PROC_TEXT, p.STARTTIME, p.ENDTIME, CASE WHEN p.STARTTIME i.ICU_START THEN ICU前 WHEN p.STARTTIME BETWEEN i.ICU_START AND i.ICU_END THEN ICU内 ELSE ICU后 END AS phase FROM PROCEDURES p JOIN ICUSTAY i ON p.ICUSTAY_ID i.ICUSTAY_ID ORDER BY p.ICUSTAY_ID, p.STARTTIME;逻辑说明对于「呼吸机使用时长」这类分析操作时间必须使用STARTTIME到ENDTIME的区间而不是只看开始时间。另外操作编码同样存在版本问题比如手术操作编码有ICD-9-CM-3和ICD-10-PCS两套但ICU常见的操作如气管插管、腰穿、CRRT持续肾脏替代治疗往往使用院内自定义编码文档里如果提供了PROC_DEFINITION字典表一定要先查清代码含义。4.4 把三类表与ICUSTAY做外连接不遗漏ICU前操作在构建特征矩阵时很多人直接用内连接INNER JOIN把诊断、医嘱、操作表关联到ICUSTAY这会导致没有诊断记录或没有用药记录的患者整行丢失。正确做法是以ICUSTAY表为左表其他表做左连接SELECT i.ICUSTAY_ID, COUNT(DISTINCT d.ICD_CODE) AS n_diagnosis, COUNT(DISTINCT m.DRUG_NAME) AS n_drugs, COUNT(DISTINCT p.PROC_TEXT) AS n_procedures FROM ICUSTAY i LEFT JOIN DIAGNOSIS d ON i.ICUSTAY_ID d.ICUSTAY_ID LEFT JOIN MEDICATIONS m ON i.ICUSTAY_ID m.ICUSTAY_ID LEFT JOIN PROCEDURES p ON i.ICUSTAY_ID p.ICUSTAY_ID GROUP BY i.ICUSTAY_ID;逻辑说明这个查询能同时看到每个患者的事件覆盖率找出完全没有诊断、医嘱或操作记录的患者。实战中这类患者通常是测试数据或转科时记录丢失后续分析要决定剔除还是单独分组。左连接看起来简单但COUNT(DISTINCT )在多表连接后膨胀的问题不容忽视如果一个患者有3条诊断、4条用药、2条手术两两连接后会产生24行中间结果统计计数必须用DISTINCT防止重复计算。5. 自贡四院ICU数据使用避坑5个能让你白忙一天的实战问题读完字段介绍不代表能直接出结果。下面的问题都是我在实际处理ICU数据时反复踩过的坑每条按「现象→原因→解决」写清楚希望你能绕开。5.1 同一患者多次入ICU单表查询时漏掉最近一次停留现象写SQL统计患者数时用SELECT COUNT(DISTINCT SUBJECT_ID) FROM ICUSTAY结果和科室手工台账对不上少了十几个患者。原因是有些患者在一次住院期间二次转入ICUICUSTAY表里有两条记录而手工台账只记最新一次。原因把「患者人数」和「ICU停留次数」混为一谈。DISTINCT SUBJECT_ID去重后只算人但后续关联事件表时如果没有带上ICUSTAY_ID两条停留的事件会混在一起。解决所有分析都明确「分析粒度」——是患者级还是ICU停留级。患者级分析用ROW_NUMBER()按SUBJECT_ID取最近一次停留ICU停留级分析则保留所有记录。取最近一次停留的写法SELECT * FROM ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY SUBJECT_ID ORDER BY ICU_START DESC ) AS rn FROM ICUSTAY ) t WHERE rn 1;5.2 生命体征表大量NULL原因是护理记录模式是「按需」而非「连续」现象提取心率序列时发现有的患者一分钟一条有的患者一小时才一条还有大量NULL。以为是数据采集故障后来发现是护理团队对病情稳定患者的监护仪只开启了每5分钟存盘对病情波动患者手动增加测量频率。原因不同ICU病区的采样策略不一致。自动采集的监护仪也有「趋势模式」和「实时模式」趋势模式下可能只保存每分钟的平均值甚至只保存异常值。解决使用前先统计每个患者在ICU停留期间的有效记录条数SELECT ICUSTAY_ID, COUNT(*) AS n_records, TIMESTAMPDIFF(MINUTE, MIN(TIME), MAX(TIME)) AS span_minutes, COUNT(*) / NULLIF(TIMESTAMPDIFF(MINUTE, MIN(TIME), MAX(TIME)), 0) AS records_per_minute FROM VITALS WHERE ITEM_ID HR GROUP BY ICUSTAY_ID;如果records_per_minute远低于监护仪声称的采样频率就要考虑把该患者标记为「低采样强度」分析时做敏感性分析或者只用插值后的值。5.3 检验结果“410”看起来是钾其实是血钾的千倍单位现象从LABS表查血钾ITEM_IDK某患者的值是410明显超出生理范围。查看原始单位字段发现UNITmmol/L但对照检验系统原始报告发现实际是4.10 mmol/L数据库里的410来自自动化的百倍冗余展示。原因医院信息系统在打印报告时会把小数点左移两位方便医生快速读数但导出到数据库时没有转换回标准单位。某些检验项还会用「小数位字段」和「值字段」分离存储直接合并读出错。解决加载LABS表后第一步先按ITEM_ID UNIT分组查看VALUE的分布区间。如果发现有两个明显量级簇说明存在单位混用。正确做法是写一个清洗函数def normalize_labs(row): # 血钾标准单位为mmol/L正常范围3.5-5.5 if row[ITEM_ID] K and row[UNIT] mmol/L: if row[VALUE] 20: return row[VALUE] / 100.0 return row[VALUE]注意这种换算必须有文档或检验科口径支撑不能凭感觉除以100。最保险的办法是抽查原始病历确认几例后再写清洗规则。5.4 ICU转入时间晚于第一张医嘱单时间轴错了还是数据录入错了现象排查用药序列时发现某患者的ICU_START是2024-05-10 10:00但第一张医嘱STARTTIME是2024-05-10 09:30差了半小时。以为是时间错位重新核对转科记录发现患者是从急诊直接入ICU急诊抢救时已经开了药医嘱时间早于ICU转入时间完全合理。原因医嘱、检验、操作都可能发生在ICU停留之前急诊、术前不能默认所有事件都在[ICU_START, ICU_END]内。解决涉及时间的分析要保留事件相对ICU边界的前后关系。用CASE WHEN标记事件发生在哪一阶段不要直接过滤掉早于ICU_START的记录。同时检查数据库是否有时区统一问题如果ICU_START存的是CST中国标准时间而医嘱表存的是UTC就会产生8小时偏移。排查方法是取同一患者同一条记录的TIME和文档标注的时区做对比出现负值立即检查连接串。5.5 使用DATEDIFF跨时区导致小时数为负数现象计算呼吸机时长时用TIMESTAMPDIFF(HOUR, STARTTIME, ENDTIME)得到负值比如-8。一开始以为是数据倒序后来发现是STARTTIME和ENDTIME来自不同系统一个用了Asia/Shanghai一个用了UTC存储。原因医院集成平台通常由多个厂商系统组成各系统时间标准不统一。ICU数据库从集成平台落库时如果没有统一转换就会出现跨时区混存。解决建表时统一使用DATETIME并约定所有时间列按Asia/Shanghai本地时间存储。如果历史数据已经混存先做时区探测SELECT STARTTIME, ENDTIME, TIMESTAMPDIFF(HOUR, STARTTIME, ENDTIME) AS diff_hour, CONVERT_TZ(STARTTIME, 00:00, 08:00) AS start_cst, CONVERT_TZ(ENDTIME, 00:00, 08:00) AS end_cst FROM PROCEDURES WHERE TIMESTAMPDIFF(HOUR, STARTTIME, ENDTIME) 0 LIMIT 100;如果发现diff_hour为负而转换后为正就确认是时区问题。处理方式不是简单地给所有时间加8小时而是要找到受影响的数据源范围白名单和黑名单结合修正。数据导入时建议在ETL阶段增加冗余字段TIME_UTC和TIME_LOCAL以后排查会省很多事。6. 正式分析前做一次数据质量审计三条SQL 一个共享视图这一章分享我每次接手ICU数据后都会做的固定动作——先审计数据质量再写业务逻辑。这个习惯帮我挡掉了很多「跑完模型才发现数据有问题」的翻车事故。6.1 用统计函数生成空值率与唯一性报告先跑一条通用统计SQL把每张表的关键列的空值率、非空行数、去重行数一次性列出来SELECT COUNT(*) AS total_rows, COUNT(SUBJECT_ID) AS subject_non_null, COUNT(DISTINCT SUBJECT_ID) AS subject_unique, COUNT(ICUSTAY_ID) AS stay_non_null, COUNT(DISTINCT ICUSTAY_ID) AS stay_unique, SUM(CASE WHEN ICU_START IS NULL OR ICU_END IS NULL THEN 1 ELSE 0 END) AS null_time_rows FROM ICUSTAY;参数说明COUNT(DISTINCT ICUSTAY_ID)与COUNT(*)如果差值较大说明存在重复的ICU停留主键需要进一步查看是否同一ICUSTAY_ID对应多条记录。null_time_rows是时间字段缺失的行数这类行在后续时间条件JOIN里会被全部丢弃必须提前决定处理方法。6.2 校验时间顺序ICU_start 事件时间 ICU_end用一条查询验证所有事件表的时间戳是否严格落在ICU停留区间内SELECT VITALS AS src, COUNT(*) AS violations FROM VITALS v JOIN ICUSTAY i ON v.ICUSTAY_ID i.ICUSTAY_ID WHERE v.TIME i.ICU_START OR v.TIME i.ICU_END UNION ALL SELECT LABS, COUNT(*) FROM LABS l JOIN ICUSTAY i ON l.ICUSTAY_ID i.ICUSTAY_ID WHERE l.TIME i.ICU_START OR l.TIME i.ICU_END;如果violations占比超过1%就不能简单过滤。要看这些事件集中在哪些患者、哪些时段毕竟ICU前事件也可能是有效数据。6.3 把常用JOIN封装成视图省下一半重复SQL质量审计通过后我会把「ICUSTAY 时间过滤条件」这个最常用的逻辑封装成数据库视图CREATE OR REPLACE VIEW v_icu_vitals AS SELECT v.* FROM VITALS v JOIN ICUSTAY i ON v.ICUSTAY_ID i.ICUSTAY_ID WHERE v.TIME BETWEEN i.ICU_START AND i.ICU_END; CREATE OR REPLACE VIEW v_icu_labs AS SELECT l.* FROM LABS l JOIN ICUSTAY i ON l.ICUSTAY_ID i.ICUSTAY_ID WHERE l.TIME BETWEEN i.ICU_START AND i.ICU_END;之后所有分析直接用视图既避免每次重复写JOIN又确保过滤条件一致。视图不是越多越好两张核心视图就够用。我自己做ICU科研项目时写完视图后还会写一句注释在代码仓库里「使用时若需要ICU前事件请直接查底层表不要用视图。」这个提醒救过不少同行。最后也提醒一句文档里的字段命名可能和这里给出的示例不完全一致甚至表名都不同。拿到真实数据后第一件事永远是对字典、看域值、跑空值统计。数据字典是死的数据是活的只有让查询先落在验证过的数据上后面的模型才靠谱。希望这些从实战里摸出来的经验帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?