转载:ORACLE-BASE - Recursive Subquery Factoring : Hierarchical Queries Using Recursive WITH Clauses
This articles gives an overview of how to query hierarchical data in Oracle databases using recursive subquery factoring. This functionality was introduced in Oracle 11g Release 2, giving an alternative to the method of performing hierarchical queries from previous versions.
A recursive subquery factoring clause must contain two query blocks combined by a UNION ALL set operator. The first block is known as the anchor member, which can not reference the query name. It can be made up of one or more query blocks combined by the UNION ALL , UNION , INTERSECT or MINUS set operators. The second query block is known as the recursive member, which must reference the query name once.
The following query uses a recursive WITH clause to perform a tree walk. The anchor member queries the root nodes by testing for records with no parents. The recursive member successively adds the children to the root nodes.
WITH t1(id, parent_id) AS ( -- Anchor member. SELECT id, parent_id FROM tab1 WHERE parent_id ISNULL UNIONALL -- Recursive member. SELECT t2.id, t2.parent_id FROM tab1 t2, t1 WHERE t2.parent_id = t1.id ) SEARCH BREADTH FIRSTBY id SET order1 SELECT id, parent_id FROM t1 ORDERBY order1;
WITH t1(id, parent_id) AS ( -- Anchor member. SELECT id, parent_id FROM tab1 WHERE parent_id ISNULL UNIONALL -- Recursive member. SELECT t2.id, t2.parent_id FROM tab1 t2, t1 WHERE t2.parent_id = t1.id ) SEARCH DEPTH FIRSTBY id SET order1 SELECT id, parent_id FROM t1 ORDERBY order1;
WITH t1(id, parent_id, lvl, root_id) AS ( -- Anchor member. SELECT id, parent_id, 1AS lvl, id AS root_id FROM tab1 WHERE parent_id ISNULL UNIONALL -- Recursive member. SELECT t2.id, t2.parent_id, lvl+1, t1.root_id FROM tab1 t2, t1 WHERE t2.parent_id = t1.id ) SEARCH DEPTH FIRSTBY id SET order1 SELECT id, parent_id, RPAD('.', (lvl-1)*2, '.') || id AS tree, lvl, root_id FROM t1 ORDERBY order1;
WITH t1(id, parent_id, lvl, root_id, path) AS ( -- Anchor member. SELECT id, parent_id, 1AS lvl, id AS root_id, TO_CHAR(id) AS path FROM tab1 WHERE parent_id ISNULL UNIONALL -- Recursive member. SELECT t2.id, t2.parent_id, lvl+1, t1.root_id, t1.path ||'-'|| t2.id AS path FROM tab1 t2, t1 WHERE t2.parent_id = t1.id ) SEARCH DEPTH FIRSTBY id SET order1 SELECT id, parent_id, RPAD('.', (lvl-1)*2, '.') || id AS tree, lvl, root_id, path FROM t1 ORDERBY order1;
The following example shows how to mimic the CONNECT_BY_ISLEAF functionality.
There is no natural way to find the leaf nodes until the result set is produced, so we use the LEAD analytic function in the main select list to check the next row in the result set. If it has a level that is less than or equal to the current row, we know the current row must be a leaf node.
WITH t1(id, parent_id, lvl, root_id, path) AS ( -- Anchor member. SELECT id, parent_id, 1AS lvl, id AS root_id, TO_CHAR(id) AS path FROM tab1 WHERE parent_id ISNULL UNIONALL -- Recursive member. SELECT t2.id, t2.parent_id, lvl+1, t1.root_id, t1.path ||'-'|| t2.id AS path FROM tab1 t2, t1 WHERE t2.parent_id = t1.id ) SEARCH DEPTH FIRSTBY id SET order1 SELECT id, parent_id, RPAD('.', (lvl-1)*2, '.') || id AS tree, lvl, root_id, path, CASE WHENLEAD(lvl, 1, 1) OVER (ORDERBY order1) <= lvl THEN1 ELSE0 END leaf FROM t1 ORDERBY order1;
-- Create a cyclic reference UPDATE tab1 SET parent_id =9WHERE id =1; COMMIT;
WITH t1(id, parent_id, lvl, root_id, path) AS ( -- Anchor member. SELECT id, parent_id, 1AS lvl, id AS root_id, TO_CHAR(id) AS path FROM tab1 WHERE id =1--Because UPDATE tab1 SET parent_id = 9 WHERE id = 1; Here cannot use parent_id IS NULL UNIONALL -- Recursive member. SELECT t2.id, t2.parent_id, lvl+1, t1.root_id, t1.path ||'-'|| t2.id AS path FROM tab1 t2, t1 WHERE t2.parent_id = t1.id ) SEARCH DEPTH FIRSTBY id SET order1 SELECT id, parent_id, RPAD('.', (lvl-1)*2, '.') || id AS tree, lvl, root_id, path FROM t1 ORDERBY order1; * ERROR at line 27: ORA-32044: cycle detected while executing recursiveWITH query
The NOCYCLE and CONNECT_BY_ISCYCLE functionality is replicated using the CYCLE clause. By specifying this clause, the cycle is detected and the recursion stops, with the cycle column set to the specified value, to indicate the row where the cycle is detected. Unlike the CONNECT BY NOCYCLE method, which stops at the row before the cycle, this method stops at the row after the cycle.
WITH t1(id, parent_id, lvl, root_id, path) AS ( -- Anchor member. SELECT id, parent_id, 1AS lvl, id AS root_id, TO_CHAR(id) AS path FROM tab1 WHERE id =1--Because UPDATE tab1 SET parent_id = 9 WHERE id = 1; Here cannot use parent_id IS NULL UNIONALL -- Recursive member. SELECT t2.id, t2.parent_id, lvl+1, t1.root_id, t1.path ||'-'|| t2.id AS path FROM tab1 t2, t1 WHERE t2.parent_id = t1.id ) SEARCH DEPTH FIRSTBY id SET order1 CYCLE id SETcycleTO1DEFAULT0 SELECT id, parent_id, RPAD('.', (lvl-1)*2, '.') || id AS tree, lvl, root_id, path, cycle FROM t1 ORDERBY order1;