技術ブログ
目次
Oracle ACE Proの渡部です。
この記事は、JPOUG Advent Calendar 2022 25日目の記事です。24日目は Kazuhiro Takahashi さんの記事『非同期グローバル索引メンテナンスについて考える』でした。
12月 1日から24日まで記事を執筆いただいたみなさま、どうもありがとうございました。
この記事が JPOUG Advent Calendar 2022 の最後の記事となります。
X'masということで、クリスマスツリーを出力するSQLを題材にして、以前から解説をまとめておきたかった再帰的WITHについて記載したいと思います。
「お遊び」として、SQLを使ってクリスマスツリーを出力する例が、インターネット上でいくつか公開されています。
やり方は色々あるのですが、今回はある程度有益な技術的なトピックを含めたい想いもあり、以下の再帰的WITHを用いた例を元にして再帰的WITHについて説明したいと思います。
WITH tree(lev, xmas) AS (
SELECT 1 lev, RPAD(' ', 10, ' ') || '*' xmas
FROM dual
UNION ALL
SELECT tree.lev+1,
RPAD(' ', 10 - tree.lev, ' ') ||
RPAD('^', tree.lev + 1, '^') ||
LPAD('^', tree.lev, '^') xmas
FROM tree
WHERE tree.lev < 10
)
SELECT ' Merry Christmas!' AS "Merry Christmas!" FROM dual
UNION ALL
SELECT xmas FROM TREE
UNION ALL
SELECT ' | |' FROM dual
UNION ALL
SELECT ' ~~/ \~~' FROM dual;
再帰的WITHとは、WITH句を用いて定義した名前付きサブクエリを「再帰的」に呼び出せる機能です。「再帰的」とは、数学やコンピュータサイエンス、プログラミングで使用される概念で、ある関数が自身の関数を呼び出すような一種の入れ子構造を持つ定義方法のことです。
事実、上記のSQLでは、サブクエリtreeの定義("WITH tree(lev, xmas) AS ( ... )")の中で、tree(=自サブクエリ)を呼び出しています。
では、再帰的WITHの動作を見てゆきましょう。ただ、上記のSQLには再帰的WITH以外の余計な部分があるため、シンプルにしたSQL(以下「SQL1」と呼ぶ)を用いて動作を説明します。
WITH tree(lev, xmas) AS (
SELECT 1 lev, LPAD('^', 1, '^') xmas
FROM dual
UNION ALL
SELECT tree.lev+1, LPAD('^', tree.lev+1, '^') xmas
FROM tree
WHERE tree.lev < 3
)
SELECT lev, xmas FROM tree;
SQL1の実行結果は以下のとおりです。
LEV XMAS
---------- ----------------------------------------
1 ^
2 ^^
3 ^^^
なお、LPADは 引数1の左側に引数3の文字を連続的に埋め込んで引数3の桁数にして戻すファクションです。
SQL> SELECT LPAD('X', 2, '-') FROM DUAL;
LP
--
-X
SQL> SELECT LPAD('X', 3, '-') FROM DUAL;
LPA
---
--X
SQL> SELECT LPAD('X', 4, '-') FROM DUAL;
LPAD
----
---X
Oracleの再帰的WITHは、
の上下にアンカーメンバーと再帰的メンバーと呼ばれるクエリを書くことで定義されます。UNION ALL
それぞれの役割および特徴は以下のとおりです。
アンカーメンバー
再帰的メンバー
説明があまりピンと来ないかもしれませんが、以下の説明に進んで下さい。
SQL1の「WITH句を用いて定義した名前付きサブクエリtree」が実行される流れを図に起こしました。
以下の流れで処理が実行される点を理解してください。
なお、このあと、SQL1の最後の1行である
が実行されますが、これはtreeの実行結果をそのまま返す形になります。SELECT lev, xmas FROM tree
これで、再帰的WITHの動作を理解できたので、冒頭に記載したSQLの動作も理解できるハズです!
それでは皆様よいお年を!!