Recursive query - Oracle - sql

I have this data and expected result:
Data Expected Result
No A B A B
1 10 500 10 500
2 10 c=20 20 400
3 20 400 30 600
4 30 600 30 700
5 30 c=40 30 800
6 30 c=50 40 700
7 40 700 50 900
8 50 c=60 60 900
9 60 c=70 70 900
10 70 900 10 400
I need to perform a self join and get the result.
For line number 1 the expected result is same as the row.
For line number 2, I need to take the substring of column B (c=20) as 20 and join with column B and get the result as 400.
Lines number 5 and 6 need to substring column B and get the result from column A.
I tried a recursive query, but still am not getting the expected result.
with rec(A, B, nested) as
(
select A, B, case when instr(B, 'C=') != 0 then substr(B, instr(B, 'C=')) as nested
from table
union all
select A, rec.B from table
inner join rec
on (table.A = rec.nested)
)
select A, B, nested from rec;

Answer for the initial version of the question
You do not need a recursive query. To get your desired output you just need to exclude the rows where B starts with c=:
SELECT a, b
FROM table_name
WHERE b NOT LIKE 'c=%';
Which, for the sample data:
CREATE TABLE table_name (no, a, b) AS
SELECT 1, 10, '500' FROM DUAL UNION ALL
SELECT 2, 10, 'c=20' FROM DUAL UNION ALL
SELECT 3, 20, '400' FROM DUAL UNION ALL
SELECT 4, 30, '600' FROM DUAL UNION ALL
SELECT 5, 30, 'c=40' FROM DUAL UNION ALL
SELECT 6, 30, 'c=50' FROM DUAL UNION ALL
SELECT 7, 40, '700' FROM DUAL UNION ALL
SELECT 8, 50, '800' FROM DUAL;
Outputs your desired output:
A
B
10
500
20
400
30
600
40
700
50
800
fiddle
Answer for the 3rd edit of the question
You can use a hierarchical query:
SELECT DISTINCT
CONNECT_BY_ROOT a AS a,
b
FROM table_name
WHERE CONNECT_BY_ISLEAF = 1
CONNECT BY
PRIOR b LIKE 'c=%'
AND PRIOR SUBSTR(b, 3) = a
ORDER BY a, b;
Which, for the sample data:
CREATE TABLE table_name (no, a, b) AS
SELECT 1, 10, '500' FROM DUAL UNION ALL
SELECT 2, 10, 'c=20' FROM DUAL UNION ALL
SELECT 3, 20, '400' FROM DUAL UNION ALL
SELECT 4, 30, '600' FROM DUAL UNION ALL
SELECT 5, 30, 'c=40' FROM DUAL UNION ALL
SELECT 6, 30, 'c=50' FROM DUAL UNION ALL
SELECT 7, 40, '700' FROM DUAL UNION ALL
SELECT 8, 50, 'c=60' FROM DUAL UNION ALL
SELECT 9, 60, 'c=70' FROM DUAL UNION ALL
SELECT 10, 70, '900' FROM DUAL;
Outputs:
A
B
10
400
10
500
20
400
30
600
30
700
30
900
40
700
50
900
60
900
70
900
fiddle

Related

Compare Difference of Successive Rows Within The Same Table

I need help in comparing data in sql query.
I need to compare within the plan_group_id, what measure_id details having different min_target and max_target compared to the other plan_id
Example:
I mean, I needed to compare what plan measure under the same plan_grp_id have different values in min and max.
what if the values are:
another example:
Group by all three columns
select m.plan_id, m.plan_grp_id, m.measure_id
from plan_measure m
group by m.plan_id, m.plan_grp_id, m.measure_id
having min(m.min_target) <> max(m.min_target) or min(m.max_target) <> max(m.max_target);
Looks like
SQL> with plan_measure (measure_id, min_target, max_target) as
2 (select 111, 10, 10 from dual union all
3 select 222, 20, 20 from dual union all
4 select 333, 30, 30 from dual union all
5 select 111, 33, 55 from dual union all
6 select 222, 20, 20 from dual union all
7 select 333, 30, 30 from dual union all
8 select 111, 10, 10 from dual union all
9 select 222, 20, 20 from dual union all
10 select 111, 10, 10 from dual
11 )
12 select measure_id
13 from plan_measure
14 group by measure_id
15 having min(min_target) <> max(max_target);
MEASURE_ID
----------
111
SQL>
I have no idea what is plan table supposed to do here, though. It isn't related to plan_measure in any obvious way (not to me, at least).

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

Compute "Amount" using values from tables

I have the following tables already in my DB
EMP
E_N E_NAM E_RATE E_DEP
--- ----- ---------- -----
1 A 400
2 B 200 1
3 C 150 2
4 D 150 3
5 E 120 1
6 F 100 1
7 G 100 2
8 H 50 2
9 I 50 3
10 J 50 3
11 K 150 3
WORKS
E_NO PR_NO HRS
--- --- ----------
2 1 10
3 2 20
5 1 20
5 2 20
5 3 20
6 1 10
6 2 10
I have to compute the amount billed to each project as AMOUNT, and that is the sum of the amount billed to the project by all employees who work on said project. The amount billed being E_RATE*HRS (product of HRS and E_RATE).
There are only 3 PR_NO: 1, 2 and 3.
I've tried this multiple times with no avail, I know that it has to be a nested query and the calculation to be shown AS AMOUNT, but no clue on how exactly to only display the 3 projects with the calculation already made.
Sounds like simple join and aggregation:
select w.pr_no,
sum(w.hrs * e.e_rate) as amount
from works w
join emp e on w.e_no = e.e_n
group by w.pr_no;
simple aggregate SUM() function after joining the tables
--test data
with EMP(e_no, e_name, e_rate, e_dep) as
(select 1, 'A', 400, null from dual union all
select 2, 'B', 200, 1 from dual union all
select 3, 'C', 150, 2 from dual union all
select 4, 'D', 150, 3 from dual union all
select 5, 'E', 120, 1 from dual union all
select 6, 'F', 100, 1 from dual union all
select 7, 'G', 100, 2 from dual union all
select 8, 'H', 50, 2 from dual union all
select 9, 'I', 50, 3 from dual union all
select 10, 'J', 50, 3 from dual union all
select 11, 'K', 150, 3 from dual),
WORKS(e_no, pr_no, hrs) as
(select 2, 1, 10 from dual union all
select 3, 2, 20 from dual union all
select 5, 1, 20 from dual union all
select 5, 2, 20 from dual union all
select 5, 3, 20 from dual union all
select 6, 1, 10 from dual union all
select 6, 2, 10 from dual)
-- actual query starts here
select w.pr_no, sum(w.hrs*e.e_rate) as amount
from works w
inner join emp e on (w.e_no = e.e_no)
group by w.pr_no;
"PR_NO"|"AMOUNT"
1|5400
2|6400
3|2400

SQL get all children of a parent and add values of children to a parent

Lets say I have a table like this.
ID Parent Value
1 NULL 1000
2 1 1000
3 2 1000
4 2 1000
5 2 1000
6 2 1000
7 2 1000
8 1 1000
9 8 1000
10 8 1000
11 8 1000
I want to add every child value of a given id recursively. The correct output would be.
ID Parent Value
1 NULL 11000
2 1 6000
3 2 1000
4 2 1000
5 2 1000
6 2 1000
7 2 1000
8 1 4000
9 8 1000
10 8 1000
11 8 1000
There is only one "top" parent and it has Parent value of "Null". I'm very new to SQL so any kind of help would be appreciated. I'm using Oracle 11 if that helps.
Yes, you can do it using the CONNECT_BY_ROOT operator.
Basically, "START WITH" every row, sum up the children for each root, and then group by root. Like this:
with test_data (id, parent, value) as (
SELECT 1, NULL, 1000 FROM DUAL UNION ALL
SELECT 2, 1, 1000 FROM DUAL UNION ALL
SELECT 3, 2, 1000 FROM DUAL UNION ALL
SELECT 4, 2, 1000 FROM DUAL UNION ALL
SELECT 5, 2, 1000 FROM DUAL UNION ALL
SELECT 6, 2, 1000 FROM DUAL UNION ALL
SELECT 7, 2, 1000 FROM DUAL UNION ALL
SELECT 8, 1, 1000 FROM DUAL UNION ALL
SELECT 9, 8, 1000 FROM DUAL UNION ALL
SELECT 10, 8, 1000 FROM DUAL UNION ALL
SELECT 11, 8, 1000 FROM DUAL)
SELECT root_id id, root_parent parent, sum(value) value
FROM (
SELECT connect_by_root(id) root_id, connect_by_root(parent) root_parent, value
FROM test_data td
connect by parent = prior id
-- notice there is no "start with" clause
)
group by root_id, root_parent
order by root_id

SQL grouping issue

Neea a help with grouping in sql.
I've table like
id1 id2 type
1 1 300
1 3 300
1 2 300
1 5 300
2 2 100
2 5 200
2 7 300
4 3 100
4 9 300
4 2 300
I need id1 that is mapped to one type only,
For eg, id1 '1' is mapped only to type 300, so it should only be retrieved If there is more than one type mapped to an id1 it shouldnt be retrieved. Please help.
Here is what I have attempted. But it will handle only for type 300.I need to retrieve all the id1's which are mapped to one particular type alone. So if id1 '2' is mapped for type '100' alone, it should also be retrieved.
SELECT distinct id1 from ID_TABLE where type = 300 and id1 not in
(SELECT id1 from type_table where type in (100, 200, 250))
and id1 in ( SELECT id1 FROM ID_TABLE type=300)
order by id1
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE tbl ( id1, id2, type ) AS
SELECT 1, 1, 300 FROM DUAL
UNION ALL SELECT 1, 3, 300 FROM DUAL
UNION ALL SELECT 1, 2, 300 FROM DUAL
UNION ALL SELECT 1, 5, 300 FROM DUAL
UNION ALL SELECT 2, 2, 100 FROM DUAL
UNION ALL SELECT 2, 5, 200 FROM DUAL
UNION ALL SELECT 2, 7, 300 FROM DUAL
UNION ALL SELECT 4, 3, 100 FROM DUAL
UNION ALL SELECT 4, 9, 300 FROM DUAL
UNION ALL SELECT 4, 2, 300 FROM DUAL
UNION ALL SELECT 4, 4, 200 FROM DUAL
UNION ALL SELECT 5, 2, 200 FROM DUAL
UNION ALL SELECT 5, 4, 200 FROM DUAL;
Query 1:
SELECT id1,
MIN( type )
FROM tbl
GROUP BY id1
HAVING COUNT( DISTINCT type ) = 1
Results:
| ID1 | MIN(TYPE) |
|-----|-----------|
| 1 | 300 |
| 5 | 200 |
After the group by below you may still have multiple id1 so having will limit to only those situations when there is a single id1 for type. Using MIN as an arbitrary aggregator here as there is only one value anyway:
SELECT MIN(id1), type
FROM type_table
GROUP BY type
HAVING COUNT(id1)=1;