How to get the root record in Oracle database - sql

Starting from the generic node (or leaf) how to get the root node?
SELECT
ID,MSG_ID,PARENT_ID
FROM
TABLE_X
CONNECT BY PRIOR PARENT_ID = ID;
ID MSG_ID PARENT_ID
4 3 NULL
5 93bea0f71b07-4037-9009-f148fa39bb62 4
4 3 NULL
6 6f5f5d4ab1ec-4f00-8448-7a6dfa6461b2 4
4 3 NULL
7 3 NULL
8 7e0fae569637-4d29-9075-c273eb39ae8e 7
7 3 NULL
9 8a3e7485b3e8-45b1-a31d-c52fd32111c0 7
7 3 NULL
10 fcc622d5af92-4e61-8d7c-add3da359a8b 7
7 3 NULL
How to get the root msg_id?

You were on the right path, but you are missing two essential ingredients.
First, to indicate the starting point, you need to use the start with clause. Obviously, it will be something like start with id = <input value>. You didn't tell us how you will provide the input value. The most common approach is to use bind variables (best for many reasons); I named the bind variable input_id in the query below.
Second, you only want the "last" row (since you are navigating the tree in the opposite direction: towards the root, not from the root; so the root is now the "leaf" as you navigate this way). For that you can use the connect_by_isleaf pseudocolumn in the where clause.
So, the query should look like this: (note that I am only selecting the root message id, as that is all you requested; if you need more columns, include them in select)
select msg_id
from table_x
where connect_by_isleaf = 1 -- keep just the root row (leaf in this traversal)
start with id = :input_id -- to give the starting node
connect by prior parent_id = id
;

You can use a recursive query:
with cte (id, msg_id, parent_id) as (
select id, msg_id, parent_id, from mytable where id = ?
union all
select t.id, t.msg_id, t.parent_id
from mytable t
inner join cte c on c.parent_id = t.id
)
select * from cte where parent_id is null

Related

Directed Graph/Hierarchy SQl

We have below data from upstream:-
child. Parent
2. 1
3. 1
14. 1
1. 4
5. 1
6. 7
8. 7
13. 8
12. 8
8. 13
9. 10
10. 11
sometimes, child appears in parent and vice versa, there is no set order and relations can be cylic too.
Expected output is
child. group
1. 1
2. 1
3. 1
4. 1
5. 1
6. 1
7. 1
8. 1
12. 1
13. 1
9. 10
10. 10
11. 10
can someone suggest the possible solutions, i have tried directed sql but cyclic relations are being excluded
Cycles make this a pain. One method is to store the values that have been visited in a string. So, the idea is to get every possible matching value and then aggregate -- the minimum match is a good descriptor for the "group":
with recursive cp as (
select child as node1, child as node2
from t
union -- remove duplicates
select parent, parent
from t
),
cte as (
select node1, node2, '|' || node1 || '|' as nodes
from cp
union all
select cte.node1, cp.node2, cte.nodes || cte.node1 || '|'
from cte join
cp
on cp.node2 = cte.node1
where cte.nodes not like '%|' || cp.node1 || '|%'
)
select cte.node1, min(cte.node2)
from cte
group by cte.node1;
This should do it:
WITH RECURSIVE cte (child, parent) AS (
-- Seed rows (initial relationships)
SELECT child, parent, 0 AS depth
FROM mytable
UNION ALL
-- Get children
SELECT mytable.child, mytable.parent, depth + 1 AS newdepth
FROM cte
INNER JOIN mytable ON cte.child = mytable.parent -- Get child row if also a parent
WHERE newdepth <= 20 -- Avoid possible infinite recursion
)
SELECT
child,
RANK() OVER(ORDER BY parent DESC) AS group -- Common group ID for rows with same parent
FROM (
SELECT DISTINCT child, parent FROM cte -- Get rid of duplicates
) src
Replace mytable with your table name. You may also need to rename group to another name, if you get a keyword error.
Give it a try and let me know.
SQL Fiddle

Oracle hierarchical sum (distance from leaf to root)

I would like to get help for a hierarchical query (Oracle 11gR2). I have a hard time with those kind of queries...
In fact, it's a 2 in 1 question (2 different approches needed).
I’m looking for a way to get the distance from all individials records to the root (not the opposite). My data are in a tree like structure:
CREATE TABLE MY_TREE
(ID_NAME VARCHAR2(1) PRIMARY KEY,
PARENT_ID VARCHAR2(1),
PARENT_DISTANCE NUMBER(2)
);
INSERT INTO MY_TREE (ID_NAME,PARENT_ID,PARENT_DISTANCE) VALUES('A',NULL,NULL);
INSERT INTO MY_TREE (ID_NAME,PARENT_ID,PARENT_DISTANCE) VALUES('B','A',1);
INSERT INTO MY_TREE (ID_NAME,PARENT_ID,PARENT_DISTANCE) VALUES('C','B',3);
INSERT INTO MY_TREE (ID_NAME,PARENT_ID,PARENT_DISTANCE) VALUES('D','B',5);
INSERT INTO MY_TREE (ID_NAME,PARENT_ID,PARENT_DISTANCE) VALUES('E','C',7);
INSERT INTO MY_TREE (ID_NAME,PARENT_ID,PARENT_DISTANCE) VALUES('F','D',11);
INSERT INTO MY_TREE (ID_NAME,PARENT_ID,PARENT_DISTANCE) VALUES('G','D',13);
Hierarchically, my data look like this (but I have multiple independents roots and many more levels):
In the first approch, I'm looking for a query that will give me this result:
LEVEL ROOT NODE ID_NAME ROOT_DISTANCE
----- ---- ---- ------- -------------
1 A null A null
2 A null B 1
3 A B C 4
4 A B E 11
3 A B D 6
4 A D F 17
4 A D G 19
In this result,
the "NODE" column mean the ID_NAME of the closest split element
the "ROOT_DISTANCE" column mean the distance from an element to the root (ex: the ROOT_DISTANCE for ID_NAME=G is the distance from G to A: G(13)+D(5)+B(1)=19)
In this approch, I will always specify a maximum of 2 roots.
The second approch must be a PL/SQL script that will do the same calculation (ROOT_DISTANCE), but in a iterative way, and that will write the result in a new table. I want to run this script one time, so all roots (~1000) will be processed.
Here's the way I see the script:
For all roots, we need to find associated leafs and then calculate the distance from the leaf to the root (for all element between the leaf and the root) and put this into a table.
This script is needed for "performance perspectives", so if an element have been already calculated (ex: a split node that was calculated by another leaf), we need to stop the calculation and pass to the next leaf because we already know the result from there to the root. For example, if the system calculates E-C-B-A, and then F-D-B-A, the B-A section should not be calcultated again because it was done in the first pass.
You can awnser one or both of those questions, but i will need the awnser to those two questions.
Thank you!
Try this one:
WITH brumba(le_vel,root,node,id_name,root_distance) AS (
SELECT 1 as le_vel, id_name as root, null as node, id_name, to_number(null) as root_distance
FROM MY_TREE WHERE parent_id IS NULL
UNION ALL
SELECT b.le_vel + 1, b.root,
CASE WHEN 1 < (
SELECT count(*) FROM MY_TREE t1 WHERE t1.parent_id = t.parent_id
)
THEN t.parent_id ELSE b.node
END,
t.id_name, coalesce(b.root_distance,0)+t.parent_distance
FROM MY_TREE t
JOIN brumba b ON b.id_name = t.parent_id
)
SELECT * FROM brumba
Demo: https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=d5c231055e989c3cbcd763f4b3d3033f
There is no need for "the second approch" using PL/SQL - the above SQL will calculate results for all root nodes (which have null in parent_id column) at once.
Just add a prefix either INSERT INTO tablename(col1,col2, ... colN) ... or CREATE TABLE name AS ... to the above query.
The above demo contains an example for the latter option CREATE TABLE xxx AS query
Here's one option which shows how to get the first part of your question:
SQL> with temp as
2 (select level lvl,
3 ltrim(sys_connect_by_path(id_name, ','), ',') path
4 from my_tree
5 start with parent_id is null
6 connect by prior id_name = parent_id
7 ),
8 inter as
9 (select t.lvl,
10 t.path,
11 regexp_substr(t.path, '[^,]+', 1, column_value) col
12 from temp t,
13 table(cast(multiset(select level from dual
14 connect by level <= regexp_count(path, ',') + 1
15 ) as sys.odcinumberlist ))
16 )
17 select i.lvl,
18 i.path,
19 sum(m.parent_distance) dis
20 from inter i join my_tree m on m.id_name = i.col
21 group by i.lvl, i.path
22 order by i.path;
LVL PATH DIS
---- ---------- ----------
1 A
2 A,B 1
3 A,B,C 4
4 A,B,C,E 11
3 A,B,D 6
4 A,B,D,F 17
4 A,B,D,G 19
7 rows selected.
SQL>
Here is how you can solve this with a hierarchical (connect by) query.
In most hierarchical problems, hierarchical queries will be faster than recursive queries (recursive with clause). However, your question is not purely hierarchical - you need to compute the distance to root, and unlike recursive with, you can't get that in a single pass with hierarchical queries. So it will be interesting to hear - from you! - whether there are any significant performance differences between the approaches. For what it's worth, on the very small data sample you provided, the Optimizer estimated a cost of 5 using connect by, vs. 48 for the recursive solution; whether that means anything in real life you will find out and hopefully you will let us know, too.
In the hierarchical query, I flag out the parents that have two or more children (I use an analytic function for this, to avoid a join). Then I build the hierarchy, and in the last step I aggregate to get the required bits. As in the recursive solution, this should give you everything you need - for all roots and all nodes - in a single SQL query; there is no need for PL/SQL code.
with
branching (id_name, parent_id, parent_distance, b) as (
select id_name, parent_id, parent_distance,
case when count(*) over (partition by parent_id) > 1 then 'y' end
from my_tree
)
, hierarchy (lvl, leaf, id_name, parent_id, parent_distance, b) as (
select level, connect_by_root id_name, id_name, parent_id, parent_distance, b
from branching
connect by id_name = prior parent_id
)
select max(lvl) as lvl,
min(id_name) keep (dense_rank last order by lvl) as root,
leaf as id_name,
min(decode(b, 'y', parent_id))
keep (dense_rank first order by decode(b, 'y', lvl)) as node,
sum(parent_distance) as root_distance
from hierarchy
group by leaf;
LVL ROOT ID_NAME NODE ROOT_DISTANCE
--- ------- ------- ------- -------------
1 A A
2 A B 1
3 A C B 4
3 A D B 6
4 A E B 11
4 A F D 17
4 A G D 19

Find children of a most top level parent

There are similar question asking how to find the top level parent of a child (this, this and this). I have a similar question but I want to find all childern of a top level parent. This is similar question but uses wordpress predefined functions.
sample table:
id parent
1 0
2 0
3 1
4 2
5 3
6 3
7 4
I want to select ID with most top parent equals 1. The output should be 3 and all children of 3 I mean (5,6) and even more deep level children if available.
I know I can select them using two times of inner join but the hirearchy may be more complex with more levels.
A simple "Recursive CTE" will do what you want:
with n as (
select id from my_table where id = 1 -- starting row(s)
union all
select t.id
from n
join my_table t on t.parent_id = n.id
)
select id from n;
This CTE will go down all levels ad infinitum. Well... by default SQL Server limits it to 128 levels (that you can increase to 65k).
Since you aren't climbing the entire ladder...
select *
from YourTable
where parent = (select top 1 parent from YourTable group by parent order by count(parent) desc)
If you were wanting to return the parent of 3, since 3 was listed most often, then you'd use a recursive CTE.

Hierarchical queries in Oracle

I have a table with a structure like (id, parent_id) in Oracle11g.
id parent_id
---------------
1 (null)
2 (null)
3 1
4 3
5 3
6 2
7 2
I'd like to query it to get all the lines that are hierarchically linked to each of these id, so the results should be :
root_id id parent_id
------------------------
1 3 1
1 4 3
1 5 3
2 6 2
2 7 2
3 4 3
3 5 3
I've been struggling with the connect by and start with for quite some time now, and all i can get is a fraction of the results i want with queries like :
select connect_by_root(id) root_id, id, parent_id from my-table
start with id=1
connect by prior id = parent_id
I'd like to not use any for loop to get my complete results.
Any Idea ?
Best regards,
Jérôme Lefrère
PS : edited after first answer, noticing me i had forgotten some of the results i want...
The query you posted is missing the from clause and left an underscore out of connect_by_root, but I'll assume those aren't actually the source of your problem.
The following query gives you the result you're looking for:
select * from (
select connect_by_root(id) root_id, id, parent_id
from test1
start with parent_id is null
connect by prior id = parent_id)
where root_id <> id
The central problem is that you were specifying a specific value to start from, rather that specifying a way to identify the root rows. Changing id = 1 to parent_id is null allows the entire contents of the table to be returned.
I also added the outer query to filter the root rows out of the result set, which wasn't mentioned in your question, but was shown in your desired result.
SQL Fiddle Example
Comment Response:
In the version provided, you do get descendants of id = 3, but not in such a way that 3 is the root. This is because we're starting at the absolute root. Resolving this is easy, just omit the start with clause:
SELECT *
FROM
(SELECT connect_by_root(id) root_id,
id,
parent_id
FROM test1
CONNECT BY
PRIOR id = parent_id)
WHERE root_id <> id
SQL Fiddle Example
try this:
select connect_by_root(id) root_id, id, parent_id
from your_table
start with parent_id is null
connect by prior id = parent_id
It will give you the exact result you want:
select connect_by_root(id) as root, id, parent_id
from test1
connect by prior id=parent_id
start with parent_id is not null;

How to get second parent with recursive query in Common Table

I am using SQL Server 2008. I have a table like this:
UnitId ParentId UnitName
---------------------------
1 0 FirstUnit
2 1 SecondUnit One
3 1 SecondUnit Two
4 3 B
5 2 C
6 4 D
7 6 E
8 5 F
I want to get second parent of the record. For example:
If I choose unit id that equal to 8, It will bring unit id is equal to 2 to me. It needs to be SecondUnit One. or If I choose unit id that equal to 7, It will bring unit id is equal to 3 to me. It needs to be SecondUnit Two.
How can I write a SQL query this way?
It took me a while, but here it is :)
with tmp as (
select unitId, parentId, unitName, 0 as iteration
from t
where unitId = 7
union all
select parent.unitId, parent.parentId, parent.unitName, child.iteration + 1
from tmp child
join t parent on child.parentId = parent.unitId
where parent.parentId != 0
)
select top 1 unitId, parentId, unitName from tmp
order by iteration desc
Here is also a fiddle to play with.
SELECT t.*, tParent1.UnitId [FirstParent], tParent2.UnitId [SecondParent]
FROM Table t
LEFT JOIN Table tParent1 ON t.ParentId = tParent1.UnitId
LEFT JOIN Table tParent2 ON tParent1.ParentId = tParent2.UnitId
WHERE t.UnitId = <Unit ID search here>
AND NOT tParent2.UnitId IS NULL
Edit: And leave out second part of the WHERE clause if you want results returned even if they don't have a second parent.