Reputation: 113
In the following example:
ID NAME ATTR
-----------------
1 A1 ROOT
2 A2
3 A3 VALX
4 A4
5 A5
6 A6
ID CHILD_ID PARENT_ID
-------------------------
1 6 4
2 5 4
3 4 3
4 3 1
5 2 1
I need a query to get the value of the ATTR column of the PARENT when it is different from null. Raising the levels until you get the first match. For example with ID 6:
ID NAME NAME_PARENT ATTR_PARENT
-----------------------------------------
6 A6 A3 VALX
I have tried with:
select T.ID, T.NAME, T2.NAME PARENT_NAME, T2.ATTR ATTR_PARENT
from TABLE T
INNER JOIN RELATIONSHIP R
ON R.CHILD_ID = T.ID
INNER JOIN TABLE T2
ON T2.ID = R.PARENT_D
WHERE T2.ATTR IS NOT NULL
START WITH T.ID = 6
CONNECT BY T.ID = PRIOR R.PARENTID
--and R.PARENTID != prior T.ID
And sorry for my bad english
Upvotes: 1
Views: 252
Reputation: 48865
Instead of using the [mostly obsolete] CONNECT BY
clause you can use standard Recursive SQL CTEs (Common Table Expressions).
For example:
with
n (id, name, name_parent, attr_parent, parent_id, lvl) as (
select t.id, t.name, b.name, b.attr, r.parent_id, 1
from t
join r on t.id = r.child_id
join t b on b.id = r.parent_id
where t.id = 6 -- starting node
union all
select n.id, n.name, b.name, b.attr, r.parent_id, lvl + 1
from n
join r on r.child_id = n.parent_id
join t b on b.id = r.parent_id
where n.attr_parent is null
)
select id, name, name_parent, attr_parent
from n
where lvl = (select max(lvl) from n)
Result:
ID NAME NAME_PARENT ATTR_PARENT
-- ---- ----------- -----------
6 A6 A3 VALX
For reference, the data script I used is:
create table t (
id number(6),
name varchar2(10),
attr varchar2(10)
);
insert into t (id, name, attr) values (1, 'A1', 'ROOT');
insert into t (id, name, attr) values (2, 'A2', null);
insert into t (id, name, attr) values (3, 'A3', 'VALX');
insert into t (id, name, attr) values (4, 'A4', null);
insert into t (id, name, attr) values (5, 'A5', null);
insert into t (id, name, attr) values (6, 'A6', null);
create table r (
id number(6),
child_id number(6),
parent_id number(6)
);
insert into r (id, child_id, parent_id) values (1, 6, 4);
insert into r (id, child_id, parent_id) values (2, 5, 4);
insert into r (id, child_id, parent_id) values (3, 4, 3);
insert into r (id, child_id, parent_id) values (4, 3, 1);
insert into r (id, child_id, parent_id) values (5, 2, 1);
Upvotes: 2
Reputation:
Here is how you can do the whole thing in a single pass of connect by
- using the various features available for this kind of query (including the connect_by_isleaf
flag and the connect_by_root
pseudo-column):
select connect_by_root(r.child_id) as id,
connect_by_root(t.name) as name,
t.name as name_parent,
t.attr as attribute_parent
from r join t on r.child_id = t.id
where connect_by_isleaf = 1
start with r.child_id = 6
connect by prior r.parent_id = r.child_id and prior t.attr is null
;
ID NAME NAME_PARENT ATTRIBUTE_PARENT
---------- ---------- ----------- ----------------
6 A6 A3 VALX
Note that this will still return a null
ATTRIBUTE_PARENT, if the entire tree is walked without ever finding an ancestor with non-null ATTRIBUTE. If in fact you only want to show something in the output if an ancestor has a non-null ATTRIBUTE (and allow the output to have no rows if there is no such ancestor), you can change the where
clause to where t.attr is not null
. In most cases, though, you would probably want the behavior as I coded it.
I used the tables and data as posted in @TheImpaler 's answer (thank you for the create table
and insert
statements!)
As I commented under his answer: recursive with
clause is in the SQL Standard, so it has some advantages over connect by
. However, whenever the same job can be done with connect by
, it's worth at least testing it that way too. In many cases, due to numerous optimizations Oracle has come up with over time, connect by
will be much faster.
One reason some developers avoid connect by
is that they don't spend the time to learn the various features (like the ones I used here). Not a good reason, in my opinion.
Upvotes: 1