中間テーブルの重要性

# 【DB設計】トレーニング管理アプリにおける中間テーブルの重要性

## はじめに

データベース設計において、多対多の関係を適切に管理することは非常に重要です。特に複数のエンティティ間の関連データを扱うアプリケーションでは、中間テーブルが必須となります。

今回は私が開発している野球トレーニング管理アプリを例に、中間テーブル`training_notes`の必要性とその役割について解説します。

## アプリケーションの概要

このアプリケーションは、野球選手とコーチがトレーニングを記録・管理するためのものです。主な機能は以下の通りです：

- コーチがトレーニングメニューを作成
- 選手が日々のトレーニング実績を記録
- コーチが選手の実績を確認・コメント

## テーブル設計

まず、アプリケーションの主要テーブル構成を簡単に紹介します。

### 主要テーブル
- `users`: ユーザー管理（選手/コーチ）
- `profiles`: ユーザーの詳細情報
- `notes`: 日々の記録（野球ノート）
- `trainings`: トレーニングメニューマスタ
- `comments`: ノートへのコメント
- `training_notes`: トレーニング実績を記録する中間テーブル ← 今回の主役

```sql
CREATE TABLE users (
  id UUID PRIMARY KEY,
  firebase_uid str UNIQUE NOT NULL,
  email VARCHAR(255) NOT NULL UNIQUE,
  role TINYINT NOT NULL DEFAULT 'user', -- 0:player, 1:coach
  -- 他のカラム省略
);

CREATE TABLE notes (
  id UUID PRIMARY KEY,
  user_id UUID NOT NULL,
  theme VARCHAR(255) NOT NULL,
  -- 他のカラム省略
  FOREIGN KEY (user_id) REFERENCES users(id)
);

CREATE TABLE trainings (
  id UUID PRIMARY KEY,
  user_id UUID NOT NULL,
  menu TEXT NOT NULL,
  -- 他のカラム省略
  FOREIGN KEY (user_id) REFERENCES users(id)
);

CREATE TABLE training_notes (
  id UUID PRIMARY KEY,
  training_id UUID NOT NULL,
  note_id UUID NOT NULL,
  count int NOT NULL,
  -- 他のカラム省略
  FOREIGN KEY (training_id) REFERENCES trainings(id),
  FOREIGN KEY (note_id) REFERENCES notes(id)
);
```

## なぜ中間テーブルが必要なのか？

当初、私はトレーニングの実績データを直接`trainings`テーブルに保存しようと考えていました。例えば：

```sql
CREATE TABLE trainings (
  id UUID PRIMARY KEY,
  user_id UUID NOT NULL,
  menu TEXT NOT NULL,
  count int NULL, -- ここに回数を記録
  -- 他のカラム
);
```

しかし、この設計には以下の問題がありました：

### 問題点1: データ入力のタイミングと責任者が異なる

- `menu`: コーチが作成時に入力
- `count`: 選手が実施後に入力

これにより、レコード作成時に`count`が空になってしまいます。

### 問題点2: 同じメニューを複数選手が実施する場合の重複

例えば「腹筋」というメニューを複数の選手が行う場合、同じメニュー情報が選手の数だけ重複して保存されてしまいます。

### 問題点3: 履歴管理ができない

同じ選手が同じメニューを日付違いで行った場合の記録ができません。

## 中間テーブルによる解決

これらの問題を解決するために、`training_notes`という中間テーブルを導入しました。

### メリット1: 責任の分離

- コーチは`trainings`テーブルにメニューのみを登録
- 選手は`notes`テーブルに日々の記録を作成
- 実績データ（回数）は`training_notes`で関連付け

### メリット2: データの正規化

- メニュー情報は`trainings`テーブルに一度だけ保存
- 実績データは`training_notes`テーブルに保存

### メリット3: 柔軟なデータ管理

- 日付別・選手別の実績を個別に記録可能
- 同じメニューの履歴を時系列で管理可能

## 実際のデータフロー

### サンプルデータ

```
# usersテーブル
id | name
---+-----
1  | A
2  | B
3  | C

# notesテーブル
id | user_id | title
---+---------+-------
1  | 1       | アアア
2  | 2       | いいい
3  | 3       | ううう
4  | 1       | えええ

# trainingsテーブル
id | menu
---+------
1  | 腹筋
2  | 背筋
3  | 素振り

# training_notesテーブル
id | note_id | training_id | count
---+---------+-------------+------
1  | 1       | 1           | 10
2  | 1       | 2           | 10
3  | 2       | 1           | 20
```

### クエリ例: 選手Aのトレーニング実績を取得

```sql
SELECT 
  u.name AS 選手名,
  n.title AS ノートタイトル,
  t.menu AS トレーニングメニュー,
  tn.count AS 回数
FROM users u
JOIN notes n ON u.id = n.user_id
JOIN training_notes tn ON n.id = tn.note_id
JOIN trainings t ON tn.training_id = t.id
WHERE u.id = 1;
```

結果:
```
選手名 | ノートタイトル | トレーニングメニュー | 回数
------+---------------+------------------+-----
A     | アアア        | 腹筋              | 10
A     | アアア        | 背筋              | 10
A     | えええ        | 腹筋              | 40
A     | えええ        | 背筋              | 50
```

## まとめ

中間テーブル`training_notes`は一見すると複雑さを増すように思えますが、以下の利点をもたらします：

1. **データ整合性の確保**: メニューと実績を適切に分離
2. **責任の明確化**: コーチはメニュー作成、選手は実績入力
3. **柔軟なデータ管理**: 同じメニューの異なる実施回数を記録可能
4. **効率的なデータ分析**: 選手別・日付別・メニュー別の集計が容易

適切な中間テーブルの設計は、アプリケーションの拡張性と保守性を大きく向上させます。多対多の関係を持つデータを扱う際は、ぜひ中間テーブルの導入を検討してみてください。

---

今回は野球トレーニング管理アプリを例に説明しましたが、この考え方は商品と注文、ユーザーとグループなど、様々な多対多関係に応用できます。皆さんのDB設計の参考になれば幸いです！