⚙️

難しいことは置いといて、データベースを設計する

2023/11/03に公開2

この記事の目的

データベースの設計段階には、論理設計物理設計エンティティ正規化...などなど小難しい用語がたくさんあり、初心者が嫌になるポイントがたくさんあります。

最初から完璧な設計をしようと思って、上記のようなことから勉強しなきゃ!って思っちゃうと、なかなか前に進めず、データベースを嫌いになってしまいます。(自分もそうでした)

ということで、こういった部分を難しく考えずに、「とりあえずデータベースを作る!」ということを目標にします。

そして書いていく中で、「ここってこうした方が後々便利なのでは?」みたいにその都度修正しながら、結果として、「小難しいことをクリアしているデータベースが出来上がっちゃった」という状態を目指します。

サービスの内容

本記事では、一般的な「ECサイト」を例にデータベース設計を行います。

手順①:まずは表を作る

データベースなんてただの表です。
データベース設計は表を作るところから始まります。

そのためにまずはサービス内で必要そうな情報をすべて書き出していきます。

ECサイトであれば、必要そうな項目はこんな感じ。

  • 注文した時間
  • 注文した人
  • 住所
  • 注文した商品の商品名、個数、単価
  • 注文の合計金額

まずはこれらをすべて一つの表に書き出していきます。
表1

手順②:横方向の繰り返しをなくす

上記の表でデータベースを作るならもう終わりなのですが、
いろいろと問題があります。

まず、青色で囲っている部分を見てください。
表2

ここは同じ内容が横方向に連続しています。
同じユーザーが同時に複数の商品を購入すれば、この表はどんどん横に拡張していくことになります。

データベースの前提として、横方向にデータを追加していくことはしません。
データは下に伸ばしていく(行を追加していく)ように設計するべきです。

以下のように表を変更すれば、横方向への繰り返しはなくすことができます。

表3

手順③:縦方向の繰り返しをなくす

横方向の繰り返しをなくすことができましたが、まだ不完全です。

例えばユーザーAが名前や住所を変更した場合、この表の中から購入者がユーザーAであるデータをすべて修正しなければならなくなります。

ではどうすればこの問題を解決できるかと言いますと、テーブルを分けてしまえばいいですね。

表4

もとのテーブルから、「購入者」と「住所」を分離し、「ユーザーID」というカラムを追加しました。

このユーザーIDは、分離したユーザーテーブルのユーザーIDと紐づいているため、例えば、注文テーブルから注文番号1の購入者は誰か知ろうと思ったら、注文番号1のユーザーIDは「Y0001」ということは注文テーブルから分かるので、ユーザーテーブルから「Y0001」のユーザーを探すことで「Y0001」のユーザーの名前と住所を取得することができます。

そして、ユーザーの名前や住所が変更になったとしても、ユーザーテーブルから該当する1行を修正するだけで済みますし、注文テーブルには一切変更が発生しません。

さて、もう一つ分離しておきたいカラムがあります。
購入商品と単価です。

表5

これも商品名や単価に変更があった場合、すべてのデータを修正することになってしまいます。
そこで同様の手順で、商品テーブルとして分離します。

表6

注文テーブルについて、だいぶスッキリしましたが、一人が複数商品を購入する場合、以下のように同じようなデータが並んでしまいます。

表7

データベースとしては、注文番号は一つのデータ(1行)を表すべきなのに、複数行に分かれてしまうのは、よくありません。

ただし、先程までのように、分離させるところはもうありませんね。

ここで、一度注文テーブルと商品テーブルとの関係性に注目してみましょう。

注文番号「1」に対して、商品ID「I0001」「I0002」「I0003」の3つが紐づいています。

逆に商品IDを基準に見てみると、例えば商品C(商品ID:I0003)は、注文番号「1」「2」「3」と紐づいてます。

つまり注文番号と商品IDは、お互いに自分自身のデータ一つにつき、複数のデータと紐づく関係にあります。(ちなみにこれを「多対多の関係」と言います。)

多対多の関係にあるカラムが存在したときに、このようなテーブルが出来上がってしまいます。

本題に戻りますが、これを解決するために、「中間テーブル」を作ります。

中間テーブルでは、注文番号と商品IDの紐づけを行うのが目的です。

表8

このように、中間テーブルを見に行けば、どの注文にどの商品が紐づいているのかを見ることができますし、注文テーブルもスッキリさせることができました。

ここまでの流れを「正規化」と言います。「第1正規形〜第3正規形」はこのような縦横方向の繰り返しをなくすように設計していくこと、と考えれば少しとっつきやすくなるかと思います。

完成形
表9

ER図を書いてみる

ここまで書いてきたことをER図に書き起こします。
ER図

ちなみにこちらは、「Draw.io」というサービスで作成しました。
VSCodeにプラグインがあるため、VSCode上で上記のようなER図を比較的作成することができます。

https://www.drawio.com/

分離されたテーブル同士を紐付けることを表すために、以下のような書き方でテーブル同士をつなぎます。

リレーション一覧

どんな関係が1対多だったっけ?ってなる方は、以下の記事がとても分かりやすかったので参考にしてみてください。
https://qiita.com/ramuneru/items/32fbf3032b625f71b69d

ポイントを一言で言えば、「自テーブルの一つのデータに対して、相手テーブルのいくつのデータが紐づく可能性があるか?」です。
これを考えていけば、理解できるようになるはずです。

まとめ

  1. まずは必要なデータを表にまとめる。(サンプルデータも入れましょう)
  2. 繰り返しているところを見つけ、分離させる。
  3. 多対多の関係性にあるものは中間テーブルを作成する。

このように考えていけば、意外と簡単に感じられるかもしれません。

もちろん、正規化の理論的な部分が不必要なわけではありませんし、この記事のやり方では不十分なところは多々あるとは思います。

それでもデータベースをこれから学習する人が、少しでもデータベースに親しみを持てるようにと思い、本記事を作成しました。

GitHubで編集を提案

Discussion

OuvillOuvill

優しく解説しようという取り組みは評価いたします。ですが、このテーブル定義には重大な欠陥があるので指摘させていただきます。

注文・商品テーブルは商品テーブルから価格を参照する想定ですが、注文したときの値段と最新の販売価格は一致するとは限りません。1年前に100円で注文したものが最新の販売価格は200円になっていることも想定できます。

最新の価格に商品テーブルを書き換えてしまうと、現在のテーブル設計では注文履歴の注文・商品テーブルから過去の商品単価が計算できません。

販売した価格と注文した価格は区別したほうが良いでしょう。

makoto hatamakoto hata

コメントありがとうございます。
その指摘はごもっともです。

今回の記事の趣旨としては、「難しいこと」をすべて端折って、
「正規化を初心者が理解できること」を目指して(ひいては自分自身が理解を深める目的もあって)書いたものです。

そのため、今回提示したDB設計には、実際にアプリケーションを構築するとしたら、必要なものが全然足りていないと思います。(ご指摘いただいた点もまさにその一つです。)

様々なことを考えた結果、カラムが増えることで、本来の「正規化を理解する」ことを妨げてしまうのではないかと思い、今回のようなシンプルなテーブルを提示させていただいた次第です。

ただ、このコメントをきっかけに、久しぶりに記事を読み返してみると、もっと分かりやすくて良いDBの例もあったかもと思いました。

貴重なご意見ありがとうございました。