-- ===================================================== -- 运动传感设备时序数据表设计(PostgreSQL/TimescaleDB) -- 表名前缀均为 ss_ -- 适用于高吞吐量、高并发的生命体征与位置数据存储 -- ===================================================== -- 1. 扩展与准备 -- ------------------------------------- -- 需要 uuid-ossp 与 timescaledb(若使用时序库) CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; CREATE EXTENSION IF NOT EXISTS timescaledb; -- 启用 TimescaleDB 扩展 -- Enable PostGIS extension for spatial data CREATE EXTENSION IF NOT EXISTS postgis; -- 2. 核心设备关联表 -- ------------------------------------- -- 设备管理表引用 ak_devices 表 -- 传感器测量数据均关联到已有设备管理 -- 3. 基础时序数据表:通用测量记录 -- ------------------------------------- CREATE TABLE IF NOT EXISTS public.ss_sensor_measurements ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), device_id UUID NOT NULL REFERENCES public.ak_devices(id) ON DELETE CASCADE, user_id UUID REFERENCES public.ak_users(id) ON DELETE SET NULL, measurement_type TEXT NOT NULL, -- 如 steps, heart_rate, spo2, stride, bp, temp, location, ecg measured_at TIMESTAMPTZ NOT NULL, unit TEXT, -- 单位,如 count, bpm, %, cm, mmHg, ℃, geojson raw_data JSONB, -- 原始数据或扩展字段 created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); -- 高并发场景下用 TimescaleDB 创建 hypertable: -- SELECT create_hypertable('public.ss_sensor_measurements', 'measured_at', chunk_time_interval => interval '1 day'); CREATE INDEX ON public.ss_sensor_measurements(device_id, measured_at DESC); CREATE INDEX ON public.ss_sensor_measurements(measurement_type, measured_at DESC); -- 4. 分专题数据表:性能优化(可选) -- ------------------------------------- -- 对于最常见的生命体征,可划分专项表,以提高查询效率 -- 步数 CREATE TABLE IF NOT EXISTS public.ss_steps ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE, step_count BIGINT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX ON public.ss_steps(step_count); -- 心率 CREATE TABLE IF NOT EXISTS public.ss_heart_rate ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE, heart_rate_bpm INT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX ON public.ss_heart_rate(heart_rate_bpm); -- 血氧 (SpO2) CREATE TABLE IF NOT EXISTS public.ss_spo2 ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE, spo2_percent INT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX ON public.ss_spo2(spo2_percent); -- 血压 CREATE TABLE IF NOT EXISTS public.ss_blood_pressure ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE, systolic INT NOT NULL, diastolic INT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX ON public.ss_blood_pressure(systolic, diastolic); -- 体温 CREATE TABLE IF NOT EXISTS public.ss_temperature ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE, temperature_c FLOAT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX ON public.ss_temperature(temperature_c); -- 步幅 CREATE TABLE IF NOT EXISTS public.ss_stride_length ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE, stride_cm FLOAT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX ON public.ss_stride_length(stride_cm); -- 位置(GeoJSON;使用经纬度替代 PostGIS 类型) CREATE TABLE IF NOT EXISTS public.ss_location ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE, latitude DOUBLE PRECISION NOT NULL, -- 纬度 longitude DOUBLE PRECISION NOT NULL, -- 经度 altitude FLOAT, speed FLOAT, heading FLOAT, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX ON public.ss_location(latitude, longitude); -- 心电图 (ECG) 波形数据 -- 分两表:测量元信息 + 样本点 CREATE TABLE IF NOT EXISTS public.ss_ecg_measurements ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE, duration_sec FLOAT NOT NULL, sample_rate_hz FLOAT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS public.ss_ecg_samples ( ecg_id UUID NOT NULL REFERENCES public.ss_ecg_measurements(id) ON DELETE CASCADE, sample_index INT NOT NULL, voltage_mv FLOAT NOT NULL, PRIMARY KEY(ecg_id, sample_index) ); CREATE INDEX ON public.ss_ecg_samples(sample_index); -- 呼吸率 CREATE TABLE IF NOT EXISTS public.ss_respiratory_rate ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE, respiratory_rate INT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX ON public.ss_respiratory_rate(respiratory_rate); -- 潮气量 CREATE TABLE IF NOT EXISTS public.ss_tidal_volume ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE, tidal_volume_ml FLOAT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX ON public.ss_tidal_volume(tidal_volume_ml); -- 呼吸音 CREATE TABLE IF NOT EXISTS public.ss_respiratory_sounds ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE, audio_data BYTEA NOT NULL, -- 原始音频 created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); -- 心率变异性 CREATE TABLE IF NOT EXISTS public.ss_hrv ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE, hrv_rmssd FLOAT, hrv_sdnn FLOAT, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX ON public.ss_hrv(hrv_rmssd); CREATE INDEX ON public.ss_hrv(hrv_sdnn); -- 血糖 CREATE TABLE IF NOT EXISTS public.ss_glucose ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE, glucose_mg_dl FLOAT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX ON public.ss_glucose(glucose_mg_dl); -- 皮肤电反应 (GSR) CREATE TABLE IF NOT EXISTS public.ss_gsr ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE, gsr_us FLOAT NOT NULL, -- 微西门子 created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX ON public.ss_gsr(gsr_us); -- 皮肤温度 CREATE TABLE IF NOT EXISTS public.ss_skin_temperature ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE, skin_temp_c FLOAT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX ON public.ss_skin_temperature(skin_temp_c); -- 卡路里消耗 CREATE TABLE IF NOT EXISTS public.ss_energy_expenditure ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE, calories_kcal FLOAT NOT NULL, met FLOAT, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX ON public.ss_energy_expenditure(calories_kcal); -- VO2 / VO2max CREATE TABLE IF NOT EXISTS public.ss_vo2 ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE, vo2_ml_per_min FLOAT, vo2max_ml_per_min FLOAT, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX ON public.ss_vo2(vo2_ml_per_min); CREATE INDEX ON public.ss_vo2(vo2max_ml_per_min); -- 睡眠指标 CREATE TABLE IF NOT EXISTS public.ss_sleep_metrics ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE, sleep_stage VARCHAR(32) NOT NULL, -- awake, light, deep, rem duration_sec INT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX ON public.ss_sleep_metrics(sleep_stage); -- 跌倒事件 CREATE TABLE IF NOT EXISTS public.ss_fall_events ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE, fall_detected BOOLEAN NOT NULL, event_time TIMESTAMPTZ NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX ON public.ss_fall_events(fall_detected); -- 姿态数据 CREATE TABLE IF NOT EXISTS public.ss_posture ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE, posture VARCHAR(32) NOT NULL, -- standing, sitting, lying, walking created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX ON public.ss_posture(posture); -- 肌电 (EMG) CREATE TABLE IF NOT EXISTS public.ss_emg_measurements ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE, duration_sec FLOAT NOT NULL, sample_rate_hz FLOAT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS public.ss_emg_samples ( emg_id UUID NOT NULL REFERENCES public.ss_emg_measurements(id) ON DELETE CASCADE, sample_index INT NOT NULL, voltage_mv FLOAT NOT NULL, PRIMARY KEY(emg_id, sample_index) ); CREATE INDEX ON public.ss_emg_samples(sample_index); -- 脑电 (EEG) CREATE TABLE IF NOT EXISTS public.ss_eeg_measurements ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE, duration_sec FLOAT NOT NULL, sample_rate_hz FLOAT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS public.ss_eeg_samples ( eeg_id UUID NOT NULL REFERENCES public.ss_eeg_measurements(id) ON DELETE CASCADE, sample_index INT NOT NULL, voltage_uv FLOAT NOT NULL, PRIMARY KEY(eeg_id, sample_index) ); CREATE INDEX ON public.ss_eeg_samples(sample_index); -- 血乳酸 CREATE TABLE IF NOT EXISTS public.ss_lactate ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE, lactate_mmol_l FLOAT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX ON public.ss_lactate(lactate_mmol_l); -- PPG 波形 CREATE TABLE IF NOT EXISTS public.ss_ppg_measurements ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), measurement_id UUID NOT NULL REFERENCES public.ss_sensor_measurements(id) ON DELETE CASCADE, duration_sec FLOAT NOT NULL, sample_rate_hz FLOAT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS public.ss_ppg_samples ( ppg_id UUID NOT NULL REFERENCES public.ss_ppg_measurements(id) ON DELETE CASCADE, sample_index INT NOT NULL, amplitude FLOAT NOT NULL, PRIMARY KEY(ppg_id, sample_index) ); CREATE INDEX ON public.ss_ppg_samples(sample_index); -- 5. 数据保留与归档(示例) -- ------------------------------------- -- 定期归档或删除旧数据,可使用 TimescaleDB retention policy -- SELECT add_retention_policy('public.ss_sensor_measurements', INTERVAL '90 days'); -- 6. 视图和物化视图示例 -- ------------------------------------- -- 最近 1 小时心率聚合 CREATE MATERIALIZED VIEW public.ss_hr_last_hour_summary AS SELECT device_id, minute_bucket, avg(heart_rate_bpm) AS avg_bpm, min(heart_rate_bpm) AS min_bpm, max(heart_rate_bpm) AS max_bpm FROM ( SELECT date_trunc('minute', sm.measured_at) AS minute_bucket, hr.heart_rate_bpm, sm.device_id FROM public.ss_sensor_measurements sm JOIN public.ss_heart_rate hr ON hr.measurement_id = sm.id WHERE sm.measured_at > now() - INTERVAL '1 hour' ) sub GROUP BY device_id, minute_bucket; -- ===================================================== -- 使用说明: -- • 通用写入:将任意指标写入 ss_sensor_measurements,随后写入专项表 -- • 查询优化:针对常用指标查询专项表 -- • 时序特性:开启 TimescaleDB hypertable 以提高写入与查询性能 -- =====================================================