ER図の読み方・書き方
「ユーザは注文を持つ」では、まだ何も決まっていない
設計レビューで「ユーザは注文を持ちます」と説明すると、その場は全員がうなずきます。ところが実装に入ると、質問が次々に出てきます。
- 注文が 1 件もないユーザを登録してよいのか
- 明細が 1 行もない、空の注文を許すのか
- 持ち主のいない注文があり得るのか
どれも NOT NULL を付けるかどうかに直結する話です。文章で書くと、この 3 つは必ず書き漏れます。線と記号で描けば、書き漏らしようがありません。それが ER 図です。
線の端の 2 文字が、最小と最大を表している
ER 図の線は、両端にそれぞれ 2 文字の記号が付きます。内側の 1 文字が最小いくつか、外側の 1 文字が最大いくつかを表します。
||— ちょうど 1 件 (最小 1、最大 1)|o— 0 件または 1 件 (最小 0、最大 1)}o— 0 件以上 (最小 0、最大は複数)}|— 1 件以上 (最小 1、最大は複数)
o は丸で「無くてもよい」、| は棒で「必ず要る」、} は鳥の足のように三方向へ広がる形で「複数あり得る」を表します。
四角の中は列です。PK は主キー、FK は別の四角を指す列であることを示す注記で、この段階では主キーと関連の列だけ書いておけば足ります。
記号は相手側から見た数を表します。USERS ||--o{ ORDERS の右端 o{ は「ユーザ 1 人につき注文は 0 件以上」、左端 || は「注文 1 件につきユーザはちょうど 1 人」と読みます。2 行目の ||--|{ は右端が |{ なので、「注文 1 件につき明細は 1 行以上」、つまり空の注文は作れないという業務ルールです。
列から書き始めると、線を引き直すはめになる
ゼロから描くときは、次の順序が手戻りを最小にします。
- 業務に出てくる名詞をすべて四角に書く
- 各四角の主キーを決める
- 四角と四角を線で結び、両端の記号を決める
- 残りの列を四角の中に埋める
先に 4 を進めると、後から線を引き直すたびに列の置き場所も動きます。線が決まるまで、列は主キーだけで止めておきます。
記号のうち、テーブル定義で守れるものと守れないものがあります。「注文には必ずユーザが 1 人」は
orders.user_idをNOT NULLにすれば守れます。一方「注文には明細が 1 行以上」は、親の行を作った時点では子が 0 行なので、列の制約では表現できません。この種のルールはアプリ側で守ることになる、と図の段階で気づけます。
テーブル構造
CREATE TABLE users (
id INT PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL
);
CREATE TABLE orders (
id INT PRIMARY KEY,
user_id INT NOT NULL,
ordered_at TIMESTAMP NOT NULL
);
CREATE TABLE order_items (
id INT PRIMARY KEY,
order_id INT NOT NULL,
product_id INT NOT NULL,
quantity INT NOT NULL
);
CREATE TABLE products (
id INT PRIMARY KEY,
name TEXT NOT NULL,
price INT NOT NULL
);
INSERT INTO users (id, name, email) VALUES
(1, '田中', 'tanaka@example.com'),
(2, '鈴木', 'suzuki@example.com'),
(3, '佐藤', 'sato@example.com');
INSERT INTO products (id, name, price) VALUES
(1, 'りんご', 200),
(2, 'バナナ', 150),
(3, 'みかん', 100);
INSERT INTO orders (id, user_id, ordered_at) VALUES
(1, 1, '2026-01-10 10:00:00'),
(2, 1, '2026-01-15 14:00:00'),
(3, 2, '2026-01-20 09:00:00');
INSERT INTO order_items (id, order_id, product_id, quantity) VALUES
(1, 1, 1, 2),
(2, 1, 2, 3),
(3, 2, 3, 5),
(4, 3, 1, 1),
(5, 3, 2, 2);期待される出力
| name | total_amount |
|---|---|
| 田中 | 1350 |
| 鈴木 | 500 |