Hack 85. Traverse a Simple Tree

You want to implement and query a tree of your ancestors. Just who is your sister's brother's cousin again?

You want to construct your ancestral tree in a database. For testing purposes you have to use your relatives' titles (such as mum and dad) rather than their names (Janette, Ian, etc.). Your test tree looks like Figure 10-4.

Figure 10-4. Family tree hierarchy

You want to be able to query the database and determine who your ancestors are.

When building a tree-like hierarchy such as this one, the foreign keys are the descendents. This structure would look like Table 10-13.

Table 10-13. The ancestors table

Name Parent
Dad's dad
NULL
Dad Dad's dad
Brother Dad
Sister Dad
Me Dad

You can find direct ancestors by following the parent link to the next ancestor. You need to repeat this for the maximum depth of the tree. In this case, the tree has a maximum depth of 3, and thus a maximum of two steps are required. For one step:

SELECT parent
FROM ancestors
WHERE parent IS NOT NULL
AND name = 'Me'
;


PARENT
-------
Dad

For two steps:

SELECT parent
FROM ancestors
WHERE parent IS NOT NULL
AND name = 'Me'
UNION
SELECT a2.parent
FROM ancestors a1 JOIN ancestors a2
 on (a1.parent = a2.name)
WHERE a2.parent IS NOT NULL
AND a1.name = 'Me' 
;

PARENT
--------
Dad
Dads Dad

Writing queries using this approach can quickly become really disgusting! One way to conceal the hack is to make a view of the parentchild relationships:

CREATE VIEW relationship AS
SELECT parent,name as child
FROM ancestors
WHERE parent IS NOT NULL
UNION
SELECT a2.parent,a1.name
FROM ancestors a1 JOIN ancestors a2
 on (a1.parent = a2.name)
WHERE a2.parent IS NOT NULL
;

As long as you have coded sufficient depth into the view, you can query parentage using:

SELECT parent
FROM relationship
WHERE child = 'Me';

You can change this to find all descendents of 'Dads Dad':

SELECT child
FROM relationship
WHERE parent = 'Dads dad'
;


CHILD
------------------------------------------------------------
Dad
Brother
Me
Sister

The weakness of this approach is that you must know the maximum depth of the tree before you create the view. If you underestimate the depth, the query will still run, but it will give only a partial answer.

10.9.1. Tree Visualization

You can adapt this technique to build a visual representation of the tree. The order of the rows in the representation is critical, so you will have to order by the root node, and then the second from the root, then the third, and so on. One tricky issue occurs when you are ordering on a component that is NULL. The SQL standard does not precisely state what happens with an ORDER BY involving NULL, and it tends to have its own rules that are different from what you would expect. You can sidestep this issue by substituting the space character where NULL would normally appear:

DROP VIEW treeinfo;
CREATE VIEW treeinfo as
SELECT a1.name as node,a1.name p1,' ' as p2,' ' as p3,1 as depth
FROM ancestors a1
WHERE parent IS NULL
UNION
SELECT a1.name as node,a2.name as p1,a1.name as p2,' ' as p3,2 as depth
FROM ancestors a1 JOIN ancestors a2 on (a1.parent = a2.name)
WHERE a2.parent IS NULL
UNION
SELECT a1.name as node,a3.name as p1,a2.name as p2,a1.name as p3,3 as depth
FROM ancestors a1 JOIN ancestors a2 on (a1.parent = a2.name)
 JOIN ancestors a3 on (a2.parent = a3.name)
WHERE a3.parent IS NULL
;

You need the p1,p2,p3 fields only to maintain the ordering of the tree. You never have to show them in the query result:

select tree from (
SELECT p1,p2,p3,'+'||node tree FROM treeinfo where depth = 1
UNION
SELECT p1,p2,p3,' +--'||node tree FROM treeinfo where depth = 2
UNION
SELECT p1,p2,p3,' +--'||node tree FROM treeinfo where depth = 3
)
ORDER by p1,p2,p3 

In MySQL, you need to use an alias for the derived table. You also need to use CONCAT rather than ||:

select tree from (
SELECT p1,p2,p3, CONCAT('+', node) tree
 FROM treeinfo where depth = 1
UNION
SELECT p1,p2,p3, CONCAT(' +--', node) tree
 FROM treeinfo where depth = 2
UNION
SELECT p1,p2,p3, CONCAT(' +--', node)
 tree FROM treeinfo where depth = 3
) t
ORDER by p1,p2,p3

For the family tree example, this produces:

TREE
------------------
+Dads Dad
 +--Dad
 +--Brother
 +--Me
 +--Sister

5 rows selected.

 

10.9.2. Oracle Recursive Extensions

Oracle includes some SQL extensions that allow you to traverse a tree without knowing the maximum depth in advance. The CONNECT BY clause allows you to link each child node to its parent. For instance, to find 'Me' and all ancestors you could do:

SQL> SELECT name
 2 FROM ancestors
 3 CONNECT BY PRIOR parent = name
 4 START WITH name = 'Me';

NAME
------------------------------------------------------------
Me
Dad
Dads Dad

You can reverse the CONNECT BY equality to get descendants, and you can view the depth of the recursion using the pseudovariable LEVEL. To see all descendants of 'Dads Dad' you could use:

SQL> SELECT name, LEVEL
 2 FROM ancestors
 3 CONNECT BY PRIOR name = parent
 4 START WITH name='Dads Dad'; 

NAME LEVEL
------------------------------------------------------------ ----------
Dads Dad 1
Dad 2
Brother 3
Sister 3
Me 3

Rudy Limeback and Gordon Russell

SQL Fundamentals

Joins, Unions, and Views

Text Handling

Date Handling

Number Crunching

Online Applications

Organizing Data

Storing Small Amounts of Data

Locking and Performance

Reporting

Users and Administration

Wider Access

Index



SQL Hacks
SQL Hacks
ISBN: 0596527993
EAN: 2147483647
Year: 2004
Pages: 147

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net