# Shinkan Jinkendo - Datenbank-Schema (Technisch) **Version:** 0.4.0 **Stand:** 2026-04-27 **Aktuell deployed:** Migration 023 (Skills Complete Import) --- ## Übersicht Dieses Dokument beschreibt die **technische Datenbankstruktur** von Shinkan Jinkendo. **Zuständig für fachliche Modellierung:** siehe `DOMAIN_MODEL.md` --- ## Migrations-Historie | Nr. | Datum | Beschreibung | Status | |-----|-------|--------------|--------| | 001 | 2026-04-20 | Initial Schema (Auth, Profiles) | ✅ Deployed | | 002 | 2026-04-20 | Clubs, Divisions, Groups | ✅ Deployed | | 003 | 2026-04-20 | Skills, Methods | ✅ Deployed | | 004 | 2026-04-21 | Training Types | ✅ Deployed | | 005 | 2026-04-21 | Exercises (Basis) | ✅ Deployed | | 006 | 2026-04-21 | Training Planning | ✅ Deployed | | 007 | 2026-04-22 | Exercise Catalogs (idempotent) | ✅ Deployed | | 008 | 2026-04-23 | M:N Exercise Relations + Hierarchical Catalogs | ✅ Deployed | | 009 | 2026-04-23 | Target Groups M:N Refactoring (BREAKING) | ✅ Deployed | | 010 | 2026-04-23 | Rename training_styles to style_directions | ✅ Deployed | | 011 | 2026-04-23 | Create Training Types | ✅ Deployed | | 012 | 2026-04-23 | Exercise Training Characters + Trainer Contexts | ✅ Deployed | | 013 | 2026-04-23 | Training Types Focus Area | ✅ Deployed | | 014 | 2026-04-24 | Variant Progression Search Legacy | ✅ Deployed | | 016 | 2026-04-24 | Saved Searches | ✅ Deployed | | 017 | 2026-04-24 | Exercise Blocks | ✅ Deployed | | 018 | 2026-04-24 | Wiki Import Tracking | ✅ Deployed | | 019 | 2026-04-24 | Exercises Optional Fields (goal/execution nullable) | ✅ Deployed | | 020 | 2026-04-27 | Exercise Skills UNIQUE Constraint | ✅ Deployed | | 021 | 2026-04-27 | ~~Import Skills from Matrix~~ (DEPRECATED) | ⚠️ Faulty | | **022** | **2026-04-27** | **Skills Schema Complete (BREAKING)** | ✅ Deployed | | **023** | **2026-04-27** | **Skills Complete Import (69 Skills)** | ✅ Deployed | --- ## Migration 022: Skills Schema Complete (BREAKING CHANGE) **Problem:** Skills hatten keine Kategorisierung (Haupt-/Unterkategorie, Fokusbereich). **Lösung:** Vollständiges hierarchisches Schema für produktionsreifen Import. **Neue Tabellen:** ```sql -- Haupt-Kategorien (KARATE / ALLGEMEINE) skill_main_categories ( id SERIAL PRIMARY KEY, name VARCHAR(200) UNIQUE NOT NULL, -- "KARATE Fähigkeiten" / "ALLGEMEINE sportliche Fähigkeiten" slug VARCHAR(50) UNIQUE NOT NULL, -- "karate" / "allgemeine" description TEXT, sort_order INT, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ) -- Level-Definitionen (1-5 Beschreibungen aus Fähigkeitsmatrix) skill_level_definitions ( id SERIAL PRIMARY KEY, skill_id INT NOT NULL REFERENCES skills(id) ON DELETE CASCADE, level INT NOT NULL CHECK (level BETWEEN 1 AND 5), description TEXT NOT NULL, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW(), UNIQUE (skill_id, level) ) ``` **Erweiterte Tabellen:** ```sql -- skill_categories erweitert ALTER TABLE skill_categories ADD COLUMN slug VARCHAR(50); ALTER TABLE skill_categories ADD COLUMN main_category_id INT REFERENCES skill_main_categories(id); ALTER TABLE skill_categories ADD CONSTRAINT skill_categories_slug_unique UNIQUE (slug); -- skills erweitert ALTER TABLE skills ADD COLUMN main_category_id INT REFERENCES skill_main_categories(id); ALTER TABLE skills ADD COLUMN focus_areas JSONB DEFAULT '[]'::jsonb; ``` **Indizes:** - `idx_skills_main_category_id` - `idx_skills_category_id` - `idx_skill_categories_main_category_id` - `idx_skill_level_definitions_skill_id` **Struktur:** ``` skill_main_categories (Hauptkategorie) └─ skill_categories (Unterkategorie) └─ skills (Fähigkeit) └─ skill_level_definitions (Level 1-5 Beschreibungen) ``` --- ## Migration 023: Skills Complete Import **Quelle:** Fähigkeitsmatrix (https://karatetrainer.net/index.php?title=Fähigkeitsmatrix) **Importiert:** - **69 Skills** mit vollständiger Kategorisierung - **2 Haupt-Kategorien:** KARATE Fähigkeiten, ALLGEMEINE sportliche Fähigkeiten - **9 Unterkategorien:** Kihon, Kumite, Kata, Selbstverteidigung, Koordination, Kondition, Kognition, Soziale Fähigkeiten, Psychische Fähigkeiten **Skills-Verteilung:** ``` KARATE Fähigkeiten (32 Skills, focus_areas: ["karate"]): ├─ Kata (8): Technik Kombination, Kata Ablauf, Bunkai, Oyo, Henka, Kakushi, Kata Atmung, Kata Rhythmus ├─ Kihon (10): Dachi Waza, Uke Waza, Zuki Waza, Uchi Waza, Geri Waza, Nage Waza, Nukite Waza, Ken Waza, Hüfteinsatz, Kime ├─ Kumite (10): Beinarbeit, Distanzkontrolle, Angriff, Abwehr Konter, Präzision, Antizipation, Timing, Taktik, Fokus, Mentale Stärke └─ Selbstverteidigung (4): Gefahrenbewustsein, Selbstbehauptung, Selbstschutz, Gefahrenabwehr ALLGEMEINE sportliche Fähigkeiten (37 Skills, focus_areas: ["universal"]): ├─ Kognition (5): Aufmerksamkeit, Wahrnehmung, Urteilsvermögen, Merkfähigkeit, Lernfähigkeit ├─ Kondition (15): Maximalkraft, Schnellkraft, Reaktivkraft, Kraftausdauer, Muskelaufbau, │ Reaktionsschnelligkeit, Bewegungsschnelligkeit, Handlungsschnelligkeit, │ Schnelligkeitsausdauer, Grundlagenausdauer, Aerobe Ausdauer, Anaerobe Ausdauer, │ Regenerationsfähigkeit, Ermüdungswiderstandsfähigkeit, Flexibilität ├─ Koordination (7): Orientierung, Differenzierung, Kopplung, Gleichgewicht, Rhythmisierung, Reaktion, Umstellung ├─ Psychische Fähigkeiten (6): Selbstvertrauen, Konzentration, Emotionale Kontrolle, Motivation, Stressresistenz, Stressregulation └─ Soziale Fähigkeiten (4): Deeskalation, Selbstdisziplin, Toleranz, Fairness ``` **Duplikat-Handling:** Einige Skills kommen in der Matrix doppelt vor (Kumite + Kondition/Koordination): - **Behalten in Kondition:** Anaerobe Ausdauer, Bewegungsschnelligkeit, Flexibilität, Reaktionsschnelligkeit, Schnelligkeitsausdauer (konditionelle Primärfähigkeiten) - **Behalten in Kumite:** Antizipation, Timing (kampfspezifisch) **Cleanup:** - DELETE FROM exercise_skills (M:N Beziehungen) - DELETE FROM skills (alte Daten) - DELETE FROM skill_categories - DELETE FROM skill_main_categories **Verifikation:** ```sql -- Sollte 69 ergeben SELECT COUNT(*) FROM skills; -- Zeigt Verteilung SELECT mc.name AS hauptkategorie, sc.name AS unterkategorie, COUNT(s.id) AS anzahl_skills FROM skills s JOIN skill_categories sc ON s.category_id = sc.id JOIN skill_main_categories mc ON s.main_category_id = mc.id GROUP BY mc.name, sc.name, mc.sort_order, sc.sort_order ORDER BY mc.sort_order, sc.sort_order; ``` --- ## Kern-Entitäten ### Auth & Profile ```sql profiles (id, name, email, password_hash, role, created_at) sessions (id, profile_id, token, created_at, expires_at) ``` ### Organisation ```sql clubs (id, name, abbreviation, description, logo_url, ...) divisions (id, club_id, name, description, ...) training_groups (id, division_id, name, description, focus_area, level, ...) ``` ### Kataloge #### Fokusbereiche & Stile (M:N mit Zielgruppen) ```sql focus_areas (id, name, abbreviation, description, color, icon, ...) style_directions (id, focus_area_id, name, abbreviation, description, parent_style_id, ...) target_groups (id, name, description, min_age, max_age, ...) -- Global, NICHT gebunden an Stil training_style_target_groups (style_direction_id, target_group_id, is_primary) -- M:N Junction ``` #### Fähigkeiten (Hierarchisches Schema ab Migration 022) ```sql -- Haupt-Kategorien (2: KARATE, ALLGEMEINE) skill_main_categories ( id, name, slug, description, sort_order, created_at, updated_at ) -- Unterkategorien (9: Kihon, Kumite, Kata, Selbstverteidigung, Koordination, Kondition, ...) skill_categories ( id, name, slug, description, sort_order, main_category_id REFERENCES skill_main_categories(id), parent_category_id, -- Optional: Hierarchie created_at, updated_at ) -- Fähigkeiten (69 Skills) skills ( id, name, description, category_id REFERENCES skill_categories(id), main_category_id REFERENCES skill_main_categories(id), focus_areas JSONB, -- ["karate"] oder ["universal"] created_at, updated_at ) -- Level-Definitionen (optional, noch nicht gefüllt) skill_level_definitions ( id, skill_id, level, description, created_at, updated_at, UNIQUE (skill_id, level) ) ``` **Focus Areas Bedeutung:** - `["karate"]` - Skill ist spezifisch für Karate (z.B. Kata Ablauf, Kihon) - `["universal"]` - Skill ist universell einsetzbar (z.B. Maximalkraft, Konzentration, Deeskalation) - Später möglich: `["karate", "selbstverteidigung"]` - Mehrfachzuordnung #### Trainingscharakter ```sql training_characters (id, name, description, ...) trainer_contexts (id, name, description, ...) -- Neue Dimension (Migration 012) ``` ### Übungen (M:N Beziehungen ab Migration 008) ```sql exercises ( id, title, summary, goal, execution, preparation, trainer_notes, duration_min, duration_max, group_size_min, group_size_max, equipment JSONB, visibility, status, created_by, club_id, import_source, import_id, wiki_page_id, -- MediaWiki Import (Migration 018) created_at, updated_at ) -- M:N Beziehungen exercise_focus_areas (exercise_id, focus_area_id, is_primary) exercise_style_directions (exercise_id, style_direction_id, is_primary) exercise_target_groups (exercise_id, target_group_id, is_primary) exercise_age_groups (exercise_id, age_group) -- Enum: Minis, Kinder, Schüler, Teenager, Erwachsene -- Fähigkeiten-Zuordnung (mit Level) exercise_skills ( exercise_id, skill_id, is_primary, intensity, required_level INT, -- Voraussetzung (1-5) target_level INT, -- Ziel (1-5) UNIQUE (exercise_id, skill_id) -- Migration 020 ) -- Varianten & Medien exercise_variants (id, exercise_id, name, description, ...) exercise_media (id, exercise_id, type, url, title, description, ...) ``` ### Training Planning ```sql training_units (id, group_id, date, title, description, ...) training_unit_exercises ( training_unit_id, exercise_id, sort_order, exercise_variant_id -- FK exercise_variants(id) ON DELETE SET NULL (Migration 030) ) exercise_blocks (id, name, description, created_by, club_id, ...) -- Migration 017 ``` ### MediaWiki Import (Migration 018) ```sql wiki_import_log ( id, category, import_type, import_status, items_total, items_imported, items_skipped, items_failed, error_log JSONB, started_at, finished_at, imported_by ) wiki_import_references ( id, wiki_page_id, entity_type, local_id, created_at, updated_at ) ``` **Import-Typen:** - `exercise` - Übungen aus `Kategorie:Übungen` - `skill` - Fähigkeiten aus `Kategorie:Fähigkeitsbeschreibung` (veraltet, nutze Migration 023) - `method` - Trainingsmethoden aus `Kategorie:Methodenbeschreibung` --- ## Indizes ### Performance-Indizes ```sql -- Hierarchie-Lookups CREATE INDEX idx_style_directions_focus_area ON style_directions(focus_area_id); CREATE INDEX idx_skill_categories_main_category ON skill_categories(main_category_id); CREATE INDEX idx_skills_category ON skills(category_id); CREATE INDEX idx_skills_main_category ON skills(main_category_id); -- M:N Joins CREATE INDEX idx_exercise_focus_areas_exercise ON exercise_focus_areas(exercise_id); CREATE INDEX idx_exercise_focus_areas_focus ON exercise_focus_areas(focus_area_id); CREATE INDEX idx_exercise_style_directions_exercise ON exercise_style_directions(exercise_id); CREATE INDEX idx_exercise_style_directions_style ON exercise_style_directions(style_direction_id); CREATE INDEX idx_exercise_target_groups_exercise ON exercise_target_groups(exercise_id); CREATE INDEX idx_exercise_target_groups_target ON exercise_target_groups(target_group_id); CREATE INDEX idx_exercise_skills_exercise ON exercise_skills(exercise_id); CREATE INDEX idx_exercise_skills_skill ON exercise_skills(skill_id); -- Import-Tracking CREATE INDEX idx_wiki_import_references_wiki_page ON wiki_import_references(wiki_page_id); CREATE INDEX idx_wiki_import_references_entity ON wiki_import_references(entity_type, local_id); ``` --- ## Kaskadier-Regeln ### Fokusbereich löschen **Verhalten:** `RESTRICT` (Löschen verweigern wenn verwendet) **Prüfung:** ```sql SELECT COUNT(*) FROM style_directions WHERE focus_area_id = :id SELECT COUNT(*) FROM exercise_focus_areas WHERE focus_area_id = :id ``` **Alternativen:** 1. Umrouten: Alle abhängigen Stile auf neuen Fokusbereich setzen 2. Archivieren: Status auf 'archived' setzen statt löschen ### Stil löschen **Verhalten:** `RESTRICT` wenn Zielgruppen oder Übungen zugeordnet **Umrouten-Workflow:** 1. Admin wählt Ziel-Stil 2. System aktualisiert: - `training_style_target_groups.style_direction_id` - `exercise_style_directions.style_direction_id` 3. Löschen erlaubt ### Zielgruppe löschen **Verhalten:** `SET NULL` oder `CASCADE` in `exercise_target_groups` **Prüfung:** ```sql SELECT COUNT(*) FROM training_style_target_groups WHERE target_group_id = :id SELECT COUNT(*) FROM exercise_target_groups WHERE target_group_id = :id ``` ### Skill löschen **Verhalten:** `CASCADE` zu `skill_level_definitions`, `RESTRICT` wenn in `exercise_skills` **Prüfung:** ```sql SELECT COUNT(*) FROM exercise_skills WHERE skill_id = :id ``` --- ## Nächste Schritte - [ ] Level-Definitionen aus Fähigkeitsmatrix extrahieren (optional) - [ ] Skills-Import testen mit Übungs-Import - [ ] Migration 024: Skills-Beschreibungen aus Wiki importieren - [ ] Admin-UI für Skill-Kategorien (CRUD) - [ ] Skill-Filter in Übungssuche integrieren --- **Letzte Aktualisierung:** 2026-04-27 **Verantwortlich:** Claude Code **Review:** Pending