アルコールに強いかどうかを診断して、次の飲み会で飲むお酒を提案するアプリを作り直しています。
当時まだテーブル設計に関する知識がなかった頃、アルコールとアルコールセットの設計を自己結合関連付けにした時の話です。
当初のテーブル設計
下記テーブル設計が当初僕が書いていたテーブルになります。
この、AccumulatedOrdersテーブルとAlcoholOrdersテーブルのカラムに問題がありました。
それぞれのやりたかった挙動をまとめます。
使用するモデル
|
|
|
|
エンティティ内のカラム
|
|
カラム説明
- is_accumulated_orders: お酒の順番を格納する
- is_order: お酒の順番を格納する
- alcohol_types: enumでお酒の種類をポリモーフィック関連させる。同じメソッドを持つ酒インスタンスを複数作ることができる。
やりたかった挙動
AccumulatedOrders
お酒の順番をまとめて管理
AlcoholOrders
お酒の順番と種類を個別に管理
accumulated_order_id: 外部キー、bmn
is_order: 何番目のお酒になるのかを格納
alcohol_types: お酒の種類を格納
|
|
- 診断結果に基づいて飲み物を飲む順番をユーザーに提供する
- AccumulatedOrdersテーブルのis_accumulated_ordersで順番を保持する。
- AlcoholOrdersテーブルのaccumulated_order_idはAccumulatedOrdersの外部キーで、AccumulatedOrdersテーブルのPK(id)と対応する。
問題点① お酒の順番を格納するカラムが重複している。
AccumulatedOrdersとAlcoholOrdersそれぞれにお酒の順番を格納するカラムがあります。
同じ挙動をするカラムが二つ以上あることはテーブル設計上冗長なので、どちらかにまとめる必要がある。
問題点② カラム名が適切でない
is_order,is_accumulated_ordersというカラム名が使われています。こちらの記事を参考にしてテーブルの切り分けを行ったためis_〇〇というカラム名にしました。
どうやらisを接頭につけると、格納される値はtrue, falseになるのがベストのようです。なぜなら、isはクローズドクエスチョンだからです。なので、カラム名をそれぞれis_order⇨orders,is_accumulated_orders⇨削除に変更しました。
クイズアプリにおけるデータベース設計のアンチパターン - Qiita
問題点①を理解する
これは生徒の例を使うと、なぜアンチパターンなのか理解することができました。
下記のような生徒クラスがあるとします。
生徒はそれぞれidと出席番号を持っていて、クラスIDの組に所属すると仮定します。
ここに4人の生徒がいます。
|
|
1組に所属する生徒を取得したい場合、SQLは以下のように書きます。
|
|
さらに、順番を昇順に取得したい場合はORDER句を使用して以下のように書きます
|
|
つまり、Studentクラスのみで、生徒の順番や所属する場所を絞り込み検索することができるので、わざわざ順番だけを保持するクラスや、所属する場所だけを保持するクラスを作る必要が内容です。
当初のテーブル設計は正規化が足りていなかったみたいです。
最終形態(第3正規化)
順番だけを持つAccumulatedOrdersテーブルが今回のテーブル設計に不必要なものになったので、削除し、設計を見直しました。
と思いきや、、
しかし、まだ正規化が足りていません。順番とお酒の種類を持つテーブルになってしまっています。一つのテーブルで守らせたい挙動は一つに決まっているので、順番とアルコールの種類を切り分けて二つのテーブルに切り出しました。
これで現段階の正規化が完了しました。
これから本格的にポートフォリオを作成していきます。結果的にテーブル数が4つになってしまいましたが、拡張性の高いアプリではあるので、機能追加と主にテーブルを増やしていければと思います。なんとなくデータベースのdの字くらいはわかってきた気がするな?