Oracle SQL query previous row result - sql

I am just stuck now, now getting the logic to solve this query. Find the below tables and the output. There is Table A and table B which matches two column ID and DATE. If Date got matched then it should multiply the qty with percent else it should pick previous percent.
Table A Table B
ID Date Percent ID Date Qty
A 01/01/17 0.5 A 01/01/17 10
A 04/01/17 1 A 02/01/17 20
A 06/01/17 2 A 03/01/17 30
B 02/01/17 5 A 05/01/17 40
B 05/01/17 10 A 06/01/17 50
A 07/01/17 60
A 08/01/17 40
B 01/01/17 10
B 02/01/17 50
============================================
column column column comment comment comment
ID Date Qty Previous percent if row not matched
A 01/01/17 0.5 * 10 0.5 got new percent
A 02/01/17 0.5 * 20 0.5
A 03/01/17 0.5 * 30 0.5
A 04/01/17 1* 0 1 got new percent but no qty found
A 05/01/17 1 * 40 1
A 06/01/17 2 * 50 2 got new percent
B 01/01/17 10 * 0 0 no percent found
B 02/02/17 5 * 10 5 got new percent
B 5/1/17 10 * 0 10 got new percent

I interpreted the problem to mean "add a few more columns to Table B" - to show the "most current" percentage, showing its date from Table A, and the "gross quantity" from Table B and the "net quantity" by multiplying by the proper percentage.
If you also need one row for every row in Table A, simply delete the WHERE clause from the outermost query (towards the bottom of the query).
with table_a ( id, dt, pct ) as (
select 'A', to_date('01/01/17', 'mm/dd/rr'), 0.5 from dual union all
select 'A', to_date('04/01/17', 'mm/dd/rr'), 1 from dual union all
select 'A', to_date('06/01/17', 'mm/dd/rr'), 2 from dual union all
select 'B', to_date('02/01/17', 'mm/dd/rr'), 5 from dual union all
select 'B', to_date('05/01/17', 'mm/dd/rr'), 10 from dual
), table_b ( id, dt, qty ) as (
select 'A', to_date('01/01/17', 'mm/dd/rr'), 10 from dual union all
select 'A', to_date('02/01/17', 'mm/dd/rr'), 20 from dual union all
select 'A', to_date('03/01/17', 'mm/dd/rr'), 30 from dual union all
select 'A', to_date('05/01/17', 'mm/dd/rr'), 40 from dual union all
select 'A', to_date('06/01/17', 'mm/dd/rr'), 50 from dual union all
select 'A', to_date('07/01/17', 'mm/dd/rr'), 60 from dual union all
select 'A', to_date('08/01/17', 'mm/dd/rr'), 40 from dual union all
select 'B', to_date('01/01/17', 'mm/dd/rr'), 10 from dual union all
select 'B', to_date('02/01/17', 'mm/dd/rr'), 50 from dual
)
-- end of test data (not part of the SQL query); query begins BELOW THIS LINE
select id, qty_date, pct_date, qty as gross_qty, pct, qty * pct as net_qty
from ( select id, dt as qty_date,
last_value(case flag when 0 then dt end ignore nulls)
over (partition by id order by dt) as pct_date,
last_value(pct ignore nulls)
over (partition by id order by dt, flag) as pct,
qty, flag
from ( select id, dt, pct, null as qty, 0 as flag
from table_a
union all
select id, dt, null as pct, qty, 1 as flag
from table_b
)
)
where flag = 1
order by id, qty_date -- if needed
;
Output:
ID QTY_DATE PCT_DATE GROSS_QTY PCT NET_QTY
-- ---------- ---------- ---------- ---------- ----------
A 2017-01-01 2017-01-01 10 .5 5
A 2017-02-01 2017-01-01 20 .5 10
A 2017-03-01 2017-01-01 30 .5 15
A 2017-05-01 2017-04-01 40 1 40
A 2017-06-01 2017-06-01 50 2 100
A 2017-07-01 2017-06-01 60 2 120
A 2017-08-01 2017-06-01 40 2 80
B 2017-01-01 10
B 2017-02-01 2017-02-01 50 5 250
9 rows selected.

Related

sql function to create sequential for repeated item- [duplicate]

This question already has an answer here:
Add a sequence column in a query
(1 answer)
Closed 7 days ago.
I would like to know if there is an easy sql function to do a sequential classification for each duplicate record as shown in the image:
enter image description here
Looks like one of analytic functions, such as row_number (or perhaps rank).
Sample data:
SQL> with test (product, value) as
2 (select 'A', 100 from dual union all
3 select 'B', 100 from dual union all
4 select 'C', 100 from dual union all
5 select 'A', 100 from dual union all
6 select 'B', 100 from dual union all
7 select 'A', 100 from dual union all
8 select 'D', 100 from dual union all
9 select 'D', 100 from dual union all
10 select 'F', 100 from dual union all
11 select 'F', 100 from dual union all
12 select 'F', 100 from dual
13 )
Query:
14 select product, value,
15 row_number() over (partition by product order by null) classification
16 from test
17 order by product;
PRODUCT VALUE CLASSIFICATION
---------- ---------- --------------
A 100 1
A 100 2
A 100 3
B 100 1
B 100 2
C 100 1
D 100 1
D 100 2
F 100 1
F 100 2
F 100 3
11 rows selected.
SQL>

How to get the entire partition based on two conditions are met in SQL

TelNO
Type
rank
date
76567
a
1
20210915
76567
b
2
20210611
76567
a
3
20210810
56597
b
1
20210818
56597
a
2
20210916
97658
b
1
20210610
97658
a
2
20210811
97658
b
3
20210915
76567
a
1
20210210
76567
a
2
20210619
I want to return the entire block (which is grouped by TelNO) if the Type= a when the
rank=1 . Expected output is as follows.
TelNO
Type
rank
date
76567
a
1
20210915
76567
b
2
20210611
76567
a
3
20210810
76567
a
1
20210210
76567
a
2
20210619
I am trying the following code. But it gives only the record which satisfices the condition. I need the entire partition to appear. Since there's no aggregation function to perform I am struggling how the partition function can use to get relevant output
select *
from table
where Type=a and rank=1
group by TelNo
This is the way I understood the question:
SQL> with test (telno, type, rank) as
2 (select 76567, 'a', 1 from dual union all
3 select 76567, 'b', 2 from dual union all
4 select 76567, 'c', 3 from dual union all
5 --
6 select 56597, 'b', 1 from dual union all
7 select 56597, 'a', 2 from dual union all
8 --
9 select 97658, 'b', 1 from dual union all
10 select 97658, 'a', 2 from dual union all
11 select 97658, 'b', 3 from dual union all
12 --
13 select 76567, 'a', 1 from dual union all
14 select 76567, 'a', 2 from dual
15 )
16 select *
17 from test
18 where telno in (select telno from test
19 where type = 'a'
20 and rank = 1
21 );
TELNO T RANK
---------- - ----------
76567 a 2
76567 a 1
76567 c 3
76567 b 2
76567 a 1
SQL>

Oracle PL/SQL SUM OVER( ) starting from certain row

I have a table who looks like this:
Pam_A Week Value_1
A 1 10
A 2 13
B 3 15
B 4 10
B 5 11
B 6 10
I want to achieve the following:
Pam_A Week Value_1 Value_2
A 1 10
A 2 13
B 3 15 28
B 4 10 38
B 5 11 49
B 6 10 59
When Pam_A=B, sum the current Value_1 and its preceding row value and keep that value increasing accordding the next value in Value_1
Any ideas for achieve this cumulative sum?
First of all you need to mark all rows that you want to count. You can do it like this:
with t(Pam_A, Week, Value_1) as (
select 'A', 1, 10 from dual union all
select 'A', 2, 13 from dual union all
select 'B', 3, 15 from dual union all
select 'B', 4, 10 from dual union all
select 'B', 5, 11 from dual union all
select 'B', 6, 10 from dual
)
select
Pam_A, Week, Value_1
,case
when Pam_A='B' or lead(Pam_A)over(order by week) = 'B'
then 'Y'
else 'N'
end as flag
from t;
Results:
PAM_A WEEK VALUE_1 FLAG
----- ---------- ---------- ----
A 1 10 N
A 2 13 Y
B 3 15 Y
B 4 10 Y
B 5 11 Y
B 6 10 Y
6 rows selected.
Then you can aggregate only rows that have flag='Y':
with t(Pam_A, Week, Value_1) as (
select 'A', 1, 10 from dual union all
select 'A', 2, 13 from dual union all
select 'B', 3, 15 from dual union all
select 'B', 4, 10 from dual union all
select 'B', 5, 11 from dual union all
select 'B', 6, 10 from dual
)
select
v.*
,case
when flag='Y' and Pam_a='B'
then sum(Value_1)over(partition by flag order by Week)
end as sums
from (
select
Pam_A, Week, Value_1
,case
when Pam_A='B' or lead(Pam_A)over(order by week) = 'B'
then 'Y'
else 'N'
end as flag
from t
) v;
Results:
PAM_A WEEK VALUE_1 FLAG SUMS
----- ---------- ---------- ---- ----------
A 1 10 N
A 2 13 Y
B 3 15 Y 28
B 4 10 Y 38
B 5 11 Y 49
B 6 10 Y 59
6 rows selected.
Using a combination of LEAD and SUM analytic functions, you can determine which rows have the next PAM_A as a B, then only SUM if the next row is a B or the current row is a B.
Query
WITH
d (pam_a, week, value_1)
AS
(SELECT 'A', 1, 10 FROM DUAL
UNION ALL
SELECT 'A', 2, 13 FROM DUAL
UNION ALL
SELECT 'B', 3, 15 FROM DUAL
UNION ALL
SELECT 'B', 4, 10 FROM DUAL
UNION ALL
SELECT 'B', 5, 11 FROM DUAL
UNION ALL
SELECT 'B', 6, 10 FROM DUAL)
SELECT pam_a,
week,
value_1,
CASE
WHEN pam_a = 'B'
THEN
SUM (CASE WHEN next_pam_a = 'B' OR pam_a = 'B' THEN value_1 ELSE 0 END)
OVER (ORDER BY week)
ELSE
NULL
END value_2
FROM (SELECT pam_a, week, value_1, LEAD (pam_a) OVER (ORDER BY week) AS next_pam_a FROM d);
Result
PAM_A WEEK VALUE_1 VALUE_2
________ _______ __________ __________
A 1 10
A 2 13
B 3 15 28
B 4 10 38
B 5 11 49
B 6 10 59
If I understand, you want a cumulative sum but with conditionality:
select t.*,
(case when pam_A = 'B' then sum(value_1) over (order by week) end) as value_2
from t;

Total Result, (Sum or Subtract some Value) according to Flag Column Oracle SQL Developer

I'm newbie in SQL Developer.
I have a query result like the above image.
I want to know how to sum all values with Flag = 1 and to subtract all the values With Flag = 2 in order to obtain a total result?
With a little help of DECODE, here's how:
SQL> with test (value, flag) as
2 (select 100, 2 from dual union -- sum of flag 2 values = 600
3 select 200, 2 from dual union
4 select 300, 2 from dual union
5 --
6 select 700, 1 from dual union -- sum of flag 1 values = 1500
7 select 800, 1 from dual
8 )
9 select sum(decode(flag, 1, value, 0)) sum_1,
10 sum(decode(flag, 2, value, 0)) sum_2,
11 --
12 sum(decode(flag, 1, value, 0)) - sum(decode(flag, 2, value, 0)) result
13 from test;
SUM_1 SUM_2 RESULT
---------- ---------- ----------
1500 600 900
SQL>

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