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.
Related
I have a table as shown below
id
previous_id
latest_id
1
null
null
2
1
null
3
2
null
4
null
null
5
4
null
6
6
null
I want to update the table by setting the latest_id column value to lowest hierarchical value, which will look like this:
id
previous_id
latest_id
1
null
3
2
1
3
3
2
3
4
null
6
5
4
6
6
5
6
I have tried to use connect by, but the query is getting too complicated as start with cannot have a static value assigned, this update is for the entire table.
Below is what I could write for a single record based on it's id, how can I generalize it for all records in the table?
UPDATE TABLENAME1
SET LATEST_ID = (SELECT MAX(ID)
FROM TABLENAME1
START WITH ID = 3
CONNECT BY PREVIOUS_ID = PRIOR ID );
You can use a correlated hierarchical query and filter to get the leaf rows:
UPDATE table_name t
SET latest_id = (SELECT id
FROM table_name h
WHERE CONNECT_BY_ISLEAF = 1
START WITH h.id = t.id
CONNECT BY previous_id = PRIOR id);
Which, for the sample data:
CREATE TABLE table_name (id, previous_id, latest_id) AS
SELECT 1, null, CAST(null AS NUMBER) FROM DUAL UNION ALL
SELECT 2, 1, null FROM DUAL UNION ALL
SELECT 3, 2, null FROM DUAL UNION ALL
SELECT 4, null, null FROM DUAL UNION ALL
SELECT 5, 4, null FROM DUAL UNION ALL
SELECT 6, 5, null FROM DUAL;
Updates the table to:
ID
PREVIOUS_ID
LATEST_ID
1
null
3
2
1
3
3
2
3
4
null
6
5
4
6
6
5
6
db<>fiddle here
To the accepted answer, I will add this alterative which might perform better for large datasets by eliminating the correlated subquery.
MERGE INTO table_name t
USING (
SELECT CONNECT_BY_ROOT(id) root_id, id latest_id
FROM table_name
WHERE connect_by_isleaf = 1
CONNECT BY previous_id = prior id ) u
ON ( t.id = u.root_id )
WHEN MATCHED THEN UPDATE SET t.latest_id = u.latest_id;
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;
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
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
I have a parent child relationship stored in a table
If I query with 1, I want to retrieve all the LAST LEVEL Children of 1.
I should get back 5, 6,8.
Ignore any cyclical data that comes along.
Please see the attached image
Use a hierarchical query and restrict the output to only the non-cyclic leaf rows using the pseudocolumns CONNECT_BY_ISLEAF and CONNECT_BY_ISCYCLE:
Oracle Setup:
CREATE TABLE your_table ( parent_column, child_column ) AS
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 3, 7 FROM DUAL UNION ALL
SELECT 7, 8 FROM DUAL;
Query:
SELECT child_column
FROM your_table
WHERE CONNECT_BY_ISLEAF = 1
AND CONNECT_BY_ISCYCLE = 0
START WITH parent_column = 1
CONNECT BY NOCYCLE PRIOR child_column = parent_column
Output:
CHILD_COLUMN
------------
5
6
8