SELECT文が実行される順番
書く順番と実行される順番は違う
SQLを書くとき、私たちは自然と次のような順番で記述します。
SELECT name, AVG(score) AS avg_score
FROM students
WHERE grade = 3
GROUP BY name
HAVING AVG(score) >= 70
ORDER BY avg_score DESC
LIMIT 10;
しかしデータベースエンジンがこのクエリを実行する順番は、書いた順番とは異なります。
実行順序を正しく把握していないと「なぜエラーになるのか」「なぜ意図した結果が返らないのか」が理解できません。このエピソードでは実行順序を徹底的に理解しましょう。
実行順序の全体像
SELECT文? テーブルからデータを取得するSQL文。`SELECT * FROM users` のように使う。WHERE で絞り込み、ORDER BY で並び替え、LIMIT で件数を制限できる。 文の実行順序は以下のとおりです。
| ステップ | 句 | 処理内容 |
|---|---|---|
| 1 | FROM | 対象テーブルを特定 |
| 2 | JOIN | テーブルを結合 |
| 3 | WHERE | 行レベルのフィルタリング |
| 4 | GROUP BY | 行をグループ化 |
| 5 | HAVING | グループレベルのフィルタリング |
| 6 | SELECT | 列の選択・式の評価 |
| 7 | DISTINCT | 重複行の除去 |
| 8 | ORDER BY | 結果の並び替え |
| 9 | LIMIT | 行数の制限 |
各ステップの詳細
FROM と JOIN
最初に実行されるのは FROM 句です。どのテーブルを対象にするかを決めます。複数テーブルがある場合は JOIN が続き、結合した仮想テーブルが生成されます。
-- この時点では全列・全行が対象
FROM orders
JOIN customers ON orders.customer_id = customers.id
WHERE
次に WHERE句? SELECT・UPDATE・DELETE文で行を絞り込む条件を指定する部分。`WHERE age >= 20 AND status = 'active'` のように複数条件を AND / OR で組み合わせられる。 句が適用されます。FROM/JOINで作られた仮想テーブルの各行に対して条件を評価し、条件を満たさない行を除去します。
WHERE orders.created_at >= '2025-01-01'
ポイント: WHERE が実行される時点ではまだ SELECT が評価されていません。つまり SELECT で定義したエイリアスは WHERE では使えません。
GROUP BY
GROUP BY? 指定した列の値が同じ行をまとめてグループ化する句。COUNT・SUM・AVGなどの集計関数と組み合わせて使う。「カテゴリ別の件数を集計」などに使用する。 句によって行がグループ化されます。同じ値を持つ行をひとまとめにし、集約関数(SUM、COUNT、AVGなど)の計算対象となるグループを作ります。
GROUP BY customer_id
HAVING
HAVING句? GROUP BY後のグループに対して絞り込み条件を適用する句。`HAVING COUNT(*) >= 5` のように集計結果でフィルタする。行レベルの絞り込みはWHERE、グループへの絞り込みはHAVING。 句はグループ化された後の結果にフィルターをかけます。WHERE とは異なり、 集計関数? グループや全行に対して計算を行う関数。COUNT(件数)・SUM(合計)・AVG(平均)・MAX(最大)・MIN(最小)がある。GROUP BY と組み合わせて使うことが多い。 関数の結果を条件に使えます。
HAVING COUNT(orders.id) >= 3
SELECT
ここで初めて SELECT 句が評価されます。列の選択、計算式の評価、エイリアスの定義が行われます。
SELECT customer_id, COUNT(*) AS order_count
ORDER BY と LIMIT
ORDER BY? クエリ結果を特定の列で並び替える句。ASC(昇順・デフォルト)またはDESC(降順)を指定できる。複数の列を指定して優先順位をつけることも可能。 は SELECT の後に実行されます。SELECT で定義したエイリアスを ORDER BY で使えるのはこのためです。最後に LIMIT? クエリ結果の取得件数を制限する句。`LIMIT 10` で最初の10件のみ取得する。OFFSET と組み合わせてページネーションを実装できる。 で行数を絞り込みます。
なぜエイリアスを WHERE で使えないか
よくある疑問として「SELECTで定義したエイリアスをWHEREで使えないのはなぜ?」というものがあります。
-- エラーになるクエリ
SELECT price * 1.1 AS price_with_tax
FROM products
WHERE price_with_tax > 1000; -- エラー: price_with_tax は未定義
実行順序を見れば一目瞭然です。WHERE(ステップ3)の時点では SELECT(ステップ6)はまだ実行されていないため、エイリアス price_with_tax は存在していないのです。
回避策は2つあります。
方法1: 式を直接書く
SELECT price * 1.1 AS price_with_tax
FROM products
WHERE price * 1.1 > 1000; -- 式を繰り返す
方法2: サブクエリを使う
SELECT *
FROM (
SELECT price * 1.1 AS price_with_tax
FROM products
) AS t
WHERE price_with_tax > 1000; -- ここは SELECT の後なので使える
ORDER BY だけはエイリアスを使える
ORDER BY はステップ8で実行されるため、SELECT で定義したエイリアスを参照できます。
SELECT name, price * 1.1 AS price_with_tax
FROM products
ORDER BY price_with_tax DESC; -- これは OK
MySQLやPostgreSQLなど多くのDBでは、GROUP BY でも SELECT のエイリアスを使える場合がありますが、これは標準SQLの仕様ではなく、DBによる独自の拡張です。移植性を考えると式を直接書くのが安全です。
実行順序を意識したクエリの書き方
実行順序を理解すると、クエリの設計方針が変わります。
パフォーマンスへの影響
WHERE でできるだけ早く行を絞り込むことが重要です。GROUP BY の前に WHERE でフィルタリングすれば、集約処理の対象となる行数を減らせます。
-- 悪い例: 全行を集約してからフィルタ
SELECT customer_id, COUNT(*) AS cnt
FROM orders
GROUP BY customer_id
HAVING order_status = 'completed'; -- HASVINGで行のフィルタはNG
-- 良い例: 先にWHEREで絞り込む
SELECT customer_id, COUNT(*) AS cnt
FROM orders
WHERE order_status = 'completed' -- 先に絞る
GROUP BY customer_id;
※ HAVING は集約後の条件(COUNT(*) >= 5 など)に使うべきものです。行レベルの条件は必ず WHERE で書きましょう。
デバッグへの活用
クエリがエラーを返したとき、「今はどのステップで失敗しているか?」を実行順序に照らし合わせて考えると原因を素早く特定できます。
まとめ
- SELECT文の実行順序は
FROM → JOIN → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT - 書く順番と実行順番は異なる
- SELECT のエイリアスは WHERE では使えないが ORDER BY では使える
- WHERE でグループ前に行を絞り込むとパフォーマンスが向上する
次回は JOIN? 複数のテーブルを結合して一つの結果セットにする操作。INNER JOIN(両方に存在する行のみ)・LEFT JOIN(左テーブルの全行 + 一致した右テーブル)などがある。 の内部アルゴリズムと各結合タイプの違いを深掘りします。