From fe9221a4dd9b2e4ccc8471b4fcf20e86f8cc10ae Mon Sep 17 00:00:00 2001 From: "Wayne H. Shephard" Date: Tue, 29 Sep 2026 01:46:16 +0300 Subject: [PATCH] sql --- #readme.md# | 21 +++++ .#readme.md | 1 + schema.sql | 224 ++++++++++++++++++++++++++++++++++++++++++++++++++++ seed.sql | 89 +++++++++++++++++++++ verify.sql | 123 +++++++++++++++++++++++++++++ 5 files changed, 458 insertions(+) create mode 100644 #readme.md# create mode 120000 .#readme.md create mode 100644 schema.sql create mode 100644 seed.sql create mode 100644 verify.sql diff --git a/#readme.md# b/#readme.md# new file mode 100644 index 0000000..3a96e26 --- /dev/null +++ b/#readme.md# @@ -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 (справочник) +``` diff --git a/.#readme.md b/.#readme.md new file mode 120000 index 0000000..492fb61 --- /dev/null +++ b/.#readme.md @@ -0,0 +1 @@ +ivan-igorevich@MacBook-Pro-16.local.32588 \ No newline at end of file diff --git a/schema.sql b/schema.sql new file mode 100644 index 0000000..75fab17 --- /dev/null +++ b/schema.sql @@ -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')) +); diff --git a/seed.sql b/seed.sql new file mode 100644 index 0000000..2f68f46 --- /dev/null +++ b/seed.sql @@ -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); -- Голень (новый тип!) + diff --git a/verify.sql b/verify.sql new file mode 100644 index 0000000..5acb40b --- /dev/null +++ b/verify.sql @@ -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;