Get parent id from level with Oracle SQL - sql

I have a hierarchical structure defined by level and order of elements. Is it possible to create "parent_id" column with Oracle SQL without using procedures?
I need to generate red values:
test data:
with t as
(
select 1 id, 'element1' name, 1 level_ from dual union all
select 2 id, 'element2' name, 2 level_ from dual union all
select 3 id, 'element3' name, 3 level_ from dual union all
select 4 id, 'element4' name, 3 level_ from dual union all
select 5 id, 'element5' name, 3 level_ from dual union all
select 6 id, 'element6' name, 3 level_ from dual union all
select 7 id, 'element7' name, 2 level_ from dual union all
select 8 id, 'element8' name, 3 level_ from dual union all
select 9 id, 'element9' name, 4 level_ from dual union all
select 10 id, 'element10' name, 4 level_ from dual union all
select 11 id, 'element11' name, 1 level_ from dual union all
select 12 id, 'element12' name, 2 level_ from dual union all
select 13 id, 'element13' name, 3 level_ from dual union all
select 14 id, 'element14' name, 4 level_ from dual union all
select 15 id, 'element15' name, 4 level_ from dual union all
select 16 id, 'element16' name, 3 level_ from dual union all
select 17 id, 'element17' name, 4 level_ from dual union all
select 18 id, 'element18' name, 4 level_ from dual union all
select 19 id, 'element19' name, 1 level_ from dual
)
select * from t

From Oracle 12, you can use MATCH_RECOGNIZE:
select *
from t
MATCH_RECOGNIZE (
ORDER BY id DESC
MEASURES
child.id AS id,
child.name AS name,
child.lvl AS lvl,
parent.id AS parent_id
ONE ROW PER MATCH
AFTER MATCH SKIP TO NEXT ROW
PATTERN (child ancestors*? (parent | $))
DEFINE
parent AS lvl = child.lvl - 1
)
ORDER BY id
Or, again from Oracle 12, a LATERAL join:
select *
from t c
LEFT OUTER JOIN LATERAL(
SELECT p.id AS parent_id
FROM t p
WHERE c.id > p.id
AND c.lvl = p.lvl + 1
ORDER BY id DESC
FETCH FIRST ROW ONLY
)
ON (1 = 1)
ORDER BY id
Or, in earlier versions:
SELECT id, name, lvl, parent_id
FROM (
SELECT c.*,
p.id AS parent_id,
ROW_NUMBER() OVER (PARTITION BY c.id ORDER BY p.id DESC) AS rn
FROM t c
LEFT OUTER JOIN t p
ON (c.id > p.id AND c.lvl = p.lvl + 1)
)
WHERE rn = 1
ORDER BY id
Which, for the sample data:
CREATE TABLE t (id, name, lvl ) as
select 1, 'element1', 1 from dual union all
select 2, 'element2', 2 from dual union all
select 3, 'element3', 3 from dual union all
select 4, 'element4', 3 from dual union all
select 5, 'element5', 3 from dual union all
select 6, 'element6', 3 from dual union all
select 7, 'element7', 2 from dual union all
select 8, 'element8', 3 from dual union all
select 9, 'element9', 4 from dual union all
select 10, 'element10', 4 from dual union all
select 11, 'element11', 1 from dual union all
select 12, 'element12', 2 from dual union all
select 13, 'element13', 3 from dual union all
select 14, 'element14', 4 from dual union all
select 15, 'element15', 4 from dual union all
select 16, 'element16', 3 from dual union all
select 17, 'element17', 4 from dual union all
select 18, 'element18', 4 from dual union all
select 19, 'element19', 1 from dual;
All output:
ID
NAME
LVL
PARENT_ID
1
element1
1
null
2
element2
2
1
3
element3
3
2
4
element4
3
2
5
element5
3
2
6
element6
3
2
7
element7
2
1
8
element8
3
7
9
element9
4
8
10
element10
4
8
11
element11
1
null
12
element12
2
11
13
element13
3
12
14
element14
4
13
15
element15
4
13
16
element16
3
12
17
element17
4
16
18
element18
4
16
19
element19
1
null
db<>fiddle here

Related

Having a node at level N

I want to write the code by Having a node at level N (for example 3) of the tree, find all members up to level M (for example 6).How can I write this code? what is the best idea for this problem?
I am new in oracle , please explain me and help me for resolve this problem .
CREATE TABLE tree (
node_id INT,
parent_id INT
)
INSERT INTO
tree
SELECT 1, NULL FROM DUAL
UNION ALL SELECT 2, 1 FROM DUAL
UNION ALL SELECT 3, 1 FROM DUAL
UNION ALL SELECT 4, 2 FROM DUAL
UNION ALL SELECT 5, 2 FROM DUAL
UNION ALL SELECT 6, 3 FROM DUAL
UNION ALL SELECT 7, 3 FROM DUAL
UNION ALL SELECT 8, 4 FROM DUAL
UNION ALL SELECT 9, 4 FROM DUAL
UNION ALL SELECT 10, 5 FROM DUAL
UNION ALL SELECT 11, 5 FROM DUAL
UNION ALL SELECT 12, 6 FROM DUAL
UNION ALL SELECT 13, 6 FROM DUAL
UNION ALL SELECT 14, 7 FROM DUAL
UNION ALL SELECT 15, 7 FROM DUAL
15 rows affected
SELECT
node_id, parent_id, level
FROM
tree
WHERE
level < 3
CONNECT BY
PRIOR node_id = parent_id
START WITH
parent_id = 3
ORDER BY
level, node_id
NODE_ID
PARENT_ID
LEVEL
6
3
1
7
3
1
12
6
2
13
6
2
14
7
2
15
7
2
fiddle

Cross SQL joins through multiple tables

I have data in multiple tables I need to cross join via different attributes to achieve the output. i.e.
ID
Node
1
A
2
B
3
C
4
D
5
E
6
G
ID
ParentID
1
100
2
200
3
300
4
400
5
500
6
600
7
100
8
200
9
300
10
700
11
800
12
800
ID
Splice Name
7
Irvine
8
Goodyear
9
Phoenix
10
Seattle
11
Augusta
12
Atlanta
Every table has a unique Child ID which corresponds to a Parent ID that is NOT unique. Multiple child IDs can be associated to the same Parent ID (as you can see above, child ID 1 and 7 for instance share Parent ID 100. The ID in each table corresponds with a unique Child ID not shared with any other tables.
What I want to do in my query is have the Nodes show what their Splice Name is. So I want to do select Node, Splice_Name. Expected output for instance would show Node A as having Splice Name Irvine. The example above is only 3 tables and a few rows but I'm working with big data (500 tables and over a million rows)
My question is, how do I write my query to do multiple cross joins? Also my example above is crossing over through one table to get data from another, but how would you cross through multiple tables?
You do not want to CROSS JOIN. Instead, you can use INNER JOINs:
SELECT n.node, s.splice_name
FROM (
relationships rn
INNER JOIN nodes n
ON (rn.id = n.id)
)
INNER JOIN
(
relationships rs
INNER JOIN splices s
ON (rs.id = s.id)
)
ON (rn.parentid = rs.parentid);
Note: The braces are not required here, I just find they add clarity to the precedence of the joins.
Which, for the sample data:
CREATE TABLE nodes (ID, Node) As
SELECT 1, 'A' FROM DUAL UNION ALL
SELECT 2, 'B' FROM DUAL UNION ALL
SELECT 3, 'C' FROM DUAL UNION ALL
SELECT 4, 'D' FROM DUAL UNION ALL
SELECT 5, 'E' FROM DUAL UNION ALL
SELECT 6, 'G' FROM DUAL;
CREATE TABLE relationships (ID, ParentID) AS
SELECT 1, 100 FROM DUAL UNION ALL
SELECT 2, 200 FROM DUAL UNION ALL
SELECT 3, 300 FROM DUAL UNION ALL
SELECT 4, 400 FROM DUAL UNION ALL
SELECT 5, 500 FROM DUAL UNION ALL
SELECT 6, 600 FROM DUAL UNION ALL
SELECT 7, 100 FROM DUAL UNION ALL
SELECT 8, 200 FROM DUAL UNION ALL
SELECT 9, 300 FROM DUAL UNION ALL
SELECT 10, 700 FROM DUAL UNION ALL
SELECT 11, 800 FROM DUAL UNION ALL
SELECT 12, 800 FROM DUAL;
CREATE TABLE splices (ID, Splice_Name) AS
SELECT 7, 'Irvine' FROM DUAL UNION ALL
SELECT 8, 'Goodyear' FROM DUAL UNION ALL
SELECT 9, 'Phoenix' FROM DUAL UNION ALL
SELECT 10, 'Seattle' FROM DUAL UNION ALL
SELECT 11, 'Augusta' FROM DUAL UNION ALL
SELECT 12, 'Atlanta' FROM DUAL;
Outputs:
NODE
SPLICE_NAME
A
Irvine
B
Goodyear
C
Phoenix
If you want all the nodes and splices, including those without a corresponding relationship then use a FULL OUTER JOIN:
SELECT n.node, s.splice_name
FROM (
relationships rn
INNER JOIN nodes n
ON (rn.id = n.id)
)
FULL OUTER JOIN
(
relationships rs
INNER JOIN splices s
ON (rs.id = s.id)
)
ON (rn.parentid = rs.parentid)
Which outputs:
NODE
SPLICE_NAME
A
Irvine
B
Goodyear
C
Phoenix
(null)
Seattle
(null)
Augusta
(null)
Atlanta
D
(null)
G
(null)
E
(null)
fiddle
Maybe you could consider the solution based on Left Joins.
WITH
nodes AS
(
Select 1 "ID", 'A' "NODE" From Dual Union All
Select 2 "ID", 'B' "NODE" From Dual Union All
Select 3 "ID", 'C' "NODE" From Dual Union All
Select 4 "ID", 'D' "NODE" From Dual Union All
Select 5 "ID", 'E' "NODE" From Dual Union All
Select 6 "ID", 'F' "NODE" From Dual
),
parents AS
(
Select 1 "ID", 100 "PARENT_ID" From Dual Union All
Select 2 "ID", 200 "PARENT_ID" From Dual Union All
Select 3 "ID", 300 "PARENT_ID" From Dual Union All
Select 4 "ID", 400 "PARENT_ID" From Dual Union All
Select 5 "ID", 500 "PARENT_ID" From Dual Union All
Select 6 "ID", 600 "PARENT_ID" From Dual Union All
Select 7 "ID", 100 "PARENT_ID" From Dual Union All
Select 8 "ID", 200 "PARENT_ID" From Dual Union All
Select 9 "ID", 300 "PARENT_ID" From Dual Union All
Select 10 "ID", 700 "PARENT_ID" From Dual Union All
Select 11 "ID", 800 "PARENT_ID" From Dual Union All
Select 12 "ID", 800 "PARENT_ID" From Dual
),
splices AS
(
Select 7 "ID", 'Irwine' "SPLICE_NAME" From Dual Union All
Select 8 "ID", 'Goodyear' "SPLICE_NAME" From Dual Union All
Select 9 "ID", 'Phoenix' "SPLICE_NAME" From Dual Union All
Select 10 "ID", 'Seattle' "SPLICE_NAME" From Dual Union All
Select 11 "ID", 'Augusta' "SPLICE_NAME" From Dual Union All
Select 12 "ID", 'Atlanta' "SPLICE_NAME" From Dual
)
Select
p.PARENT_ID,
n.ID "NODE_ID", n.NODE,
p.SPLICE_ID, p.ID "ID",
CASE WHEN p.SPLICE_ID Is Null Then p.SPLICE_NAME
ELSE (Select SPLICE_NAME From splices Where ID = p.SPLICE_ID)
END "SPLICE_NAME"
From
(
Select
p.PARENT_ID, p.ID "ID",
p.SPLICE_ID "SPLICE_ID", p.SPLICE_NAME "SPLICE_NM", Max(p.SPLICE_NAME) OVER(Partition By PARENT_ID) "SPLICE_NAME"
From
(
Select p1.PARENT_ID, p1.ID, s1.ID "SPLICE_ID", s1.SPLICE_NAME
From parents p1
Left Join splices s1 ON(s1.ID = p1.ID)
)p
) p
Left Join nodes n ON(n.ID = p.ID)
Order By p.ID
/*
PARENT_ID NODE_ID NODE SPLICE_ID ID SPLICE_NAME
---------- ---------- ---- ---------- ---------- -----------
100 1 A 1 Irwine
200 2 B 2 Goodyear
300 3 C 3 Phoenix
400 4 D 4
500 5 E 5
600 6 F 6
100 7 7 Irwine
200 8 8 Goodyear
300 9 9 Phoenix
700 10 10 Seattle
800 11 11 Augusta
800 12 12 Atlanta
*/
The resulting dataset could be seelected/filtered/grouped/ordered or whatever you want...
Additionaly:
if there is another table with node data like here:
nodes_2 AS
(
Select 7 "ID", 'G' "NODE" From Dual Union All
Select 8 "ID", 'H' "NODE" From Dual
),
... then just change last Left Join to union of nodes and nodes_2 (_3, _4, _n). The same could be done for splices (Left Join in inner query).. and the result would be:
PARENT_ID NODE_ID NODE SPLICE_ID ID SPLICE_NAME
---------- ---------- ---- ---------- ---------- -----------
100 1 A 1 Irwine
200 2 B 2 Goodyear
300 3 C 3 Phoenix
400 4 D 4
500 5 E 5
600 6 F 6
100 7 G 7 7 Irwine
200 8 H 8 8 Goodyear
300 9 9 Phoenix
700 10 10 Seattle
800 11 11 Augusta
800 12 12 Atlanta

To find the starting and ending points in a sequence

I've a table (T1) with one column (C1) with the below values
1
2
3
5
6
8
9
10
I want the output to print the continuous sequences with start and ending points like below.
1-3
5-6
8-10
Could you please help?
Any Database is fine.
Oracle: sample data first, while code you really need begins at line #11.
SQL> with t1 (c1) as
2 (select 1 from dual union all
3 select 2 from dual union all
4 select 3 from dual union all
5 select 5 from dual union all
6 select 6 from dual union all
7 select 8 from dual union all
8 select 9 from dual union all
9 select 10 from dual
10 )
11 select min(c1), max(c1)
12 from (select c1, c1 - row_number() over (order by c1) rn
13 from t1
14 )
15 group by rn
16 order by rn;
MIN(C1) MAX(C1)
---------- ----------
1 3
5 6
8 10
SQL>
You can use the following query. I have tested it with SQL Server, but I think it will work without modifications in Oracle:
create table t1(c1 int);
insert into t1
select *
from (values(1),(2),(3),(5),(6),(8),(9),(10))t(x);
select case when count(*) >1 then
concat(min(c1),'-',max(c1))
else concat(max(c1),'')
end as concat_cs
from (
select c1
,ROW_NUMBER() over(order by c1 asc) as rnk
,c1 - ROW_NUMBER() over(order by c1 asc) as grp
from t1
)x
group by x.grp
Output
concat_cs
1-3
5-6
8-10
with stab as (
select 1 as val from dual union all
select 2 as val from dual union all
select 3 as val from dual union all
select 5 as val from dual union all
select 6 as val from dual union all
select 8 as val from dual union all
select 9 as val from dual union all
select 10 as val from dual union all
select 13 as val from dual union all
select 15 as val from dual union all
select 16 as val from dual union all
select 17 as val from dual union all
select 18 as val from dual union all
select 19 as val from dual union all
select 23 as val from dual
),sq2 as(
select
row_number() over(order by 1) as rownumber,val
from stab
)
select
a.val,b.val
from sq2 A
join sq2 b on b.rownumber = a.rownumber+2
where mod(A.rownumber,3)=1
Output:
1 3
5 8
9 13
15 17
18 23

recursive query to find the root parent in oracle

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.

Print message when no data is found

Need a query to get the Employee name, total fuel used by each employee.
If fuel is not used by an employee then the second column should have a
text “No fuel used”.
These are the following two tables:
Table1: EmployeeID, FirstName
1 Vikas
2 nikita
3 Ashish
4 Nikhil
5 anish
Table2: ID, Fuel
1 10
2 9
3 8
4 6
5 12
6 11
7 10
8 9
9 8
10 10
11 9
12 12
13 7
14 15
where The column table2.ID is a foreign key to table1.EmployeeID.
This is code which I have written, Which is most probably wrong.
select ID, FirstName, sum(table2.Fuel) sum_fuel
from table2,table1
where EmployeeID=ID IN (
select ID, coalesce(ID, 'No-fuel used') as ID
from table1 t1
left join table2 t2 on t2.ID = t1.EmployeeID
)
group by fuel
order by ID DESC;
As you can see from two tables that employee with from 1 to 5 of table1 are in table2. So for these employee I need to show total fuel used by every individual. And for employee with ID from 6 to 14 are not available in table1 so for these employee “No fuel used” message should be printed.
You can use a left join. This way, whenever the Id values for tables don't match you'll get null values for sum(fuel) value, and will assign the string 'No fuel used'for sum_fuel column by using nvl() function:
with table1( EmployeeID, FirstName ) as
(
select 1,'Vikas' from dual union all
select 2,'nikita' from dual union all
select 3,'Ashish' from dual union all
select 4,'Nikhil' from dual union all
select 5,'anish' from dual union all
select 15,'pratteek' from dual
), table2( ID, Fuel ) as
(
select 1, 10 from dual union all
select 2, 9 from dual union all
select 3, 8 from dual union all
select 4, 6 from dual union all
select 5, 12 from dual union all
select 6, 11 from dual union all
select 7, 10 from dual union all
select 8, 9 from dual union all
select 9, 8 from dual union all
select 10, 10 from dual union all
select 11, 9 from dual union all
select 12, 12 from dual union all
select 13, 7 from dual union all
select 14, 15 from dual
)
select EmployeeID, FirstName, nvl(to_char(sum(t2.Fuel)),'No fuel used') as sum_fuel
from table1 t1
left join table2 t2
on t1.EmployeeID = t2.ID
group by EmployeeID, FirstName
order by EmployeeID desc;
EMPLOYEEID FIRSTNAME SUM_FUEL
---------- --------- ------------
15 pratteek No fuel used
5 anish 12
4 Nikhil 6
3 Ashish 8
2 nikita 9
1 Vikas 10
Demo
This may work---
SELECT ID
, FirstName
, CASE
WHEN SUM(f.Fuel) > 0 THEN CAST(SUM(f.Fuel) AS NVARCHAR(25))
ELSE 'No fuel used'
END sum_fuel
FROM #emp e
LEFT JOIN #fuel f ON e.EmployeeID = f.id
GROUP BY ID,FirstName
ORDER BY ID DESC