1.

SQL 基本構文完全ガイド — SELECT から JOIN まで

編集
この記事の要点
  • SQL は DML / DDL / DCL / TCL の 4 種に大別される。本記事は実務で 9 割使う DML SELECT / INSERT / UPDATE / DELETE を中心に解説
  • SELECT 句の評価順 は記述順と違う: FROM → JOIN → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT
  • JOIN は INNER / LEFT / RIGHT / FULL OUTER の 4 種。NULL 安全に扱うには LEFT JOIN + IS NULL で「片側のみ」を抽出
  • 集約関数 COUNT/SUM/AVG/MAX/MIN は GROUP BY とセット。SELECT 句に書ける非集約列は GROUP BY 句にも書く必要がある
  • サブクエリより CTE (WITH) を使うと可読性が劇的に向上。再帰 CTE で階層構造も扱える
  • トランザクションは BEGIN → 更新群 → COMMIT / 失敗時 ROLLBACK。自動コミットを切るのが安全

SQL とは — 4 つのカテゴリ

SQL (Structured Query Language) はリレーショナルデータベースを操作する標準言語です。命令は役割で 4 種に分類されます:

分類正式名代表的なコマンド用途
DMLData Manipulation LanguageSELECT / INSERT / UPDATE / DELETEデータの参照・更新
DDLData Definition LanguageCREATE / ALTER / DROP / TRUNCATEテーブル・スキーマ定義
DCLData Control LanguageGRANT / REVOKE権限制御
TCLTransaction Control LanguageBEGIN / COMMIT / ROLLBACK / SAVEPOINTトランザクション制御

SELECT — データを取り出す

もっとも使う構文。基本形:

-- 全列・全行
SELECT * FROM users;

-- 列を指定
SELECT id, name, email FROM users;

-- 条件で絞り込み
SELECT id, name FROM users
WHERE status = 'active'
  AND created_at >= '2025-01-01';

-- ソート(ASC=昇順 / DESC=降順)
SELECT id, name FROM users
ORDER BY created_at DESC, id ASC;

-- 件数制限(ページネーション)
SELECT id, name FROM users
ORDER BY id
LIMIT 10 OFFSET 20;     -- 21~30 件目

SELECT 句の評価順(書き順と違う!)

記述順 SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY ですが、DB エンジンが評価する順番は次の通り:

1. FROM        -- ベーステーブル決定
2. JOIN        -- 結合
3. WHERE       -- 行のフィルタ(集約前)
4. GROUP BY    -- グループ化
5. HAVING      -- グループのフィルタ(集約後)
6. SELECT      -- 列を確定(エイリアスもここで定義)
7. DISTINCT
8. ORDER BY    -- 並び替え(SELECT のエイリアスが使える)
9. LIMIT/OFFSET

これを覚えると「WHERE で SELECT のエイリアスが使えない」「HAVING と WHERE の使い分け」がすっきり理解できます。

INSERT — データを追加

-- 単一行
INSERT INTO users (name, email, created_at)
VALUES ('田中太郎', 'tanaka@example.com', NOW());

-- 複数行(MySQL/PostgreSQL)
INSERT INTO users (name, email) VALUES
  ('佐藤', 'sato@example.com'),
  ('鈴木', 'suzuki@example.com'),
  ('高橋', 'takahashi@example.com');

-- SELECT 結果を挿入
INSERT INTO users_archive (id, name, email)
SELECT id, name, email FROM users WHERE deleted_at IS NOT NULL;

UPDATE — 既存行を更新

-- WHERE 必須! 付け忘れると全行更新の事故
UPDATE users
SET status = 'inactive', updated_at = NOW()
WHERE last_login_at < '2024-01-01';

-- 別テーブルから値を引いて更新(MySQL)
UPDATE orders o
JOIN customers c ON o.customer_id = c.id
SET o.customer_name = c.name
WHERE o.customer_name IS NULL;

-- PostgreSQL は UPDATE ... FROM
UPDATE orders SET customer_name = c.name
FROM customers c
WHERE orders.customer_id = c.id AND orders.customer_name IS NULL;

事故防止: 本番では BEGIN; UPDATE ...; SELECT 件数確認; COMMIT; の手順で。

DELETE — 行を削除

-- 条件削除
DELETE FROM logs WHERE created_at < '2024-01-01';

-- 全行削除(DDL の TRUNCATE のほうが速い)
DELETE FROM tmp_table;     -- DML、行毎に削除、ROLLBACK 可
TRUNCATE TABLE tmp_table;  -- DDL、テーブルを切り詰め、高速、ROLLBACK 不可

JOIN — テーブルを結合

種類挙動典型用途
INNER JOIN両側にマッチする行のみ注文と顧客の交差
LEFT (OUTER) JOIN左を全て残し、右は NULL 許容「全ユーザーとその注文(注文無しも表示)」
RIGHT (OUTER) JOIN右を全て残すLEFT で書き換えるのが一般的
FULL OUTER JOIN両側を全て残す差分検出(MySQL 非対応 → UNION で代替)
CROSS JOIN直積カレンダー × 商品 等の組み合わせ
-- 注文と顧客(INNER)
SELECT o.id, o.amount, c.name
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id;

-- 注文のないユーザーを抽出(LEFT JOIN + IS NULL の定番)
SELECT u.id, u.name
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.id IS NULL;

GROUP BY / HAVING / 集約

-- カテゴリ別の売上集計
SELECT category_id,
       COUNT(*)     AS order_count,
       SUM(amount)  AS total_amount,
       AVG(amount)  AS avg_amount,
       MAX(amount)  AS max_amount,
       MIN(amount)  AS min_amount
FROM orders
WHERE created_at >= '2025-01-01'   -- 集約前の絞り込み
GROUP BY category_id
HAVING SUM(amount) >= 1000000      -- 集約後の絞り込み
ORDER BY total_amount DESC;

WHERE は行単位 / HAVING はグループ単位。COUNT(*) を WHERE に書くとエラーになります。

UNION — 結果セットを縦に結合

-- UNION: 重複を除く(遅い)
SELECT id, name FROM users_active
UNION
SELECT id, name FROM users_archive;

-- UNION ALL: 重複そのまま(速い、ふつうはこちら)
SELECT 'active' AS type, COUNT(*) FROM users_active
UNION ALL
SELECT 'archive', COUNT(*) FROM users_archive;

サブクエリと CTE (WITH)

-- サブクエリ(読みにくい)
SELECT id, name
FROM users
WHERE id IN (SELECT user_id FROM orders WHERE amount >= 10000);

-- CTE で書き直す(可読性 ◎)
WITH high_spenders AS (
  SELECT DISTINCT user_id FROM orders WHERE amount >= 10000
)
SELECT u.id, u.name
FROM users u
JOIN high_spenders h ON u.id = h.user_id;

-- 再帰 CTE(階層データ)
WITH RECURSIVE org_tree AS (
  SELECT id, name, parent_id, 1 AS depth FROM departments WHERE parent_id IS NULL
  UNION ALL
  SELECT d.id, d.name, d.parent_id, t.depth + 1
  FROM departments d JOIN org_tree t ON d.parent_id = t.id
)
SELECT * FROM org_tree ORDER BY depth, id;

トランザクション

-- 銀行振替の典型例
BEGIN;
  UPDATE accounts SET balance = balance - 1000 WHERE id = 1;
  UPDATE accounts SET balance = balance + 1000 WHERE id = 2;
  -- どちらか失敗したらロールバック
COMMIT;

-- 失敗時
ROLLBACK;

-- 部分ロールバック
BEGIN;
  INSERT INTO logs (msg) VALUES ('start');
  SAVEPOINT sp1;
  UPDATE accounts SET balance = balance - 1000 WHERE id = 1;
  ROLLBACK TO sp1;        -- ここまで戻す(INSERT は残る)
COMMIT;

FAQ

Q: NULL の比較で = NULL がヒットしない
A: NULL は「不明」を意味し、= では比較できません。IS NULL / IS NOT NULL を使います。

Q: 大文字小文字を区別する?
A: SQL のキーワード (SELECT 等) は通常大小無視。テーブル名・列名は DB と照合順序設定次第(MySQL Linux はデフォルトで大小区別)。

Q: 文字列のシングルクォートをエスケープする
A: '' と 2 連で書きます。例: 'It''s'。プレースホルダを使えば気にしなくて良い。

編集
Post Share
子ページ
  1. SELECT
  2. INSERT
同階層のページ
  1. 基本構文
  2. データベース関連
  3. テーブル関連
  4. ユーザー関連
  5. メタデータ関連
  6. NULL判定を伴うCASE分の使用方法