Files
akmon/doc_ble/ss_sensor_data_schema.sql
2026-01-20 08:04:15 +08:00

329 lines
13 KiB
SQL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- =====================================================
-- 运动传感设备时序数据表设计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 以提高写入与查询性能
-- =====================================================