Cross SQL joins through multiple tables - sql

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

Related

Get parent id from level with Oracle 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

Oracle SQL query to fetch manyToMany record in a single column

I have the following three tables, my requirement is to fetch manytoMany joined tabled as a column. Can someone pls help me in writing the query.
IPtype can only be of two types public and private, so the result should have two more columns as mentioned below. If there is multiple public or private ip mapped to single asset then is should be displayed as comma separated.
Thanks
Looks like outer join (to return assets that don't have any IP addresses) with listagg (to aggregate IP addresses per asset) problem.
SQL> with
2 -- Sample data
3 asset (assetid) as
4 (select 1 from dual union all
5 select 2 from dual union all
6 select 3 from dual union all
7 select 4 from dual
8 ),
9 ip (ipid, ipnumber, iptype) as
10 (select 1, '1.2.3.4' , 'Public' from dual union all
11 select 2, '99.22.3.4', 'Private' from dual union all
12 select 3, '11.22.3.4', 'Public' from dual union all
13 select 4, '55.22.3.4', 'Public' from dual union all
14 select 5, '66.22.3.4', 'Private' from dual union all
15 select 6, '77.22.3.4', 'Private' from dual
16 ),
17 asset_ip_map (assetid, ipid) as
18 (select 1, 1 from dual union all
19 select 1, 2 from dual union all
20 select 2, 3 from dual union all
21 select 2, 4 from dual union all
22 select 3, 5 from dual union all
23 select 3, 6 from dual
24 )
25 -- Query you need
26 select a.assetid,
27 listagg(case when iptype = 'Public' then i.ipnumber end, ', ') within group (order by null) public_ip,
28 listagg(case when iptype = 'Private' then i.ipnumber end, ', ') within group (order by null) private_ip
29 from asset a left join asset_ip_map m on m.assetid = a.assetid
30 left join ip i on i.ipid = m.ipid
31 group by a.assetid
32 order by a.assetid;
ASSETID PUBLIC_IP PRIVATE_IP
---------- ------------------------------ ------------------------------
1 1.2.3.4 99.22.3.4
2 11.22.3.4, 55.22.3.4
3 66.22.3.4, 77.22.3.4
4
SQL>

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

How to sum two different fields from two tables with one field is common

I have two tables Sales and Charges.
Tables having data as:
'Sales' 'Charges'
SID F_AMT SID C_AMT
1 100 1 10
1 100 1 10
1 100 1 20
1 200 2 20
2 200 2 10
2 300 3 20
4 300 3 30
4 300 3 10
4 300 5 20
4 200 5 10
I want the output as below:
SID Total_Fees Total_charges
1 500 40
2 500 30
3 0 60
4 1100 0
5 0 30
Assuming you want to do it for the whole tables this is the simplest approach:
Select Sid
, Sum(f_amt) as total_fees
, Sum(c_amt) as total_charges
From ( select sid, f_amt, 0 as c_amt
From sales
Union all
select sid, 0 as f_amt, c_amt
From charges
)
Group by sid
Use full join and nvl():
select sid, nvl(sum(f_amt), 0) fees, nvl(sum(c_amt), 0) charges
from sales s
full join charges c using (sid)
group by sid
order by sid
Demo:
with sales(sid, f_amt) as (
select 1, 100 from dual union all select 1, 100 from dual union all
select 1, 100 from dual union all select 1, 200 from dual union all
select 2, 200 from dual union all select 2, 300 from dual union all
select 4, 300 from dual union all select 4, 300 from dual union all
select 4, 300 from dual union all select 4, 200 from dual ),
charges (sid, c_amt) as (
select 1, 10 from dual union all select 1, 10 from dual union all
select 1, 20 from dual union all select 2, 20 from dual union all
select 2, 10 from dual union all select 3, 20 from dual union all
select 3, 30 from dual union all select 3, 10 from dual union all
select 5, 20 from dual union all select 5, 10 from dual )
select sid, nvl(sum(f_amt), 0) fees, nvl(sum(c_amt), 0) charges
from sales s
full join charges c using (sid)
group by sid
order by sid
Output:
SID FEES CHARGES
------ ---------- ----------
1 1500 160
2 1000 60
3 0 60
4 1100 0
5 0 30
You could use conditional aggregation:
SELECT SID,
COALESCE(SUM(CASE WHEN t=1 THEN AMT END),0) AS Total_Fees,
COALESCE(SUM(CASE WHEN t=2 THEN AMT END),0) AS Total_Charges
FROM (SELECT SID, F_AMT AS AMT, 1 AS t
FROM Sales
UNION ALL
SELECT SID, C_AMT AS AMT, 2 AS t
FROM Charges) sub
GROUP BY SID
ORDER BY SID;
DB Fiddle Demo