はじめに
SQLを「クエスト形式」で学習したら、めちゃくちゃ理解が深まったので共有します!
実際にDockerでPostgreSQLを立ち上げて、ECサイト風のサンプルデータで実践しました。
使用した環境
- Docker + PostgreSQL 15
- db-ui(SQLをブラウザで実行できるツール)
- サンプルDB:ユーザー、カテゴリ、商品、注文、注文詳細の5テーブル
データベース構造
users (ユーザー) ─────┐
├──→ orders (注文) ──→ order_items (注文詳細)
categories (カテゴリ) ─┴──→ products (商品) ──────────┘
```
## Chapter 1: SELECT基礎
```sql
-- 全データ取得
SELECT * FROM users
-- 特定カラムだけ
SELECT username, email FROM users
学び: SELECT で取りたいカラムを、FROM でテーブルを指定。
Chapter 2: WHERE(条件絞り込み)
-- 条件で絞り込む
SELECT * FROM users WHERE is_active = true
-- 比較演算子
SELECT * FROM products WHERE price >= 10000
学び: >=(以上)と >(より大きい)の違いに注意! ```
---
Chapter 3: AND / OR(複数条件)
-- 両方満たす
SELECT * FROM products WHERE price >= 10000 AND stock_quantity >= 30
-- どちらか満たす
SELECT * FROM orders WHERE status = 'completed' OR status = 'shipped'
-- IN を使うとスッキリ
SELECT * FROM orders WHERE status IN ('completed', 'shipped')
```
Chapter 4: ORDER BY & LIMIT
-- 高い順に並べる
SELECT * FROM products ORDER BY price DESC
-- 上位3件だけ
SELECT * FROM products ORDER BY price DESC LIMIT 3
-- 2番目を取得(OFFSETは0始まり)
SELECT * FROM products ORDER BY price DESC LIMIT 1 OFFSET 1
学び: OFFSET 1 で「1件スキップ」= 2番目から取得。
Chapter 5: 集計関数 ```
SELECT COUNT(*) FROM products -- 件数
SELECT MAX(price) FROM products -- 最大値
SELECT MIN(price) FROM products -- 最小値
SELECT SUM(total_amount) FROM orders -- 合計
SELECT AVG(price) FROM products -- 平均
---
Chapter 6: GROUP BY
-- カテゴリごとの商品数
SELECT category_id, COUNT(*)
FROM products
GROUP BY category_id
-- 集計結果で絞り込み(HAVING)
SELECT category_id, COUNT()
FROM products
GROUP BY category_id
HAVING COUNT() >= 2
学び:
- WHERE → グループ化「前」のフィルタ(個別の行)
- HAVING → グループ化「後」のフィルタ(集計結果)
---
Chapter 7: JOIN(最重要!)
基本のJOIN
-- ユーザー名付きの注文一覧
SELECT * FROM orders
JOIN users ON orders.user_id = users.id
JOINのコツ
〇〇_id = 〇〇.id で繋ぐ!
user_id = users.id
category_id = categories.id
order_id = orders.id
product_id = products.id
主キー(PK)と外部キー(FK)
┌───────────────┬───────────────────────┬───────────────────────┐
│ 種類 │ 特徴 │ 例 │
├───────────────┼───────────────────────┼───────────────────────┤
│ 主キー (PK) │ 自動採番される id │ id SERIAL PRIMARY KEY │
├───────────────┼───────────────────────┼───────────────────────┤
│ 外部キー (FK) │ 「〇〇_id」という名前 │ user_id, category_id │
└───────────────┴───────────────────────┴───────────────────────┘
FKを持っている方 = 子 = 多の側
3テーブル結合
SELECT users.username, order_items.product_id, order_items.quantity
FROM orders
JOIN users ON orders.user_id = users.id
JOIN order_items ON order_items.order_id = orders.id
---
Chapter 8: サブクエリ ```
-- 平均価格より高い商品
SELECT * FROM products
WHERE price > (SELECT AVG(price) FROM products)
-- 注文したことがあるユーザー
SELECT * FROM users
WHERE id IN (SELECT user_id FROM orders)
-- 注文されたことがない商品
SELECT * FROM products
WHERE id NOT IN (SELECT product_id FROM order_items)
JOINとサブクエリの使い分け
┌──────────────────────────────────┬────────────┐
│ やりたいこと │ 使う │
├──────────────────────────────────┼────────────┤
│ 2つのテーブルを合体して見たい │ JOIN │
├──────────────────────────────────┼────────────┤
│ 他テーブルの値で絞り込みたいだけ │ サブクエリ │
└──────────────────────────────────┴────────────┘
Chapter 9: DISTINCT / LIKE / BETWEEN ```
-- 重複を除く
SELECT DISTINCT category_id FROM products
-- あいまい検索
SELECT * FROM users WHERE last_name LIKE '%田%' -- 「田」を含む
SELECT * FROM products WHERE name LIKE '%ン' -- 「ン」で終わる
-- 範囲指定
SELECT * FROM products WHERE price BETWEEN 5000 AND 50000
┌──────────┬────────────┐
│ パターン │ 意味 │
├──────────┼────────────┤
│ '%田%' │ 田を含む │
├──────────┼────────────┤
│ '%ン' │ ンで終わる │
├──────────┼────────────┤
│ 'ン%' │ ンで始まる │
└──────────┴────────────┘
Chapter 10: INSERT / UPDATE / DELETE
INSERT(追加)
INSERT INTO categories (name, description)
VALUES ('食品', '食料品、飲料、お菓子など')
UPDATE(更新)
-- 特定の行を更新
UPDATE products SET price = 3500 WHERE id = 3
-- 計算して更新
UPDATE products
SET stock_quantity = stock_quantity + 10
WHERE category_id = 1
注意:WHEREを忘れると全行更新される!
DELETE(削除)
DELETE FROM orders WHERE status = 'pending'
注意:WHEREを忘れると全行削除される!
外部キー制約
エラー: update or delete on table "orders" violates foreign key constraint
FK(外部キー)がある = 勝手に消せない!
参照されてる親は、子が残ってると削除エラーになる。これはデータの整合性を守る仕組み。
-- 解決方法:先に参照してる方を消す
DELETE FROM order_items
WHERE order_id IN (SELECT id FROM orders WHERE status = 'pending')
DELETE FROM orders WHERE status = 'pending'
まとめ
習得したスキル
┌──────────┬─────────────────────────────────┐
│ カテゴリ │ 内容 │
├──────────┼─────────────────────────────────┤
│ 読む │ SELECT, WHERE, JOIN, サブクエリ │
├──────────┼─────────────────────────────────┤
│ 集計 │ COUNT, SUM, AVG, GROUP BY │
├──────────┼─────────────────────────────────┤
│ 書く │ INSERT, UPDATE, DELETE │
├──────────┼─────────────────────────────────┤
│ 概念 │ PK/FK, 1:多, 外部キー制約 │
└──────────┴─────────────────────────────────┘
重要な気づき
- スキーマを確認する重要性: テーブル名だけでなく、カラム定義を見ないと関係性はわからない
- FKの見分け方: 〇〇_id があればそれがFK、参照先の 〇〇.id がPK
- JOINとサブクエリの使い分け: 両方のデータが欲しいならJOIN、条件だけならサブクエリ
おわりに
クエスト形式で学ぶと、間違えた時に「なぜ間違えたか」が明確になって理解が深まりました。
特にJOINとPK/FKの関係は、図で理解してから実際に書くとスッと入ってきます。
SQLは怖くない!実際に手を動かして学ぶのが一番です。