Retrieve Parents and Child Rows In Single Row - sql

I have a lot of SQL experience but this one is beyond my understanding. I have a table such as:
ID NAME PARENT_ID
1 A 0
2 B 1
3 C 1
4 D 0
5 E 4
6 F 4
7 G 4
And I need to write a single SQL statement that will return all the parents and their children in a single row:
ID PARENT CHILD_1 CHILD_2 CHILD_3 ... CHILD_N
1 A B C
4 D E F G
I do not know how many children each parent has before hand - it is variable.
Is there anyway to do this in a single SQL statement?
Thanks.

You can use some of the very cool "dynamic pivot" PL/SQL solutions out there (which I don't recommend for production code -- they work 99% but fail for some odd-ball cases, in my experience).
Otherwise, you need to tell Oracle ahead of time which columns you expect your SQL to output. That means, you can only do what you're looking to do if you implement a hard cap on the maximum number of child columns you'll include.
If you can live with having to do that, then this should work. I took some guesses about how you would want it to work if your data had a hierarchy with multiple levels. (Take a look at row "H" in the sample data and think about how you would want that displayed.)
WITH d AS (
SELECT 1 id, 'A' name, 0 parent_id
FROM DUAL UNION ALL
SELECT 2, 'B', 1
FROM DUAL UNION ALL
SELECT 3, 'C', 1
FROM DUAL UNION ALL
SELECT 4, 'D', 0
FROM DUAL UNION ALL
SELECT 5, 'E', 4
FROM DUAL UNION ALL
SELECT 6, 'F', 4
FROM DUAL UNION ALL
SELECT 7, 'G', 4
FROM DUAL UNION ALL
SELECT 8, 'H', 7
FROM DUAL
),
h as (
select prior d.name parent,
level lvl,
case when level = 1 then null else d.name end child_name,
case when level = 1 then null else row_number() over ( partition by prior d.name, level order by d.name) end child_Number
from d
connect by parent_id = prior id
start with parent_id = 0 )
select * from h
pivot ( max(child_name) for (child_number) in (1 AS "CHILD_1",2 AS "CHILD_2",3 AS "CHILD_3",4 AS "CHILD_4",5 AS "CHILD_5") )
where lvl > 1
order by parent;

Related

get the sum of children nodes right below for every parent in a hierarchical query

i have a table objective with columns id, label, cost, parent_id
and i have this query
select
b.id,
b.parent_id,
b.label,
b.cost
from objective b
start with b.parent_id is null
connect by
prior b.id=b.parent_id
ORDER SIBLINGS BY b.id
only a leaf (and sometimes a parent of a leaf) has the column cost not null the others are almost always null, is it possible to get the cost of every node (summing the cost of the nodes right below) or not.
i know it can be done with join and sum but i thought maybe there is a simpler way to do it.
if this was the table content :
ID PARENT_ID LABEL COST
1 null A 0
2 1 B 1
3 2 C 3
4 1 D 0
5 4 E 3
6 5 F 7
7 4 G 5
the result should be something like this :
ID PARENT_ID LABEL COST CALCULATED_COST
1 null A 0 15(meaning 0 is a wrong value)
2 1 B 1 3 (meaning 1 is a wrong value)
3 2 C 3 3
4 1 D 0 12
5 4 E 3 7 (meaning 3 is a wrong value)
6 5 F 7 7
7 4 G 5 5
only a leaf (and sometimes a parent of a leaf) has the column cost not null the others are almost always null
If the rule is that "only a leaf or a parent of a leaf have a non-null cost and the others are always null" then you can use:
SELECT id,
parent_id,
label,
COALESCE(PRIOR cost, 0) + COALESCE(cost, 0) AS cost
FROM objective
START WITH
parent_id IS NULL
CONNECT BY
PRIOR id = parent_id
ORDER SIBLINGS BY
id
If the rule is that "any row can have a non-null cost" then you will need to parse the data structure in both directions:
SELECT id,
parent_id,
label,
( SELECT COALESCE(SUM(cost), 0)
FROM objective c
START WITH c.id = o.id
CONNECT BY PRIOR parent_id = id -- Reverse the direction
) AS cost
FROM objective o
START WITH
parent_id IS NULL
CONNECT BY
PRIOR id = parent_id
ORDER SIBLINGS BY
id
or use a recursive sub-query factoring clause:
WITH rsqfc (id, parent_id, label, cost) AS (
SELECT id, parent_id, label, COALESCE(cost, 0)
FROM objective
WHERE parent_id IS NULL
UNION ALL
SELECT o.id, o.parent_id, o.label, COALESCE(o.cost, 0) + r.cost
FROM objective o
INNER JOIN rsqfc r
ON (r.id = o.parent_id)
) SEARCH DEPTH FIRST BY id SET order_id
SELECT *
FROM rsqfc;
fiddle
Update
If you are looking to sum the cost for a row and all its descendants then you can use:
SELECT id,
parent_id,
label,
cost,
( SELECT COALESCE(SUM(cost), 0)
FROM objective c
START WITH c.id = o.id
CONNECT BY PRIOR id = parent_id
) AS total_cost
FROM objective o
START WITH
parent_id IS NULL
CONNECT BY
PRIOR id = parent_id
ORDER SIBLINGS BY
id;
Which, for the sample data outputs:
CREATE TABLE objective ( id, parent_id, label, cost ) AS
SELECT 1, NULL, 'A', 0 FROM DUAL UNION ALL
SELECT 2, 1, 'B', 1 FROM DUAL UNION ALL
SELECT 3, 2, 'C', 3 FROM DUAL UNION ALL
SELECT 4, 1, 'D', 0 FROM DUAL UNION ALL
SELECT 5, 4, 'E', 3 FROM DUAL UNION ALL
SELECT 6, 5, 'F', 7 FROM DUAL UNION ALL
SELECT 7, 4, 'G', 5 FROM DUAL;
Outputs:
ID
PARENT_ID
LABEL
COST
TOTAL_COST
1
null
A
0
19
2
1
B
1
4
3
2
C
3
3
4
1
D
0
15
5
4
E
3
10
6
5
F
7
7
7
4
G
5
5
fiddle
Update 2:
If you only want to total the descendants that are leaves then:
SELECT id,
parent_id,
label,
cost,
( SELECT COALESCE(SUM(cost), 0)
FROM objective c
WHERE CONNECT_BY_ISLEAF = 1
START WITH c.id = o.id
CONNECT BY PRIOR id = parent_id
) AS total_cost
FROM objective o
START WITH
parent_id IS NULL
CONNECT BY
PRIOR id = parent_id
ORDER SIBLINGS BY
id;
Which, for the sample data, outputs:
ID
PARENT_ID
LABEL
COST
TOTAL_COST
1
null
A
0
15
2
1
B
1
3
3
2
C
3
3
4
1
D
0
12
5
4
E
3
7
6
5
F
7
7
7
4
G
5
5
fiddle

Finding downstream member of tree within SQL Hierarchy Query?

I have a binary tree with devices attached to nodes (connected by an adjacency list). I'm trying to compare a device's value to the device downstream of it. I'm having trouble getting the downstream device.
Let's say I have a table:
DEVICE
NODE
PARENT_NODE
LEVEL
1
a
null
1
null
b
a
2
null
c
b
3
2
d
c
4
3
e
d
5
9
m
b
3
null
n
m
4
7
o
n
5
How would I go about joining the closest downstream device to each device row? I'm expecting:
DEVICE
DOWNSTREAM_DEVICE
1
null
2
1
3
2
9
1
7
9
Some assumptions: There is no order to the devices or the nodes (assume they're both unique ids). LEVEL is the hierarchy level. I'm using Oracle SQL.
I thought I could just use a lag function to perform this query, but obviously this will not work due to the tree branching. My incorrect results are as follows:
DEVICE
DOWNSTREAM_DEVICE
1
null
2
1
3
2
9
3 <- wrong
7
9
Any leads would be appreciated.
Use a hierarchical query:
SELECT CONNECT_BY_ROOT device AS device,
CASE WHEN LEVEL = 1 THEN NULL ELSE device END AS downstream_device
FROM table_name
WHERE CONNECT_BY_ISLEAF = 1
START WITH device IS NOT NULL
CONNECT BY PRIOR parent_node = node
AND (LEVEL = 2 OR PRIOR device IS NULL)
Which, for the sample data:
CREATE TABLE table_name (DEVICE, NODE, PARENT_NODE, LVL) AS
SELECT 1, 'a', null, 1 FROM DUAL UNION ALL
SELECT null, 'b', 'a', 2 FROM DUAL UNION ALL
SELECT null, 'c', 'b', 3 FROM DUAL UNION ALL
SELECT 2, 'd', 'c', 4 FROM DUAL UNION ALL
SELECT 3, 'e', 'd', 5 FROM DUAL UNION ALL
SELECT 9, 'm', 'b', 3 FROM DUAL UNION ALL
SELECT null, 'n', 'm', 4 FROM DUAL UNION ALL
SELECT 7, 'o', 'n', 5 FROM DUAL;
Outputs:
DEVICE
DOWNSTREAM_DEVICE
1
null
2
1
3
2
9
1
7
9
fiddle

Find oldest ancestor for each entry in a SQL table

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;

SQL Query to get all the ancestors of a node , in a single row

I have a table with the following structure
Child_id
Parent_Id
Child_name
Parent_name
Child_Description
I want a query to get all the parents of all leaf level nodes in a single row.
For eg : If X and Y are leaf level nodes in the following:
A->B->C->D->X
F->G->H->I->Y
The query should return 2 rows as following
Child Parent1 Parent2 Parent3 Parent4
X D C B A
Y I H G F
Thanks,
Dev
Does this make any sense?
SQL> with test (child_id, parent_id) as
2 (select 'x', 'd' from dual union all
3 select 'd', 'c' from dual union all
4 select 'c', 'b' from dual union all
5 select 'b', 'a' from dual union all
6 select 'a', null from dual union all
7 --
8 select 'y', 'i' from dual union all
9 select 'i', 'h' from dual union all
10 select 'h', 'g' from dual union all
11 select 'g', 'f' from dual union all
12 select 'f', null from dual
13 ),
14 anc as
15 (select sys_connect_by_path(child_Id, '>') pth
16 from test
17 where connect_by_isleaf = 1
18 connect by prior child_id = parent_id
19 start with parent_id is null
20 )
21 select regexp_substr(pth, '[^>]+', 1, 5) c1,
22 regexp_substr(pth, '[^>]+', 1, 4) c2,
23 regexp_substr(pth, '[^>]+', 1, 3) c3,
24 regexp_substr(pth, '[^>]+', 1, 2) c4,
25 regexp_substr(pth, '[^>]+', 1, 1) c5
26 from anc;
C1 C2 C3 C4 C5
-- -- -- -- --
x d c b a
y i h g f
SQL>
What does it do?
test CTE simulates your data (at least, I think so)
anc(estors) CTE selects the "longest" path because CONNECT_BY_ISLEAF shows whether current row can (or can not) be expanded any further. If it returns 1, it can not.
the final query uses regular expressions to convert a CSV string (delimiter is > in my example; could be something else) into columns. There's nothing dynamic in it, so - if data you have is different from up to 5 "columns", you'd have to fix it

How to convert table using SQL

I'm sorry if this is a duplicate question, but I had no idea which search keywords to use (that's why the question is vague as well)
I have a table like this
Parent_ID Parent_item Child_Item
1 A B
1 A C
2 H I
2 H J
2 H K
And I would like to have the results in the following format:
Parent_ID Parent_or_Child
1 A
1 B
1 C
2 H
2 I
2 J
2 K
How can this be done?
Thanks!
You need to unpivot the data, use UNION
select Parent_ID,Parent_item
from yourtable
Union
select Parent_ID,Child_Item
from yourtable
You need to unpivot the data, and it seems you don't want duplicates so you need to select distinct. In Oracle 11.1 or above, you can use the UNPIVOT operator - the advantage is that the base table will be read just once.
You can add an order by clause if you need it; I didn't, so the rows in the output are in arbitrary order (if you compare to your "desired output").
with
test_data ( Parent_ID, Parent_item, Child_Item ) as (
select 1, 'A', 'B' from dual union all
select 1, 'A', 'C' from dual union all
select 2, 'H', 'I' from dual union all
select 2, 'H', 'J' from dual union all
select 2, 'H', 'K' from dual
)
-- End of test data (NOT part of the query).
-- SQL query begins BELOW THIS LINE.
select distinct parent_id, parent_or_child
from test_data
unpivot (parent_or_child
for col in (parent_item as 'parent_item', child_item as 'child_item'))
;
PARENT_ID PARENT_OR_CHILD
---------- ---------------
2 I
1 A
1 B
1 C
2 H
2 J
2 K
7 rows selected.