【手を動かす道場】SQL実戦10本ノック(抽出・集計・結合の「芯」をブラウザ内DBで完全制覇)
「SQLの本を1冊読んだのに、いざ実務のデータベースを前にすると、最初の1行すら手が動かない」 「100本ノックのサイトを開いたが、環境構築でつまずき、問題が多すぎて3日で挫折した」
そんな経験はありませんか? 断言します。実務の現場(データ抽出、売上集計、システム調査)で日常的に使われるSQLの90%以上は、マニアックな関数ではなく**「たった10個の基本パターン(型)の組み合わせ」**で書かれています。
しかも、SQLが書けない最大の原因は「英単語を忘れたから」ではありません。 「人間がSQLを書く順番」と「データベースが裏側でデータを処理する物理的な順番」がまったく逆であるという事実を知らないまま、丸暗記しようとしているからです。
この記事では、環境構築は一切不要です。 まずは5分で「SQLが裏で動く物理順序」を腹落ちさせ、そのままページ内の**「埋め込みSQL道場」で実際にコードを打ち込み、自分の手で10本ノックをクリア**してください。
1. なぜSQLは混乱するのか?「書く順番」と「動く順番」のねじれ
私たちがSQLを書くとき、必ず SELECT(どの列を表示するか)から書き始めます。英語の語順(“Select A from B where C…”)に似せるため、1970年代にIBMの研究者たちがそう設計したからです。
しかし、データベースエンジン(MySQLやPostgreSQL、SQLiteなど)がメモリとディスクの上で実際にデータを処理する順番は、『FROM(どの表を広げるか)』が絶対の最初であり、『SELECT(どの列を切り出すか)』はほぼ最後です。
この「物理順序」を知ると、長年の謎が一瞬で解ける
- なぜ
WHEREの中でSUM()やCOUNT()が使えないのか?WHEREが実行されるのは第2段階、データを束ねるGROUP BY(第3段階)の前だからです。まだ束ねてすらいない段階で「束の合計が100以上」という条件は物理的に判定できません。だから束ねた後に判定するHAVING(第4段階)が存在するのです。 - なぜ
SELECTでつけた別名(AS totalなど)がWHEREで使えず、ORDER BYでは使えるのか?SELECTが実行されるのは第5段階だからです。第2段階のWHEREの時点では、まだその別名はこの世に誕生していません。一方、第6段階のORDER BYの時点ではすでにSELECTが終わっているため、別名が使えるのです。
これから挑む10本ノックでは、常に頭の中で**「①まず FROM で表を思い浮かべ、② WHERE で行を削り、③最後に SELECT で列を抜く」**という物理イメージを持ってキーボードを叩いてください。
🛒 ミニSQL実行体験:商品テーブルを絞って、並べる
下の商品テーブル(products)に対して、WHERE(行の絞り込み)と ORDER BY(並び替え)の部品ボタンを選んでください。選ぶたびにSQL文が組み上がり、結果表がその場で変わります。第1節の「物理順序」どおり、まず WHERE で行が削られ、最後に ORDER BY で並び替えられる流れを目で追ってみてください(下の本格道場のウォーミングアップです)。
WHERE(行の絞り込み)
ORDER BY(並び替え)
2. 【実践】ブラウザ内完結・SQL実戦10本ノック道場
下のボックスは、あなたのブラウザの中で実際に動いているデータベース環境です。
とあるIT企業「アストロ商事」の社員テーブル(employees)、部署テーブル(departments)、**売上テーブル(sales)**の3つが格納されています。
問題を読み、エディタにSQLを入力して**「▶ SQLを実行&採点」**ボタンを押してください(自由にクエリを書き換えて実験しても壊れません)。
📊 データベースのテーブル構造(クリックで開閉)
・
id (社員ID)・
name (氏名)・
dept_id (部署ID)・
salary (月給・万円)・
join_year (入社年)
・
id (部署ID: 10, 20, 30)・
dept_name (部署名: 営業部, 開発部, 総務部)
・
id (売上ID)・
emp_id (担当社員ID)・
amount (売上金額・万円)・
sale_month (売上月)
3. 全10本ノックの「急所」と模範解答・完全解説
道場で手を動かした後に読むことで、記憶が10倍定着する「全10問の物理メカニズム解説」です。スマホで読んでいる方も、ここを読むだけで現場のSQLの急所がすべて頭に入ります。
第1問〜第4問:データを削り出し、整列させる基本の型
【Q1】特定の列だけを取り出す(プロジェクション:射影)
SELECT name, salary FROM employees;
- 現場の鉄則:実務では
SELECT *(全列取得)は原則禁止です。数百万行あるテーブルで不要な列までディスクから読み出すと、メモリとネットワーク帯域(I/O)を無駄に食い潰すからです。必要な列だけを指名するのがプロの第一歩です。
【Q2】条件で行を絞り込む(セレクション:選択)
SELECT * FROM employees WHERE salary >= 45;
- 物理の急所:
WHEREはテーブルを1行ずつスキャン(またはインデックス検索)し、条件がTRUEになる行だけを次のステップへ通す「関所」です。
【Q3】複数条件の組み合わせ(論理演算 AND / OR)
SELECT name, join_year FROM employees
WHERE dept_id = 10 AND join_year >= 2020;
- 現場の罠:もし
ANDとORを混ぜて使う場合は、必ずカッコ()で囲む癖をつけてください。掛け算と足し算のように、ANDの方がORより優先順位が高いため、カッコを省略すると意図しないデータが混입する重大バグになります。
【Q4】並び替えと件数制限(ORDER BY + LIMIT)
SELECT name, salary FROM employees
ORDER BY salary DESC
LIMIT 3;
- 昇順と降順:小さい順(昇順)は
ASC(省略可)、大きい順(降順)はDESCです。売上ランキングや「直近の最新ログ10件」を取る際に毎日使う黄金コンビです。
第5問〜第7問:データを束ねて集計する型(GROUP BY と HAVING)
【Q5】テーブル全体の集計関数
SELECT COUNT(*), AVG(salary) FROM employees;
- 5大集計関数:件数を数える
COUNT()、合計のSUM()、平均のAVG()、最大値のMAX()、最小値のMIN()。この5つだけで世の中の集計レポートの9割が作られています。
【Q6】グループごとの集計(GROUP BY)
SELECT dept_id, COUNT(*) FROM employees
GROUP BY dept_id;
- 絶対のルール:
GROUP BYを使ったとき、SELECT句に書けるのは**「GROUP BYに指定した列(ここではdept_id)」か「集計関数(COUNTなど)」**の2種類だけです。束ねた後に「個人の名前(name)」を表示しようとしても、1つの束の中に複数人の名前が入っているため、データベースはどれを表示していいか分からずエラーになります。
【Q7】集計した結果に対する絞り込み(HAVING)
SELECT emp_id, SUM(amount) FROM sales
GROUP BY emp_id
HAVING SUM(amount) >= 200;
- 試験にも実務にも出る最重要ポイント:
- グループ化する前に「1件ごとの売上」で絞るなら
WHERE amount >= 100 - グループ化した後に「合計した売上」で絞るなら
HAVING SUM(amount) >= 200この2つの使い分けができれば、脱・初心者です。
- グループ化する前に「1件ごとの売上」で絞るなら
第8問〜第10問:複数の表を繋ぎ、高度な抽出を行う型(JOIN とサブクエリ)
【Q8】内部結合(INNER JOIN)
SELECT e.name, d.dept_name
FROM employees e
JOIN departments d ON e.dept_id = d.id;
- なぜ表を分けて保存するのか?(正規化):もし社員テーブルに直接「営業部」と文字で書いてしまうと、部署名が「第一営業部」に変わったときに何千人ものデータを書き換えなければなりません。ID(数字)だけを持たせておき、表示するときに
JOINでくっつけるのがリレーショナルデータベース(RDB)の心臓部です。
【Q9】外部結合と COUNT の罠(LEFT JOIN)
SELECT e.name, COUNT(s.id)
FROM employees e
LEFT JOIN sales s ON e.id = s.emp_id
GROUP BY e.id, e.name;
- プロでも引っかかる最大の罠:
売上がない社員も「0件」として一覧に出したいとき、普通の
JOIN(内部結合)を使うと、売上テーブルにデータがない「田中さん」と「伊藤さん」は結果から消えてしまいます。 必ずLEFT JOINを使い、さらにCOUNT(*)ではなくCOUNT(s.id)(右側のテーブルの列)を数えるのが鉄則です。COUNT(*)にすると、NULLの行まで「1行ある」と数えてしまい、売上がないのに「1件」と誤集計されてしまいます。
【Q10】サブクエリ(クエリの中のクエリ)
SELECT name, salary FROM employees
WHERE salary > (
SELECT AVG(salary) FROM employees
);
- 二段階ロケットの思考法:「平均月給より高い人」を出したいときは、まずカッコの中で
SELECT AVG(salary) FROM employees(=47.33万円)を計算させ、その結果を外側のWHEREに渡します。複雑な問題も、小さなSQLに分解してカッコで繋げば必ず解けます。
結論:SQLとは「集合を削り出す彫刻」である
プログラミング言語(PythonやJavaなど)が「1行ずつループを回して手順を指示する(手続き型)」のに対し、SQLは**「どんなデータの集合が欲しいかという『完成形』を宣言する(宣言型)」**言語です。
FROMとJOINで、必要な粘土の塊(テーブル)を作業台に広げる。WHEREで、いらない部分を大まかに削ぎ落とす。GROUP BYとHAVINGで、形を束ねて整える。SELECTとORDER BYで、最後に必要な断面だけを切り出して磨く。
この彫刻の順番さえ指先に馴染めば、どんなに巨大で複雑なデータベースを前にしても、迷うことは二度とありません。
📝 3行で頭に入る『試験・実務・投資の要点まとめ』
- 【実行順序の物理】 SQLは
SELECTから書くが、DB内部の物理処理はFROM/JOIN➔WHERE➔GROUP BY➔HAVING➔SELECT➔ORDER BYの順で実行される。 - 【WHEREとHAVINGの境界】 グループ化する前の「個別の行」を絞るのが
WHERE、グループ化した後の「集計結果(合計や件数)」を絞るのがHAVINGである。 - 【LEFT JOINとCOUNTの急所】 「実績0件の人」も一覧に出すには
LEFT JOINが必須であり、その際はCOUNT(*)ではなく結合先の列名COUNT(s.id)を指定しないと0件が1件と誤判定される。