Connect by prior get parents and children - sql

I'm working on a query which use connect by prior.
I have written a query which retrieves all children of an entity. What I want is to retrieve both children and parents rows.
Here is my SQL :
Select *
From myTable tab
Connect By Prior tab.id= tab.child_id
Start With tab.id= 2;
How could I retrieve parents ?
Thanks.

Use UNION ALL to combine a query to get the children with another to get the ancestors.
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE myTable ( id, child_id ) AS
SELECT 0, 1 FROM DUAL UNION ALL
SELECT 1, 2 FROM DUAL UNION ALL
SELECT 2, 3 FROM DUAL UNION ALL
SELECT 3, 4 FROM DUAL UNION ALL
SELECT 4, 5 FROM DUAL UNION ALL
SELECT 3, 6 FROM DUAL UNION ALL
SELECT 0, 7 FROM DUAL UNION ALL
SELECT 1, 8 FROM DUAL;
Query 1:
SELECT * -- Child Rows
FROM mytable
START WITH id = 2
CONNECT BY PRIOR child_id = id
UNION ALL
SELECT * -- Ancestor Rows
FROM mytable
START WITH child_id = 2
CONNECT BY PRIOR id = child_id
Results:
| ID | CHILD_ID |
|----|----------|
| 2 | 3 |
| 3 | 4 |
| 4 | 5 |
| 3 | 6 |
| 1 | 2 |
| 0 | 1 |

An alternative approach to a hierarchical query, if you're on 11gR2 or higher, is recursive subquery factoring:
with rcte (id, child_id, some_other_col) as (
select id, child_id, some_other_col -- whichever columns you're interested in
from myTable
where id = 2
union all
select t.id, t.child_id, t.some_other_col -- whichever columns you're interested in
from rcte r
join myTable t
on t.id = r.child_id -- match parents
or t.child_id = r.id -- match children
)
cycle id set is_cycle to 1 default 0
select id, child_id, some_other_col -- whichever columns you're interested in
from rcte
where is_cycle = 0;
The anchor member finds your initial target row. The recursive member then looks for parents or children of each row found so far.
The final query can get whichever columns you want, do aggregation, etc.
(Possibly worth testing both approaches with real data to see if there is a performance difference, of course).

Related

Oracle table hierarchy level and grouping

I have a oracle database where foreign key is enabled and I have tables A,B,C,D,E,F,AB,ABC,EF
Attaching the picture for hierarchy
'B' has parent 'D',
'AB' has parent 'A' as well as 'B',
'ABC' has parent 'AB' and 'C',
'EF' has parent 'E' & 'F',
Now {A,B,C,D,AB, ABC} has no link with {E,F,EF}. I have 2 requirements
I need to group them as group 1 & 2
I need to assign a hierarchy level for each group maintaining referential integrity as follows
How can I do it using sql/pl/sql in oracle database?
Assuming that you always end up at a single leaf node for each group then you can use a hierarchical query twice: from leaf-to-root to determine the groups; and then from root-to-leaf to determine the depths.
WITH groups ( child, parent, grp ) AS (
SELECT child,
parent,
DENSE_RANK() OVER ( ORDER BY CONNECT_BY_ROOT( child ) )
FROM table_name t
START WITH child NOT IN ( SELECT parent FROM table_name )
CONNECT BY PRIOR parent = child
),
depths ( child, parent, grp, depth ) AS (
SELECT child, parent, grp, LEVEL
FROM groups
START WITH parent NOT IN ( select child FROM table_name )
CONNECT BY PRIOR child = parent
)
SELECT table_name,
grp,
MAX( depth + depth_modifier ) AS depth
FROM depths
UNPIVOT ( table_name FOR depth_modifier IN ( child AS 1, parent AS 0 ) )
GROUP BY grp, table_name
ORDER BY grp, depth, table_name
Which, for your sample data:
CREATE TABLE table_name ( child, parent ) AS
SELECT 'B', 'D' FROM DUAL UNION ALL
SELECT 'AB', 'A' FROM DUAL UNION ALL
SELECT 'AB', 'B' FROM DUAL UNION ALL
SELECT 'ABC', 'AB' FROM DUAL UNION ALL
SELECT 'ABC', 'C' FROM DUAL UNION ALL
SELECT 'EF', 'E' FROM DUAL UNION ALL
SELECT 'EF', 'F' FROM DUAL;
Outputs:
TABLE_NAME | GRP | DEPTH
:--------- | --: | ----:
A | 1 | 1
C | 1 | 1
D | 1 | 1
B | 1 | 2
AB | 1 | 3
ABC | 1 | 4
E | 2 | 1
F | 2 | 1
EF | 2 | 2
If you don't always have a single leaf node for each group then you'll need a MUCH more complicated solution (probably written in PL/SQL).
db<>fiddle here

hierarchical query without side rows

I have "id-parent_id related" data, like this:
1
/ \
/ \
2 4
/
/
3
I have the code, that returns data for all rows related to particular (related to condition in the start with clause) tree - in both sides ("up" and "down"), for example:
with
temp_cte(id, parent_id) as
(select 1, null from dual
union all
select 2, 1 from dual
union all
select 3, 2 from dual
union all
select 4, 1 from dual
union all
select 5, null from dual)
select *
from temp_cte t
connect by nocycle (prior id = parent_id) or (prior parent_id = id)
start with t.id = 2
order by id
How do i get data without "side" ("right"/"left") rows?
e.g. for the drawn above -
I need data without 4 when I start with 2 or 3,
and I need data without 2 and 3 when I start with 4
(if start with 1, I still want the full tree)
This is because of OR predicate in CONNECT BY: your query traverses in both directions, so on the second step of CONNECT BY you'll have all the childs of the parent and finally all the tree.
You need to split the query into union of two directions.
with
temp_cte(id, parent_id) as
(select 1, null from dual
union all
select 2, 1 from dual
union all
select 3, 2 from dual
union all
select 4, 1 from dual
union all
select 5, null from dual
)
select id, parent_id
from temp_cte t
where level > 1
connect by nocycle (prior id = parent_id)
start with t.id = 2
union all
select id, parent_id
from temp_cte t
connect by (prior parent_id = id)
start with t.id = 2
order by id
ID | PARENT_ID
-: | --------:
1 | null
2 | 1
3 | 2
db<>fiddle here

How can I get all the parent element by providing child element ID in oracle?

My Oracle table looks like this
ID | ParentID
-----------------
1 | 0
2 | 1
3 | 2
4 | 3
5 | 3
If I know only ID and need to get all parent elements in oracle, what is the query I need to use?
ex:- If I pass 5, need to get 5 > 3 > 2 > 1
For example:
SQL> with test (id, parent) as
2 (select 1, 0 from dual union
3 select 2, 1 from dual union
4 select 3, 2 from dual union
5 select 4, 3 from dual union
6 select 5, 3 from dual
7 )
8 select listagg(id, '->') within group (order by level) result
9 from test
10 start with id = &par_id
11 connect by prior parent = id;
Enter value for par_id: 5
RESULT
---------------------------------------------------------------------
5->3->2->1
SQL>
You may use a recursive CTE
WITH cte (id, parentid, p)
AS (SELECT id,
parentid,
To_char(id) AS p
FROM t
WHERE id = :p_id --enter 5
UNION ALL
SELECT t.id,
t.parentid,
c.p
|| '>'
|| t.id AS p
FROM t
JOIN cte c
ON ( c.parentid = t.id ))
SELECT p
FROM cte
WHERE parentid = 0 --Highest parent.
Demo

SQL Database Parent/Child recursion

Here is my table:
parent_id | child_id
--------------
1 | 2
1 | 3
1 | 4
2 | 5
2 | 6
5 | 8
8 | 9
9 | 5
I need to get all of the items under parent 2. I've found a few things similar to this, but but couldn't figure out how to make it work for my case. I keep getting maximum recursion limit reached. Here's what I have:
WITH CTE AS
(
SELECT gt.[child_id]
FROM [CHSPortal].[dbo].[company_adgroupstoadgroups] gt
WHERE gt.parent_id='2'
UNION ALL
SELECT g.[child_id]
FROM [CHSPortal].[dbo].[company_adgroupstoadgroups] g
INNER JOIN CTE g2 on g.parent_id=g2.child_id
)
select distinct child_id from CTE
The desired result is going to be: 2,3,4,5,6,8,9.
What modification do I need to make to get a list of all the items under child 2. I would also prefer 2 (the parent node) to be in the list. Any help would be appreciated.
First of all, there is a loop in your example (5|8, 8|9, 9|5), that is why you reach the maximum recursion limit.
Regarding the filtering question,below you can find an example for filtering by root node:
;WITH MTree (parent_id, child_id, LEVEL) AS (
SELECT t.parent_id , t.child_id, 0 AS LEVEL
FROM table_1 t
WHERE child_id = 2 --here you can filter the root node
UNION ALL
SELECT m.parent_id , m.child_id, LEVEL + 1
FROM Table_1 m
INNER JOIN MTree t ON t.child_id = m.parent_id
)
SELECT * FROM Mtree;
Not sure what's wrong with your query, aside from not relating to the sample data you provided, but this works just fine:
;WITH src AS (SELECT 1 AS parent_id, 2 AS child_id
UNION SELECT 1, 3
UNION SELECT 1, 4
UNION SELECT 2, 5
UNION SELECT 2, 6
UNION SELECT 5, 8
UNION SELECT 8, 9
UNION SELECT 9, 5)
,cte AS (SELECT *
FROM src
WHERE child_id = 2
UNION ALL
SELECT a.*
FROM src a
JOIN cte b
ON a.parent_id = b.child_id
)
SELECT TOP 100 *
FROM cte
--Limited to top 100 because of infinite recursion problem with sample data.

oracle 9i get highest member of tree with given child

I have a parent-child relationship in an Oracle 9i database-table
like:
parent | child
1 | 2
2 | 3
2 | 4
null | 1
1 | 8
I need to get the absolute parent from a given child.
Say, I have child 4, it has to give me parent: 1
I already looked to CONNECT BY , but I can't find the solution.
you could use a CONNECT BY query to build the list of parents and then filter :
SQL> WITH tree AS (
2 SELECT 1 parent_id, 2 child_id FROM DUAL
3 UNION ALL SELECT 2 , 3 FROM DUAL
4 UNION ALL SELECT 2 , 4 FROM DUAL
5 UNION ALL SELECT null, 1 FROM DUAL
6 UNION ALL SELECT 1 , 8 FROM DUAL
7 )
8 SELECT child_id
9 FROM (SELECT *
10 FROM tree
11 CONNECT BY PRIOR parent_id = child_id
12 START WITH child_id = 4)
13 WHERE parent_id IS NULL;
CHILD_ID
----------
1
SELECT parent
FROM (
SELECT parent
FROM (
SELECT parent, level AS l
FROM mytable
START WITH
child = 4
CONNECT BY
child = PRIOR parent
)
ORDER BY
l DESC
)
WHERE rownum = 1
This will give you NULL as the absolute parent.
If you want 1, replace parent with child:
SELECT child
FROM (
SELECT child
FROM (
SELECT child, level AS l
FROM mytable
START WITH
child = 4
CONNECT BY
child = PRIOR parent
)
ORDER BY
l DESC
)
WHERE rownum = 1