エンティティと属性
2 つ目の住所が、どこにも入らない
会員登録の画面を作るとき、住所は 1 つで足りると思って、こう書いたとします。
SQL クエリ
CREATE TABLE users_bad (
id INT PRIMARY KEY,
name TEXT NOT NULL,
postal_code TEXT,
city TEXT
);半年後、「自宅と職場を使い分けたい」という要望が来ます。列を増やして postal_code_2 を足します。その次に「実家にも送りたい」が来て、postal_code_3 を足します。
このとき困るのは列を足す手間ではありません。住所を数えられなくなることです。「3 件登録している会員は何人か」を出そうとすると、3 本の列を 1 本ずつ見て、NULL でないものを数える式を書くことになります。住所が 5 本になれば式も 5 本に伸びます。市区町村で絞り込む検索も、列の数だけ条件が増えます。
「個別に数えたい」ものは、列ではなく表にする
エンティティとは、業務の中で個別に識別したい物事のことです。物だけでなく、注文やログインのような出来事も、カテゴリのような概念も含みます。属性はその性質で、表でいえば列にあたります。
住所は、最初は会員の属性のように見えます。しかし次のどれかに当てはまったら、属性ではなくエンティティです。
- 1 件の会員に対して、複数あり得る
- 1 件ずつ追加したり消したりしたい
- 「何件あるか」を数えたい、または 1 件を名指しで指定したい
同じ意味の列が postal_code_2、postal_code_3 と番号付きで並び始めたら、それは属性がエンティティになりたがっているサインです。
逆に、会員の name は 1 人に 1 つしかなく、個別に数えることもないので属性のままで足ります。属性かエンティティかは、値の見た目ではなく「1 件に対して複数あり得るか」で決まります。
分けてしまえば、何件でも足せる
住所を別の表に切り出し、どの会員のものかを 1 本の列で指し示します。
SQL クエリ
CREATE TABLE users (
id INT PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE addresses (
id INT PRIMARY KEY,
user_id INT NOT NULL,
label TEXT NOT NULL,
postal_code TEXT NOT NULL,
city TEXT NOT NULL
);住所は何件でも INSERT で足せます。1 件だけ消すのも DELETE 1 回です。「自宅」「職場」という区別も label という普通の列で表せます。列を増やす設計では、この label に相当する情報が列名の中に埋まってしまい、SQL から触れなくなっていました。
計算で出せる値は、原則として列で持ちません。税込価格は税抜価格と税率から出せるので、列にすると税率が変わった日に全行を書き換えることになります。持つなら「いつ誰が更新するか」を先に決めてください。
テーブル構造
CREATE TABLE users (
id INT PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE addresses (
id INT PRIMARY KEY,
user_id INT NOT NULL,
label TEXT NOT NULL,
postal_code TEXT NOT NULL,
city TEXT NOT NULL
);
INSERT INTO users (id, name) VALUES
(1, '田中'),
(2, '鈴木'),
(3, '佐藤');
INSERT INTO addresses (id, user_id, label, postal_code, city) VALUES
(1, 1, '自宅', '100-0001', '東京都千代田区'),
(2, 1, '職場', '150-0002', '東京都渋谷区'),
(3, 1, '実家', '530-0001', '大阪市北区'),
(4, 2, '自宅', '231-0001', '横浜市中区'),
(5, 2, '職場', '100-0001', '東京都千代田区');期待される出力
| name | address_count |
|---|---|
| 田中 | 3 |
| 鈴木 | 2 |
| 佐藤 | 0 |