329 lines
13 KiB
SQL
329 lines
13 KiB
SQL
-- =====================================================
|
||
-- 运动传感设备时序数据表设计(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 以提高写入与查询性能
|
||
-- =====================================================
|