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この順序から導かれる実務上の帰結を整理しましょう。
-- ❌ 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行)"]
endNULL と 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
- WHERE句でSELECTのエイリアスが使えない — 論理実行順序で WHERE(②) が SELECT(⑤) より先だから
- COUNT(*) と COUNT(col) の違い — 前者は全行、後者はNULLでない行だけカウント
- GROUP BYに指定しない列はSELECTできない — PostgreSQLは厳格、MySQLは緩い(ONLY_FULL_GROUP_BYで切替可)
- JOINした後のWHEREとON句の違い — OUTER JOINで条件を
WHEREに書くとINNER JOIN相当になってしまう - 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 (入力関係構築)