0% found this document useful (0 votes)
51 views

Representing Trees in Oracle SQL

This document discusses representing trees in an Oracle SQL database. It shows that objects can be represented as rows, and pointers between objects can be stored as integer keys. The document demonstrates using Oracle's CONNECT BY clause to retrieve all rows in a tree structure from a table representing an organizational chart. It also shows how to limit the results or add indentation using the LEVEL pseudocolumn.

Uploaded by

mkeskin85
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
51 views

Representing Trees in Oracle SQL

This document discusses representing trees in an Oracle SQL database. It shows that objects can be represented as rows, and pointers between objects can be stored as integer keys. The document demonstrates using Oracle's CONNECT BY clause to retrieve all rows in a tree structure from a table representing an organizational chart. It also shows how to limit the results or add indentation using the LEVEL pseudocolumn.

Uploaded by

mkeskin85
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 16

19.09.

2019 Represent ng Trees n Oracle SQL

Trees n Oracle SQL


part of SQL for Web Nerds by Ph l p Greenspun

On ts face, the relat onal database management system would


appear to be a very poor tool for represent ng and
man pulat ng trees. Th s chapter s des gned to accompl sh the
follow ng th ngs:

show you that a row n an SQL database can be thought


of as an object
show you that a po nter from one object to another can
be represented by stor ng an nteger key n a regular
database column
demonstrate the Oracle tree extens ons (CONNECT BY
... PRIOR)
show you how to work around the l m tat ons of
CONNECT BY w th PL/SQL

The canon cal example of trees n Oracle s the org chart.

create table corporate_slaves (


slave_id integer primary key,
supervisor_id references corporate_slaves,
name varchar(100)
);

insert into corporate_slaves values (1, NULL, 'Big Boss Man');


insert into corporate_slaves values (2, 1, 'VP Marketing');
insert into corporate_slaves values (3, 1, 'VP Sales');
insert into corporate_slaves values (4, 3, 'Joe Sales Guy');
insert into corporate_slaves values (5, 4, 'Bill Sales Assistant');
insert into corporate_slaves values (6, 1, 'VP Engineering');
insert into corporate_slaves values (7, 6, 'Jane Nerd');
insert into corporate_slaves values (8, 6, 'Bob Nerd');

SQL> column name format a20


SQL> select * from corporate_slaves;

SLAVE_ID SUPERVISOR_ID NAME


---------- ------------- --------------------
1 Big Boss Man
2 1 VP Marketing
3 1 VP Sales
4 3 Joe Sales Guy
6 1 VP Engineering
7 6 Jane Nerd
8 6 Bob Nerd
5 4 Bill Sales Assistant

https://ph l p.greenspun.com/sql/trees.html 1/16


19.09.2019 Represent ng Trees n Oracle SQL

8 rows selected.

The ntegers n the supervisor_id are actually po nters to other rows n the
corporate_slaves table. Need to d splay an org chart? W th only standard SQL
ava lable, you'd wr te a program n the cl ent language (e.g., C, L sp, Perl, or Tcl) to
do the follow ng:

1. query Oracle to f nd the employee where supervisor_id is null, call th s


$big_kahuna_id
2. query Oracle to f nd those employees whose supervisor_id =
$big_kahuna_id
3. for each subord nate, query Oracle aga n to f nd the r subord nates.
4. repeat unt l no subord nates found, then back up one level

W th the Oracle CONNECT BY clause, you can get all the rows out at once:

select name, slave_id, supervisor_id


from corporate_slaves
connect by prior slave_id = supervisor_id;

NAME SLAVE_ID SUPERVISOR_ID


-------------------- ---------- -------------
Big Boss Man 1
VP Marketing 2 1
VP Sales 3 1
Joe Sales Guy 4 3
Bill Sales Assistant 5 4
VP Engineering 6 1
Jane Nerd 7 6
Bob Nerd 8 6

VP Marketing 2 1

VP Sales 3 1
Joe Sales Guy 4 3
Bill Sales Assistant 5 4

Joe Sales Guy 4 3


Bill Sales Assistant 5 4

VP Engineering 6 1
Jane Nerd 7 6
Bob Nerd 8 6

Jane Nerd 7 6

Bob Nerd 8 6

Bill Sales Assistant 5 4

20 rows selected.

Th s seems a l ttle strange. It looks as though Oracle has produced all poss ble trees and subtrees. Let's
add a START WITH clause:

select name, slave_id, supervisor_id


from corporate_slaves
connect by prior slave_id = supervisor_id
start with slave_id in (select slave_id
from corporate_slaves
where supervisor_id is null);

NAME SLAVE_ID SUPERVISOR_ID


-------------------- ---------- -------------

https://ph l p.greenspun.com/sql/trees.html 2/16


19.09.2019 Represent ng Trees n Oracle SQL
Big Boss Man 1
VP Marketing 2 1
VP Sales 3 1
Joe Sales Guy 4 3
Bill Sales Assistant 5 4
VP Engineering 6 1
Jane Nerd 7 6
Bob Nerd 8 6

8 rows selected.

Not ce that we've used a subquery n the START WITH clause to f nd out who
s/are the b g kahuna(s). For the rest of th s example, we'll just hard-code n the
slave_id 1 for brev ty.
Though these folks are n the correct order, t s k nd of tough to tell from the
preced ng report who works for whom. Oracle prov des a mag c pseudo-column
that s mean ngful only when a query ncludes a CONNECT BY. The pseudo-
column s level:

select name, slave_id, supervisor_id, level


from corporate_slaves
connect by prior slave_id = supervisor_id
start with slave_id = 1;

NAME SLAVE_ID SUPERVISOR_ID LEVEL


-------------------- ---------- ------------- ----------
Big Boss Man 1 1
VP Marketing 2 1 2
VP Sales 3 1 2
Joe Sales Guy 4 3 3
Bill Sales Assistant 5 4 4
VP Engineering 6 1 2
Jane Nerd 7 6 3
Bob Nerd 8 6 3

8 rows selected.

The level column can be used for ndentat on. Here we w ll use the concatenat on operator (||) to add
spaces n front of the name column:

column padded_name format a30

select
lpad(' ', (level - 1) * 2) || name as padded_name,
slave_id,
supervisor_id,
level
from corporate_slaves
connect by prior slave_id = supervisor_id
start with slave_id = 1;

PADDED_NAME SLAVE_ID SUPERVISOR_ID LEVEL


------------------------------ ---------- ------------- ----------
Big Boss Man 1 1
VP Marketing 2 1 2
VP Sales 3 1 2
Joe Sales Guy 4 3 3
Bill Sales Assistant 5 4 4
VP Engineering 6 1 2
Jane Nerd 7 6 3
Bob Nerd 8 6 3

8 rows selected.

If you want to l m t your report, you can use standard WHERE clauses:

https://ph l p.greenspun.com/sql/trees.html 3/16


19.09.2019 Represent ng Trees n Oracle SQL

select
lpad(' ', (level - 1) * 2) || name as padded_name,
slave_id,
supervisor_id,
level
from corporate_slaves
where level <= 3
connect by prior slave_id = supervisor_id
start with slave_id = 1;

PADDED_NAME SLAVE_ID SUPERVISOR_ID LEVEL


------------------------------ ---------- ------------- ----------
Big Boss Man 1 1
VP Marketing 2 1 2
VP Sales 3 1 2
Joe Sales Guy 4 3 3
VP Engineering 6 1 2
Jane Nerd 7 6 3
Bob Nerd 8 6 3

7 rows selected.

Suppose that you want people at the same level to sort alphabet cally. Sadly, the ORDER BY clause
doesn't work so great n conjunct on w th CONNECT BY:

select
lpad(' ', (level - 1) * 2) || name as padded_name,
slave_id,
supervisor_id,
level
from corporate_slaves
connect by prior slave_id = supervisor_id
start with slave_id = 1
order by level, name;

PADDED_NAME SLAVE_ID SUPERVISOR_ID LEVEL


------------------------------ ---------- ------------- ----------
Big Boss Man 1 1
VP Engineering 6 1 2
VP Marketing 2 1 2
VP Sales 3 1 2
Bob Nerd 8 6 3
Jane Nerd 7 6 3
Joe Sales Guy 4 3 3
Bill Sales Assistant 5 4 4

select
lpad(' ', (level - 1) * 2) || name as padded_name,
slave_id,
supervisor_id,
level
from corporate_slaves
connect by prior slave_id = supervisor_id
start with slave_id = 1
order by name;

PADDED_NAME SLAVE_ID SUPERVISOR_ID LEVEL


------------------------------ ---------- ------------- ----------
Big Boss Man 1 1
Bill Sales Assistant 5 4 4
Bob Nerd 8 6 3
Jane Nerd 7 6 3
Joe Sales Guy 4 3 3
VP Engineering 6 1 2
VP Marketing 2 1 2
VP Sales 3 1 2

https://ph l p.greenspun.com/sql/trees.html 4/16


19.09.2019 Represent ng Trees n Oracle SQL

SQL s a set-or ented language. In the result of a CONNECT BY query, t s prec sely the order that has
value. Thus t doesn't make much sense to also have an ORDER BY clause.

JOIN doesn't work w th CONNECT BY


If we try to bu ld a report show ng each employee and h s or her
superv sor's name, we are treated to one of Oracle's few nformat ve error
messages:

select
lpad(' ', (level - 1) * 2) || cs1.name as padded_name,
cs2.name as supervisor_name
from corporate_slaves cs1, corporate_slaves cs2
where cs1.supervisor_id = cs2.slave_id(+)
connect by prior cs1.slave_id = cs1.supervisor_id
start with cs1.slave_id = 1;

ERROR at line 4:
ORA-01437: cannot have join with CONNECT BY

We can work around th s part cular problem by creat ng a v ew:

create or replace view connected_slaves


as
select
lpad(' ', (level - 1) * 2) || name as padded_name,
slave_id,
supervisor_id,
level as the_level
from corporate_slaves
connect by prior slave_id = supervisor_id
start with slave_id = 1;

Not ce that we've had to rename level so that we d dn't end up w th a v ew column named after a reserved
word. The v ew works just l ke the raw query:

select * from connected_slaves;

PADDED_NAME SLAVE_ID SUPERVISOR_ID THE_LEVEL


------------------------------ ---------- ------------- ----------
Big Boss Man 1 1
VP Marketing 2 1 2
VP Sales 3 1 2
Joe Sales Guy 4 3 3
Bill Sales Assistant 5 4 4
VP Engineering 6 1 2
Jane Nerd 7 6 3
Bob Nerd 8 6 3

8 rows selected.

but we can JOIN now

select padded_name, corporate_slaves.name as supervisor_name


from connected_slaves, corporate_slaves
where connected_slaves.supervisor_id = corporate_slaves.slave_id(+);

PADDED_NAME SUPERVISOR_NAME
------------------------------ --------------------
Big Boss Man
VP Marketing Big Boss Man
VP Sales Big Boss Man
Joe Sales Guy VP Sales
https://ph l p.greenspun.com/sql/trees.html 5/16
19.09.2019 Represent ng Trees n Oracle SQL
Bill Sales Assistant Joe Sales Guy
VP Engineering Big Boss Man
Jane Nerd VP Engineering
Bob Nerd VP Engineering

8 rows selected.

If you have sharp eyes, you'll not ce that we've actually OUTER JOINed so that our results don't exclude
the b g boss.

Select-l st subquer es do work w th CONNECT BY


Instead of the VIEW and JOIN, we could have added a subquery to the select l st:

select
lpad(' ', (level - 1) * 2) || name as padded_name,
(select name
from corporate_slaves cs2
where cs2.slave_id = cs1.supervisor_id) as supervisor_name
from corporate_slaves cs1
connect by prior slave_id = supervisor_id
start with slave_id = 1;

PADDED_NAME SUPERVISOR_NAME
------------------------------ --------------------
Big Boss Man
VP Marketing Big Boss Man
VP Sales Big Boss Man
Joe Sales Guy VP Sales
Bill Sales Assistant Joe Sales Guy
VP Engineering Big Boss Man
Jane Nerd VP Engineering
Bob Nerd VP Engineering

8 rows selected.

The general rule n Oracle s that you can have a subquery that returns a s ngle row anywhere n the select
l st.

Does th s person work for me?

Suppose that you've bu lt an ntranet Web serv ce. There are th ngs that your software should show to an
employee's boss (or boss's boss) that t shouldn't show to a subord nate or peer. Here we try to f gure out f
the VP Market ng (#2) has superv sory author ty over Jane Nerd (#7):

select count(*)
from corporate_slaves
where slave_id = 7
and level > 1
start with slave_id = 2
connect by prior slave_id = supervisor_id;

COUNT(*)
----------
0

Apparently not. Not ce that we start w th the VP Market ng (#2) and st pulate level > 1 to be sure that we
w ll never conclude that someone superv ses h m or herself. Let's ask f the B g Boss Man (#1) has
author ty over Jane Nerd:

select count(*)
from corporate_slaves
where slave_id = 7
https://ph l p.greenspun.com/sql/trees.html 6/16
19.09.2019 Represent ng Trees n Oracle SQL
and level > 1
start with slave_id = 1
connect by prior slave_id = supervisor_id;

COUNT(*)
----------
1

Even though B g Boss Man sn't Jane Nerd's d rect superv sor, ask ng Oracle to compute the relevant
subtree y elds us the correct result. In the ArsD g ta Commun ty System Intranet module, we dec ded that
th s computat on was too mportant to be left as a query n nd v dual Web pages. We central zed the
quest on n a PL/SQL procedure:

create or replace function intranet_supervises_p


(query_supervisor IN integer, query_user_id IN integer)
return varchar
is
n_rows_found integer;
BEGIN
select count(*) into n_rows_found
from intranet_users
where user_id = query_user_id
and level > 1
start with user_id = query_supervisor
connect by supervisor = PRIOR user_id;
if n_rows_found > 0 then
return 't';
else
return 'f';
end if;
END intranet_supervises_p;

Fam ly trees
What f the graph s a l ttle more compl cated than employee-superv sor? For example, suppose that you
are represent ng a fam ly tree. Even w thout allow ng for d vorce and remarr age, exot c South Afr can
fert l ty cl n cs, etc., we st ll need more than one po nter for each node:

create table family_relatives (


relative_id integer primary key,
spouse references family_relatives,
mother references family_relatives,
father references family_relatives,
-- in case they don't know the exact birthdate
birthyear integer,
birthday date,
-- sadly, not everyone is still with us
deathyear integer,
first_names varchar(100) not null,
last_name varchar(100) not null,
sex char(1) check (sex in ('m','f')),
-- note the use of multi-column check constraints
check ( birthyear is not null or birthday is not null)
);

-- some test data

insert into family_relatives


(relative_id, first_names, last_name, sex, spouse, mother, father, birthyear)
values
(1, 'Nick', 'Gittes', 'm', NULL, NULL, NULL, 1902);

insert into family_relatives


(relative_id, first_names, last_name, sex, spouse, mother, father, birthyear)
values
https://ph l p.greenspun.com/sql/trees.html 7/16
19.09.2019 Represent ng Trees n Oracle SQL
(2, 'Cecile', 'Kaplan', 'f', 1, NULL, NULL, 1910);

update family_relatives
set spouse = 2
where relative_id = 1;

insert into family_relatives


(relative_id, first_names, last_name, sex, spouse, mother, father, birthyear)
values
(3, 'Regina', 'Gittes', 'f', NULL, 2, 1, 1934);

insert into family_relatives


(relative_id, first_names, last_name, sex, spouse, mother, father, birthyear)
values
(4, 'Marjorie', 'Gittes', 'f', NULL, 2, 1, 1936);

insert into family_relatives


(relative_id, first_names, last_name, sex, spouse, mother, father, birthyear)
values
(5, 'Shirley', 'Greenspun', 'f', NULL, NULL, NULL, 1901);

insert into family_relatives


(relative_id, first_names, last_name, sex, spouse, mother, father, birthyear)
values
(6, 'Jack', 'Greenspun', 'm', 5, NULL, NULL, 1900);

update family_relatives
set spouse = 6
where relative_id = 5;

insert into family_relatives


(relative_id, first_names, last_name, sex, spouse, mother, father, birthyear)
values
(7, 'Nathaniel', 'Greenspun', 'm', 3, 5, 6, 1930);

update family_relatives
set spouse = 7
where relative_id = 3;

insert into family_relatives


(relative_id, first_names, last_name, sex, spouse, mother, father, birthyear)
values
(8, 'Suzanne', 'Greenspun', 'f', NULL, 3, 7, 1961);

insert into family_relatives


(relative_id, first_names, last_name, sex, spouse, mother, father, birthyear)
values
(9, 'Philip', 'Greenspun', 'm', NULL, 3, 7, 1963);

insert into family_relatives


(relative_id, first_names, last_name, sex, spouse, mother, father, birthyear)
values
(10, 'Harry', 'Greenspun', 'm', NULL, 3, 7, 1965);

In apply ng the lessons from the employee examples, the most obv ous problem that we face now s
whether to follow the mother or the father po nters:

column full_name format a25

-- follow patrilineal (start with my mom's father)


select lpad(' ', (level - 1) * 2) || first_names || ' ' || last_name as full_name
from family_relatives
connect by prior relative_id = father
start with relative_id = 1;

FULL_NAME
-------------------------
Nick Gittes
Regina Gittes
https://ph l p.greenspun.com/sql/trees.html 8/16
19.09.2019 Represent ng Trees n Oracle SQL
Marjorie Gittes

-- follow matrilineal (start with my mom's mother)


select lpad(' ', (level - 1) * 2) || first_names || ' ' || last_name as full_name
from family_relatives
connect by prior relative_id = mother
start with relative_id = 2;

FULL_NAME
-------------------------
Cecile Kaplan
Regina Gittes
Suzanne Greenspun
Philip Greenspun
Harry Greenspun
Marjorie Gittes

Here's what the off c al Oracle docs have to say about CONNECT BY:

spec f es the relat onsh p between parent rows and ch ld rows of the h erarchy. cond t on can
be any cond t on as descr bed n "Cond t ons". However, some part of the cond t on must use
the PRIOR operator to refer to the parent row. The part of the cond t on conta n ng the
PRIOR operator must have one of the follow ng forms:
PRIOR expr comparison_operator expr
expr comparison_operator PRIOR expr

There s noth ng that says comparison_operator has to be merely the equals s gn. Let's start aga n w th my
mom's father but CONNECT BY more than one column:

-- follow both
select lpad(' ', (level - 1) * 2) || first_names || ' ' || last_name as full_name
from family_relatives
connect by prior relative_id in (mother, father)
start with relative_id = 1;

FULL_NAME
-------------------------
Nick Gittes
Regina Gittes
Suzanne Greenspun
Philip Greenspun
Harry Greenspun
Marjorie Gittes

Instead of arb trar ly start ng w th Grandpa N ck, let's ask Oracle to show us all the trees that start w th a
person whose parents are unknown:

select lpad(' ', (level - 1) * 2) || first_names || ' ' || last_name as full_name


from family_relatives
connect by prior relative_id in (mother, father)
start with relative_id in (select relative_id from family_relatives
where mother is null
and father is null);

FULL_NAME
-------------------------
Nick Gittes
Regina Gittes
Suzanne Greenspun
Philip Greenspun
Harry Greenspun
Marjorie Gittes
Cecile Kaplan
Regina Gittes
Suzanne Greenspun

https://ph l p.greenspun.com/sql/trees.html 9/16


19.09.2019 Represent ng Trees n Oracle SQL
Philip Greenspun
Harry Greenspun
Marjorie Gittes
Shirley Greenspun
Nathaniel Greenspun
Suzanne Greenspun
Philip Greenspun
Harry Greenspun
Jack Greenspun
Nathaniel Greenspun
Suzanne Greenspun
Philip Greenspun
Harry Greenspun

22 rows selected.

PL/SQL nstead of JOIN


The preced ng report s nterest ng but confus ng because t s hard to tell
where the trees meet n marr age. As noted above, you can't do a JOIN
w th a CONNECT BY. We demonstrated the workaround of bury ng the
CONNECT BY n a v ew. A more general workaround s us ng PL/SQL:

create or replace function family_spouse_name


(v_relative_id family_relatives.relative_id%TYPE)
return varchar
is
v_spouse_id integer;
spouse_name varchar(500);
BEGIN
select spouse into v_spouse_id
from family_relatives
where relative_id = v_relative_id;
if v_spouse_id is null then
return null;
else
select (first_names || ' ' || last_name) into spouse_name
from family_relatives
where relative_id = v_spouse_id;
return spouse_name;
end if;
END family_spouse_name;
/
show errors

column spouse format a20

select
lpad(' ', (level - 1) * 2) || first_names || ' ' || last_name as full_name,
family_spouse_name(relative_id) as spouse
from family_relatives
connect by prior relative_id in (mother, father)
start with relative_id in (select relative_id from family_relatives
where mother is null
and father is null);

FULL_NAME SPOUSE
------------------------- --------------------
Nick Gittes Cecile Kaplan
Regina Gittes Nathaniel Greenspun
Suzanne Greenspun
Philip Greenspun
Harry Greenspun
Marjorie Gittes
Cecile Kaplan Nick Gittes
Regina Gittes Nathaniel Greenspun
Suzanne Greenspun

https://ph l p.greenspun.com/sql/trees.html 10/16


19.09.2019 Represent ng Trees n Oracle SQL
Philip Greenspun
Harry Greenspun
Marjorie Gittes
Shirley Greenspun Jack Greenspun
Nathaniel Greenspun Regina Gittes
Suzanne Greenspun
Philip Greenspun
Harry Greenspun
Jack Greenspun Shirley Greenspun
Nathaniel Greenspun Regina Gittes
Suzanne Greenspun
Philip Greenspun
Harry Greenspun

PL/SQL nstead of JOIN and GROUP BY


Suppose that n add t on to d splay ng the fam ly tree n a Web page, we also want to show a flag when a
story about a fam ly member s ava lable. F rst we need a way to represent stor es:

create table family_stories (


family_story_id integer primary key,
story clob not null,
item_date date,
item_year integer,
access_control varchar(20)
check (access_control in ('public', 'family', 'designated')),
check (item_date is not null or item_year is not null)
);

-- a story might be about more than one person


create table family_story_relative_map (
family_story_id references family_stories,
relative_id references family_relatives,
primary key (relative_id, family_story_id)
);

-- put in a test story


insert into family_stories
(family_story_id, story, item_year, access_control)
values
(1, 'After we were born, our parents stuck the Wedgwood in a cabinet
and bought indestructible china. Philip and his father were sitting at
the breakfast table one morning. Suzanne came downstairs and, without
saying a word, took a cereal bowl from the cupboard, walked over to
Philip and broke the bowl over his head. Their father immediately
started laughing hysterically.', 1971, 'public');

insert into family_story_relative_map


(family_story_id, relative_id)
values
(1, 8);

insert into family_story_relative_map


(family_story_id, relative_id)
values
(1, 9);

insert into family_story_relative_map


(family_story_id, relative_id)
values
(1, 7);

To show the number of stor es alongs de a fam ly member's l st ng, we would typ cally do an OUTER
JOIN and then GROUP BY the columns other than the count(family_story_id). In order not to d sturb
the CONNECT BY, however, we create another PL/SQL funct on:

https://ph l p.greenspun.com/sql/trees.html 11/16


19.09.2019 Represent ng Trees n Oracle SQL

create or replace function family_n_stories (v_relative_id family_relatives.relative_id%TYPE)


return integer
is
n_stories integer;
BEGIN
select count(*) into n_stories
from family_story_relative_map
where relative_id = v_relative_id;
return n_stories;
END family_n_stories;
/
show errors

select
lpad(' ', (level - 1) * 2) || first_names || ' ' || last_name as full_name,
family_n_stories(relative_id) as n_stories
from family_relatives
connect by prior relative_id in (mother, father)
start with relative_id in (select relative_id from family_relatives
where mother is null
and father is null);

FULL_NAME N_STORIES
------------------------- ----------
Nick Gittes 0
...
Shirley Greenspun 0
Nathaniel Greenspun 1
Suzanne Greenspun 1
Philip Greenspun 1
Harry Greenspun 0
...

Work ng Backwards
What does t look l ke to start at the youngest generat on and work back?

select
lpad(' ', (level - 1) * 2) || first_names || ' ' || last_name as full_name,
family_spouse_name(relative_id) as spouse
from family_relatives
connect by relative_id in (prior mother, prior father)
start with relative_id = 9;

FULL_NAME SPOUSE
------------------------- --------------------
Philip Greenspun
Regina Gittes Nathaniel Greenspun
Nick Gittes Cecile Kaplan
Cecile Kaplan Nick Gittes
Nathaniel Greenspun Regina Gittes
Shirley Greenspun Jack Greenspun
Jack Greenspun Shirley Greenspun

We ought to be able to v ew all the trees start ng from all the leaves but Oracle seems to be exh b t ng
strange behav or:

select
lpad(' ', (level - 1) * 2) || first_names || ' ' || last_name as full_name,
https://ph l p.greenspun.com/sql/trees.html 12/16
19.09.2019 Represent ng Trees n Oracle SQL
family_spouse_name(relative_id) as spouse
from family_relatives
connect by relative_id in (prior mother, prior father)
start with relative_id not in (select mother from family_relatives
union
select father from family_relatives);

no rows selected

What's wrong? If we try the subquery by tself, we get a reasonable result. Here are all the relative_ids
that appear n the mother or father column at least once.

select mother from family_relatives


union
select father from family_relatives

MOTHER
----------
1
2
3
5
6
7

7 rows selected.

The answer l es n that extra blank l ne at the bottom. There s a NULL n th s result set. Exper mentat on
reveals that Oracle behaves asymmetr cally w th NULLs and IN and NOT IN:

SQL> select * from dual where 1 in (1,2,3,NULL);

D
-
X

SQL> select * from dual where 1 not in (2,3,NULL);

no rows selected

The answer s bur ed n the Oracle documentat on of NOT IN: "Evaluates to FALSE f any member of the
set s NULL." The correct query n th s case?

select
lpad(' ', (level - 1) * 2) || first_names || ' ' || last_name as full_name,
family_spouse_name(relative_id) as spouse
from family_relatives
connect by relative_id in (prior mother, prior father)
start with relative_id not in (select mother
from family_relatives
where mother is not null
union
select father
from family_relatives
where father is not null);

FULL_NAME SPOUSE
------------------------- --------------------
Marjorie Gittes
Nick Gittes Cecile Kaplan
Cecile Kaplan Nick Gittes
Suzanne Greenspun
Regina Gittes Nathaniel Greenspun
Nick Gittes Cecile Kaplan
Cecile Kaplan Nick Gittes
https://ph l p.greenspun.com/sql/trees.html 13/16
19.09.2019 Represent ng Trees n Oracle SQL
Nathaniel Greenspun Regina Gittes
Shirley Greenspun Jack Greenspun
Jack Greenspun Shirley Greenspun
Philip Greenspun
Regina Gittes Nathaniel Greenspun
Nick Gittes Cecile Kaplan
Cecile Kaplan Nick Gittes
Nathaniel Greenspun Regina Gittes
Shirley Greenspun Jack Greenspun
Jack Greenspun Shirley Greenspun
Harry Greenspun
Regina Gittes Nathaniel Greenspun
Nick Gittes Cecile Kaplan
Cecile Kaplan Nick Gittes
Nathaniel Greenspun Regina Gittes
Shirley Greenspun Jack Greenspun
Jack Greenspun Shirley Greenspun

24 rows selected.

Performance and Tun ng


Oracle s not gett ng any help from the Tree Fa ry n produc ng results from a
CONNECT BY. If you don't want tree quer es to take O(N^2) t me, you need to
bu ld nd ces that let Oracle very qu ckly answer quest ons of the form "What are all
the ch ldren of Parent X?"
For the corporate slaves table, you'd want two concatenated nd ces:

create index corporate_slaves_idx1


on corporate_slaves (slave_id, supervisor_id);
create index corporate_slaves_idx2
on corporate_slaves (supervisor_id, slave_id);

Reference

SQL Reference sect on on CONNECT BY


PL/SQL User's Gu de and Reference

Gratu tous Photos

https://ph l p.greenspun.com/sql/trees.html 14/16


19.09.2019 Represent ng Trees n Oracle SQL

Next: dates

ph lg@m t.edu

Reader's Comments
Oracle9 does CONNECT BY on jo ns. It also adds an "ORDER SIBLINGS BY" clause,
f x ng the om ss on that prevents you from order ng each level of the query.
Couldn't f nd the art cle at Dartmouth :(, t looked really nterest ng!

-- Andrew Wolfe, March 24, 2004

Interested readers should check out Joe Celko's nested set model for represent ng trees n
SQL. No need to be locked nto propr etary SQL d alects and probably a couple of orders of
magn tude faster to query!

Here's some l nks...

+ http://www. ntell gententerpr se.com/001020/celko.jhtml


+ http://www.dbmsmag.com/9603d06.html
+ http://www.dbmsmag.com/9604d06.html
+ http://www.dbmsmag.com/9605d06.html
https://ph l p.greenspun.com/sql/trees.html 15/16
19.09.2019 Represent ng Trees n Oracle SQL

+ http://www.dbmsmag.com/9606d06.html
+ http://www.sqlteam.com/Forums/top c.asp?TOPIC_ID=14099
+ http://www.dbaz ne.com/oracle/or-art cles/tropashko4
+ http://mrnaz.com/stat c/art cles/trees_ n_sql_tutor al/mptt_overv ew.php

Regards, Mattster

-- Matt Anon, Apr l 3, 2007

Add a comment

Related L nks
represent ng an m-ary tree n sql- Th s method allows for very fast retr eval of descendants and
mod f cat on of an m-ary tree. no self-referenc ng or nested select statements are necessary to
retr eve some or all descendants. the labell ng of nodes s such that t allows very s mple and fast
query ng for DFS order of nodes. t was part ally nsp red by huffman encod ng. (contr buted by
Anthony D'Aur a)

Dead l nk- The l nk above to Dartmouth college appears to be dead, but Web Arch ve kept a copy
of the page (contr buted by Tom Lebr)

Add a l nk

https://ph l p.greenspun.com/sql/trees.html 16/16

You might also like