Oracle was late to the table with recursive common table expressions which have been part of the SQL standard since 2003 but, to Oracle’s credit, it has provided the CONNECT BY clause for hierarchical queries from the very beginning. However, recursive common table expressions can be used for much more than hierarchical queries. Also note that Oracle uses the non-standard terms “subquery factoring” and “recursive subquery factoring” for “common table expressions” and “recursive common table expressions” respectively. Common table expressions of the non-recursive kind are an alternative to inline views and make a complex SQL query much easier to read and maintain. Another advantage is that they only need to be evaluated once if they are used more than once within the query. A recursive common table expression (recursive CTE) contains subqueries called “anchor members” and “recursive members.” The rows produced by the anchor members are processed by the recursive members. The recursive members produce other rows that are fed right back to them for further processing. Recursion stops only when the recursive members fail to produce additional rows. Here is a simple example of a recursive common table expression: a number generator. The following example generates the consecutive numbers from 1 to 9. The anchor member generates the first row which is then processed by the recursive member. The recursive member uses the name of the recursive CTE as a placeholder for the output of the anchor member or a previous execution of the recursive CTE. In this example, each execution of the recursive member produces one more row. Recursion stops when nine records have been produced. WITH -- The following number generator is a simple example of a recursive CTE. It -- produces the consecutive digits from 1 to 9. Numbers(n) AS ( -- The "anchor member." It contains exactly one row (N = 1). SELECT 1 AS N FROM dual UNION ALL -- The "recursive member." Notice that it references the name of the recursive -- CTE as a placeholder for the results of the anchor member or the previous -- execution of the recursive CTE. Each iteration of the recursive member -- produces the next value of N. Recursive execution stops when N = 9. SELECT N + 1 AS N FROM Numbers WHERE N < 9 ) SELECT * FROM Numbers; The above example can be simply duplicated using the CONNECT BY clause as follows: SELECT level AS N FROM dual CONNECT BY level <= 9; Next consider a standard hierarchical query; an org-chart of managers and employees. First, here’s the old solution using the CONNECT BY clause; it is short and sweet. SELECT LPAD (' ', 4 * (LEVEL - 1)) || first_name || ' ' || last_name AS name FROM employees START WITH manager_id IS NULL CONNECT BY manager_id = PRIOR employee_id; The solution using recursive common table expressions is much more verbose. Note especially the SEARCH DEPTH FIRST clause; refer to the Oracle documentation for an explanation. WITH RecursiveCTE (employee_id, first_name, last_name, lvl) AS ( -- The "anchor member" of the recursive CTE. It locates employees who don't -- have any manager; presumably there is at least one such employee. SELECT employee_id, first_name, last_name, 1 AS lvl FROM employees WHERE manager_id IS NULL UNION ALL -- The "recursive member" of the recursive CTE. Notice that it uses the name -- of the recursive CTE as a placeholder for the results of the anchor member -- or the previous execution of the recursive CTE. Each iteration of the -- recursive member locates the employees who report to the employees located -- in the previous iteration. Recursive execution stops when all employees -- have been located. SELECT e.employee_id, e.first_name, e.last_name, lvl + 1 AS lvl FROM RecursiveCTE r INNER JOIN employees e ON (r.employee_id = e.manager_id) ) -- Go deep in order to produce records in exactly the same order as the CONNECT -- BY clause. The default order of processing is BREADTH FIRST which would -- produce all managers at the same level before any of their employees; this is -- not not the order in which the CONNECT BY produces rows. The pseudocolumn -- seq# has been designated here to capture the order in which records are -- produced by the recursive CTE; it will be used in the main query. SEARCH DEPTH FIRST BY employee_id ASC SET seq# -- This is the main query. It processes the results produced by the recursive -- CTE. SELECT LPAD (' ', 4 * (lvl - 1)) || first_name || ' ' || last_name AS name FROM RecursiveCTE ORDER BY seq#; From the two examples above, it might appear that a recursive CTE is little more than a verbose way of specifying what could be more succinctly achieved with the CONNECT BY clause. However recursive common table expressions are more powerful than the CONNECT BY clause. See Results of the Second International NoCOUG SQL Challenge and Results of the Third International NoCOUG SQL Challenge for examples of the use of the recursive subquery factoring technique.
↧