Initial commit of akmon project
This commit is contained in:
328
doc_ble/ss_sensor_data_schema.sql
Normal file
328
doc_ble/ss_sensor_data_schema.sql
Normal file
@@ -0,0 +1,328 @@
|
||||
-- =====================================================
|
||||
-- 运动传感设备时序数据表设计(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 以提高写入与查询性能
|
||||
-- =====================================================
|
||||
Reference in New Issue
Block a user