【DB設計】トレーニング管理アプリにおける中間テーブルの重要性
はじめに
データベース設計において、多対多の関係を適切に管理することは非常に重要です。特に複数のエンティティ間の関連データを扱うアプリケーションでは、中間テーブルが必須となります。
今回は私が開発している野球トレーニング管理アプリを例に、中間テーブルtraining_notesの必要性とその役割について解説します。
アプリケーションの概要
このアプリケーションは、野球選手とコーチがトレーニングを記録・管理するためのものです。主な機能は以下の通りです:
- コーチがトレーニングメニューを作成
- 選手が日々のトレーニング実績を記録
- コーチが選手の実績を確認・コメント
テーブル設計
まず、アプリケーションの主要テーブル構成を簡単に紹介します。
主要テーブル
-
users: ユーザー管理(選手/コーチ) -
profiles: ユーザーの詳細情報 -
notes: 日々の記録(野球ノート) -
trainings: トレーニングメニューマスタ -
comments: ノートへのコメント -
training_notes: トレーニング実績を記録する中間テーブル ← 今回の主役
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テーブルに保存しようと考えていました。例えば:
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のトレーニング実績を取得
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は一見すると複雑さを増すように思えますが、以下の利点をもたらします:
- データ整合性の確保: メニューと実績を適切に分離
- 責任の明確化: コーチはメニュー作成、選手は実績入力
- 柔軟なデータ管理: 同じメニューの異なる実施回数を記録可能
- 効率的なデータ分析: 選手別・日付別・メニュー別の集計が容易
適切な中間テーブルの設計は、アプリケーションの拡張性と保守性を大きく向上させます。多対多の関係を持つデータを扱う際は、ぜひ中間テーブルの導入を検討してみてください。
今回は野球トレーニング管理アプリを例に説明しましたが、この考え方は商品と注文、ユーザーとグループなど、様々な多対多関係に応用できます。皆さんのDB設計の参考になれば幸いです!