DDL / DML / DCL / TCL — SQLに含まれる4つの言語

SQLは一枚岩の言語ではなく、機能別に4つのサブ言語に分類されます。 役割を分けて覚えると、権限設計や用途の理解が格段に整理されます。

分類 略称 主要構文 用途
DDL Data Definition Language スキーマ定義 CREATE / ALTER / DROP / TRUNCATE テーブル・インデックス・ビュー等「」の定義
DML Data Manipulation Language データ操作 SELECT / INSERT / UPDATE / DELETE / MERGE 器の中の「中身」の読み書き
DCL Data Control Language 権限制御 GRANT / REVOKE ユーザー・ロールへの権限付与/剥奪
TCL Transaction Control Language トランザクション BEGIN / COMMIT / ROLLBACK / SAVEPOINT トランザクションの境界管理

論理処理順序 — SELECTが先じゃない

SQL初学者が最初に詰まる最大の罠は「書く順序と実行順序が違う」ことです。 FROMで指定したテーブルがWHEREの時点ではまだ絞られていない、と誤解するとバグの温床になります。

flowchart TD
    F["① FROM / JOIN<br/>(入力関係を構築)"] --> W["② WHERE<br/>(行フィルタ)"]
    W --> G["③ GROUP BY<br/>(グループ化)"]
    G --> H["④ HAVING<br/>(グループフィルタ)"]
    H --> S["⑤ SELECT<br/>(射影・式評価)"]
    S --> D["⑥ DISTINCT<br/>(重複除去)"]
    D --> O["⑦ ORDER BY<br/>(並び替え)"]
    O --> L["⑧ LIMIT / OFFSET<br/>(件数絞り込み)"]
    style F fill:#3b82f6,color:#fff
    style L fill:#10b981,color:#fff
SQLの論理実行順序 — 記述順(SELECT→FROM→WHERE)とは異なる

この順序から導かれる実務上の帰結を整理しましょう。

-- ❌ NG: WHERE句で「SELECTで作ったエイリアス」は参照できない
SELECT price * 1.1 AS price_with_tax
FROM products
WHERE price_with_tax > 1000;  -- ERROR: "price_with_tax" doesn't exist yet

-- ✅ OK: サブクエリかCTEで回避する
SELECT price_with_tax
FROM (
  SELECT price * 1.1 AS price_with_tax FROM products
) t
WHERE price_with_tax > 1000;

-- ✅ あるいはWHEREで式を直接書く
SELECT price * 1.1 AS price_with_tax
FROM products
WHERE price * 1.1 > 1000;

-- ✅ ORDER BY では SELECT のエイリアスを参照できる
-- (ORDER BY は SELECT より後に実行されるから)
SELECT price * 1.1 AS price_with_tax
FROM products
ORDER BY price_with_tax DESC;

JOIN 6種類 — INNER / LEFT / RIGHT / FULL / CROSS / SELF

JOINはSQL初学者が最も混乱するポイントの一つです。6種類を視覚的かつ集合論的に理解しましょう。

種類 SQL構文 意味 NULLの扱い
INNER JOIN A INNER JOIN B ON ... 両テーブルで条件を満たす行だけ 一致しない行は除外
LEFT OUTER JOIN A LEFT JOIN B ON ... Aを全部残す。Bに対応がなければB側はNULL B側の列がNULLで埋まる
RIGHT OUTER JOIN A RIGHT JOIN B ON ... Bを全部残す。Aに対応がなければA側はNULL A側の列がNULLで埋まる
FULL OUTER JOIN A FULL JOIN B ON ... 両方を全部残す。対応がなければ反対側はNULL 両側ともNULL可
CROSS JOIN A CROSS JOIN B デカルト積 (m×n行)
SELF JOIN A a1 JOIN A a2 ON ... 同じテーブルを別名で自己結合 木構造・組織階層でよく使う
-- 例: users(id, name, dept_id), departments(id, name)
-- LEFT JOIN: 部署未所属のユーザーも残したい
SELECT u.name, d.name AS dept
FROM users u
LEFT JOIN departments d ON u.dept_id = d.id;
-- 結果: dept_id が NULL or 存在しない d.id のユーザーも残り、d.name は NULL

-- FULL JOIN: ユーザーも部署も漏らしたくない(存在しない側はNULL)
SELECT u.name, d.name AS dept
FROM users u
FULL JOIN departments d ON u.dept_id = d.id;
-- MySQL には FULL JOIN がないので UNION で代替:
-- (LEFT JOIN の結果) UNION (LEFT JOIN を逆向きにした結果)

-- SELF JOIN: 同僚(同じdept_id)の組を一覧化
SELECT e1.name AS employee, e2.name AS coworker
FROM employees e1
JOIN employees e2
  ON e1.dept_id = e2.dept_id
 AND e1.id < e2.id;  -- 同じ組を2回数えない工夫
flowchart LR
    subgraph INNER["INNER JOIN"]
      I1["A ∩ B<br/>(条件を満たす組のみ)"]
    end
    subgraph LEFT["LEFT JOIN"]
      L1["A 全部<br/>+ 対応する B"]
    end
    subgraph FULL["FULL JOIN"]
      F1["A ∪ B<br/>対応無しはNULL"]
    end
    subgraph CROSS["CROSS JOIN"]
      C1["A × B<br/>(m×n行)"]
    end
JOIN 4種類の集合論的イメージ

NULL と 3値論理 — SQL最大の落とし穴

NULLはSQLの3値論理(Three-Valued Logic, 3VL)の産物で、初学者を最も苦しめる概念です。 「値がない」という状態を表すマーカーで、値ではない点がポイントです。

結果 説明
NULL = NULL NULL (UNKNOWN) 「未知 = 未知」は未知。TRUEにならない
NULL = 1 NULL (UNKNOWN) 未知と比較するとすべて未知
NULL != NULL NULL (UNKNOWN) 不等号でも結果は未知
NULL IS NULL TRUE IS NULL / IS NOT NULL が唯一の正しい判定
NULL AND FALSE FALSE FALSEが効く(短絡)
NULL AND TRUE NULL 未知とTRUEは未知
NULL OR TRUE TRUE TRUEが効く(短絡)
NULL OR FALSE NULL 未知とFALSEは未知
-- NULLのアンチパターン
SELECT * FROM orders WHERE cancel_reason != 'fraud';
-- ↑ cancel_reason が NULL の行は「詐欺ではない」はずなのに結果から消える!

-- 正しい書き方
SELECT * FROM orders
WHERE cancel_reason != 'fraud' OR cancel_reason IS NULL;
-- または COALESCE で NULL を代替値に
SELECT * FROM orders WHERE COALESCE(cancel_reason, '') != 'fraud';

-- NULL を集約関数はスキップする(COUNT(*)は含めるがCOUNT(col)は除外)
SELECT COUNT(*) FROM t;        -- 全行数
SELECT COUNT(col) FROM t;      -- col が NULL でない行数
SELECT AVG(col) FROM t;        -- NULL を除外して平均
SELECT SUM(col) FROM t;        -- NULL を除外して合計
-- 全部NULLなら AVG/SUM は NULL(0ではない)を返す

初学者が詰まる Top 5

  1. WHERE句でSELECTのエイリアスが使えない — 論理実行順序で WHERE(②) が SELECT(⑤) より先だから
  2. COUNT(*) と COUNT(col) の違い — 前者は全行、後者はNULLでない行だけカウント
  3. GROUP BYに指定しない列はSELECTできない — PostgreSQLは厳格、MySQLは緩い(ONLY_FULL_GROUP_BYで切替可)
  4. JOINした後のWHEREとON句の違い — OUTER JOINで条件を WHERE に書くとINNER JOIN相当になってしまう
  5. NULLを等号で比較してしまう — IS NULL / IS NOT NULL を使う。COALESCEで代替値を入れるのも常套手段
-- 詰まりポイント④: LEFT JOIN + WHERE vs LEFT JOIN + ON
-- ❌ WHERE に右側の条件を書くと OUTER → INNER に劣化する
SELECT u.name, o.total
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.total > 1000;
-- ↑ orders がない user は o.total が NULL なので WHERE で消える
-- → 結果的に INNER JOIN と同じになる

-- ✅ 右側の追加条件は ON 句に置く
SELECT u.name, o.total
FROM users u
LEFT JOIN orders o
  ON u.id = o.user_id
 AND o.total > 1000;
-- ↑ LEFT JOIN が先に実行され、users 全員が残る
-- orders が無い or 1000以下のユーザーも o.total = NULL で出現

第4章のまとめ

  • SQLはDDL(定義)/ DML(操作)/ DCL(権限)/ TCL(トランザクション)の4つのサブ言語からなる
  • 論理実行順序はFROM → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT。記述順と異なる
  • WHERE は集約前の行フィルタ、HAVING はグループ化後のフィルタ。集約条件はHAVING
  • JOINは6種類(INNER/LEFT/RIGHT/FULL/CROSS/SELF)。OUTER JOINの右側条件はON句に置く
  • NULLは値ではなく「未知」マーカー。3値論理(TRUE/FALSE/UNKNOWN)に従う。WHEREはTRUEのみ通すため、NULLの混じる列は IS NULL や COALESCE で明示的に扱う

次章では、現代SQLの3本柱であるサブクエリ・CTE・ウィンドウ関数に進みます。 これらはSQL:1999とSQL:2003で標準化された比較的新しい機能ですが、 現代SQLのエレガンスはここで花開きます。

理解度チェック

問題 0 / 50%
Q1

SQLの論理実行順序として正しい順に並べてください。

矢印ボタンで正しい順序に並べ替えてください

1WHERE (行フィルタ)
2GROUP BY (グループ化)
3SELECT (射影)
4ORDER BY (並び替え)
5FROM (入力関係構築)