I have a SQL table where the rows in the table have a parent-child relationship with each other. I would like to write a recursive SQL query to find the oldest ancestor for each of these rows (or the row itself if it has no parent). So in other words, if my table looks like this:
Child
Parent
1
null
2
1
3
2
4
null
5
4
6
null
7
null
8
3
9
5
Then my final output should be:
Child
OldestAncestor
1
1
2
1
3
1
4
4
5
4
6
6
7
7
8
1
9
4
Can this be done? I know how to use recursive SQL to find an individual parent-child relationship, I'm just not sure if that can be taken a step further to find the parent's parent, etc.
Update - I was able to figure out a solution based on this article:https://www.webcodeexpert.com/2020/04/sql-server-cte-recursive-query-to-get.html
WITH parentChild(Child, Parent) AS
(
SELECT 1, null UNION ALL
SELECT 2, 1 UNION ALL
SELECT 3, 2 UNION ALL
SELECT 4, null UNION ALL
SELECT 5, 4 UNION ALL
SELECT 6, null UNION ALL
SELECT 7, null UNION ALL
SELECT 8, 3 UNION ALL
SELECT 9, 5
),
MyCTE AS
(
SELECT pc.Child, pc.Child AS 'OldestAncestor'
FROM parentChild pc
WHERE pc.Parent IS NULL
UNION ALL
SELECT pc2.Child, m.OldestAncestor AS 'OldestAncestor'
FROM parentChild pc2
INNER JOIN MyCTE m ON pc2.Parent = m.Child
)
SELECT * FROM MyCTE ORDER BY Child
In Oracle, you can use:
SELECT child,
CONNECT_BY_ROOT child AS oldest_ancestor
FROM table_name
START WITH parent IS NULL
CONNECT BY parent = PRIOR child
ORDER BY child;
or a recursive sub-query factoring clause (WITH clause):
WITH rsqfc (child, oldest_ancestor) AS (
SELECT child, child
FROM table_name
WHERE parent IS NULL
UNION ALL
SELECT t.child, r.oldest_ancestor
FROM rsqfc r
INNER JOIN table_name t
ON (t.parent = r.child)
)
SELECT *
FROM rsqfc
ORDER BY child;
Which, for the sample data:
CREATE TABLE table_name (Child, Parent) AS
SELECT 1, null FROM DUAL UNION ALL
SELECT 2, 1 FROM DUAL UNION ALL
SELECT 3, 2 FROM DUAL UNION ALL
SELECT 4, null FROM DUAL UNION ALL
SELECT 5, 4 FROM DUAL UNION ALL
SELECT 6, null FROM DUAL UNION ALL
SELECT 7, null FROM DUAL UNION ALL
SELECT 8, 3 FROM DUAL UNION ALL
SELECT 9, 5 FROM DUAL;
Both output:
CHILD
OLDEST_ANCESTOR
1
1
2
1
3
1
4
4
5
4
6
6
7
7
8
1
9
4
db<>fiddle here
You can use a recursive CTE to find the paths from the parents to the leaf nodes, and then join the CTE back onto the original table:
with recursive cte(n1, n2) as (
select child, child from tbl where parent is null
union all
select c.n1, t.child from cte c join tbl t on c.n2 = t.parent
)
select t1.child, c.n1 from tbl t1 join cte c on t1.child = c.n2;
Related
I have these rows in the specific parent child table:
Id - parentId
1 - null
2 - null
3 - 2
4 - 2
5 - 4
6 - 1
Indeed for example i want to enter 3 and get 2, enter 5 and get 2, and enter 6 and get 1, how can i do that?
You need a hierarchical query (connect by ...) Something like this:
with
tbl (id, parentid) as (
select 1, null from dual union all
select 2, null from dual union all
select 3, 2 from dual union all
select 4, 2 from dual union all
select 5, 4 from dual union all
select 6, 1 from dual
)
select id
from tbl
where connect_by_isleaf = 1
start with id = 5
connect by id = prior parentid
;
ID
-----
2
"In real life" you would have a table tbl with columns id and parentid (I simulated that in the with clause, but that is just for convenience; remove it first.) And you wouldn't hard-code the starting node; you would likely use a bind variable in the start with clause.
The key here is the where clause, using the connect_by_isleaf pseudocolumn.
I am trying to figure out the root parent in a table with hierarchical data. The following example works as expected but I need to do something extra. I want to avoid the query to ignore null id1 and show the (root parent - 1) if the root parent is null.
with table_a ( id1, child_id ) as (
select null, 1 from dual union all
select 1, 2 from dual union all
select 2, 3 from dual union all
select 3, NULL from dual union all
select 4, NULL from dual union all
select 5, 6 from dual union all
select 6, 7 from dual union all
select 7, 8 from dual union all
select 8, NULL from dual
)
select connect_by_root id1 as id, id1 as root_parent_id
from table_a
where connect_by_isleaf = 1
connect by child_id = prior id1
order by id 1
This brings up the following data
4 4
6 5
7 5
8 5
5 5
3 null
null null
2 null
1 null
what I want is
3 1
1 1
2 1
4 4
7 5
8 5
5 5
6 5
is it possible?
Thanks for the help
Using a recursive CTE you can do:
with table_a ( id1, child_id ) as (
select null, 1 from dual union all
select 1, 2 from dual union all
select 2, 3 from dual union all
select 3, NULL from dual union all
select 4, NULL from dual union all
select 5, 6 from dual union all
select 6, 7 from dual union all
select 7, 8 from dual union all
select 8, NULL from dual
),
n (s, e) as (
select id1 as s, child_id as e from table_a where id1 not in
(select child_id from table_a
where id1 is not null and child_id is not null)
union all
select n.s, a.child_id
from n
join table_a a on a.id1 = n.e
)
select
coalesce(e, s) as c, s
from n
order by s
Result:
C S
- -
3 1
1 1
2 1
4 4
5 5
7 5
8 5
6 5
As a side note, "Recursive CTEs" are more flexible than the old-school CONNECT BY.
This looks like it works but it may be incorrect, as I do not quite understand the logic behind choosing 1 for this, looks arbitrary to me, not much like real data will be.
As Hogan has asked already, it would be helpful if you could perhaps provide an explanation or an expanded data set to test this hierarchy.
with table_a ( id1, child_id ) as (
select null, 1 from dual union all
select 1, 2 from dual union all
select 2, 3 from dual union all
select 3, NULL from dual union all
select 4, NULL from dual union all
select 5, 6 from dual union all
select 6, 7 from dual union all
select 7, 8 from dual union all
select 8, NULL from dual
)
select connect_by_root id1 as id, id1 as root_parent_id
from table_a
where connect_by_isleaf = 1 and connect_by_root id1 is not null
connect by nocycle child_id = prior nvl(id1, 1)
order by 2, 1;
Sample execution:
FSITJA#dbd01 2019-07-19 13:51:13> with table_a ( id1, child_id ) as (
2 select null, 1 from dual union all
3 select 1, 2 from dual union all
4 select 2, 3 from dual union all
5 select 3, NULL from dual union all
6 select 4, NULL from dual union all
7 select 5, 6 from dual union all
8 select 6, 7 from dual union all
9 select 7, 8 from dual union all
10 select 8, NULL from dual
11 )
12 select connect_by_root id1 as id, id1 as root_parent_id
13 from table_a
14 where connect_by_isleaf = 1 and connect_by_root id1 is not null
15 connect by nocycle child_id = prior nvl(id1, 1)
16 order by 2, 1;
ID ROOT_PARENT_ID
---------- --------------
1 1
2 1
3 1
4 4
5 5
6 5
7 5
8 5
8 rows selected.
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
Say I have table A:
id child_id
1 2
2 3
3 NULL
4 NULL
5 6
6 7
7 8
8 NULL
I need a query to get root parent id of each row, this output for example:
id root_parent_id
1 1
2 1
3 1
4 4
5 5
6 5
7 5
8 5
I've tried both CONNECT BY and CTE by examples, but seems all I found based on rows with parent_id and not child_id and doesn't work.
CONNECT BY query I tried:
SELECT id, child_id, LEVEL, connect_by_root id
FROM a
CONNECT BY id = PRIOR child_id
CTE query I tried:
WITH recursion_view (base,
id,
child_id)
AS (
SELECT id base, id, child_id FROM a
UNION ALL
SELECT
previous_level.base,
current_level.id,
current_level.child_id
FROM recursion_view previous_level, a current_level
WHERE current_level.id = previous_level.child_id)
SELECT base,
id,
child_id
FROM recursion_view
ORDER BY base, id, child_id
You need to traverse the hierarchy in reverse order - view the root parents as leaves. Something like this:
with table_a ( id, child_id ) as (
select 1, 2 from dual union all
select 2, 3 from dual union all
select 3, NULL from dual union all
select 4, NULL from dual union all
select 5, 6 from dual union all
select 6, 7 from dual union all
select 7, 8 from dual union all
select 8, NULL from dual
)
select connect_by_root id as id, id as root_parent_id
from table_a
where connect_by_isleaf = 1
connect by child_id = prior id
order by id
;
ID ROOT_PARENT_ID
-- --------------
1 1
2 1
3 1
4 4
5 5
6 5
7 5
8 5
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.