初めに
再帰・・と呼んでるので
同じ処理を繰り返し実行しているのでしょうが
他の言語と同じように、処理を追う事が容易ではありません
SQLでの再帰処理が実行される様子??を
図を使って解説してみます
CTEの使い方を理解されている前提で
すすめさせていただきます
再帰処理については、多数記事が公開されているので
こちらと合わせて確認いただければとおもいます
⇒ 下にいくつかリンクを張っています
まずは、シンプルな例をみてみます
【再帰処理】0 から 25 まで『5』づつ加算
WITH RECURSIVE cte_table AS (
-- 初回だけ実行される
SELECT 0 AS num
UNION ALL
-- 2回目以降~実行される
SELECT num + 5 AS num
FROM cte_table
WHERE num < 25
)
SELECT * FROM cte_table;
▼出力結果
num |
---|
0 |
5 |
10 |
15 |
20 |
25 |
再帰処理の挙動
ここでは、ざっくりとした再帰処理の流れを
可視化してみます
再帰処理は2つにわかれています
下記2つのSQLの結果を UNION ALL で結合してます
■ 初回だけ実行される処理
SELECT 0 AS num
⇒ 初期データを入れてる・・感じです
⇒ cte_table の num カラムに 0 を入れる
■ 2回目以降~に実行される処理
SELECT num + 5 AS num FROM cte_table WHERE num < 25
⇒ CTE の名称と同じcte_table をFROM句で使用
⇒ 自分自身を呼び出してる・・イメージ
⇒ numに5 を加算する処理を
numが25より小さい時は処理を続ける
⇒ 再帰処理の終了条件を WHERE句に記述
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
少し複雑な処理を見てみます
【再帰処理】ツリー構造 | 準備
-- テーブル定義
CREATE TABLE tbl_employee (
emp_id INTEGER -- 従業員ID
,employee_name TEXT -- 従業員名
,manager_id INTEGER -- 従業員の上司ID
);
-- データ登録
INSERT INTO tbl_employee VALUES (1, 'King', NULL);
INSERT INTO tbl_employee VALUES (33, 'Jeff', 1);
INSERT INTO tbl_employee VALUES (55, 'Nancy', 1);
INSERT INTO tbl_employee VALUES (88, 'Bob', 1);
INSERT INTO tbl_employee VALUES (222, 'Dan', 33);
INSERT INTO tbl_employee VALUES (666, 'Kate', 33);
INSERT INTO tbl_employee VALUES (4444, 'Bill', 666);
SELECT * FROM tbl_employee;
▼出力結果
emp_id | employee_name | manager_id |
---|---|---|
1 | King | NULL |
33 | Jeff | 1 |
55 | Nancy | 1 |
88 | Bob | 1 |
222 | Dan | 33 |
666 | Kate | 33 |
4444 | Bill | 666 |
親(King)の配下に所属するスタッフを取出す
WITH RECURSIVE cte_table AS (
-- King の情報
SELECT emp_id, emp_name, manager_id
FROM tbl_employee WHERE emp_id = 1
UNION ALL
-- King 以下に配置されてるスタッフを取出す
SELECT emp.emp_id,emp.emp_name, emp.manager_id
FROM cte_table AS cte
INNER JOIN tbl_employee AS emp ON emp.manager_id = cte.emp_id
)
SELECT * FROM cte_table
▼出力結果
employee_id | employee_name | manager_id |
---|---|---|
1 | King | NULL |
33 | Jeff | 1 |
55 | Nancy | 1 |
55 | Bob | 1 |
222 | Dan | 33 |
666 | Kate | 33 |
4444 | Bill | 666 |
tbl_employee と同じ?? 確かにそうなりますね
Kingは一番TOPの人(root
)なので、
その人物の配下のスタッフは、全員が対象です
tbl_employee をSELECTしているのと何が違うの??
FROM句でcte_tableを使ってるけど再帰処理してるの (๑• •๑)??
こんな風に考えてしまいます・・が、再帰処理してます
もう少し、細かくみてみます・・・
親(Jeff)の配下に所属するスタッフを取出す
WITH RECURSIVE cte_table AS (
-- Jeff の情報
SELECT emp_id, emp_name, manager_id
FROM tbl_employee WHERE emp_id = 33 -- 修正
UNION ALL
-- Jeff 以下に配置されてるスタッフを取出す
SELECT emp.emp_id,emp.emp_name, emp.manager_id
FROM cte_table AS cte
INNER JOIN tbl_employee AS emp ON emp.manager_id = cte.emp_id
)
SELECT * FROM cte_table
▼出力結果
employee_id | employee_name | manager_id |
---|---|---|
33 | Jeff | 1 |
222 | Dan | 33 |
666 | Kate | 33 |
4444 | Bill | 666 |
▼ Jeffの配下に所属するスタッフの取出しイメージ
Step1:Jeff を取出す
WITH RECURSIVE cte_table AS (
-- Jeff の情報
SELECT emp_id, emp_name, manager_id
FROM tbl_employee WHERE emp_id = 33 -- 修正
-- UNION ALL
-- Jeff 以下に配置されてるスタッフを取出す
-- SELECT emp.emp_id,emp.emp_name, emp.manager_id
-- FROM cte_table AS cte
-- INNER JOIN tbl_employee AS emp ON emp.manager_id = cte.emp_id
)
SELECT * FROM cte_table
▼ 出力結果 | Jeffが取出せた
employee_id | employee_name | manager_id |
---|---|---|
33 | Jeff | 1 |
Step2:Jeffの部下を取出す
WITH RECURSIVE cte_table AS (
-- Jeff の情報
-- SELECT emp_id, emp_name, manager_id
-- FROM tbl_employee WHERE emp_id = 33 -- 修正
-- UNION ALL
-- Jeff 以下に配置されてるスタッフを取出す
SELECT emp.emp_id,emp.emp_name, emp.manager_id
FROM cte_table AS cte
INNER JOIN tbl_employee AS emp ON emp.manager_id = cte.emp_id
)
SELECT * FROM cte_table
再帰処理の部分だけを実行したかった・・が
記述ルールに反している・・のでエラーになる
この部分を、再帰処理を使わずに書換えてみる
⇒ Step2:Jeffの部下を取出す
を書換え
Step2:Jeffの部下、Dan,Kate を取出す(非再帰)
-- Dan,Kate の情報
SELECT emp.emp_id,emp.emp_name, emp.manager_id
FROM (
-- Jeff の情報
SELECT emp_id, emp_name, manager_id
FROM tbl_employee WHERE emp_id = 33
) AS cte
INNER JOIN tbl_employee AS emp ON emp.manager_id = cte.emp_id
▼ 出力結果 Dan, Kate 取出せた
employee_id | employee_name | manager_id |
---|---|---|
222 | Dan | 33 |
666 | Kate | 33 |
動作の詳細
⇒ Jeffを上司に持つスタッフ(Dan,Kate)を取出すには
最初に取得したJeffの情報に対して
tbl_employee を join してます
⇒ cte_table には前回取得したJeffの情報が保持されてる
⇒ 上司であるJeffのID(cte.emp_id
:33) と
同じmanager_id
を持つ行をtbl_employeeから取出
Step3:Kateの部下、Bill を取出す(非再帰)
-- Bill の情報
SELECT emp.emp_id,emp.emp_name, emp.manager_id
FROM(
-- Dan,Kate の情報
SELECT emp.emp_id,emp.emp_name, emp.manager_id
FROM (
-- Jeff の情報
SELECT emp_id, emp_name, manager_id
FROM tbl_employee WHERE emp_id = 33
) AS cte
INNER JOIN tbl_employee AS emp ON emp.manager_id = cte.emp_id
) AS cte
INNER JOIN tbl_employee AS emp ON emp.manager_id = cte.emp_id
▼ 出力結果 Bill が 取出せた
employee_id | employee_name | manager_id |
---|---|---|
4444 | Bill | 666 |
動作の詳細
⇒ 前回取得した情報(Dan,Kate)に対して
tbl_employee を join してます
⇒ cte_table には前回取得した情報が保持されてる
⇒ 上司であるDan,KateのID(cte.emp_id
:222,666) と
同じmanager_id
を持つ行をtbl_employeeから取出
Step4:Bill の部下がいない。再帰処理が終わる
-- Bill の部下
SELECT emp.emp_id,emp.emp_name, emp.manager_id
FROM (
-- Bill の情報
SELECT emp.emp_id,emp.emp_name, emp.manager_id
FROM(
-- Dan,Kate の情報
SELECT emp.emp_id,emp.emp_name, emp.manager_id
FROM (
-- Jeff の情報
SELECT emp_id, emp_name, manager_id
FROM tbl_employee WHERE emp_id = 33
) AS cte
INNER JOIN tbl_employee AS emp ON emp.manager_id = cte.emp_id
) AS cte
INNER JOIN tbl_employee AS emp ON emp.manager_id = cte.emp_id
) AS cte
INNER JOIN tbl_employee AS emp ON emp.manager_id = cte.emp_id
▼ 出力結果 データ無い
employee_id | employee_name | manager_id |
---|---|---|
動作の詳細
⇒ 前回取得した情報(Bill)に対して
tbl_employee を join してます
⇒ cte_table には前回取得した情報が保持されてる
⇒ 上司であるBillのID(cte.emp_id
:4444) と
同じmanager_id
を持つ行をtbl_employeeから取出
戻り値が無いので、再帰処理が終了
Bill を上司に持つスタッフが存在しない
⇒ SQL の結果が返ってこない
⇒ ここで、再帰処理が終わる
⇒ Step4 が最後の処理
最初の例では、再帰処理が終わる条件を
WHERE句に記述していました
⇒ WHERE num < 25
この再帰クエリでは、『結果が返ってこない』
を条件にして再帰処理が終ります
『処理の流れ』と『全体のイメージ図』
Step1 : Jeff の情報を取得
UNION ALL
Step2 : Dan, Kate の情報を取得
UNION ALL
Step3 : Bill の情報を取得
Step4 : 情報がないので終了
【再帰処理クエリ】↓↓↓
SELECT emp.emp_id,emp.emp_name, emp.manager_id
FROM cte_table AS cte
INNER JOIN tbl_employee AS emp ON emp.manager_id = cte.emp_id
再帰処理されるクエリは
Step2, Step3, Step4 で実行されている
Step4 で結果が返ってこないので
全体の処理がここで終了する
以上となります
再帰クエリがなんとなく理解いただければ幸いです
参考サイト