SQLの句の順番:書く順と実行される順の違いを図表で整理
書く順と実行される順のずれを知ると、別名エラーや HAVING の迷いがなくなる。
SQL の句には「書く順」と「実行される順」の 2 つがある
SELECT 文を書き始めたばかりのころにつまずきやすいのが、句(キーワードで始まるかたまり)の順番です。SQL には、人が書くときに守らなければならない「書く順」と、データベースが意味を解釈するときの「評価される順」(論理的な処理順)の 2 種類があります。この 2 つは一致しません。書く順を間違えると構文エラーになり、評価される順を知らないと「なぜこの別名が使えないのか」「なぜ WHERE に COUNT を書くとエラーになるのか」が理解できません。
なお「評価される順」は、結果がどう決まるかを説明するための論理的な順番です。実際のデータベースは、インデックスの利用や条件の先回りなど、結果が変わらない範囲で内部の処理を最適化します。ここでは結果を正しく予想するための考え方として扱います。
| 句 | 書く順 | 評価される順 | 役割 |
|---|---|---|---|
| WITH | 0(最初) | 0(最初) | 名前付きの一時的な結果(CTE)を定義する |
| SELECT | 1 | 5 | 出力する列や式、別名を決める |
| FROM / JOIN | 2 | 1 | どのテーブルから読むか、どう結合するか |
| WHERE | 3 | 2 | 行を条件で絞り込む(集計の前) |
| GROUP BY | 4 | 3 | 行をグループにまとめる |
| HAVING | 5 | 4 | グループを集計結果で絞り込む(集計の後) |
| ORDER BY | 6 | 6 | 結果を並べ替える |
| LIMIT / OFFSET | 7 | 7 | 先頭から何件を返すか、何件読み飛ばすか |
覚え方としては、書く順は「SELECT が先頭、あとは FROM から下へ流れる」、評価される順は「FROM から始まり、SELECT は集計が終わったあと」と押さえると整理しやすくなります。DISTINCT は SELECT の直後に書き、SELECT の評価と同じ段階で重複が除かれます。
書く順:SELECT から LIMIT まで
書く順は文法で決まっています。どれか 1 つでも順番を入れ替えると、多くのデータベースは構文エラー(syntax error)を返します。特に間違えやすいのは、ORDER BY と LIMIT の前後、WHERE と GROUP BY の前後、そして既存の SQL に条件を後から足すときの位置です。
WITH active_users AS ( SELECT id, dept FROM users WHERE active = 1)SELECT dept, COUNT(*) AS nFROM active_usersWHERE dept <> 'temp'GROUP BY deptHAVING COUNT(*) >= 2ORDER BY n DESCLIMIT 10 OFFSET 0;条件や件数を後から足すときの位置
すでに動いている SQL に条件や件数制限を足すときは、末尾にそのまま付け足すと順番が崩れがちです。WHERE の条件は ORDER BY より前、LIMIT は ORDER BY より後に入れます。
SELECT name FROM users WHERE active = 1 ORDER BY name AND age >= 20;SELECT name FROM users LIMIT 3 ORDER BY name;SELECT name FROM users WHERE active = 1 AND age >= 20 ORDER BY name;SELECT name FROM users ORDER BY name LIMIT 3;1 行目の悪い例は、データベースによってはエラーにならず「name AND age >= 20」という式で並べ替えるだけになり、20 歳未満の行も返ってきます。エラーが出ないことは正しいことの証明になりません。条件を足したら、結果の件数や中身も確かめます。
LIMIT と OFFSET
LIMIT は返す件数、OFFSET は先頭から読み飛ばす件数です。ORDER BY で並べ替えた後に適用されるので、ORDER BY id DESC LIMIT 2 OFFSET 1 なら「id の大きい順で 2 番目と 3 番目」が返ります。ORDER BY を付けずに LIMIT を使うと、どの行が返るかは保証されません。ページ送りなど順番に意味がある場面では、必ず ORDER BY と組み合わせます。LIMIT は PostgreSQL、MySQL、SQLite で使えますが、標準 SQL では FETCH FIRST n ROWS ONLY という書き方もあり、製品によって対応が異なります。
WITH 句は SELECT より前に書く
WITH 句(共通テーブル式、CTE)は、サブクエリに名前を付けて先に定義する書き方です。SELECT より前に書き、定義した名前は後ろの FROM などでテーブルのように使えます。長いサブクエリを入れ子にするより読みやすく、レビューもしやすくなります。
評価される順:FROM から始まり SELECT は後半
データベースは論理的には、まず FROM で対象のテーブル(JOIN があれば結合した結果)を用意し、WHERE で行を絞り、GROUP BY でまとめ、HAVING でグループを絞り、そこで初めて SELECT の列や式を計算します。その後に ORDER BY で並べ替え、最後に LIMIT と OFFSET で件数を切り出します。この順番を知っていると、次の 2 つの疑問に答えられます。
なぜ WHERE で別名が使えないのか
SELECT 句で AS を使って付けた別名(エイリアス)は、SELECT が評価された時点で生まれます。WHERE は SELECT より前に評価されるので、その時点では別名がまだ存在しません。そのため標準 SQL や PostgreSQL では、WHERE に別名を書くと「列が存在しない」というエラーになります。一方、ORDER BY は SELECT より後なので別名を使えます。
SELECT price * qty AS totalFROM order_itemsWHERE total > 1000;SELECT price * qty AS totalFROM order_itemsWHERE price * qty > 1000ORDER BY total DESC;MySQL や SQLite など一部の製品は、独自の拡張として WHERE や HAVING で別名を受け付ける場合があります。手元で動いても、別のデータベースへ移すとエラーになることがあるため、移植性を考えるなら式をそのまま書くか、サブクエリや WITH 句で別名を先に確定させます。
WHERE と HAVING の違い
WHERE はグループ化の前に、1 行ずつ条件を判定します。HAVING はグループ化の後に、グループごとの集計結果で条件を判定します。COUNT(*) のような集約関数は、グループができてからでないと値が決まりません。そのため WHERE COUNT(*) >= 2 はエラーになり、HAVING に書く必要があります。逆に、個々の行の条件(active = 1 など)は WHERE に書くほうが、集計する行がはじめから減るので意図が明確になり、効率の面でも有利なことが多くなります。
-- 悪い例:WHERE に集約関数SELECT dept, COUNT(*) AS n FROM emp WHERE COUNT(*) >= 2 GROUP BY dept;-- 良い例SELECT dept, COUNT(*) AS nFROM empWHERE active = 1GROUP BY deptHAVING COUNT(*) >= 2;| 観点 | WHERE | HAVING |
|---|---|---|
| 判定するタイミング | GROUP BY の前 | GROUP BY の後 |
| 判定する単位 | 1 行ずつ | 1 グループずつ |
| 集約関数(COUNT、SUM など) | 使えない | 使える |
| 主な用途 | 対象の行を絞る | 集計結果でグループを絞る |
よくある打ち間違いとレビューで見る点
句の順番と並んで多いのが、キーワードの打ち間違いです。FROM を FORM、HAVING を HAVNG、JOIN を JION、LIMIT を LIMT と打つと、データベースは見慣れない単語として扱い、エラーになります。また、WHEREdept のようにキーワードと列名の間のスペースが抜けると、WHEREdept という 1 つの名前として読まれます。列名の間のカンマ抜けは特に厄介で、SELECT name salary は「salary という別名を付けた name 列」と解釈され、エラーにならず 1 列だけが出力されます。
SELECT name FORM emp;SELECT * FROM emp WHEREdept = 'hr';SELECT name salary FROM emp;SELECT name, salary, FROM emp;SELECT dept, COUNT(*) FROM emp GROUP BY dept HAVNG COUNT(*) > 1;文字列の比較で "b" のようにダブルクォートを使うのも、よくある誤りです。標準 SQL ではダブルクォートは列名などの識別子を囲む記号で、文字列はシングルクォートで囲みます。SQLite のように互換のためダブルクォートを文字列として受け付ける製品もありますが、同名の列があると列として解釈されるため、意図しない結果になることがあります。
SQL を書いたら確かめるチェックリスト
- 句が SELECT、FROM、WHERE、GROUP BY、HAVING、ORDER BY、LIMIT の順に並んでいるか
- 後から足した条件が WHERE の中に AND でつながっているか(ORDER BY の後ろに付いていないか)
- WHERE で SELECT の別名を参照していないか
- 集約関数の条件が WHERE ではなく HAVING に書かれているか
- LIMIT を使うときに ORDER BY があり、ORDER BY の後に LIMIT があるか
- FROM、WHERE、HAVING、JOIN、LIMIT などの綴りと、キーワードの前後のスペース
- 列名の間のカンマが抜けていないか、最後の列の後ろに余計なカンマがないか
- 文字列がシングルクォートで囲まれているか
句の順番は、一度表で整理すれば忘れにくい知識です。一方で、打ち間違いやカンマ抜けは知識があっても起きます。書いた SQL を実行する前に声に出すつもりで 1 句ずつ読み、結果の件数と中身を想定と比べる習慣が、見落としを減らします。
問題で確かめる
この記事の内容を、制限時間つきの問題で確かめられます。
- SELECT 文の句を書く正しい順番は?SQL / 4 択
- SELECT 文の句をデータベースが評価する正しい順番は?SQL / 4 択
- SELECT 文で、データベースが最初に評価する句は?SQL / 4 択
- 先頭 5 件だけに絞る LIMIT 5 を足したい。正しく直したものは?SQL / 4 択
- 20 歳以上に絞る条件 AND age >= 20 を足したい。正しく直したものは?SQL / 4 択
- 名前順で先頭 3 件を取りたい。正しく直したものは?SQL / 4 択
- 次の SQL の結果は?(id は 1 から 5 の 5 行)SQL / 4 択
- 2 人以上いる部署だけを出したい。正しく直したものは?SQL / 4 択
- 2 人以上の部署に絞る HAVING COUNT(*) >= 2 を足したい。正しく直したものは?SQL / 4 択
- この SQL で綴りが間違っている語は?SQL / 4 択
- この SQL はエラーになる。正しく直して書いてください。SQL / 記述
- name と salary の 2 列を出したいのに 1 列しか出ない。正しく直したものは?SQL / 4 択