Dynamic column creation with Oracle SQL - sql

Currently I am working on fixed asset register report wand and I need to add Projection columns in main query.
Depreciation Remaining Life of
Assed_No Amount asset in months
-------- ------------ -----------------
1 400 6
2 200 3
3 100 4
4 600 1
Now, I want modification in SQL that according remaining life of asset in months column should generate. For 1st asset remaining life of asset in months is 6, so total 6 projection columns should come with value as 400. If remaining life is less than maximum no. i.e 6 then it should give 0 for rest of columns.
I want final solution like below,
Depreciation Remaining Life of
Assed_No Amount asset in months Projection 1 Projection 2 Projection 3 Projection 4 Projection 5 Projection 6
-------- ------------ ----------------- ------------ ------------ ------------ ------------ ------------ ------------
1 400 6 400 400 400 400 400 400
2 200 3 200 200 0 0 0 0
3 100 4 100 100 100 100 0 0
4 600 1 600 0 0 0 0 0

You can use a simple case expression for each projection:
-- CTE for sample data
with your_table (asset_no, amount, remaining_months) as (
select 1, 400, 6 from dual
union all select 2, 200, 3 from dual
union all select 3, 100, 4 from dual
union all select 4, 600, 1 from dual
)
-- query using my CTE column names
select asset_no, amount, remaining_months,
case when remaining_months >= 1 then amount else 0 end as proj_1,
case when remaining_months >= 2 then amount else 0 end as proj_2,
case when remaining_months >= 3 then amount else 0 end as proj_3,
case when remaining_months >= 4 then amount else 0 end as proj_4,
case when remaining_months >= 5 then amount else 0 end as proj_5,
case when remaining_months >= 6 then amount else 0 end as proj_6
from your_table;
ASSET_NO AMOUNT REMAINING_MONTHS PROJ_1 PROJ_2 PROJ_3 PROJ_4 PROJ_5 PROJ_6
---------- ---------- ---------------- ---------- ---------- ---------- ---------- ---------- ----------
1 400 6 400 400 400 400 400 400
2 200 3 200 200 200 0 0 0
3 100 4 100 100 100 100 0 0
4 600 1 600 0 0 0 0 0

This is basically the dynamic version of Alex's query ( as a bonus !)
Note the use of refcursor Bind variables. This will work when you run it in SQl* Plus or as script (F5) in SQL developer or Toad.
You may also use DBMS_SQL.RETURN_RESULT in Oracle 12c and above to do the same.
VARIABLE x REFCURSOR;
DECLARE
v_case_expr VARCHAR2(1000);
BEGIN
SELECT
listagg('CASE WHEN remaining_months > = '
|| level
|| '
then amount else 0 end as proj_'
|| level,',') WITHIN GROUP ( ORDER BY level)
INTO v_case_expr
FROM
dual
CONNECT BY
level <= (
SELECT
MAX(remaining_months)
FROM
assets
);
OPEN :x FOR 'select asset_no, amount, remaining_months, '
|| v_case_expr
|| ' FROM assets';END;
/
PRINT x;
PL/SQL procedure successfully completed.
ASSET_NO AMOUNT REMAINING_MONTHS PROJ_1 PROJ_2 PROJ_3 PROJ_4 PROJ_5 PROJ_6
---------- ---------- ---------------- ---------- ---------- ---------- ---------- ---------- ----------
1 400 6 400 400 400 400 400 400
2 200 3 200 200 200 0 0 0
3 100 4 100 100 100 100 0 0
4 600 1 600 0 0 0 0 0

When the amount of calculations increase sometimes pivot could be better.
Here I use a cartesian product before, to create the data:
with dat (asset_no, dep_amount, r_life)as
( select 1, 400, 6 from dual union all
select 2, 200, 3 from dual union all
select 3, 100, 4 from dual union all
select 4, 600, 1 from dual )
, mon as (select level lv from dual connect by level <= 6)
, bas as (
select asset_no, dep_amount, r_life, lv, case when r_life >= lv then dep_amount else 0 end proj_m from dat, mon)
select *
from bas
pivot(sum(proj_m) for lv in (1 as proj_1,2 as proj_2,3 as proj_3,4 as proj_4,5 as proj_5,6 as proj_6))
order by asset_no
Think the solution with case expressions is better here

Related

Oracle Hierarchical Query at depth level

I have a requirement to build a table from a hierarchical table. Table structure as below:
emp_hier table:
emp_id
supervisorId
100
null
1
100
2
1
3
2
New table:
I have to write a select query on the emp_heir table and the selected data should look like this:
sel_emp_id
rel_emp_id
relation
depth_lvl
100
100
self
0
100
1
My Repotee
-1
100
2
My Repotee
-2
100
3
My Repotee
-3
1
100
My Mgr
1
1
1
self
0
1
2
My Repotee
-1
1
3
My Repotee
-2
2
1
My Mgr
1
2
2
self
0
2
3
My Repotee
-1
3
100
My Mgr
3
3
1
My Mgr
2
3
2
My Mgr
1
3
3
self
0
You can use UNION ALL to combine a hierarchical query to get each row and its children to another hierarchical query to get all the ancestors:
SELECT CONNECT_BY_ROOT emp_id AS sel_emp_id,
emp_id AS rel_emp_id,
CASE LEVEL WHEN 1 THEN 'Self' ELSE 'My Reportee' END AS relation,
1 - LEVEL AS depth_lvl
FROM emp_hier
CONNECT BY PRIOR emp_id = supervisorid
UNION ALL
SELECT CONNECT_BY_ROOT emp_id,
emp_id,
'My Mgr',
LEVEL - 1
FROM emp_hier
WHERE LEVEL > 1
CONNECT BY PRIOR supervisorid = emp_id
ORDER BY sel_emp_id, depth_lvl DESC
Which, for your sample data:
CREATE TABLE emp_hier (emp_id, supervisorId) AS
SELECT 100, null FROM DUAL UNION ALL
SELECT 1, 100 FROM DUAL UNION ALL
SELECT 2, 1 FROM DUAL UNION ALL
SELECT 3, 2 FROM DUAL;
Outputs:
SEL_EMP_ID
REL_EMP_ID
RELATION
DEPTH_LVL
1
100
My Mgr
1
1
1
Self
0
1
2
My Reportee
-1
1
3
My Reportee
-2
2
100
My Mgr
2
2
1
My Mgr
1
2
2
Self
0
2
3
My Reportee
-1
3
100
My Mgr
3
3
1
My Mgr
2
3
2
My Mgr
1
3
3
Self
0
100
100
Self
0
100
1
My Reportee
-1
100
2
My Reportee
-2
100
3
My Reportee
-3
db<>fiddle here
Using CONNECT BY, you can connect all the employees and their relationships to each other. Then by joining that information together, you can print out the information in the format you desire.
WITH
hier
AS
( SELECT e.*, LEVEL AS lvl
FROM emp_hier e
CONNECT BY PRIOR emp_id = supervisorid
START WITH supervisorid IS NULL)
SELECT h1.emp_id AS sel_emp_id,
h2.emp_id AS rel_emp_id,
CASE
WHEN h1.lvl - h2.lvl = 0 THEN 'self'
WHEN h1.lvl - h2.lvl > 0 THEN 'My Mgr'
ELSE 'My Reportee'
END AS relation,
h1.lvl - h2.lvl AS depth_level
FROM hier h1, hier h2
ORDER BY CASE WHEN h1.supervisorid IS NULL THEN 0 ELSE 1 END, h1.emp_id, h1.lvl - h2.lvl DESC;
SEL_EMP_ID REL_EMP_ID RELATION DEPTH_LEVEL
_____________ _____________ ______________ ______________
100 100 self 0
100 1 My Reportee -1
100 2 My Reportee -2
100 3 My Reportee -3
1 100 My Mgr 1
1 1 self 0
1 2 My Reportee -1
1 3 My Reportee -2
2 100 My Mgr 2
2 1 My Mgr 1
2 2 self 0
2 3 My Reportee -1
3 100 My Mgr 3
3 1 My Mgr 2
3 2 My Mgr 1
3 3 self 0
You can get the entire desired result with a single pass through the hierarchy (a single CONNECT BY query), with no self-join and no union all.
Instead, I use a helper inline view (with just one row and two numeric columns, with the values -1 and 1); since each "relationship" appears exactly twice in the output, with the exception of "Self", I use this to do an ad-hoc duplication of the rows from the hierarchical query.
I used the table in MT0's post for testing. I don't show the result - it's the same (just ordered differently).
with
h (x) as (select 1 from dual union all select -1 from dual)
, p (ancestor, emp, depth) as (
select connect_by_root(emp_id), emp_id, level - 1
from emp_hier
connect by supervisorid = prior emp_id
)
select case h.x when 1 then emp else ancestor end as emp_self,
case h.x when 1 then ancestor else emp end as emp_related,
case when h.x = 1 then 'Mgr'
when p.depth != 0 then 'Reportee'
else 'Self' end as rel,
h.x * p.depth as depth_level
from p join h on h.x = -1 or p.depth != 0 -- do not duplicate "Self"
order by emp_self, depth_level desc, emp_related -- or whatever (if/as needed)
;

How to find missing Number along with the numbers present in table in Oracle

I am trying to fetch data for one of my clients, but there are missing tokens in his data. I tried the below query for fetching the missing data and inserting into one dump table, but would like to find a more optimized way where I find the missing tokens along with the token's present in the table.
SELECT MinToken + 1 Level
FROM (SELECT Min(Token) AS MINTOKEN, MAX(Token) AS MAXTOKEN
FROM dmp_SellerOrders
Where OrderNo = 1
AND TradeDate = '27-Oct-20')
CONNECT BY LEVEL < MAXTOKEN - MINTOKEN
MINUS
SELECT TOKEN
FROM dmp_SellerOrders
Where (OrderNo, TranserialNo) IN (SELECT OrderNo, MAX(TranserialNo)
FROM dmp_SellerOrders
GROUP BY OrderNo)
AND TradeDate = '27-Oct-20';
Actual Data in Table looks like this,
Original Order OrderNo TranserialNo Token Qty Price
1 1 25 100 100
1 1 26 100 100
1 1 27 100 100
1 1 28 100 100
1 1 30 100 100
1 1 31 100 100
Order Price Modified OrderNo TranserialNo Token Qty Price
1 2 25 100 200
1 2 26 100 200
1 2 27 100 200
1 2 28 100 200
1 2 30 100 200
1 2 31 100 200
I need data to show by grouping Qty, as show below,
OrderNo MinToken MaxToken Qty Price
1 25 28 100 200
1 29 29 0 0
1 30 31 100 200
But if I group qty then I get below out,
OrderNo MinToken MaxToken Qty Price
1 25 31 100 200
1 29 29 0 0
Can any one please help me, how can I get the output as expected.
Regards,
Mehul
Here a simplified solution without orderNo and TradeDate to demonstrate the concept.
You perform following steps in subsequent subqueries
get all tokens and outer join to the order table to get a complete sequence
find the LAG of the qty column
set grp_id to 1 if there is a break. i.e. if the previos qty is null and the current not null or vice versa - else to 0
cummulate the grp_id using analytic form of SUM
here the result with the sample data
TOKEN QTY PRICE QTY_LAG GRP_ID CUM_GRP_ID
---------- ---------- ---------- ---------- ---------- ----------
25 100 100 1 1
26 100 100 100 0 1
27 100 100 100 0 1
28 100 100 100 0 1
29 100 1 2
30 100 100 1 3
The last step is a simple GROUP BY on qty, price with added cummulated group id cum_grp_id which splits the non adjacent groups.
Query
with orders as (
select 25 token, 100 qty, 100 price from dual union all
select 26 token, 100 qty, 100 price from dual union all
select 27 token, 100 qty, 100 price from dual union all
select 28 token, 100 qty, 100 price from dual union all
select 30 token, 100 qty, 100 price from dual union all
select 31 token, 100 qty, 100 price from dual),
tokens as (
select min(token) min_token, max(token) max_token from orders),
all_tokens as (
select min_token - 1 + level token from tokens
connect by level <= max_token - min_token),
grp as (
select
t.token,
o.qty, o.price,
lag(o.qty) over (order by t.token) as qty_lag
from all_tokens t
left outer join orders o
on t.token = o.token),
grp2 as (
select
token, qty, price, qty_lag,
case when qty is null and qty_lag is null or qty is not null and qty_lag is not null then 0 else 1 end as grp_id
from grp),
grp3 as (
select
token, qty, price, qty_lag, grp_id,
sum(grp_id) over (order by token) cum_grp_id
from grp2)
select min(token), max(token), qty, price
from grp3
group by cum_grp_id, qty, price
order by 1
result
MIN(TOKEN) MAX(TOKEN) QTY PRICE
---------- ---------- ---------- ----------
25 28 100 100
29 29
30 30 100 100
Adapt if required by partitioning the analytic function by orderNo and/or tradeDate.

Incorrect first value when using dense_rank with union all

I want to 'batch' my result (coming from a union of several queries) with a predefined 'batch size' but I can't figure out why the first batch is always incorrect?
For instance with the following code:
DECLARE #BATCHSIZE AS INT = 2;
DECLARE #TEMPTABLE TABLE(ITEMID VARCHAR (10))
INSERT INTO #TEMPTABLE VALUES ('100'),('200'),('300'),('400'),('500'),('600'),('700'),('800'),('900'),('1000'),('1100'),('1200'),('1300'),('1400'),('1500')
;
WITH TEMP AS
(
SELECT * FROM #TEMPTABLE
)
SELECT *, BatchId = (dense_rank() over (order by ITEMID) / #BatchSize + 1)
FROM (
SELECT * From TEMP
UNION ALL
SELECT * From TEMP
) AS temptable
I get a result:
100 1
100 1
1000 2
1000 2
1100 2
1100 2
1200 3
1200 3
1300 3
1300 3
1400 4
1400 4
1500 4
1500 4
200 5
200 5
300 5
300 5
400 6
400 6
500 6
500 6
600 7
600 7
700 7
700 7
800 8
800 8
900 8
900 8
It seems like they're all ok except for batch 1 which only consist of itemid 100?
Must be doing something wrong here..
dense_rank() starts with 1. Shift it to start with 0 :
...
SELECT *, BatchId = (dense_rank() over (order by ITEMID) - 1 )/ #BatchSize + 1
...

SQL: Difference in amounts between rows and columns

I have a Table
With the below data
Table X
Seq_no A Claim Payment Balance (to be calculated)
1 abc 100 10 90 (100-10)
2 abc 50 40 (90-50)
3 abc 20 20 (40-20)
1 xyz 150 10 140 (150-10)
1 qwe 200 10 190 (200-10)
I need to calculate the column Balance.
I am trying with the below Query
SQL >
Select
Seq_no, a, Claim, Payment,
CASE
When Seq_no =1
then (claim-Payment)
Else ( lag(Balance)- Payment over (order by Balance))
END as Balance
from table X
However i am getting a error
ORA-00904: "Balance": invalid identifier
00904. 00000 - "%s: invalid identifier"
I believe this is because Balance is not an existing column name.
Is there a correct way to achieve the results.?
Update:
*I missed an important part. *
The data i have is in the below format:
Table X
Seq_no A Claim Payment
1 abc 100 10
2 abc 100 50
3 abc 100 20
1 xyz 150 10
1 qwe 200 10
I need the results in the below format.
Table X
Seq_no A Claim Payment Balance (to be calculated)
1 abc 100 10 90 (100-10)
2 abc 50 40 (90-50)
3 abc 20 20 (40-20)
1 xyz 150 10 140 (150-10)
1 qwe 200 10 190 (200-10)
The Seq_no calculation has been done to make the claim column null for the cases of duplicate claims, which i had figured out already.
You cannot reference balance before create it.
You can use a "running sum" for what you want to achieve.
Notice that I partitioned by A because you want another balance for every A.
Select
Seq_no, a, Claim, Payment,
sum(nvl(claim,0) - payment) over (partition by A order by seq_no) as Balance
from X;
Result:
SEQ_NO A CLAIM PAYMENT BALANCE
1 abc 100 10 90
2 abc 50 40
3 abc 20 20
1 qwe 200 10 190
1 xyz 150 10 140
EDIT: With newer dataset you just need to replace nvl function with a case when seq=1:
Select
Seq_no,
a,
case when seq_no=1 then claim else 0 end as Claim,
Payment,
sum(case when seq_no=1 then claim else 0 end - payment)
over (partition by A order by seq_no) as Balance
from X;
In order to use balance as a column identifier, you must go one level deep, i.e. make your existing query as a sub-query.
Working demo:
SQL> WITH sample_data AS(
2 SELECT 1 Seq_no, 'abc' A, 100 Claim, 10 Payment FROM dual UNION ALL
3 SELECT 2 Seq_no, 'abc' A, NULL Claim, 50 FROM dual UNION ALL
4 SELECT 3 Seq_no, 'abc' A, NULL Claim, 20 FROM dual UNION ALL
5 SELECT 1 Seq_no, 'xyz' A, 150 Claim, 10 FROM dual UNION ALL
6 SELECT 1 Seq_no, 'qwe' A, 200 Claim, 10 FROM dual
7 )
8 -- end of sample_data mimicking real table
9 SELECT seq_no, A, claim, payment,
10 CASE
11 WHEN lag(balance) OVER(PARTITION BY A ORDER BY seq_no) IS NULL
12 THEN balance
13 ELSE lag(balance) OVER(PARTITION BY A ORDER BY seq_no) - payment
14 END balance
15 FROM
16 (SELECT seq_no, A, claim, payment,
17 CASE
18 WHEN seq_no = 1
19 THEN claim - payment
20 ELSE lag(claim - payment) OVER(PARTITION BY A ORDER BY seq_no) - payment
21 END balance
22 FROM sample_data
23 );
SEQ_NO A CLAIM PAYMENT BALANCE
---------- --- ---------- ---------- ----------
1 abc 100 10 90
2 abc 50 40
3 abc 20 20
1 qwe 200 10 190
1 xyz 150 10 140
SQL>
UPDATE : If you not-null values always for claim, then you could do a running diff:
SQL> WITH sample_data AS(
2 SELECT 1 Seq_no, 'abc' A, 100 Claim, 10 Payment FROM dual UNION ALL
3 SELECT 2 Seq_no, 'abc' A, 100 Claim, 50 FROM dual UNION ALL
4 SELECT 3 Seq_no, 'abc' A, 100 Claim, 20 FROM dual UNION ALL
5 SELECT 4 Seq_no, 'abc' A, 100 Claim, 10 FROM dual UNION ALL
6 SELECT 5 Seq_no, 'abc' A, 100 Claim, 10 FROM dual UNION ALL
7 SELECT 1 Seq_no, 'xyz' A, 150 Claim, 10 FROM dual UNION ALL
8 SELECT 1 Seq_no, 'qwe' A, 200 Claim, 10 FROM dual
9 )
10 -- end of sample_data mimicking real table
11 SELECT Seq_no,
12 a,
13 Claim,
14 Payment,
15 CASE
16 WHEN seq_no = 1
17 THEN claim - payment
18 ELSE SUM(claim - payment) over (partition BY A order by seq_no) - claim*(seq_no-1)
19 END balance
20 FROM sample_data;
SEQ_NO A CLAIM PAYMENT BALANCE
---------- --- ---------- ---------- ----------
1 abc 100 10 90
2 abc 100 50 40
3 abc 100 20 20
4 abc 100 10 10
5 abc 100 10 0
1 qwe 200 10 190
1 xyz 150 10 140
7 rows selected.
SQL>
You are correct that the error occurs because Balance is not a column in your table. You need to add that column.
The bigger problem is that unfortunately it's not possible to compute a running total using a single SQL statement the way you are trying to do it. SQL is not well suited to an operation like this. It can be done in a stored procedure using a cursor but the end result is procedural and not set-based, thus performance would be poor. Here's how to do it in SQL Server (as an example, not tested):
DECLARE PaymentCursor CURSOR FOR SELECT A, Claim, Payment FROM X ORDER BY A, Seq_no
OPEN PaymentCursor
DECLARE #A VARCHAR(16)
DECLARE #Claim MONEY
DECLARE #Payment MONEY
DECLARE #Balance MONEY
DECLARE #PreviousA VARCHAR(16)
SET #PreviousA = ''
FETCH NEXT FROM PaymentCursor INTO #A, #Claim, #Payment
WHILE ##FETCH_STATUS = 0
BEGIN
IF #A <> #PreviousA
BEGIN
SET #Balance = 0
SET #PreviousA = #A
END
SET #Balance = #Balance + #Claim - #Payment
UPDATE X SET Balance = #Balance WHERE CURRENT OF PaymentCursor
FETCH NEXT FROM PaymentCursor INTO #A, #Claim, #Payment
END
CLOSE PaymentCursor
DEALLOCATE PaymentCursor
If your application really needs to know the account balance associated with each transaction, the better approach is to compute the balance each time a row is inserted. Wrap the insertion logic in a stored procedure:
CREATE PROCEDURE InsertAndComputeBalance
#A VARCHAR(16),
#Claim MONEY,
#Payment MONEY
AS
DECLARE #MAX_Seq_no INTEGER = (SELECT MAX(Seq_no) FROM X WHERE A = #A)
INSERT INTO X
SELECT #MAX_Seq_no + 1, #A, #Claim, #Payment, Balance + #Claim - #Payment FROM X
WHERE A = #A AND Seq_no = #MAX_Seq_no
In theory you could call similar logic from a trigger so that it is automatically executed each time a row is inserted, but triggers can be evil so proceed very carefully with that approach.

Conditional transpose columns into rows in oracle sql

I have this row in my table mtinre:
INREPRCO INRESELO INRECAPI INRECFRA INRECAPO
---------- ---------- ---------- ---------- ----------
32.42 1.87 1 5
I build a query to transpose this row as 5 different rows for each column.
SELECT CASE pivot
WHEN 1 THEN 'VAPRS'
WHEN 2 THEN 'VAFRC'
WHEN 3 THEN 'VACTA'
WHEN 4 THEN 'VIMSL'
WHEN 5 THEN 'VINEM'
END
component,
CASE pivot
WHEN 1 THEN inreprco
WHEN 2 THEN inrecfra
WHEN 3 THEN inrecapo
WHEN 4 THEN inreselo
WHEN 5 THEN inreinem
ELSE NULL
END
VALUE,
CASE pivot
WHEN 4
THEN
(NVL (inreprco, 0) + NVL (inrecfra, 0) + NVL (inrecapo, 0))
WHEN 5
THEN
(NVL (inreprco, 0) + NVL (inrecfra, 0) + NVL (inrecapo, 0))
ELSE
NULL
END
AS base
FROM mtinre,
( SELECT ROWNUM pivot
FROM DUAL
CONNECT BY LEVEL <= 5)
The output is:
COMPONENT VALUE BASE
--------- ---------- ----------
VAPRS 32.42
VAFRC
VACTA 5
VIMSL 1.87 37.42
VINEM .94 37.42
But these 5 fields(INREPRCO,INRESELO,INRECAPI,INRECFRA,INRECAPO) can have null or zero(0) values. So I need to select only those how have values.
In the last example, just show me:
COMPONENT VALUE BASE
--------- ---------- ----------
VAPRS 32.42
VACTA 5
VIMSL 1.87 37.42
VINEM .94 37.42
I've tried to put some where conditions, but the connect by level statement creates me always 5 rows.
So, I changed my query and made this:
SELECT *
FROM (SELECT CASE pivot
WHEN 1 THEN 'VAPRS'
WHEN 2 THEN 'VAFRC'
WHEN 3 THEN 'VACTA'
WHEN 4 THEN 'VIMSL'
WHEN 5 THEN 'VINEM'
END
component,
CASE pivot
WHEN 1 THEN inreprco
WHEN 2 THEN inrecfra
WHEN 3 THEN inrecapo
WHEN 4 THEN inreselo
WHEN 5 THEN inreinem
ELSE NULL
END
VALUE,
CASE pivot
WHEN 4
THEN
( NVL (inreprco, 0)
+ NVL (inrecfra, 0)
+ NVL (inrecapo, 0))
WHEN 5
THEN
( NVL (inreprco, 0)
+ NVL (inrecfra, 0)
+ NVL (inrecapo, 0))
ELSE
NULL
END
AS base
FROM mtinre,
( SELECT ROWNUM pivot
FROM DUAL
CONNECT BY LEVEL <= 5))
WHERE VALUE IS NOT NULL
It works, but is there any other way to do that without using a sub select statement?
Any suggestion?
Thanks
Filipe
Using UNPIVOT and a little trick can do the job. Almost all tables have an id column (primary or unique key). Assuming that the table has id_col as id column, this query will do the job
SQL> WITH table_(id_col, inreprco,inreselo,inrecapi,inrecfra,inrecapo) AS
2 (SELECT 1, 32.42,1.87,0.94,NULL,5 FROM dual UNION ALL
3 SELECT 2, 33.43,2.87,0.87,12,9 FROM dual ),
4 ---------
5 -- End of data preparation
6 ---------
7 table2_ AS (SELECT id_col, component, VALUE
8 FROM table_
9 UNPIVOT (VALUE FOR component IN (inreprco AS 'VAPRS', inrecfra AS 'VAFRC', inrecapo AS 'VACTA', inreselo AS 'VIMSL', inrecapi AS 'VINEM')))
10 select a.id_col,
11 a.COMPONENT,
12 a.VALUE,
13 CASE WHEN a.component IN ('VIMSL', 'VINEM') THEN nvl(b.inreprco, 0) + nvl(b.inrecfra, 0) + NVL(b.inrecapo, 0) ELSE NULL END AS base
14 FROM table2_ a
15 INNER JOIN table_ b
16 ON b.id_col = a.id_col;
ID_COL COMPONENT VALUE BASE
---------- --------- ---------- ----------
1 VAPRS 32.42
1 VACTA 5
1 VIMSL 1.87 37.42
1 VINEM 0.94 37.42
2 VAPRS 33.43
2 VAFRC 12
2 VACTA 9
2 VIMSL 2.87 54.43
2 VINEM 0.87 54.43
9 rows selected
But if there are no Id column, then modifying the join as cross join will do but that will return correct result if there is only one row in the table.
SQL> WITH table_(inreprco,inreselo,inrecapi,inrecfra,inrecapo) AS
2 (SELECT 32.42,1.87,0.94,NULL,5 FROM dual),
3 ---------
4 -- End of data preparation
5 ---------
6 table2_ AS (SELECT component, VALUE
7 FROM table_
8 UNPIVOT (VALUE FOR component IN (inreprco AS 'VAPRS', inrecfra AS 'VAFRC', inrecapo AS 'VACTA', inreselo AS 'VIMSL', inrecapi AS 'VINEM')))
9 select a.COMPONENT,
10 a.VALUE,
11 CASE WHEN a.component IN ('VIMSL', 'VINEM') THEN nvl(b.inreprco, 0) + nvl(b.inrecfra, 0) + NVL(b.inrecapo, 0) ELSE NULL END AS base
12 FROM table2_ a
13 CROSS JOIN table_ b
14 /
COMPONENT VALUE BASE
--------- ---------- ----------
VAPRS 32.42
VACTA 5
VIMSL 1.87 37.42
VINEM 0.94 37.42
Or wait for someone who comes with some other approach ;)