SQL / LESSON 15 / SQL15

SQL:テーブル設計とCREATE TABLE

1学習の目的

2基礎解説

やりたいこと書き方効果
主キーにするid INTEGER PRIMARY KEY重複不可・自動採番
空を禁止するname TEXT NOT NULLNULLの挿入を拒否
重複を禁止するemail TEXT UNIQUE同じ値を2回入れられない
初期値を決めるstatus TEXT DEFAULT '有効'指定しないと自動で入る
✅ 覚えるべき重要ポイント
  1. 主キー(PRIMARY KEY)は、その行を一意に特定できる値productsid がまさにこれで、重複できず、SQLiteでは省略すると自動採番される。
  2. 制約は「入れさせない」ための仕組みNOT NULL は空を、UNIQUE は重複を、CHECK は条件を満たさない値を、それぞれ挿入の時点で拒否する。データが入った後に直すより確実。
  3. FOREIGN KEY(外部キー)は別テーブルのIDを参照する列に付ける制約。orders.customer_idcustomers.id に存在しない値を指すことを防げる。
  4. ⚠ SQLiteは型を厳密にチェックしないprice INTEGER と定義した列に文字列を入れてもエラーにならないことがある。MySQLやPostgreSQLはより厳格。「型を書いたから安全」とは限らない
  5. テーブルを消すのは DROP TABLEWHEREで絞れず、テーブルごと消える。DELETEより影響範囲が大きい、最も慎重を要する操作。

現場使用例:新機能のためのテーブル設計、会員登録フォームの裏側にあるNOT NULL制約、メールアドレスの重複登録防止(UNIQUE)、注文が実在する商品を指しているかの保証(FOREIGN KEY)。

-- 基本のテーブル作成
CREATE TABLE members (
  id       INTEGER PRIMARY KEY,
  name     TEXT NOT NULL,
  email    TEXT UNIQUE,
  status   TEXT DEFAULT '有効',
  age      INTEGER CHECK (age >= 0)
);

-- NOT NULL違反:エラーになる
INSERT INTO members (name) VALUES (NULL);
-- → NOT NULL constraint failed

-- UNIQUE違反:同じメールを2回登録できない
INSERT INTO members (name, email) VALUES ('田中', 'a@b.com');
INSERT INTO members (name, email) VALUES ('鈴木', 'a@b.com');
-- → UNIQUE constraint failed

-- CHECK違反:条件を満たさない値は拒否される
INSERT INTO members (name, age) VALUES ('佐藤', -5);
-- → CHECK constraint failed

-- 外部キー:存在しない親を参照できない
CREATE TABLE orders (
  id          INTEGER PRIMARY KEY,
  member_id   INTEGER REFERENCES members(id)
);
INSERT INTO orders (member_id) VALUES (999);  -- membersに無いid
-- → FOREIGN KEY constraint failed

-- テーブルを削除する(WHEREでは絞れない)
DROP TABLE orders;

3基本ドリル(10問)

SQL15-D01 id(主キー)・name(TEXT、必須)・price(INTEGER)を持つ items テーブルを作るSQLを書け。 コーディング★☆☆無料
期待される結果

itemsテーブルが作成される

ヒント

CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT NOT NULL, price INTEGER);

模範解答
CREATE TABLE items (
  id INTEGER PRIMARY KEY,
  name TEXT NOT NULL,
  price INTEGER
);
解説

これがテーブル設計の最小形。列名・型・制約をカンマで区切って並べる。id を PRIMARY KEY にすると、SQLiteでは自動採番の主キーになる。

SQL15-D02 name TEXT NOT NULL と定義した列に、値を指定せず(NULLで)INSERTしようとするとどうなるか。
A. 自動で空文字が入る B. エラーになり挿入が拒否される C. 0が入る D. 警告だけ出て挿入される
選択★☆☆無料
期待される結果

解答 == B

ヒント

NOT NULL は「空を許さない」制約。

模範解答
B
解説

制約違反はエラーとして即座に拒否される。データが不正な状態のままテーブルに入ることはない。

SQL15-D03 email 列に UNIQUE 制約を持つ members テーブル(id 主キー、email TEXT)を作るSQLを書け。 コーディング★☆☆無料
期待される結果

membersテーブルが作成される

ヒント

email TEXT UNIQUE のように、型の後ろに制約を書く。

模範解答
CREATE TABLE members (
  id INTEGER PRIMARY KEY,
  email TEXT UNIQUE
);
解説

UNIQUE を付けると、同じ値を2回挿入できなくなる。会員登録でメールアドレスの重複を防ぐのに使われる、実務で最も身近な制約の1つ。

SQL15-D04 status に初期値「有効」を設定するSQLを完成させよ。 穴埋め★☆☆無料
コード
CREATE TABLE members (
  id INTEGER PRIMARY KEY,
  status TEXT ____ '有効'
);
期待される結果

membersテーブルが作成される

ヒント

「既定値」を意味する7文字のキーワード。

模範解答
DEFAULT
解説

DEFAULT を指定すると、INSERTで値を省略したときに自動で入る。会員登録時にステータスを毎回書かなくても「有効」が入る。

SQL15-D05 テーブルを完全に削除する命令はどれか。
A. DELETE TABLE B. DROP TABLE C. REMOVE TABLE D. CLEAR TABLE
選択★☆☆無料
期待される結果

解答 == B

ヒント

「捨てる」という意味の英単語。

模範解答
B
解説

DROP TABLE はテーブルの構造ごと消す。DELETEは行だけを消すが、DROPは列の定義もテーブル自体も消える。WHEREでは絞れない。

SQL15-D06 price が0以上であることを保証する CHECK 制約を持つ items テーブル(id 主キー、price INTEGER)を作るSQLを書け。 コーディング★★☆無料
期待される結果

itemsテーブルが作成される

ヒント

price INTEGER CHECK (price >= 0) のように書く。

模範解答
CREATE TABLE items (
  id INTEGER PRIMARY KEY,
  price INTEGER CHECK (price >= 0)
);
解説

CHECK は任意の条件を指定できる制約。負の価格や、範囲外の年齢など、業務ルールをテーブルの定義そのものに埋め込める。

SQL15-D07 orders テーブルの customer_id が customers テーブルの id を参照するSQLを完成させよ。 穴埋め★★☆無料
コード
CREATE TABLE orders (
  id INTEGER PRIMARY KEY,
  customer_id INTEGER ____ customers(id)
);
期待される結果

ordersテーブルが作成される

ヒント

「参照する」を意味する9文字のキーワード。

模範解答
REFERENCES
解説

REFERENCES で外部キーを設定する。これにより、存在しない顧客IDを注文に登録することを防げる(外部キー制約が有効な設定の場合)。

SQL15-D08 SQLiteで INTEGER 型の列に文字列 'abc' をINSERTしようとすると、多くの場合どうなるか。
A. 必ずエラーになる B. 型を厳密にチェックしないため挿入されてしまうことがある C. 自動的に0に変換される D. 自動的にTEXT型に列が変わる
選択★★☆無料
期待される結果

解答 == B

ヒント

SQLiteの型システムは他のDBと比べて緩い。

模範解答
B
解説

SQLiteは「動的型付け」と呼ばれる方式で、型の定義があっても厳密には強制しない。MySQLやPostgreSQLではエラーになることが多く、環境によって挙動が違う点に注意が必要。

SQL15-D09 id(主キー)・name(必須)・stock(INTEGER、0以上、初期値0)を持つ inventory テーブルを作るSQLを書け。 コーディング★★☆無料
期待される結果

inventoryテーブルが作成される

ヒント

CHECKとDEFAULTを両方、stock列に付ける。順番は CHECK と DEFAULT のどちらが先でもよい。

模範解答
CREATE TABLE inventory (
  id INTEGER PRIMARY KEY,
  name TEXT NOT NULL,
  stock INTEGER DEFAULT 0 CHECK (stock >= 0)
);
解説

1つの列に複数の制約を重ねられる。DEFAULTで「省略時は0」、CHECKで「負の値は拒否」——2つを組み合わせることで、在庫が常に0以上の整数であることが保証される。

SQL15-D10 logs テーブルを削除するSQLを書け。 コーディング★★☆無料
期待される結果

logsテーブルが削除される

ヒント

DROP TABLE logs; と書く。

模範解答
DROP TABLE logs;
解説

DROP TABLEはバックアップが無い限り取り消せない。実務では「本当にこのテーブルを消してよいか」を必ず複数人で確認してから実行する。

4実践シナリオ(5問)

SQL15-S01 【会員テーブルの設計】id(主キー)・name(必須)・email(重複不可)・joined_at(TEXT、初期値なし)を持つ members テーブルを設計するSQLを書け。 コーディング★★☆無料
期待される結果

membersテーブルが作成される

ヒント

各列に適切な制約を1つずつ組み合わせる。

模範解答
CREATE TABLE members (
  id INTEGER PRIMARY KEY,
  name TEXT NOT NULL,
  email TEXT UNIQUE,
  joined_at TEXT
);
解説

会員登録フォームの裏側で動いているテーブルそのもの。名前を必須にし、メールを一意にする——これだけで「名前未入力」「メール重複登録」という2つの事故を仕組みで防げる。

SQL15-S02 【注文明細テーブルの設計】id(主キー)・order_id(INTEGER、必須)・product_id(INTEGER、必須)・quantity(INTEGER、1以上、初期値1)を持つ order_items テーブルを設計するSQLを書け。 コーディング★★☆無料
期待される結果

order_itemsテーブルが作成される

ヒント

quantity に CHECK と DEFAULT を組み合わせる。

模範解答
CREATE TABLE order_items (
  id INTEGER PRIMARY KEY,
  order_id INTEGER NOT NULL,
  product_id INTEGER NOT NULL,
  quantity INTEGER DEFAULT 1 CHECK (quantity >= 1)
);
解説

数量が0や負の値になることを、テーブル自体が拒否する。アプリ側のチェックが漏れていても、データベースが最後の砦として守ってくれる。

SQL15-S03 【外部キーを持つテーブル】reviews テーブル(id 主キー、product_id が products.id を参照、rating INTEGER で1〜5の範囲、comment TEXT)を作るSQLを書け。 コーディング★★☆無料
期待される結果

reviewsテーブルが作成される

ヒント

CHECK (rating >= 1 AND rating <= 5) のように範囲を指定する(SQL03のANDを思い出す)。

模範解答
CREATE TABLE reviews (
  id INTEGER PRIMARY KEY,
  product_id INTEGER REFERENCES products(id),
  rating INTEGER CHECK (rating >= 1 AND rating <= 5),
  comment TEXT
);
解説

SQL03で学んだANDが、CHECK制約の中でも使える。星1〜5段階のレビュー機能は、まさにこの制約で不正な評価点を防いでいる。

SQL15-S04 【型の緩さを確かめる】items テーブル(id 主キー、price INTEGER)を作り、price に文字列 'abc' を挿入してみるSQLを書け(エラーになるか、挿入されるかを確認する)。 コーディング★★★無料
期待される結果

SQLiteでは挿入が成功する(型が緩いため)

ヒント

CREATE TABLEしてからINSERTする。2本のSQLを書く。

模範解答
CREATE TABLE items (
  id INTEGER PRIMARY KEY,
  price INTEGER
);

INSERT INTO items (price) VALUES ('abc');
解説

SQLiteではこれが成功してしまうことがある。型を書いたからといって、それだけでデータの正しさは保証されない。CHECK制約やアプリ側の検証が別途必要になる理由がここにある。

SQL15-S05 【総合的なテーブル設計】products_v2 テーブルを設計せよ。id(主キー)・name(必須)・category(初期値「未分類」)・price(0以上、必須)・stock(0以上、初期値0)を持つこと。 コーディング★★★無料
期待される結果

products_v2テーブルが作成される

ヒント

5つの列それぞれに適切な制約を組み合わせる。price は NOT NULL と CHECK の両方を付ける。

模範解答
CREATE TABLE products_v2 (
  id INTEGER PRIMARY KEY,
  name TEXT NOT NULL,
  category TEXT DEFAULT '未分類',
  price INTEGER NOT NULL CHECK (price >= 0),
  stock INTEGER DEFAULT 0 CHECK (stock >= 0)
);
解説

これまで扱ってきた products テーブルより、はるかに堅牢な設計になっている。SQL05で悩まされたNULLだらけのデータは、最初からこの制約があれば生まれなかった——制約は「後から直す」よりも「最初から防ぐ」ための道具だと分かる。

5仕上げ課題

SQL15-FINAL 【新規サービスのテーブル設計】書籍レンタルサービスを立ち上げることになった。books テーブルと rentals テーブルを設計せよ。

books テーブル
・id(主キー)
・title(必須)
・author(必須)
・isbn(重複不可、TEXT)
・stock(0以上、初期値1)

rentals テーブル
・id(主キー)
・book_id(INTEGER、必須、books.id を参照)
・renter_name(必須)
・status(TEXT、初期値「貸出中」、'貸出中' または '返却済' のいずれか)

2つのテーブルを作ったあと、動作確認として次の2件を挿入せよ
① books に「SQL入門」(author:'山田太郎', isbn:'978-001', stock:3)
② rentals に book_id:1(①で作った本のid)、renter_name:'田中'

最後に SELECT title, renter_name, status FROM rentals JOIN books ON rentals.book_id = books.id; で確認すること。

期待される確認結果
title | renter_name | status
SQL入門 | 田中 | 貸出中
期待される結果

確認SELECTの結果が一致する

ヒント

status の '貸出中' or '返却済' は CHECK (status IN ('貸出中', '返却済')) と書ける(SQL02のIN)。rentals の book_id は REFERENCES books(id)。INSERT時、booksのidは自動採番なので1になる想定で book_id に 1 を指定する。

模範解答
CREATE TABLE books (
  id INTEGER PRIMARY KEY,
  title TEXT NOT NULL,
  author TEXT NOT NULL,
  isbn TEXT UNIQUE,
  stock INTEGER DEFAULT 1 CHECK (stock >= 0)
);

CREATE TABLE rentals (
  id INTEGER PRIMARY KEY,
  book_id INTEGER NOT NULL REFERENCES books(id),
  renter_name TEXT NOT NULL,
  status TEXT DEFAULT '貸出中' CHECK (status IN ('貸出中', '返却済'))
);

INSERT INTO books (title, author, isbn, stock)
VALUES ('SQL入門', '山田太郎', '978-001', 3);

INSERT INTO rentals (book_id, renter_name)
VALUES (1, '田中');

SELECT title, renter_name, status
FROM rentals
JOIN books ON rentals.book_id = books.id;
解説

ここまで学んだ制約がすべて1つの設計に集約された。UNIQUE(ISBNの重複防止)、CHECK(在庫と状態の範囲制限)、DEFAULT(初期値)、REFERENCES(存在する本だけ貸し出せる)、NOT NULL(必須項目の保証)。

特に statusCHECK (status IN ('貸出中', '返却済')) に注目してほしい。SQL02で学んだIN演算子が、SELECTの条件だけでなくCHECK制約の中でも使える。これにより「貸出中でも返却済でもない謎のステータス」がテーブルに入り込む余地が最初から無い。

この章で学んだ設計思想は、「アプリのコードでチェックする」より「データベースの制約で防ぐ」ほうが確実だということだ。アプリのコードにはバグが混ざりうるし、複数のアプリから同じデータベースを触ることもある。制約はデータベース自身が守ってくれるので、どの経路から書き込まれても不正なデータは入らない。

次章ではインデックスを学ぶ。テーブルの構造を決めるここまでの内容から一歩進み、大量のデータでも高速に検索できる仕組みを扱う。