【SQL入門】データを手足のように操る技術 — SELECT から JOIN まで完全攻略
はじめに
データベースを使う上で避けて通れないのが SQL(Structured Query Language) です。
「なんとなく SELECT は書けるけど、JOIN になると怪しい…」
「WHERE と HAVING の違いがよくわからない…」
この記事では、そんな方に向けて 「データを自由自在に取り出し、加工し、操作する力」 を身につけてもらうことを目指します。
実際のデモデータを使いながら、手を動かして理解できる構成にしています。
この記事で使うデモデータ
まず、記事全体を通して使うテーブルを紹介します。
「社員管理システム」を想定した3テーブルです。
departments(部署)
| id | name |
|---|---|
| 1 | 営業部 |
| 2 | 開発部 |
| 3 | 人事部 |
employees(社員)
| id | name | department_id | salary | hire_date |
|---|---|---|---|---|
| 1 | 田中 | 1 | 300000 | 2022-04-01 |
| 2 | 鈴木 | 2 | 450000 | 2021-06-15 |
| 3 | 佐藤 | 2 | 400000 | 2023-01-10 |
| 4 | 高橋 | 1 | 350000 | 2020-09-01 |
| 5 | 伊藤 | 3 | 320000 | 2023-07-01 |
| 6 | 山本 | NULL | 280000 | 2024-01-15 |
山本さんは配属先未定(
NULL)です。NULLの扱いも後ほど解説します。
projects(プロジェクト)
| id | name | leader_id |
|---|---|---|
| 1 | ECサイトリニューアル | 2 |
| 2 | 社内ツール開発 | 3 |
| 3 | 採用管理システム | 5 |
このデータを作成するSQLも載せておきます。手元で試したい方はどうぞ。
CREATE TABLE departments (
id INT PRIMARY KEY,
name VARCHAR(50)
);
CREATE TABLE employees (
id INT PRIMARY KEY,
name VARCHAR(50),
department_id INT,
salary INT,
hire_date DATE
);
CREATE TABLE projects (
id INT PRIMARY KEY,
name VARCHAR(100),
leader_id INT
);
INSERT INTO departments VALUES
(1, '営業部'), (2, '開発部'), (3, '人事部');
INSERT INTO employees VALUES
(1, '田中', 1, 300000, '2022-04-01'),
(2, '鈴木', 2, 450000, '2021-06-15'),
(3, '佐藤', 2, 400000, '2023-01-10'),
(4, '高橋', 1, 350000, '2020-09-01'),
(5, '伊藤', 3, 320000, '2023-07-01'),
(6, '山本', NULL, 280000, '2024-01-15');
INSERT INTO projects VALUES
(1, 'ECサイトリニューアル', 2),
(2, '社内ツール開発', 3),
(3, '採用管理システム', 5);
1. SQL文の全体像 — 4つの操作(CRUD)
SQLの操作は大きく 4種類 に分かれます。
| 操作 | SQL文 | 意味 | 頻度 |
|---|---|---|---|
| Create | INSERT |
データを追加する | ★★☆ |
| Read | SELECT |
データを取得する | ★★★ |
| Update | UPDATE |
データを更新する | ★★☆ |
| Delete | DELETE |
データを削除する | ★☆☆ |
現場で最も書く機会が多いのは圧倒的に SELECT です。
この記事でも SELECT を中心に、厚めに解説します。
2. SELECT — データを取り出す
基本の形
SELECT 列名 FROM テーブル名;
全列を取得するなら *(アスタリスク)を使います。
SELECT * FROM employees;
結果:
| id | name | department_id | salary | hire_date |
|---|---|---|---|---|
| 1 | 田中 | 1 | 300000 | 2022-04-01 |
| 2 | 鈴木 | 2 | 450000 | 2021-06-15 |
| ... | ... | ... | ... | ... |
実務のコツ:
SELECT *は楽ですが、本番コードでは必要な列だけ指定しましょう。不要な列まで取得するとパフォーマンスが落ちます。
特定の列だけ取りたいなら、カンマ区切りで列名を指定します。
SELECT name, salary FROM employees;
| name | salary |
|---|---|
| 田中 | 300000 |
| 鈴木 | 450000 |
| 佐藤 | 400000 |
| 高橋 | 350000 |
| 伊藤 | 320000 |
| 山本 | 280000 |
WHERE — 条件で絞り込む
SELECT * FROM employees WHERE salary >= 350000;
| id | name | department_id | salary | hire_date |
|---|---|---|---|---|
| 2 | 鈴木 | 2 | 450000 | 2021-06-15 |
| 3 | 佐藤 | 2 | 400000 | 2023-01-10 |
| 4 | 高橋 | 1 | 350000 | 2020-09-01 |
比較演算子まとめ
| 演算子 | 意味 | 例 |
|---|---|---|
= |
等しい | WHERE name = '田中' |
!= / <>
|
等しくない | WHERE id != 1 |
> |
より大きい | WHERE salary > 300000 |
>= |
以上 | WHERE salary >= 300000 |
< |
より小さい | WHERE salary < 400000 |
<= |
以下 | WHERE salary <= 400000 |
AND / OR — 複数条件
-- 開発部かつ給与40万以上
SELECT * FROM employees
WHERE department_id = 2 AND salary >= 400000;
| id | name | department_id | salary | hire_date |
|---|---|---|---|---|
| 2 | 鈴木 | 2 | 450000 | 2021-06-15 |
| 3 | 佐藤 | 2 | 400000 | 2023-01-10 |
-- 営業部または人事部
SELECT * FROM employees
WHERE department_id = 1 OR department_id = 3;
注意: AND と OR を混ぜるときは 括弧 で意図を明確にしましょう。
WHERE a = 1 OR b = 2 AND c = 3はWHERE a = 1 OR (b = 2 AND c = 3)と解釈されます(ANDが先)。
IN — 複数の値のどれかに一致
OR を何個も並べるのは面倒なので、IN を使います。
-- 上のORと同じ意味
SELECT * FROM employees
WHERE department_id IN (1, 3);
BETWEEN — 範囲指定
SELECT * FROM employees
WHERE salary BETWEEN 300000 AND 400000;
これは salary >= 300000 AND salary <= 400000 と同じ意味です。両端を含みます。
LIKE — あいまい検索
| パターン | 意味 | 例 |
|---|---|---|
% |
0文字以上の任意の文字列 |
'田%' → 田中、田村、田… |
_ |
ちょうど1文字 |
'_藤' → 佐藤、伊藤 |
SELECT * FROM employees WHERE name LIKE '%藤';
| id | name | department_id | salary | hire_date |
|---|---|---|---|---|
| 3 | 佐藤 | 2 | 400000 | 2023-01-10 |
| 5 | 伊藤 | 3 | 320000 | 2023-07-01 |
IS NULL / IS NOT NULL — NULLの判定
NULLは = で比較できません。 これは初心者がハマる最大のポイントです。
-- ❌ これは動かない(結果が返らない)
SELECT * FROM employees WHERE department_id = NULL;
-- ✅ 正しい書き方
SELECT * FROM employees WHERE department_id IS NULL;
| id | name | department_id | salary | hire_date |
|---|---|---|---|---|
| 6 | 山本 | NULL | 280000 | 2024-01-15 |
なぜ
= NULLはダメなのか?
SQLの世界では NULL は「値が存在しない」という意味です。「存在しないもの」と何かを比較しても、結果は TRUE でも FALSE でもなく UNKNOWN になります。だから専用のIS NULLを使います。
ORDER BY — 並び替え
-- 給与の高い順(降順)
SELECT name, salary FROM employees
ORDER BY salary DESC;
| name | salary |
|---|---|
| 鈴木 | 450000 |
| 佐藤 | 400000 |
| 高橋 | 350000 |
| 伊藤 | 320000 |
| 田中 | 300000 |
| 山本 | 280000 |
| キーワード | 意味 |
|---|---|
ASC |
昇順(小さい順)— デフォルト |
DESC |
降順(大きい順) |
複数列での並び替えもできます。
-- 部署ごとに、給与の高い順
SELECT * FROM employees
ORDER BY department_id ASC, salary DESC;
LIMIT — 取得件数を制限する
-- 上位3件だけ
SELECT name, salary FROM employees
ORDER BY salary DESC
LIMIT 3;
| name | salary |
|---|---|
| 鈴木 | 450000 |
| 佐藤 | 400000 |
| 高橋 | 350000 |
DB製品による違い: MySQL・PostgreSQL・SQLite は
LIMIT、Oracle はFETCH FIRST 3 ROWS ONLY、SQL Server はTOP 3を使います。
DISTINCT — 重複を排除する
SELECT DISTINCT department_id FROM employees;
| department_id |
|---|
| 1 |
| 2 |
| 3 |
| NULL |
3. 集約関数とGROUP BY — データをまとめる
主な集約関数
| 関数 | 意味 | 例 |
|---|---|---|
COUNT(*) |
行数を数える | 全社員数 |
COUNT(列) |
NULLを除いた行数 | 部署が決まっている社員数 |
SUM(列) |
合計 | 給与の合計 |
AVG(列) |
平均 | 平均給与 |
MAX(列) |
最大値 | 最高給与 |
MIN(列) |
最小値 | 最低給与 |
SELECT
COUNT(*) AS 全社員数,
AVG(salary) AS 平均給与,
MAX(salary) AS 最高給与,
MIN(salary) AS 最低給与
FROM employees;
| 全社員数 | 平均給与 | 最高給与 | 最低給与 |
|---|---|---|---|
| 6 | 350000 | 450000 | 280000 |
ASは列に別名(エイリアス)をつけます。結果が読みやすくなるので積極的に使いましょう。
GROUP BY — グループごとに集計
SELECT
department_id,
COUNT(*) AS 人数,
AVG(salary) AS 平均給与
FROM employees
GROUP BY department_id;
| department_id | 人数 | 平均給与 |
|---|---|---|
| 1 | 2 | 325000 |
| 2 | 2 | 425000 |
| 3 | 1 | 320000 |
| NULL | 1 | 280000 |
HAVING — グループに対する条件
WHERE は「各行」に対する条件、HAVING は「グループ」に対する条件です。
-- 平均給与が35万以上の部署だけ
SELECT
department_id,
AVG(salary) AS 平均給与
FROM employees
GROUP BY department_id
HAVING AVG(salary) >= 350000;
| department_id | 平均給与 |
|---|---|
| 2 | 425000 |
WHERE vs HAVING 早見表
| WHERE | HAVING | |
|---|---|---|
| 対象 | 各行 | グループ |
| 使える場所 | GROUP BY の前 | GROUP BY の後 |
| 集約関数 | 使えない ❌ | 使える ✅ |
-- ❌ エラーになる
SELECT department_id, AVG(salary)
FROM employees
WHERE AVG(salary) >= 350000 -- 集約関数はWHEREで使えない
GROUP BY department_id;
-- ✅ 正しい
SELECT department_id, AVG(salary)
FROM employees
GROUP BY department_id
HAVING AVG(salary) >= 350000;
4. SELECT文の実行順序 — 最重要ポイント
SQLは 書く順番 と 実行される順番 が違います。これを知らないとエラーの原因がわからなくなります。
| 実行順 | 句 | 何をしているか |
|---|---|---|
| 1 | FROM |
テーブルを決める |
| 2 | WHERE |
行を絞り込む |
| 3 | GROUP BY |
グループ化する |
| 4 | HAVING |
グループを絞り込む |
| 5 | SELECT |
列を選ぶ・計算する |
| 6 | ORDER BY |
並び替える |
| 7 | LIMIT |
件数を制限する |
これが分かると解ける疑問:
- WHEREで集約関数が使えないのはなぜ? → WHEREはGROUP BYの前に実行されるから、まだグループが存在しない
- SELECTでつけた別名がWHEREで使えないのはなぜ? → SELECTはWHEREの後に実行されるから
- ORDER BYでは別名が使えるのはなぜ? → ORDER BYはSELECTの後に実行されるから
5. JOIN — テーブルを結合する
ここからが本番です。実務では複数のテーブルを組み合わせてデータを取り出すケースがほとんどです。
INNER JOIN — 一致するデータだけ結合
SELECT
e.name AS 社員名,
d.name AS 部署名,
e.salary
FROM employees e
INNER JOIN departments d ON e.department_id = d.id;
| 社員名 | 部署名 | salary |
|---|---|---|
| 田中 | 営業部 | 300000 |
| 鈴木 | 開発部 | 450000 |
| 佐藤 | 開発部 | 400000 |
| 高橋 | 営業部 | 350000 |
| 伊藤 | 人事部 | 320000 |
山本さん(
department_id = NULL)は結果に含まれません。一致する部署がないからです。
e や d はテーブルの別名(エイリアス) です。毎回 employees.name と書くのは長いので、短い別名をつけます。
LEFT JOIN — 左テーブルを全て残す
SELECT
e.name AS 社員名,
d.name AS 部署名
FROM employees e
LEFT JOIN departments d ON e.department_id = d.id;
| 社員名 | 部署名 |
|---|---|
| 田中 | 営業部 |
| 鈴木 | 開発部 |
| 佐藤 | 開発部 |
| 高橋 | 営業部 |
| 伊藤 | 人事部 |
| 山本 | NULL |
山本さんも結果に含まれるようになりました。一致する部署がない場合、部署名は NULL になります。
JOIN の種類まとめ
INNER JOIN LEFT JOIN RIGHT JOIN FULL OUTER JOIN
┌───┬───┐ ┌───┬───┐ ┌───┬───┐ ┌───┬───┐
│ A │ B │ │ A │ B │ │ A │ B │ │ A │ B │
│ │███│ │███│███│ │███│███│ │███│███│
│ │███│ │███│███│ │███│███│ │███│███│
│ │ │ │███│ │ │ │███│ │███│███│
└───┴───┘ └───┴───┘ └───┴───┘ └───┴───┘
両方に一致 左を全て残す 右を全て残す 両方全て残す
| 種類 | 説明 | 使用頻度 |
|---|---|---|
INNER JOIN |
両テーブルに一致するデータのみ | ★★★ |
LEFT JOIN |
左テーブルは全て残す。右に一致がなければNULL | ★★★ |
RIGHT JOIN |
右テーブルは全て残す | ★☆☆ |
FULL OUTER JOIN |
両方全て残す | ★☆☆ |
実務のコツ: RIGHT JOIN はテーブルの順番を入れ替えれば LEFT JOIN で書けるので、ほぼ使いません。LEFT JOIN だけ覚えておけばOKです。
3テーブル以上の結合
JOINは何個でもつなげられます。
-- 社員 + 部署 + 担当プロジェクト
SELECT
e.name AS 社員名,
d.name AS 部署名,
p.name AS プロジェクト名
FROM employees e
LEFT JOIN departments d ON e.department_id = d.id
LEFT JOIN projects p ON e.id = p.leader_id;
| 社員名 | 部署名 | プロジェクト名 |
|---|---|---|
| 田中 | 営業部 | NULL |
| 鈴木 | 開発部 | ECサイトリニューアル |
| 佐藤 | 開発部 | 社内ツール開発 |
| 高橋 | 営業部 | NULL |
| 伊藤 | 人事部 | 採用管理システム |
| 山本 | NULL | NULL |
6. サブクエリ — SQLの中にSQLを書く
クエリの結果を別のクエリの条件に使えます。
-- 平均給与より高い社員を取得
SELECT name, salary
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
| name | salary |
|---|---|
| 鈴木 | 450000 |
| 佐藤 | 400000 |
-- プロジェクトリーダーの一覧
SELECT name FROM employees
WHERE id IN (SELECT leader_id FROM projects);
| name |
|---|
| 鈴木 |
| 佐藤 |
| 伊藤 |
実務のコツ: サブクエリで書けるものは JOIN でも書けることが多いです。パフォーマンス面では JOIN の方が有利なケースが多いので、複雑になったら JOIN への書き換えを検討しましょう。
7. INSERT / UPDATE / DELETE — データの変更
INSERT — 追加
-- 1件追加
INSERT INTO employees (id, name, department_id, salary, hire_date)
VALUES (7, '渡辺', 2, 380000, '2024-04-01');
-- 複数件を一括追加
INSERT INTO employees (id, name, department_id, salary, hire_date)
VALUES
(8, '中村', 1, 310000, '2024-04-01'),
(9, '小林', 3, 290000, '2024-04-01');
UPDATE — 更新
-- 田中の給与を上げる
UPDATE employees
SET salary = 330000
WHERE id = 1;
⚠️ WHERE を忘れると全行が更新されます。 これは取り返しのつかない事故になるので、UPDATE/DELETEを実行する前に、まず同じ WHERE 条件で SELECT して対象行を確認する癖をつけましょう。
-- 先にこれで確認
SELECT * FROM employees WHERE id = 1;
-- 確認できたら UPDATE
UPDATE employees SET salary = 330000 WHERE id = 1;
DELETE — 削除
DELETE FROM employees WHERE id = 9;
WHERE を忘れると 全データが消えます。UPDATE と同じく、先に SELECT で確認しましょう。
8. 実践:よくある実務クエリ集
ここまでの知識を組み合わせた、実務でよく見るパターンです。
部署ごとの人数と平均給与(部署名付き)
SELECT
d.name AS 部署名,
COUNT(e.id) AS 人数,
AVG(e.salary) AS 平均給与
FROM departments d
LEFT JOIN employees e ON d.id = e.department_id
GROUP BY d.id, d.name
ORDER BY 平均給与 DESC;
| 部署名 | 人数 | 平均給与 |
|---|---|---|
| 開発部 | 2 | 425000 |
| 営業部 | 2 | 325000 |
| 人事部 | 1 | 320000 |
各部署で最も給与が高い社員
SELECT e.name, d.name AS 部署名, e.salary
FROM employees e
INNER JOIN departments d ON e.department_id = d.id
WHERE e.salary = (
SELECT MAX(salary)
FROM employees
WHERE department_id = e.department_id
);
| name | 部署名 | salary |
|---|---|---|
| 高橋 | 営業部 | 350000 |
| 鈴木 | 開発部 | 450000 |
| 伊藤 | 人事部 | 320000 |
プロジェクトを持っていない社員
SELECT e.name
FROM employees e
LEFT JOIN projects p ON e.id = p.leader_id
WHERE p.id IS NULL;
| name |
|---|
| 田中 |
| 高橋 |
| 山本 |
9. チートシート
最後に、この記事の内容を1枚にまとめます。
【SELECT文の構文】
SELECT 列名 -- 5番目に実行
FROM テーブル -- 1番目
JOIN テーブル ON 条件 -- 1番目(FROMと一緒)
WHERE 行の条件 -- 2番目
GROUP BY グループ化列 -- 3番目
HAVING グループの条件 -- 4番目
ORDER BY 並び替え -- 6番目
LIMIT 件数制限 -- 7番目
【CRUD】
INSERT INTO t (列) VALUES (値);
UPDATE t SET 列 = 値 WHERE 条件; ← WHERE忘れ厳禁
DELETE FROM t WHERE 条件; ← WHERE忘れ厳禁
【JOIN】
INNER JOIN → 両方一致のみ
LEFT JOIN → 左を全て残す(これだけ覚えればOK)
【NULL】
= NULL は使えない → IS NULL / IS NOT NULL
おわりに
SQLは覚えることが多く見えますが、核になるのは SELECT + WHERE + JOIN + GROUP BY の4つです。この4つを自在に組み合わせられれば、大抵のデータは取り出せます。
まずはデモデータを自分のDBに入れて、この記事のクエリを実際に動かしてみてください。手を動かした分だけ、SQLは身体に染み込んでいきます。