#02 データベースを設計する

エンティティを洗い出す

設計の最初のステップは「何のデータを管理するか」を明確にすることです。このステップで洗い出すのがエンティティです。エンティティの粒度や属性の決め方が、その後の設計全体の品質を左右します。

エンティティとは

エンティティ? データベースで管理する「もの」や「概念」。ユーザー・商品・注文などがエンティティの例。ER図ではテーブルとして表現される。 とは、現実世界の「もの」や「こと」をデータとして表現したものです。データベースでは通常、1つのエンティティが1つの テーブル? データベース内のデータの入れ物。Excelのシートのように行と列でデータを管理する。 に対応します。

エンティティの特徴は次のとおりです。

  • 識別可能 — 個々のインスタンスを区別できる(注文Aと注文Bは別もの)
  • 属性を持つ — エンティティの特性を表すデータ項目がある
  • 他のエンティティと関係がある — ユーザーは注文を持つ、など

よく混乱するのが「エンティティ」と「属性」の区別です。例えば「住所」は、単純なシステムではユーザーの属性(カラム)でよいですが、複数の配送先を管理したいなら「住所」を独立したエンティティにすべきです。判断基準は「それ単独で管理する必要があるか」です。

ECサイトのエンティティを洗い出す

ECサイトのエンティティ一覧 users ユーザー(会員) 名前・メール・パスワード 登録日時 categories 商品カテゴリ カテゴリ名・スラッグ addresses 配送先住所 都道府県・市区町村 番地・デフォルト設定 products 販売商品 商品名・説明・価格 在庫数・カテゴリ 作成日時 carts カート(一時商品リスト) ユーザー・商品・数量 reviews 商品レビュー 評価(1-5)・コメント orders 注文 ユーザー・住所スナップ 合計金額・ステータス 注文日時 order_items 注文明細(中間テーブル) 注文・商品・数量 注文時単価(スナップ) 8エンティティ中の要 ユーザー系 商品系 注文系
図1: ECサイトのエンティティ一覧

ECサイトの要件を考えてみましょう。

ユーザーが商品を検索して購入できる。商品はカテゴリで分類される。購入履歴を確認できる。商品にレビューを書ける。

この要件から、次のエンティティが見えてきます。

エンティティ説明
ユーザー (users)サービスの会員
商品 (products)販売する商品
カテゴリ (categories)商品の分類
注文 (orders)ユーザーが行った購入
注文明細 (order_items)注文に含まれる各商品と数量
カート (carts)購入前の一時的な商品リスト
レビュー (reviews)商品に対するユーザーの評価

「注文明細」が独立したエンティティになっている点に注目してください。1つの注文に複数の商品が含まれるため、注文と商品の関係を表す中間的なエンティティが必要です。

属性(カラム)を決める

エンティティが決まったら、それぞれの属性を決めます。

エンティティと属性の関係 users 🔑 id BIGINT PK name VARCHAR(100) email VARCHAR UNIQUE password VARCHAR(255) created_at TIMESTAMP updated_at TIMESTAMP products 🔑 id BIGINT PK 🔗 category_id BIGINT FK name VARCHAR(255) description TEXT NULL price INT UNSIGNED stock INT DEFAULT 0 created_at TIMESTAMP orders 🔑 id BIGINT PK 🔗 user_id BIGINT FK shipping_addr TEXT (JSON) total_price INT UNSIGNED status ENUM ordered_at TIMESTAMP 凡例 🔑 主キー(Primary Key)— レコードを一意に識別する 🔗 外部キー(Foreign Key)— 他テーブルの主キーを参照する
図2: エンティティと属性の関係

users テーブル

users
- id          BIGINT UNSIGNED AUTO_INCREMENT  -- 主キー
- name        VARCHAR(100) NOT NULL           -- 氏名
- email       VARCHAR(255) NOT NULL UNIQUE    -- メールアドレス
- password    VARCHAR(255) NOT NULL           -- ハッシュ済みパスワード
- created_at  TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
- updated_at  TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP

products テーブル

products
- id           BIGINT UNSIGNED AUTO_INCREMENT
- category_id  BIGINT UNSIGNED NOT NULL  -- カテゴリへの外部キー
- name         VARCHAR(255) NOT NULL
- description  TEXT
- price        INT UNSIGNED NOT NULL     -- 円単位で整数管理
- stock        INT UNSIGNED NOT NULL DEFAULT 0
- created_at   TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
- updated_at   TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP

属性を決めるときのポイントをいくつか挙げます。

  • 金額は整数で — 浮動小数点(FLOAT, DOUBLE)は丸め誤差が出るため、円単位なら INT、銭単位なら DECIMAL を使います
  • NULL を安易に許可しない — NULL は「値が不明」を意味し、扱いが複雑になります。できるだけ NOT NULL にしましょう
  • created_at / updated_at は必ず入れる — いつ作成・更新されたかのトレースは後々必ず役に立ちます

主キーの選び方

主キー? テーブルの各行を一意に識別するためのカラム。通常は自動で採番される`id`が使われる。 は各レコードを一意に識別するためのカラムです。選択肢は大きく2つあります。

代理キー(Surrogate Key)

システムが自動的に割り当てる意味のない連番や UUID です。

id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY
-- または
id CHAR(36) DEFAULT (UUID()) PRIMARY KEY

メリット:

  • 設計がシンプル
  • 値が変わらない(メールアドレスが変わっても主キーに影響しない)
  • 外部キーとして参照しやすい

自然キー(Natural Key)

現実世界のデータをそのまま主キーにする方法です(メールアドレス、ISBN コードなど)。

email VARCHAR(255) PRIMARY KEY

デメリット:

  • 値が変わる可能性がある
  • 文字列型は連番に比べてインデックスが大きくなる
  • 外部キーとして参照すると結合クエリが重くなる

結論として、ほとんどのケースでは代理キー(id BIGINT AUTO_INCREMENT)を推奨します。

エンティティ候補の見極め方

洗い出したエンティティ候補が本当にエンティティとすべきか、属性にすべきかを判断する基準を整理します。

独立したエンティティにすべきケース:

  • 複数の値を持つ可能性がある(ユーザーが複数の住所を持てる)
  • それ自体に複数の属性がある(住所なら都道府県・市区町村・番地が必要)
  • 他のエンティティとも関係を持つ(配送先住所は注文からも参照される)

属性(カラム)のままでよいケース:

  • 常に1つの値しか持たない
  • 属性が1つか2つしかない
  • 他から参照されることがない

例えば「性別」は属性(カラム)で十分ですが、「タグ」は複数付与できるので独立したエンティティにする必要があります。

まとめ

エンティティの洗い出しは設計の根幹です。後から「このエンティティが足りなかった」となると、大きな改修が必要になります。要件を丁寧に読み込み、「管理が必要なもの・ことはすべて拾えているか」を確認しましょう。

次回は、洗い出したエンティティのデータを整理するための正規化を学びます。