テーブル作成演習(社員研修管理を例に)
概要
- 日程: Day 2 / セッション 10
- 時間: 15:10-15:40
- 形式: 実習
- ゴール: 「研修コース」「受講履歴」テーブルを、制約付きで自分で設計・CREATE TABLEし、サンプルデータを挿入できる
- 学習形式: ハンズオン実習(AIペアプログラミング)
導入(5分)
座学で学んだ CREATE TABLE と制約を、ここからは実際に手を動かして使います。題材は「社員研修管理」。会社で社員がいろいろな研修コースを受講する状況を、テーブルで表現してみましょう。
これは、いわば建築の図面引きです。座学では材料(制約・データ型)を見てきました。ここでは実際に図面(CREATE TABLE文)を書いて、家(テーブル)を建てます。
ターミナルで psql に接続して始めましょう。
docker exec -it postgres-sandbox psql -U postgres -d sandbox
実習・演習(20分)
課題1: courses テーブルを設計・作成
「研修コース」テーブルを作ります。次の要件を満たすように CREATE TABLE を書いてください。
要件
course_id:主キー、自動採番course_name:必須、最大100文字duration_hours:必須、整数、0より大きいcategory:任意、最大50文字created_at:作成日時、デフォルトで現在時刻
ヒント
- 主キーは
SERIAL PRIMARY KEY - 「0より大きい」は
CHECK (duration_hours > 0) - 「現在時刻」は
DEFAULT CURRENT_TIMESTAMP
解答例
CREATE TABLE courses (
course_id SERIAL PRIMARY KEY,
course_name VARCHAR(100) NOT NULL,
duration_hours INTEGER NOT NULL CHECK (duration_hours > 0),
category VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
作成後 \d courses でテーブル定義を確認しましょう。制約がきちんと付いていれば成功です。
課題2: enrollments テーブルを設計・作成
「受講履歴」テーブルを作ります。社員がどのコースをいつ受けたかを記録します。
要件
enrollment_id:主キー、自動採番employee_id:必須、employeesテーブルへの外部キーcourse_id:必須、coursesテーブルへの外部キーenrolled_at:必須、申込日時、デフォルトで現在時刻completed_at:完了日時、未完了ならNULLscore:得点、0〜100の範囲、未採点ならNULL
ヒント
- 外部キーは
INTEGER REFERENCES 親テーブル(主キー列)の形 - 「0〜100の範囲」は
CHECK (score BETWEEN 0 AND 100)
解答例
CREATE TABLE enrollments (
enrollment_id SERIAL PRIMARY KEY,
employee_id INTEGER NOT NULL REFERENCES employees(employee_id),
course_id INTEGER NOT NULL REFERENCES courses(course_id),
enrolled_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
completed_at TIMESTAMP,
score INTEGER CHECK (score BETWEEN 0 AND 100)
);
\d enrollments で「Foreign-key constraints」の欄を見て、employees と courses への参照が表示されていれば成功です。
課題3: サンプルデータを入れる
作ったテーブルにデータを入れて、SELECTで確認しましょう。
コース3件
INSERT INTO courses (course_name, duration_hours, category) VALUES
('SQL基礎', 14, 'データベース'),
('ビジネスマナー', 6, 'マナー'),
('プログラミング入門', 21, '開発');
受講履歴3件
existing の社員ID 1, 2, 3 と、上で作ったコースを組み合わせます。
INSERT INTO enrollments (employee_id, course_id, completed_at, score) VALUES
(1, 1, '2024-04-15 17:00:00', 85),
(2, 1, '2024-04-15 17:00:00', 92),
(3, 2, NULL, NULL);
3件目は「申込みしたけどまだ完了していない」というデータです。completed_at と score がNULLでも、CHECK制約に引っかからないことに注目してください。
確認SELECT
SELECT
e.last_name || ' ' || e.first_name AS employee_name,
c.course_name,
en.enrolled_at,
en.completed_at,
en.score
FROM enrollments en
JOIN employees e ON en.employee_id = e.employee_id
JOIN courses c ON en.course_id = c.course_id
ORDER BY en.enrolled_at DESC;
3件出ますか?社員名・コース名・得点が並んで見えれば完璧です。
課題4(チャレンジ): 制約の動作確認
制約は「ちゃんと弾いてくれる」ことを確認すると安心できます。次のINSERTを試してください。わざとエラーになる例です。
-- 1) 存在しない部署IDで社員を登録(失敗するはず)
INSERT INTO enrollments (employee_id, course_id) VALUES (9999, 1);
-- 2) 得点が範囲外(失敗するはず)
INSERT INTO enrollments (employee_id, course_id, score) VALUES (1, 1, 150);
-- 3) 受講時間が0以下(失敗するはず)
INSERT INTO courses (course_name, duration_hours) VALUES ('テスト', 0);
それぞれどんなエラーメッセージが出るか、AIに「このエラーは何を意味するか」を聞いてみましょう。
成果物
coursesenrollmentsの CREATE TABLE 文- それぞれにINSERTしたサンプルデータ
- JOINで「誰がどのコースを受けたか」を表示できたSELECT結果
- 制約が機能してエラーが出たことの確認メモ
まとめ(5分)
CREATE TABLEで「列名・型・制約」を書き、INSERTで「データを入れる」、SELECTで「確認する」。この3工程をひと続きで体験できたら、もうテーブル設計者の入り口に立っています。
制約はうるさく感じるかもしれませんが、運用に乗せたときに「変なデータが入る前に止まってくれる」最強の味方です。設計時の数行が、未来の数百件のトラブルを防いでくれます。
🔄 振り返りチェック
- 自分で書いた CREATE TABLE 文を、明日見ても理解できますか?
- 外部キーが「向こうの主キーに存在する値だけ入れさせない」働きを実感しましたか?
- CHECK制約がどんなときに役立つか、自分の例を1つ言えますか?
補足資料
- 参考リンク: PostgreSQL公式 - データ定義
- 発展課題: 作った
enrollmentsテーブルに、「同じ社員が同じコースに2回申し込めない」制約を追加するにはどうすればよいか考えてみる(ヒント: 複合UNIQUE)
学習ガイド
想定される質問と回答例
| 質問 | ヒント |
|---|---|
テーブル名は単数 course ?複数 courses ? |
チームのルール次第。世間では「複数形」が主流(テーブル=行の集まり、と考えると複数形が自然) |
| 一度作ったテーブルの列を増やしたい | ALTER TABLE courses ADD COLUMN description TEXT; のように後から追加可能 |
| 間違って作ったテーブルを消したい | DROP TABLE テーブル名; で削除。本番では絶対に確認してから |
| 外部キーの参照先テーブルが先に必要? | はい。CREATE順は「親→子」、DROP順は「子→親」(依存関係の方向に注意) |
つまずきやすいポイント
| つまずきポイント | ヒント |
|---|---|
relation "xxx" does not exist エラー |
テーブル名のタイポ、または作っていないテーブルを参照している |
violates foreign key constraint エラー |
親テーブルに該当する値がない。SELECT で確認してから子にINSERT |
| 制約を後で名前付きで識別したい | CONSTRAINT chk_score CHECK (score BETWEEN 0 AND 100) のように名前を付けると、エラー時に分かりやすい |