This commit is contained in:
2026-09-29 01:46:16 +03:00
parent e05ff1449b
commit fe9221a4dd
5 changed files with 458 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
# gym bro
``` text
clients (ФИО, ДР, цель)
│
│ 1:N
├──► workouts ──────► workout_types (справочник)
│ │
│ │ 1:N
│ ├──► workout_exercises ──► exercises ──► workout_types
│ │ │
│ │ │ 1:N
│ │ └──► sets ──► dropset_items
│ │
│ └──► cardio
│
└──► body_metrics
│
│ 1:N
└──► body_metric_values ──► metric_types (справочник)
```
+1
View File
@@ -0,0 +1 @@
ivan-igorevich@MacBook-Pro-16.local.32588
+224
View File
@@ -0,0 +1,224 @@
-- ============================================================
-- Gym Tracker — SQLite schema v2
-- Гибкие справочники + мультиклиентская схема
-- ============================================================
PRAGMA foreign_keys = ON;
PRAGMA journal_mode = WAL;
-- ============================================================
-- 1. КЛИЕНТЫ (люди, чьи тренировки ведём)
-- ============================================================
CREATE TABLE clients (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sync_id TEXT NOT NULL UNIQUE, -- UUID для синхронизации
full_name TEXT NOT NULL,
birth_date TEXT, -- ISO8601 (YYYY-MM-DD)
goal TEXT DEFAULT '', -- цель тренировок
notes TEXT DEFAULT '',
is_active INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
);
CREATE INDEX idx_clients_name ON clients(full_name);
CREATE INDEX idx_clients_active ON clients(is_active);
-- ============================================================
-- 2. СПРАВОЧНИК ТИПОВ ТРЕНИРОВОК
-- ============================================================
CREATE TABLE workout_types (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sync_id TEXT NOT NULL UNIQUE,
name TEXT NOT NULL UNIQUE,
sort_order INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
);
-- ============================================================
-- 3. СПРАВОЧНИК УПРАЖНЕНИЙ
-- ============================================================
CREATE TABLE exercises (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sync_id TEXT NOT NULL UNIQUE,
workout_type_id INTEGER, -- FK на workout_types
name TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
FOREIGN KEY (workout_type_id) REFERENCES workout_types(id) ON DELETE SET NULL
);
CREATE INDEX idx_exercises_name ON exercises(name);
CREATE INDEX idx_exercises_type ON exercises(workout_type_id);
-- ============================================================
-- 4. ТРЕНИРОВКИ
-- ============================================================
CREATE TABLE workouts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sync_id TEXT NOT NULL UNIQUE,
client_id INTEGER, -- FK на clients
workout_type_id INTEGER, -- FK на workout_types
start_time TEXT NOT NULL, -- ISO8601
end_time TEXT, -- NULL = активная
notes TEXT DEFAULT '',
synced INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
FOREIGN KEY (client_id) REFERENCES clients(id) ON DELETE SET NULL,
FOREIGN KEY (workout_type_id) REFERENCES workout_types(id) ON DELETE SET NULL
);
CREATE INDEX idx_workouts_client ON workouts(client_id);
CREATE INDEX idx_workouts_start ON workouts(start_time);
CREATE INDEX idx_workouts_synced ON workouts(synced);
-- ============================================================
-- 5. УПРАЖНЕНИЯ ВНУТРИ ТРЕНИРОВКИ
-- ============================================================
CREATE TABLE workout_exercises (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sync_id TEXT NOT NULL UNIQUE,
workout_id INTEGER NOT NULL,
exercise_id INTEGER, -- FK на exercises (может быть NULL)
exercise_name TEXT NOT NULL, -- дублируем для истории
sort_order INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
FOREIGN KEY (workout_id) REFERENCES workouts(id) ON DELETE CASCADE,
FOREIGN KEY (exercise_id) REFERENCES exercises(id) ON DELETE SET NULL
);
CREATE INDEX idx_wex_workout ON workout_exercises(workout_id);
CREATE INDEX idx_wex_exercise ON workout_exercises(exercise_id);
CREATE INDEX idx_wex_name ON workout_exercises(exercise_name);
-- ============================================================
-- 6. ПОДХОДЫ
-- ============================================================
CREATE TABLE sets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sync_id TEXT NOT NULL UNIQUE,
workout_exercise_id INTEGER NOT NULL,
is_dropset INTEGER NOT NULL DEFAULT 0,
weight REAL,
reps INTEGER,
rating INTEGER CHECK (rating BETWEEN 1 AND 5),
notes TEXT DEFAULT '',
sort_order INTEGER NOT NULL DEFAULT 0,
timestamp TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
FOREIGN KEY (workout_exercise_id) REFERENCES workout_exercises(id) ON DELETE CASCADE,
CHECK (
(is_dropset = 0 AND weight IS NOT NULL AND reps IS NOT NULL) OR
(is_dropset = 1 AND weight IS NULL AND reps IS NULL)
)
);
CREATE INDEX idx_sets_wex ON sets(workout_exercise_id);
CREATE INDEX idx_sets_timestamp ON sets(timestamp);
-- ============================================================
-- 7. ЭЛЕМЕНТЫ ДРОП-СЕТА
-- ============================================================
CREATE TABLE dropset_items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
set_id INTEGER NOT NULL,
weight REAL NOT NULL,
reps INTEGER NOT NULL,
sort_order INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY (set_id) REFERENCES sets(id) ON DELETE CASCADE,
CHECK (weight > 0 AND reps > 0)
);
CREATE INDEX idx_dropset_set ON dropset_items(set_id);
-- ============================================================
-- 8. КАРДИО
-- ============================================================
CREATE TABLE cardio (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sync_id TEXT NOT NULL UNIQUE,
workout_id INTEGER NOT NULL,
cardio_type TEXT NOT NULL CHECK (cardio_type IN (
'вело', 'ходьба', 'бег', 'эллипс', 'гребля'
)),
purpose TEXT NOT NULL CHECK (purpose IN (
'warmup', 'cooldown', 'separate'
)),
duration INTEGER,
distance REAL,
speed REAL,
incline REAL,
resistance INTEGER,
notes TEXT DEFAULT '',
sort_order INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
FOREIGN KEY (workout_id) REFERENCES workouts(id) ON DELETE CASCADE
);
CREATE INDEX idx_cardio_workout ON cardio(workout_id);
CREATE INDEX idx_cardio_purpose ON cardio(purpose);
-- ============================================================
-- 9. СПРАВОЧНИК ТИПОВ ЗАМЕРОВ ТЕЛА
-- ============================================================
CREATE TABLE metric_types (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sync_id TEXT NOT NULL UNIQUE,
name TEXT NOT NULL UNIQUE, -- "Вес", "Грудь", "Бицепс", "Предплечье"...
unit TEXT NOT NULL DEFAULT 'см', -- единица измерения
category TEXT DEFAULT 'body', -- для группировки: body, strength, etc.
sort_order INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
);
-- ============================================================
-- 10. ЗАМЕРЫ ТЕЛА (факт замера)
-- ============================================================
CREATE TABLE body_metrics (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sync_id TEXT NOT NULL UNIQUE,
client_id INTEGER, -- FK на clients
metric_date TEXT NOT NULL, -- ISO8601
notes TEXT DEFAULT '',
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
FOREIGN KEY (client_id) REFERENCES clients(id) ON DELETE SET NULL
);
CREATE INDEX idx_body_metrics_client ON body_metrics(client_id);
CREATE INDEX idx_body_metrics_date ON body_metrics(metric_date);
-- ============================================================
-- 11. ЗНАЧЕНИЯ ЗАМЕРОВ (нормализованно)
-- ============================================================
CREATE TABLE body_metric_values (
id INTEGER PRIMARY KEY AUTOINCREMENT,
body_metric_id INTEGER NOT NULL,
metric_type_id INTEGER NOT NULL,
value REAL NOT NULL,
FOREIGN KEY (body_metric_id) REFERENCES body_metrics(id) ON DELETE CASCADE,
FOREIGN KEY (metric_type_id) REFERENCES metric_types(id) ON DELETE CASCADE,
UNIQUE (body_metric_id, metric_type_id) -- один тип замера — одно значение в замере
);
CREATE INDEX idx_bmv_metric ON body_metric_values(body_metric_id);
CREATE INDEX idx_bmv_type ON body_metric_values(metric_type_id);
-- ============================================================
-- 12. ЛОГ СИНХРОНИЗАЦИИ
-- ============================================================
CREATE TABLE sync_log (
device_id TEXT PRIMARY KEY,
last_sync TEXT NOT NULL,
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
);
+89
View File
@@ -0,0 +1,89 @@
-- ============================================================
-- Тестовые данные v2
-- ============================================================
-- Клиенты
INSERT INTO clients (sync_id, full_name, birth_date, goal) VALUES
('cl-001', 'Иванов Иван Иванович', '1990-05-15', 'Набор мышечной массы'),
('cl-002', 'Петрова Анна Сергеевна', '1995-11-22', 'Снижение веса, тонус');
-- Типы тренировок
INSERT INTO workout_types (sync_id, name, sort_order) VALUES
('wt-001', 'грудь-руки', 1),
('wt-002', 'ноги', 2),
('wt-003', 'спина-плечи', 3),
('wt-004', 'круговая', 4),
('wt-005', 'силовая', 5),
('wt-006', 'многоповторная', 6);
-- Типы замеров
INSERT INTO metric_types (sync_id, name, unit, category, sort_order) VALUES
('mt-001', 'Вес', 'кг', 'body', 1),
('mt-002', 'Грудь', 'см', 'body', 2),
('mt-003', 'Талия', 'см', 'body', 3),
('mt-004', 'Бёдра', 'см', 'body', 4),
('mt-005', 'Бицепс', 'см', 'body', 5),
('mt-006', 'Предплечье', 'см', 'body', 6),
('mt-007', 'Бедро', 'см', 'body', 7),
('mt-008', 'Голень', 'см', 'body', 8),
('mt-009', 'Шея', 'см', 'body', 9);
-- Упражнения (с привязкой к типам тренировок)
INSERT INTO exercises (sync_id, workout_type_id, name) VALUES
('ex-001', 1, 'Жим лёжа'), -- грудь-руки
('ex-002', 3, 'Горизонтальная тяга'), -- спина-плечи
('ex-003', 2, 'Приседания'), -- ноги
('ex-004', 3, 'Становая тяга'); -- спина-плечи
-- Тренировка клиента Иванова
INSERT INTO workouts (sync_id, client_id, workout_type_id, start_time, end_time, notes, synced) VALUES
('w-001', 1, 3, '2026-09-24T18:00:00Z', '2026-09-24T19:30:00Z', 'Хорошая тренировка', 1);
-- Упражнения в тренировке
INSERT INTO workout_exercises (sync_id, workout_id, exercise_id, exercise_name, sort_order) VALUES
('we-001', 1, 2, 'Горизонтальная тяга', 1),
('we-002', 1, NULL, 'Тяга верхнего блока', 2);
-- Обычные подходы
INSERT INTO sets (sync_id, workout_exercise_id, is_dropset, weight, reps, rating, sort_order, timestamp) VALUES
('s-001', 1, 0, 15.0, 12, 3, 1, '2026-09-24T18:10:00Z'),
('s-002', 1, 0, 20.0, 12, 4, 2, '2026-09-24T18:15:00Z');
-- Дроп-сет
INSERT INTO sets (sync_id, workout_exercise_id, is_dropset, weight, reps, rating, sort_order, timestamp) VALUES
('s-003', 1, 1, NULL, NULL, 5, 3, '2026-09-24T18:20:00Z');
INSERT INTO dropset_items (set_id, weight, reps, sort_order) VALUES
(3, 25.0, 8, 1),
(3, 20.0, 10, 2),
(3, 15.0, 12, 3);
-- Кардио
INSERT INTO cardio (sync_id, workout_id, cardio_type, purpose, duration, speed, sort_order) VALUES
('c-001', 1, 'вело', 'warmup', 10, 20.0, 1),
('c-002', 1, 'ходьба', 'cooldown', 15, 6.0, 2);
-- Замеры тела клиента Иванова (с разными типами)
INSERT INTO body_metrics (sync_id, client_id, metric_date) VALUES
('bm-001', 1, '2026-09-24T08:00:00Z');
INSERT INTO body_metric_values (body_metric_id, metric_type_id, value) VALUES
(1, 1, 78.5), -- Вес
(1, 2, 102), -- Грудь
(1, 3, 84), -- Талия
(1, 5, 36), -- Бицепс
(1, 6, 29); -- Предплечье
-- Второй замер через неделю (добавили новые типы)
INSERT INTO body_metrics (sync_id, client_id, metric_date) VALUES
('bm-002', 1, '2026-10-01T08:00:00Z');
INSERT INTO body_metric_values (body_metric_id, metric_type_id, value) VALUES
(2, 1, 79.0), -- Вес
(2, 2, 103), -- Грудь
(2, 3, 83), -- Талия
(2, 5, 36.5), -- Бицепс
(2, 6, 29.5), -- Предплечье
(2, 7, 58), -- Бедро (новый тип!)
(2, 8, 38); -- Голень (новый тип!)
+123
View File
@@ -0,0 +1,123 @@
-- ============================================================
-- Проверочные запросы v2
-- ============================================================
-- 1. Все тренировки клиента с типом тренировки
SELECT
c.full_name,
w.start_time,
wt.name AS workout_type,
w.notes,
CASE WHEN w.end_time IS NULL THEN '🔥 Активная' ELSE '✅ Завершена' END AS status
FROM workouts w
JOIN clients c ON c.id = w.client_id
LEFT JOIN workout_types wt ON wt.id = w.workout_type_id
ORDER BY w.start_time DESC;
-- 2. Полная тренировка с дроп-сетами
SELECT
w.start_time,
wt.name AS workout_type,
we.exercise_name,
CASE
WHEN s.is_dropset = 0 THEN printf('%s кг × %s', s.weight, s.reps)
ELSE '🔥 DROPSET'
END AS set_info,
s.rating
FROM workouts w
LEFT JOIN workout_types wt ON wt.id = w.workout_type_id
JOIN workout_exercises we ON we.workout_id = w.id
JOIN sets s ON s.workout_exercise_id = we.id
WHERE w.id = 1
ORDER BY we.sort_order, s.sort_order;
-- 3. Элементы дроп-сета
SELECT
s.id AS set_id,
di.sort_order,
di.weight,
di.reps
FROM sets s
JOIN dropset_items di ON di.set_id = s.id
WHERE s.is_dropset = 1
ORDER BY s.id, di.sort_order;
-- 4. История упражнения (для подсказок)
SELECT
w.start_time,
s.weight,
s.reps,
s.is_dropset
FROM workout_exercises we
JOIN sets s ON s.workout_exercise_id = we.id
JOIN workouts w ON w.id = we.workout_id
WHERE we.exercise_name = 'Горизонтальная тяга'
AND w.end_time IS NOT NULL
ORDER BY w.start_time DESC
LIMIT 10;
-- 5. Динамика замеров клиента (для дашборда)
SELECT
bm.metric_date,
mt.name AS metric_name,
mt.unit,
bmv.value
FROM body_metrics bm
JOIN body_metric_values bmv ON bmv.body_metric_id = bm.id
JOIN metric_types mt ON mt.id = bmv.metric_type_id
WHERE bm.client_id = 1
ORDER BY bm.metric_date DESC, mt.sort_order;
-- 6. Динамика конкретного замера (например, бицепс)
SELECT
date(bm.metric_date) AS day,
bmv.value
FROM body_metrics bm
JOIN body_metric_values bmv ON bmv.body_metric_id = bm.id
JOIN metric_types mt ON mt.id = bmv.metric_type_id
WHERE bm.client_id = 1
AND mt.name = 'Бицепс'
ORDER BY bm.metric_date DESC;
-- 7. Статистика по клиентам (для тренера)
SELECT
c.full_name,
c.goal,
COUNT(DISTINCT w.id) AS total_workouts,
SUM(CASE WHEN w.end_time IS NOT NULL THEN 1 ELSE 0 END) AS completed,
MAX(w.start_time) AS last_workout
FROM clients c
LEFT JOIN workouts w ON w.client_id = c.id
GROUP BY c.id
ORDER BY last_workout DESC;
-- 8. Тренировки клиента за последнюю неделю
SELECT
w.start_time,
wt.name AS workout_type,
COUNT(DISTINCT we.id) AS exercises_count,
SUM(CASE WHEN s.is_dropset = 0 THEN 1 ELSE (SELECT COUNT(*) FROM dropset_items WHERE set_id = s.id) END) AS total_sets
FROM workouts w
LEFT JOIN workout_types wt ON wt.id = w.workout_type_id
LEFT JOIN workout_exercises we ON we.workout_id = w.id
LEFT JOIN sets s ON s.workout_exercise_id = we.id
WHERE w.client_id = 1
AND w.start_time >= date('now', '-7 days')
GROUP BY w.id
ORDER BY w.start_time DESC;
-- 9. Все упражнения с их типами тренировок
SELECT
e.name,
wt.name AS workout_type
FROM exercises e
LEFT JOIN workout_types wt ON wt.id = e.workout_type_id
ORDER BY wt.sort_order, e.name;
-- 10. Все доступные типы замеров
SELECT
name,
unit,
category
FROM metric_types
ORDER BY sort_order;