The Oracle (tm) Users' Co-Operative FAQ
In a 'start with / connect by' query, how do I get child rows under each parent to come out in sorted order ?
| Author's name: Mike Madland Author's Email: mikemadland@yahoo.com |
Date written: 14 October 2001 Oracle version(s): 7-8.1.7 |
| The start with / connect by query has been a part of oracle since version 5 (at least) and is a very powerful way to perform a hierarchical query on a table. However, it does have limitations especially when used with joins and sorting. |
There are three techniques that can be used for sorting the child rows in a connect by query. These are fairly well documented in MetaLink articles (see references below). Each has advantages and disadvantages.
The first technique is to create a recursive function that will calculate the correct order for a given row and then order by that function. The second technique is a variation on the first that saves the value in a new column in the table, then orders by the new column. The advantage of these two techniques is that they are guaranteed to work even if the execution plan of the query changes. The disadvantage is that they both require a stored function and the second requires changes to the table schema.
The third technique is to create an index and modify the query so that the optimizer uses the index, resulting in the child rows coming out in sorted order. The advantage of this technique is that it is fairly simple to implement, runs quickly and doesn't require any changes to the table. The disadvantage is that it relies on the execution plan of the query which can change if indexes or other structures change.
The code samples below were copied directly from the MetaLink articles with a few changes.
Here is the basic query, not sorted:
SELECT LPAD(' ' ,2*level) || ename employee
FROM emp
START WITH ename ='KING'
CONNECT BY PRIOR empno= mgr
/
EMPLOYEE
------------------------------
KING
JONES
SCOTT
ADAMS
FORD
SMITH
BLAKE
ALLEN
WARD
MARTIN
TURNER
JAMES
CLARK
MILLER
Here is a simple recursive function that computes the sort order
(lineage) for a given employee.
CREATE OR REPLACE FUNCTION lineage(v_empno number)
RETURN VARCHAR2
IS
v_mgr NUMBER(10);
v_ename VARCHAR2(30);
BEGIN
SELECT mgr, ename
INTO v_mgr, v_ename
FROM emp
WHERE empno=v_empno;
IF v_mgr IS NULL THEN
RETURN(v_ename);
ELSE
RETURN(lineage(v_mgr) ||'-' || v_ename);
END if;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN(NULL);
END;
/
Once you've created the function, you can modify the query to use
it for the sort order:
SELECT LPAD(' ' ,2*level) || ename employee,
lineage(empno) lineage
FROM emp
START WITH ename ='KING'
CONNECT BY PRIOR empno= mgr
ORDER BY 2
/
EMPLOYEE LINEAGE
------------------------------ ------------------------------
KING KING
BLAKE KING-BLAKE
ALLEN KING-BLAKE-ALLEN
JAMES KING-BLAKE-JAMES
MARTIN KING-BLAKE-MARTIN
TURNER KING-BLAKE-TURNER
WARD KING-BLAKE-WARD
CLARK KING-CLARK
MILLER KING-CLARK-MILLER
JONES KING-JONES
FORD KING-JONES-FORD
SMITH KING-JONES-FORD-SMITH
SCOTT KING-JONES-SCOTT
ADAMS KING-JONES-SCOTT-ADAMS
If you have a large table, it may make sense to store the sort
order in a column with the table so that your query will run
faster. I've used a very simple technique for creating the new
column:
CREATE TABLE tmp_emp AS
SELECT empno, lineage(empno) lineage
FROM emp
/
ALTER TABLE emp ADD lineage VARCHAR2(4000);
ALTER FUNCTION lineage COMPILE;
UPDATE emp e
SET lineage =
(SELECT lineage
FROM tmp_emp t
WHERE t.empno = e.empno)
/
COMMIT;
SELECT LPAD(' ', 2*(level)) || ename employee
FROM emp
START WITH ename = 'KING'
CONNECT BY prior empno = mgr
ORDER BY lineage
/
EMPLOYEE
------------------------------
KING
BLAKE
ALLEN
JAMES
MARTIN
TURNER
WARD
CLARK
MILLER
JONES
FORD
SMITH
SCOTT
ADAMS
You should also note that you don't really need the connect by to
get the correct sort order unless you need the level
pseudo-column.
SELECT ename employee FROM emp ORDER BY lineage / EMPLOYEE ------------------------------ KING BLAKE ALLEN JAMES MARTIN TURNER WARD CLARK MILLER JONES FORD SMITH SCOTT ADAMS
The final technique, and my preferred one, is to create an index on the column that you want to sort by and then persuade the optimizer to use it:
CREATE INDEX org_chart_idx ON emp(ename);
SELECT LPAD(' ', 2*(level)) || ename employee
FROM emp
START WITH ename = 'KING'
CONNECT BY PRIOR empno = mgr
AND ename =' ' -- This predicate should make the optimizer use the index.
/
EMPLOYEE
------------------------------
KING
BLAKE
ALLEN
JAMES
MARTIN
TURNER
WARD
CLARK
MILLER
JONES
FORD
SMITH
SCOTT
ADAMS
Further reading: Metalink articles:
73830.1 How to sort records in connect by select statement w/o destroying tree structure
1009345.6 Want to order rows in a hierarchical query




