予約テーブルを設計せよ
師範 / 目安 35分
貸出中の本を予約できるようにします。要件は次のとおりです。
誰がどの本を、いつ予約したかを残す。同じ人が同じ本を二重に予約することはできない。予約には「待機中」「確保済み」「取消」のいずれかの状態がある。
要件文のルールを制約に置き換えるのが今回の課題です。列の名前と型はひな形にあります。外部キー2つ、一意制約1つ、状態の CHECK 1つを、名前付きで書いてください。今回は状態の記録までを扱います。
完成条件
reservations テーブルを作ってください。
id integer PRIMARY KEYbook_id integer NOT NULL—books (id)を参照member_id integer NOT NULL—members (id)を参照reserved_on date NOT NULLstatus text NOT NULL—'待機中'/'確保済み'/'取消'のいずれか
制約には名前を付けます。reservations_book_fk、reservations_member_fk、reservations_member_book_uk、reservations_status_ck の4つです。
ひな形には予約3件の INSERT と、仕上げの確認用 SQL もあります。確認用 SQL は information_schema.table_constraints から public スキーマの reservations の制約を constraint_type ごとに数えます。出す列は constraint_type と cnt、並びは constraint_type の昇順です。constraint_name が '%not_null' で終わるものは除きます。
名前付き制約の書き方
イベント申込の例です。events (id) と people (id) は既にあり、どちらも id が主キーだとします。
SQL クエリ
CREATE TABLE signups (
id integer PRIMARY KEY,
event_id integer NOT NULL,
person_id integer NOT NULL,
state text NOT NULL,
CONSTRAINT signups_event_fk FOREIGN KEY (event_id) REFERENCES events (id),
CONSTRAINT signups_person_fk FOREIGN KEY (person_id) REFERENCES people (id),
CONSTRAINT signups_person_event_uk UNIQUE (person_id, event_id),
CONSTRAINT signups_state_ck CHECK (state IN ('申込', '参加'))
);PRIMARY KEY は行を識別し、重複と NULL を防ぎます。NOT NULL はその列の値を必須にします。CONSTRAINT 名前 の後ろがルールの本体です。FOREIGN KEY と REFERENCES は参照先に存在する番号だけを許します。UNIQUE は指定した列の組み合わせの重複を禁止します。CHECK は条件が満たされるかを検査し、IN は列の値がかっこの中のいずれかであることを表します。
この例なら、同じ人が同じイベントに2回申し込むことや、状態を '欠席' にすることは拒否されます。列定義と制約はカンマで区切り、閉じかっこの直前にはカンマを置きません。
information_schema.table_constraints はデータベースが用意する制約一覧です。作った申込テーブルの制約を確認する例を示します。
SQL クエリ
SELECT constraint_type, count(*) AS cnt
FROM information_schema.table_constraints
WHERE table_schema = 'public' AND table_name = 'signups'
AND constraint_name NOT LIKE '%not_null'
GROUP BY constraint_type
ORDER BY constraint_type;table_schema と table_name で対象のテーブルを選び、constraint_type ごとに数えます。NOT LIKE はパターンに一致しないものを残します。% は任意の文字列を表すので、ここでは名前が not_null で終わる制約を除いています。演習ではこの確認部分もひな形にあり、書き直す必要はありません。
進め方
ひな形の CREATE TABLE の中に、4つの名前付き制約を1行ずつ足してください。制約はカンマで区切り、最後の制約にはカンマを付けません。手が止まったらヒントを開けます。ヒントは3段階で、①方針、②使う構文、③部分解の順に出ます。1つも開けずに通せたら、その問題は自分のものです。
開かずに10分粘って進まないなら、①だけ開けてください。粘る時間そのものには価値がありません。価値があるのは、自分で書いた行が動いた瞬間です。
「二重に予約できない」は一意制約、「3つのいずれか」は CHECK です。どちらも要件文の日本語に対応が付きます。
テーブル構造
CREATE TABLE members (
id integer PRIMARY KEY,
name text NOT NULL,
joined_on date NOT NULL,
plan text NOT NULL
);
CREATE TABLE books (
id integer PRIMARY KEY,
title text NOT NULL,
author text NOT NULL,
genre text NOT NULL,
published_year integer NOT NULL,
price integer NOT NULL
);
CREATE TABLE loans (
id integer PRIMARY KEY,
book_id integer NOT NULL REFERENCES books (id),
member_id integer NOT NULL REFERENCES members (id),
borrowed_on date NOT NULL,
returned_on date
);
INSERT INTO members VALUES
(1, '田中あかり', DATE '2024-04-01', '一般'),
(2, '佐藤けんじ', DATE '2024-06-15', '一般'),
(3, '鈴木みゆき', DATE '2025-01-20', '学生'),
(4, '高橋なおと', DATE '2025-03-05', '学生'),
(5, '伊藤さくら', DATE '2025-08-11', '一般'),
(6, '渡辺ゆうた', DATE '2026-02-01', '一般');
INSERT INTO books VALUES
(1, '銀河の設計図', '星野りく', 'SF', 2019, 1800),
(2, '沈黙の実験室', '星野りく', 'SF', 2021, 2200),
(3, '夜明けのレシピ', '森田はるか', '料理', 2020, 1500),
(4, '家庭料理の教科書', '森田はるか', '料理', 2018, 2800),
(5, 'やさしい統計入門', '大村しんじ', '技術', 2022, 3200),
(6, 'データベース実践', '大村しんじ', '技術', 2023, 3600),
(7, '山と湖の記録', '川瀬あおい', '生活', 2017, 1600),
(8, '北の島へ', '川瀬あおい', '生活', 2021, 1900),
(9, '猫と暮らす', '林ももこ', '生活', 2024, 1400),
(10, '小さな庭の作り方', '林ももこ', '生活', 2022, 1700),
(11, '消えた手紙', '星野りく', 'SF', 2016, 1300),
(12, '三日月の約束', '藤田けい', 'ミステリ', 2025, 2000),
(13, '軌道の彼方', '星野りく', 'SF', 2024, 2400),
(14, 'ネットワーク入門', '大村しんじ', '技術', 2020, 2900),
(15, '朝のパン作り', '林ももこ', '生活', 2023, 1600);
INSERT INTO loans VALUES
(1, 1, 1, DATE '2026-01-05', DATE '2026-01-19'),
(2, 1, 2, DATE '2026-01-12', DATE '2026-01-26'),
(3, 1, 3, DATE '2026-02-03', DATE '2026-02-17'),
(4, 1, 4, DATE '2026-03-09', DATE '2026-03-23'),
(5, 1, 5, DATE '2026-04-06', DATE '2026-04-20'),
(6, 1, 6, DATE '2026-05-11', NULL),
(7, 9, 1, DATE '2026-01-08', DATE '2026-01-22'),
(8, 9, 2, DATE '2026-02-10', DATE '2026-02-24'),
(9, 9, 3, DATE '2026-03-02', DATE '2026-03-16'),
(10, 9, 4, DATE '2026-04-13', DATE '2026-04-27'),
(11, 9, 5, DATE '2026-05-18', NULL),
(12, 5, 1, DATE '2026-02-16', DATE '2026-03-02'),
(13, 5, 3, DATE '2026-03-23', DATE '2026-04-06'),
(14, 5, 4, DATE '2026-04-20', DATE '2026-05-04'),
(15, 5, 6, DATE '2026-06-01', NULL),
(16, 3, 2, DATE '2026-01-19', DATE '2026-02-02'),
(17, 3, 5, DATE '2026-03-16', DATE '2026-03-30'),
(18, 3, 6, DATE '2026-05-25', NULL),
(19, 12, 1, DATE '2026-04-27', DATE '2026-05-11'),
(20, 12, 3, DATE '2026-06-08', NULL),
(21, 2, 2, DATE '2026-02-23', DATE '2026-03-09'),
(22, 7, 4, DATE '2026-05-04', DATE '2026-05-18'),
(23, 13, 5, DATE '2026-06-15', DATE '2026-06-29');
期待される出力
| constraint_type | cnt |
|---|---|
| CHECK | 1 |
| FOREIGN KEY | 2 |
| PRIMARY KEY | 1 |
| UNIQUE | 1 |
ヒント
前のヒントを開くと次が開きます。開かずに解けると未開封クリアです