SQLにはさまざまな構文がありますが、「再帰的SQL」は一歩進んだ理解が求められる領域です。特に応用情報技術者試験などの午後試験では、ツリー構造や階層構造のデータ操作に関連して、WITH RECURSIVE句を用いた問題がしばしば出題されます。
「そもそも再帰的SQLとは何か?」「WITH RECURSIVEで何ができるのか?」「使いどころと避けるべきケースは?」といった基本的な疑問に加え、「実際の試験で問われやすいポイント」「間違えやすい選択肢」などを明らかにしながら、学習者がつまずきやすい“再帰”というテーマを解きほぐしていきます。
本記事では、体系的な整理(Aスタイル)に加え、身近な例え(C)や職場の構造(D)、中学生でも分かる会話形式(E)などを用い、理解と実践力の両方を鍛えます。さらに図解・表・具体例も活用し、午後試験で差がつく“再帰的SQL”を完全攻略しましょう。
目次
再帰的SQLとは何か?│WITH RECURSIVE句の基本構造と考え方
「再帰的SQL」という言葉を聞くと、少し難しそうな印象を受けるかもしれませんが、本質的には「ある処理を繰り返して階層的なデータを取得する」仕組みです。
例えば、会社の組織構造を想像してみてください。ある社員の上司をたどると、その上にもさらに上司がいます。このように「階層」があるデータを扱うとき、従来のSQL(JOINやサブクエリ)では「何階層あるか」が事前に分かっていないと、すべての階層を取得できません。
たとえば3階層までならJOINを3回重ねればいいですが、5階層か10階層か分からない場合、事前に何回JOINすべきか分からず、SQLが書けません。ここで活躍するのがWITH RECURSIVE句です。
WITH RECURSIVEを使うと、初期状態から1階層ずつ再帰的に処理を繰り返し、自動的に全階層を走査できます。つまり「階層の深さが不明でも、すべての親子関係を辿れる」のが最大の強みなのです。
■ WITH RECURSIVE句とは?
WITH RECURSIVE 句は、自己参照(再帰)するようなクエリを記述するためのSQL構文です。以下の2つのパートで構成されます。
| パート名 | 説明 |
|---|---|
| アンカー部(初期値) | 再帰のスタート地点となるデータ(例:最上位の社員) |
| 再帰部 | アンカーの結果を基に、さらに1階層ずつ下のデータを取得していく |
これらを繰り返すことで、SQL自身がループを回すように動作し、階層データを1行ずつ下って取得していくことができます。
■ 基本構造(テンプレート)
WITH RECURSIVE 階層名(列1, 列2, ...) AS (
-- アンカー部(初期状態)
SELECT ...
FROM ...
WHERE 条件
UNION ALL
-- 再帰部(次の階層)
SELECT ...
FROM 元テーブル JOIN 階層名
ON 結合条件
)
SELECT * FROM 階層名;
■ 身近な例え:家系図をたどる
家族の家系図を想像してください。あなたの祖父母を調べ、そこから親→自分→子→孫…と世代を下っていくのが再帰処理のイメージです。1回で全員を見るのではなく、「1ステップずつ下へ下へ」と降りていくのがポイントです。
通常のWITH(CTE)とは
SQLの CTE(Common Table Expression: 共通テーブル式) は、WITH ~ AS (...) の形で一時的な結果セットに名前をつけ、クエリの中で再利用できる仕組みです。
WITH cte_sales AS (
SELECT customer_id, SUM(amount) AS total
FROM sales
GROUP BY customer_id
)
SELECT *
FROM cte_sales
WHERE total > 1000;
この場合、cte_sales が一時的な「仮想テーブル」として使われています。
WITH RECURSIVEはCTEの拡張版
通常の WITH は単純な一時テーブルの作成ですが、WITH RECURSIVE は「自己参照できるCTE」 を作るための拡張機能です。
WITH… 一度作ったら使うだけWITH RECURSIVE… 自分自身を呼び出しながら繰り返し処理できる
つまり、WITH RECURSIVE は CTEの派生形 と言えます。
なぜ必要か?
親子関係や階層構造を持つデータ(組織ツリー、カテゴリ階層など)を扱うときに有効です。
WITH RECURSIVE を使うと、親 → 子 → 孫 といった構造をシンプルなSQLでたどれます。
具体例:カテゴリの階層取得
WITH RECURSIVE hierarchy AS (
-- 基点(親)
SELECT id, parent_id, name, 1 AS level
FROM categories
WHERE parent_id IS NULL
UNION ALL
-- 再帰(子をたどる)
SELECT c.id, c.parent_id, c.name, h.level + 1
FROM categories c
INNER JOIN hierarchy h ON c.parent_id = h.id
)
SELECT * FROM hierarchy;
このクエリでは、hierarchy がCTE名として定義され、
親カテゴリから子カテゴリ、さらに孫カテゴリまで階層的に展開できます。
まとめ
WITHは一時的なテーブル(CTE)を作る仕組みWITH RECURSIVEはCTEの派生形で、自己参照による再帰処理が可能- 階層データ(ツリー構造など)の取得に便利
WITH RECURSIVEの要点まとめ(CTEの派生形/行が増える流れ)
1. CTE名(hierarchyなど)について
WITH RECURSIVE hierarchy AS (...)の hierarchy は「共通テーブル式(CTE)の名前」。実テーブルではありません。- クエリ実行中だけ存在し、
FROM hierarchyのように参照できます。 - 名前は固定で、増えるのは中身の行です。
2. WITH と WITH RECURSIVE の違い(簡潔版)
WITH:一時結果(CTE)を定義して再利用する。WITH RECURSIVE:CTEが自己参照でき、階層データを「親→子→孫…」と段階的に展開できる。
3. 行がどんどん増える工程(実データ例つき)
元テーブル categories:
| id | parent_id | name |
|---|---|---|
| 1 | NULL | 家電 |
| 2 | 1 | テレビ |
| 3 | 1 | 冷蔵庫 |
| 4 | 2 | 4Kテレビ |
| 5 | 2 | 液晶テレビ |
WITH RECURSIVE hierarchy AS (
-- ① アンカー(基点)
SELECT id, parent_id, name, 1 AS level
FROM categories
WHERE parent_id IS NULL
UNION ALL
-- ② 再帰(親=hierarchy にぶら下がる子を追加)
SELECT c.id, c.parent_id, c.name, h.level + 1
FROM categories AS c
JOIN hierarchy AS h ON c.parent_id = h.id
)
SELECT * FROM hierarchy;
工程ごとの hierarchy の中身:
工程①(アンカー実行後)
| id | parent_id | name | level |
|---|---|---|---|
| 1 | NULL | 家電 | 1 |
工程②(再帰1回目:親=1の子を追加)
| id | parent_id | name | level |
|---|---|---|---|
| 1 | NULL | 家電 | 1 |
| 2 | 1 | テレビ | 2 |
| 3 | 1 | 冷蔵庫 | 2 |
工程③(再帰2回目:親=2の子を追加)
| id | parent_id | name | level |
|---|---|---|---|
| 1 | NULL | 家電 | 1 |
| 2 | 1 | テレビ | 2 |
| 3 | 1 | 冷蔵庫 | 2 |
| 4 | 2 | 4Kテレビ | 3 |
| 5 | 2 | 液晶テレビ | 3 |
工程④(再帰3回目:新しい子が見つからず終了)
最終的に hierarchy には親→子→孫までの全行が入ります。
4. なぜ JOIN が必要?(要点)
JOINは、「すでに見つかった親(h.id)」にぶら下がる子(c.parent_id)だけを次の工程に追加するため。- これにより、階層が1段ずつ正しく広がり、全件を一気に入れてしまう誤りを防げます。
5. JOINしても列が増えない理由
- 内部では増えている:
JOIN時点ではcategoriesとhierarchyの列が結合され、一時的に列は増えます。 - 結果はSELECTで決まる: しかし最終的に出力される列は
SELECT句で指定したものだけ(例:c.id, c.parent_id, c.name, h.level + 1)。不要な列は出力されません。 - UNION ALLの制約: アンカー部と再帰部は
UNION ALLで結合するため、列数・型を一致させる必要があります。よって再帰部でも列は固定に保ちます。
合言葉: JOIN は「行を決める」・SELECT は「列を決める」
6. 使い回しテンプレ(列名は環境に合わせて変更)
WITH RECURSIVE hierarchy (id, parent_id, name, level) AS (
SELECT id, parent_id, name, 1
FROM categories
WHERE parent_id IS NULL
UNION ALL
SELECT c.id, c.parent_id, c.name, h.level + 1
FROM categories AS c
JOIN hierarchy AS h ON c.parent_id = h.id
)
SELECT * FROM hierarchy
ORDER BY level, id;
WITH RECURSIVE句の実行手順と再帰SQLの流れを図で理解する
再帰的SQLがどのように処理を進めていくかは、図でイメージするととても分かりやすくなります。ここでは、WITH RECURSIVE句がどのように「初期状態」から「再帰ループ」を回し、「終了条件」に達するのかを、手順ごとに分けて見ていきましょう。
■ 再帰SQLの処理フロー
以下は、従業員の上司―部下関係を辿る例です。
ステップ1:アンカー部が実行され、最初の行(例:社長)が取得される
↓
ステップ2:再帰部が実行され、1階層下のデータ(社長の部下)が追加される
↓
ステップ3:さらにその部下の部下…と繰り返し処理が続く
↓
ステップ4:新しいデータが取得できなくなるまでループ(データが空で終了)
■ 処理の順序(テーブルで確認)
| フェーズ | 実行内容 | SQLにおける該当箇所 |
|---|---|---|
| スタート | アンカー部を実行 | SELECT ... FROM ... WHERE ... |
| ループ1回目 | アンカー結果とJOINして再帰部を実行 | UNION ALL SELECT ... JOIN ... |
| ループ2回目以降 | 再帰部を繰り返し実行 | 同上 |
| 終了条件 | 新しい結果が返らなくなる | 自動終了(SQLエンジン判断) |
■ 試験で狙われやすいポイント
UNION ALLでつなぐ必要がある(UNIONだと重複排除され誤動作)- 「アンカー部が1回だけ実行される」点
- 「終了条件」が暗黙的(= 返す行がなくなる)である点
- 列の数・型が一致していないとエラーになる
階層の深さが不明なテーブルとは?再帰的SQLが必要になる典型構造
再帰的SQLが真価を発揮するのは、「階層の深さが事前に分からないテーブル」を扱うときです。では、そういった構造とは具体的にどんなものでしょうか?
■ 例:社員テーブル(自己参照)
CREATE TABLE employees (
emp_id INT PRIMARY KEY,
name VARCHAR(50),
manager_id INT -- 上司のemp_id(NULLなら社長)
);
| emp_id | name | manager_id |
|---|---|---|
| 1 | 社長 | NULL |
| 2 | 部長A | 1 |
| 3 | 課長A1 | 2 |
| 4 | リーダーA1 | 3 |
| 5 | メンバーA1 | 4 |
■ なぜJOINを使う発想になるのか?
このような「上司と部下が同じテーブルに入っている」構造では、自分の上司を知るには、同じテーブルの別の行と“結合”する必要があります。このように、同じテーブルを自分自身と結合する操作を「自己結合(自己JOIN)」と呼びます。
たとえば「各社員とその上司を1行で出したい」場合、以下のようにJOINします:
SELECT
emp.name AS employee,
mgr.name AS manager
FROM
employees emp
LEFT JOIN employees mgr ON emp.manager_id = mgr.emp_id;
さらに「上司の上司」や「上司の上司の上司」まで表示したいときは、JOINを何度も重ねる必要があります:
SELECT
e1.name AS 自分,
e2.name AS 上司,
e3.name AS 上司の上司
FROM
employees e1
LEFT JOIN employees e2 ON e1.manager_id = e2.emp_id
LEFT JOIN employees e3 ON e2.manager_id = e3.emp_id;
■ なぜ従来のJOINでは限界があるのか?
上記のようなJOINは、あらかじめ「何階層必要か」が分かっていなければ使えません。5階層や10階層になるかもしれないのに、JOINを何回書けばいいか事前に分からない――これがJOINの限界です。
■ WITH RECURSIVEで自動走査!
WITH RECURSIVE hierarchy AS (
SELECT emp_id, name, manager_id, 1 AS level
FROM employees
WHERE manager_id IS NULL
UNION ALL
SELECT e.emp_id, e.name, e.manager_id, h.level + 1
FROM employees e
JOIN hierarchy h ON e.manager_id = h.emp_id
)
SELECT * FROM hierarchy;
このようにWITH RECURSIVEを使えば、階層が尽きるまで自動で繰り返してくれます。
■ 出力結果のイメージ
上記のクエリを実行すると、以下のような「階層レベル付きの一覧」が得られます:
| emp_id | name | manager_id | level |
|---|---|---|---|
| 1 | 社長 | NULL | 1 |
| 2 | 部長A | 1 | 2 |
| 3 | 課長A1 | 2 | 3 |
| 4 | リーダーA1 | 3 | 4 |
| 5 | メンバーA1 | 4 | 5 |
階層の深さ(level列)があることで、「どの社員がどの階層にいるか」が一目で分かるのも、再帰SQLのメリットです。
■ こうした構造は試験でも頻出!
IPAの応用情報などでは、このような「自己参照の階層構造テーブル」が繰り返し登場します。正確に理解しておきましょう。
なぜ階層構造をSQLで取得するのか?再帰的SQLが求められる実務と試験の理由
そもそも「階層構造をSQLで取得する必要があるのか?」という素朴な疑問は非常に重要です。実務でも試験でも、この能力が求められる理由を整理します。
■ 実務での必要性
| シーン | 説明 |
|---|---|
| 組織図 | 社長から末端社員までの階層をたどりたい |
| カテゴリ管理 | ECサイトの「カテゴリ→サブカテゴリ→商品」構造 |
| ファイル構造 | フォルダ→サブフォルダ→ファイルの一覧取得 |
| ナビ表示 | パンくずリストなど、上位から下位へ構造を表示したい |
■ 試験での必要性(午後問題)
- 「親→子→孫ノードを辿るロジック」をSQLで正確に書けるか
- 「ID/親ID」形式の表を読み取り、再帰構造と認識できるか
- 「構造をたどる処理の流れ」を日本語で説明できるか
■ 再帰的SQLの価値
単にSQLの文法を覚えるのではなく、「ツリー構造の走査方法」「繰り返し処理の理解」「階層の全体像を出力する力」など、構造的な考え方を身につける訓練になります。
再帰的SQLを使うべき/避けるべきケース│試験対策での判断基準
再帰的SQLは非常に強力な機能ですが、「どんなときでも使うべき」というわけではありません。試験では「これは再帰SQLで解くべきか?」という判断が問われることがあります。このセクションでは、「再帰を使う/使わない」判断基準を実例ベースで整理します。
■ 再帰的SQLが適しているケース
| シチュエーション | 内容 |
|---|---|
| 階層構造の探索 | 上司―部下、ディレクトリ構造、親カテゴリ―子カテゴリ など |
| ツリー構造の出力 | ノードから全ての子ノードを一覧にする場合 |
| 終端まで到達するルートを列挙したい | たとえば「A→B→C→D」という経路を1行ずつ確認したいとき |
■ 再帰的SQLを避けるべきケース
| シチュエーション | 理由 |
|---|---|
| 階層が深すぎる or データ量が非常に多い | 処理時間が長くなり、パフォーマンスに影響 |
| 階層構造が不要な集計・JOIN | 単純な結合や集計で済むなら、再帰は過剰設計 |
| 終了条件が不明確 or ループしそうなデータ | 無限ループの危険がある(テストにありがちなトラップ) |
■ 試験での「判断のコツ」
- 問題文に「階層」「構造」「繰り返し」「親子関係」「すべての関連要素を列挙」といったキーワードがあれば、再帰SQLの可能性が高い
- 表形式で「ID/親ID」の形になっているときはツリー構造を想定する
- 「再帰を使わないと解けない」問題は限られており、多くは「再帰でも解けるが、非再帰でも可」のパターンがある
■ 試験での“罠”に注意!
UNIONとUNION ALLの違いを問う選択肢- 再帰部で結合条件をミスらせるパターン
- 終了条件が指定されていない→無限再帰になるケース
- 列の数が揃っていないエラー(ヒントに注目)
試験で狙われやすい再帰的SQLのひっかけパターンと解き方のコツ
応用情報技術者試験の午後問題では、「SQLの構文そのもの」よりも、「読み違い」や「判断ミス」を誘うようなひっかけ方がよく見られます。再帰的SQLに関しても、典型的なパターンがいくつか存在します。
■ よくあるひっかけパターン
| パターン | 解説 |
|---|---|
| UNIONを使わせる選択肢 | UNION ALLでなければ再帰処理は途中で止まる(重複が消える) |
| 再帰部のJOIN条件にミスがある | 親子関係の結び方が逆になっているなど、結果が空になる |
| 終了条件が曖昧 | WHERE句がなくループし続ける危険、特にLEVELなどの制御がない場合 |
| 列数・型が一致しない | アンカー部と再帰部のSELECT句で列の数がずれると即エラー |
■ コツ1:アンカーと再帰部の「列名・順序・型」を確認
午後問題ではSQL文の一部だけが与えられ、残りを選択肢から選ぶ形式も多いです。このとき、最も重要なのが「列の一致」です。
✅ アンカーと再帰部のSELECT文で、列数・順番・データ型が同じになっているか?
→ これが一致していないと、エラーになるか、結果が出ないケースになります。
■ コツ2:処理の流れを擬似コード的に書いてみる
-- 例:
1. 最初に「親IDがNULL」のレコードを取得
2. そのIDを使って「子ID = 親ID」のJOINを繰り返す
3. 終了条件は、JOINでマッチしなくなったとき
このように、問題文をSQLだけでなく「日本語の処理手順」に言い換えてみると、見落としが減ります。
■ コツ3:「1回目だけ実行される部分」がどこかを見極める
アンカー部は1回しか実行されません。そのため、そこにWHERE句やLIMIT句があると、その後の再帰処理に直接影響を与えることがあります。
■ 試験テクニック:迷ったらアンカー→再帰→最終SELECTの順に確認
- アンカー部:どこから始まっている?
- 再帰部:何をJOINしている?
- 最終SELECT:何を出力している?
再帰的SQLの理解まとめ│WITH RECURSIVE句で階層データを制す
再帰的SQLは、「階層構造」「ツリー構造」といったデータに強く、WITH RECURSIVE句を用いることで、従来のSQLでは難しかった処理も簡潔に表現できます。ただし、強力な一方で、書き方のルールや使いどころを誤ると結果が得られない、あるいは無限ループやエラーの原因にもなり得ます。
午後試験では「SQLの正確な構文知識」に加え、「処理の流れを追う力」「文脈から再帰が必要かを判断する力」が求められます。以下のポイントを意識しておくと良いでしょう。
■ 再帰SQLの攻略ポイントまとめ
WITH RECURSIVEは「アンカー部+再帰部」の2段構成- 必ず
UNION ALLを使い、列数と型を合わせる - アンカー部は1回、再帰部は繰り返し実行される
- 再帰が必要な構造かどうかは「ID/親ID」型の表をチェック
- 終了条件やJOIN条件に不備があると意図通りに動かない
- SQL全体を「手順」や「流れ」で理解するとミスが減る
実務でも再帰的SQLはデータ分析やシステム開発で重宝されるスキルです。応用情報の合格を目指すと同時に、現場でも通用する力をこの機会にぜひ養いましょう。