DB 設計って何?まずはゴールのイメージから
DB 設計は、「アプリで扱う情報を、テーブル・カラム・関係に分解して、矛盾なく・拡張しやすく・速く扱える形にする作業」です。
言い換えると、「現実の世界(ユーザー・商品・注文…)を、データベースの世界にきれいに写し取る作業」です。
ここが雑だと、あとから必ず苦しくなります。
カラムが足りない、同じ情報があちこちに重複する、変更のたびにバグる、などが起きます。
逆に、ここがしっかりしていると、アプリのコードも自然にシンプルになります。
ここでは、初心者でもイメージしやすい「タスク管理アプリ」を例に、
DB 設計の考え方を一歩ずつかみ砕いていきます。
例題:シンプルなタスク管理アプリを題材にする
まずは「何を管理したいか」を言葉で書き出す
いきなりテーブルを考えずに、「アプリとして何をしたいか」を日本語で書き出します。
ユーザーがいる
ユーザーはタスクをいくつも持てる
タスクにはタイトルと説明と締切日がある
タスクには完了フラグがある
このくらいの粒度で十分です。
ここから「登場人物」と「関係」を抜き出します。
登場人物(エンティティ)
ユーザー
タスク
関係
ユーザー 1 人は、複数のタスクを持てる(1 対多)
この「エンティティ」と「関係」が、テーブルとリレーションの元になります。
エンティティをテーブルに落とす
ユーザー → users テーブル
タスク → tasks テーブル
という対応を考えます。
まずは素直に、こんなテーブルをイメージします。
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE
);
CREATE TABLE tasks (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL,
title TEXT NOT NULL,
description TEXT,
due_date DATE,
is_done BOOLEAN NOT NULL DEFAULT FALSE
);
SQLここで重要なのは、tasks.user_id です。
「このタスクはどのユーザーのものか」を表す外部キーになります。
主キーと外部キーをしっかり理解する
主キー(PRIMARY KEY)は「その行を一意に特定する ID」
users.id や tasks.id のようなカラムが主キーです。
役割はシンプルで、「このテーブルの中で、この行を一意に識別するための番号」です。
Python から見ると、主キーがあることで、
特定のユーザーを 1 人だけ取る
特定のタスクを 1 件だけ更新する
といった操作がやりやすくなります。
SELECT * FROM users WHERE id = 1;
UPDATE tasks SET is_done = TRUE WHERE id = 10;
SQL主キーは、基本的に「変わらない値」にするのが鉄則です。
名前やメールアドレスを主キーにすると、変更が大変になるので避けます。
外部キー(FOREIGN KEY)は「別テーブルとのつながり」
tasks.user_id は、「このタスクは users テーブルのどの行に属するか」を表すカラムです。
SQL としては、こう定義できます。
CREATE TABLE tasks (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id),
title TEXT NOT NULL,
...
);
SQLこれで、「tasks.user_id は必ず users.id のどれかでなければならない」という制約が付きます。
つまり、「存在しないユーザーに紐づくタスク」は作れなくなります。
DB 設計では、
各テーブルに主キーを必ず持たせる
テーブル同士の関係は外部キーで表現する
という 2 点がとても重要です。
正規化の基本を「やりがちな失敗」から理解する
失敗例:1 テーブルに全部詰め込む
初心者がやりがちなのが、「とりあえず 1 テーブルに全部入れる」パターンです。
CREATE TABLE users_and_tasks (
user_id INTEGER,
user_name TEXT,
user_email TEXT,
task_id INTEGER,
task_title TEXT,
task_done BOOLEAN
);
SQL一見シンプルですが、すぐに問題が出ます。
同じユーザーにタスクが 3 つあると、ユーザー情報が 3 行に重複する
ユーザー名を変更したいとき、3 行全部を更新しないといけない
タスクがないユーザーを表現しづらい
こういう「同じ情報が何度も出てくる」「一箇所の変更が複数行にまたがる」状態を避けるのが、正規化の基本的な考え方です。
分けるべきものはテーブルに分ける
さっきの users と tasks のように、
ユーザーの情報は users テーブルに 1 行だけ
タスクの情報は tasks テーブルに 1 行ずつ
両者の関係は user_id で表現
という形に分けることで、
ユーザー名を変えたいときは users の 1 行だけ更新すればよい
タスクを追加しても、ユーザー情報は重複しない
という「スッキリした状態」になります。
正規化を難しく考えすぎなくてよくて、
初心者のうちはまず、
同じ意味の情報を、複数の場所に重複して持たない
「もの」と「ものの関係」は分けて考える
この 2 つを意識するだけで、かなりマシな設計になります。
DB 設計から Python コードへのつながりをイメージする
テーブル設計がそのまま ORM モデルになる
さっきの users / tasks を SQLAlchemy のモデルにすると、こうなります。
from sqlalchemy import Column, Integer, String, Boolean, Date, ForeignKey
from sqlalchemy.orm import declarative_base, relationship
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String, nullable=False)
email = Column(String, nullable=False, unique=True)
tasks = relationship("Task", back_populates="user")
class Task(Base):
__tablename__ = "tasks"
id = Column(Integer, primary_key=True, autoincrement=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
title = Column(String, nullable=False)
description = Column(String)
due_date = Column(Date)
is_done = Column(Boolean, nullable=False, default=False)
user = relationship("User", back_populates="tasks")
PythonDB 設計で決めたことが、そのままコードに反映されています。
users テーブル → User クラス
tasks テーブル → Task クラス
主キー → primary_key=True
外部キー → ForeignKey(“users.id”)
1 対多の関係 → relationship
つまり、「DB 設計がきれいだと、ORM モデルもきれいになる」ということです。
クエリも自然な形になる
この設計だと、Python からの操作も自然になります。
ユーザーを作る:
user = User(name="Taro", email="taro@example.com")
session.add(user)
session.commit()
Pythonタスクを作る:
task = Task(
user=user,
title="レポートを書く",
description="金曜までに提出",
)
session.add(task)
session.commit()
Pythonユーザーとそのタスクを読む:
u = session.query(User).filter_by(email="taro@example.com").first()
for t in u.tasks:
print(t.title, t.is_done)
Pythonこの「コードが自然に書けるかどうか」は、
元の DB 設計が素直かどうかにかなり依存します。
DB 設計で特に意識してほしいポイント
「今」だけでなく「少し先」も想像する
最初から完璧な設計は無理ですが、
「ちょっと先の拡張」を想像しておくと、後悔が減ります。
例えば、タスク管理アプリで、
今は「個人用」だけど、将来「チームで共有」したくなるかもしれない
今は「完了フラグ」だけど、将来「ステータス(未着手・進行中・完了)」にしたくなるかもしれない
こういう未来を少しだけ意識しておくと、
ステータスを boolean ではなく文字列や別テーブルにする
ユーザーとチームの関係を表すテーブルを用意しておく
といった設計の選択肢が見えてきます。
「1 テーブルに詰め込みすぎない」「分けすぎもしない」
何でもかんでも 1 テーブルに詰め込むと、重複と矛盾だらけになります。
逆に、細かく分けすぎると、JOIN だらけで扱いづらくなります。
初心者のうちは、
明らかに「別のもの」はテーブルを分ける(ユーザーとタスク、商品と注文など)
「同じものの属性」は 1 テーブルにまとめる(ユーザーの名前・メール・パスワードなど)
このくらいの感覚で十分です。
まとめ(DB 設計は「現実をきれいに分解して、矛盾なくつなぐ作業」)
DB 設計を初心者目線で整理すると、こうなります。
まず「アプリで扱うもの(エンティティ)」と「それらの関係」を日本語で整理する。
エンティティごとにテーブルを作り、各テーブルに主キーを持たせる。
テーブル同士の関係は外部キーで表現し、「1 対多」「多対多」を意識して設計する。
同じ情報を複数の場所に重複させないように分ける(正規化の基本)。
その設計が、そのまま ORM モデルや Python コードの形に直結する。

