I need to find the remaining amount for each credit no. The expected result is this:
CREDIT_NO CREDIT_TYPE CREDIT_AMOUNT TOTAL_A REMAINING_AMT FINAL_TOTAL_PER_BILL
A1 W 100 1000 900 600
A1 X 100 1000 800 600
A1 Y 100 1000 700 600
A1 Z 100 1000 600 600
B1 X 100 2000 1900 1700
B1 Y 100 2000 1800 1700
B1 Z 100 2000 1700 1700
Here's the query I have done so far (please pardon the noob):
WITH TEMP AS
(SELECT 1 ID,
'A1' CREDIT_NO,
'X' CREDIT_TYPE,
100 CREDIT_AMOUNT,
1000 TOTAL_A
FROM DUAL
UNION ALL
SELECT 2, 'A1' , 'Y', 100, 1000 FROM DUAL
UNION ALL
SELECT 4, 'A1' , 'Z', 100, 1000 FROM DUAL
UNION ALL
SELECT 3, 'B1', 'X', 100, 2000 FROM DUAL
UNION ALL
SELECT 5, 'B1', 'Y', 100, 2000 FROM DUAL
UNION ALL
SELECT 6, 'B1', 'Z', 100, 2000 FROM DUAL
UNION ALL
SELECT 7, 'A1', 'W', 100, 1000 FROM DUAL
)
SELECT
TEMP1.CREDIT_NO ,
TEMP1.CREDIT_TYPE,
TEMP1.CREDIT_AMOUNT ,
TEMP1.TOTAL_A ,
CASE
WHEN TEMP1.CREDIT_NO = (LAG (TEMP1.CREDIT_NO,1) OVER (ORDER BY TEMP1.CREDIT_NO) ) -- set remaining CREDIT_AMOUNT
OR (LAG (TEMP1.CREDIT_NO,1) OVER (ORDER BY TEMP1.CREDIT_NO) ) IS NULL
THEN TEMP1.TOTAL_A - (SUM(TEMP1.CREDIT_AMOUNT) OVER ( ORDER BY TEMP1.CREDIT_NO ROWS BETWEEN
UNBOUNDED PRECEDING
AND CURRENT ROW ) )
WHEN TEMP1.CREDIT_NO <> -- new bill, new total CREDIT_AMOUNT
(LAG (TEMP1.CREDIT_NO,1) OVER (ORDER BY TEMP1.CREDIT_NO) )
THEN TEMP1.TOTAL_A - TEMP1.CREDIT_AMOUNT
END AS REMAINING_AMT
,TEMP1.TOTAL_A - (SUM(TEMP1.CREDIT_AMOUNT) OVER (PARTITION BY CREDIT_NO)) AS FINAL_TOTAL_PER_BILL
FROM TEMP TEMP1
ORDER BY CREDIT_NO, CREDIT_TYPE
My problem is I don't know how to compute for the remaining amount for the 2nd credit no. The result of the above query is:
CREDIT_NO CREDIT_TYPE CREDIT_AMOUNT TOTAL_A REMAINING_AMT FINAL_TOTAL_PER_BILL
A1 W 100 1000 900 600
A1 X 100 1000 800 600
A1 Y 100 1000 700 600
A1 Z 100 1000 600 600
B1 X 100 2000 1900 1700
B1 Y 100 2000 1400 1700
B1 Z 100 2000 1300 1700
Is it possible to get a running remaining amount without using a stored procedure? I tried basing it on the rownum but it is not sequential.
Even though I have found similar questions to this (Link 1, Link 2, Link 3)
(I'm still going over the third link though), I hope you guys can help me.
use a running subtotal, and be careful at partition clause as:
select credit_no, credit_type,
total_a - sum(credit_amount) over (partition by credit_no order by id) as remaining_credit,
total_a,
total_a - sum(credit_amount) over (partition by credit_no) as FINAL_TOTAL_PER_BILL
from temp
see sqlfiddle
Related
I'm struggling to obtain the following results in a query
Here is my table
Line_num
Line_typ
Cost
1000
6
0
2000
7
5000
3000
7
3000
4000
7
2000
5000
6
0
6000
9
3000
7000
7
2000
8000
1
2000
What I want as result is this
Line_num
Line_typ
Cost
1000
6
10000 (0+5000+3000+2000)
5000
6
5000 (0+3000+2000)
8000
1
2000
Basically to display only rows with line_typ in (6,1) but sum the column costs of all other lines in between.
Thank you for your ideas and help !!
Ivan
The reason Tim Biegeleisen's answer is excellent, but needs some intermediate products and oracle features, can be seen when one uses minimal SQL:
Select line_typ in (1, 6), order by line_num.
Sum the cost for second run over the table with larger line_num n, but
where (third run) there is no line_typ in (1, 6) before n.
Basic complexity O(N³), with indices a bit less.
So:
select h.line_num, h.line_typ,
(select sum(cost)
from tbl g
where g.line_num >= h.line_num
and not exists (select *
from tbl
where line_num > h.line_num
and line_typ in (1, 6)
and line_num <= g.line_num)
) as cost
from tbl h
where h.line_typ in (1, 6)
order by h.line_num
(Given for non-oracle searchers.)
Here is a link to a working demo.
This is a play on a gaps and islands problem. Each island begins upon encountering a Line_typ value of 1 or 6, and ends right before a record containing another 1 or 6 value. We can use COUNT() as an analytic function to find the groups, then report the sums of cost.
WITH cte AS (
SELECT t.*, CASE WHEN Line_typ IN (1, 6) THEN 1 ELSE 0 END AS flag
FROM yourTable t
),
cte2 AS (
SELECT t.*, SUM(flag) OVER (ORDER BY Line_num) AS grp
FROM cte t
),
cte3 AS (
SELECT t.*, MIN(Line_num) OVER (PARTITION BY grp) AS Min_Line_num
FROM cte2 t
)
SELECT MIN(Line_num) AS Line_num,
MAX(CASE WHEN Line_num = Min_Line_num THEN Line_typ END) AS Line_typ,
SUM(Cost) AS Cost
FROM cte3 t
GROUP BY grp
ORDER BY MIN(t.Line_num);
Demo
From Oracle 12, you can use MATCH_RECOGNIZE to perform row-by-row pattern matching:
SELECT *
FROM table_name
MATCH_RECOGNIZE(
ORDER BY line_num
MEASURES
FIRST(line_num) AS line_num,
FIRST(line_typ) AS line_typ,
SUM(cost) AS total_cost
PATTERN (match_type other_type*)
DEFINE
match_type AS line_typ IN (6, 1),
other_type AS line_typ NOT IN (6, 1)
)
Which, for the sample data:
CREATE TABLE table_name (line_num, line_typ, cost) AS
SELECT 1000, 6, 0 FROM DUAL UNION ALL
SELECT 2000, 7, 5000 FROM DUAL UNION ALL
SELECT 3000, 7, 3000 FROM DUAL UNION ALL
SELECT 4000, 7, 2000 FROM DUAL UNION ALL
SELECT 5000, 6, 0 FROM DUAL UNION ALL
SELECT 6000, 9, 3000 FROM DUAL UNION ALL
SELECT 7000, 7, 2000 FROM DUAL UNION ALL
SELECT 8000, 1, 2000 FROM DUAL;
Outputs:
LINE_NUM
LINE_TYP
TOTAL_COST
1000
6
10000
5000
6
5000
8000
1
2000
db<>fiddle here
I need help to write a query in Oracle to get minimum value for each row, comparing the current row amount with the previous minimum value.
In another word, calculate minimum value for each row from top to that row, dataset for minimum function is from the first row to the current row.
For example: retrieve Min(previous, current) value for each row as below
Rank
Amount
Calc Min (previous, current)
1
600
600
2
800
600
3
300
300
4
500
300
5
500
300
6
800
300
7
200
200
8
550
200
Thanks in Advance
Ash
You are looking for the analytic function MIN OVER.
select
rank,
amount,
min(amount) over (order by rank) as min_amount_so_far
from mytable
order by rank;
You can also solve this using MATCH_RECOGNIZE:
SELECT rank, amount, min_amount
FROM table_name
MATCH_RECOGNIZE(
ORDER BY Rank
MEASURES
MIN(Amount) AS min_amount
ALL ROWS PER MATCH
PATTERN (^ any_row+ )
DEFINE any_row AS 1 = 1
)
Which, for the sample data:
CREATE TABLE table_name (Rank, Amount) AS
SELECT 1, 600 FROM DUAL UNION ALL
SELECT 2, 800 FROM DUAL UNION ALL
SELECT 3, 300 FROM DUAL UNION ALL
SELECT 4, 500 FROM DUAL UNION ALL
SELECT 5, 500 FROM DUAL UNION ALL
SELECT 6, 800 FROM DUAL UNION ALL
SELECT 7, 200 FROM DUAL UNION ALL
SELECT 8, 550 FROM DUAL;
Outputs:
RANK
AMOUNT
MIN_AMOUNT
1
600
600
2
800
600
3
300
300
4
500
300
5
500
300
6
800
300
7
200
200
8
550
200
db<>fiddle here
I've following data:
SELECT 1 note, 1000 amt FROM dual union all
SELECT 2 note, 2000 amt FROM dual union all
SELECT 3 note, 8000 amt FROM dual union all
SELECT 4 note, 3000 amt FROM dual union all
SELECT 5 note, 1500 amt FROM dual union all
SELECT 6 note, 1600 amt FROM dual union all
SELECT 7 note, 20000 amt FROM dual union all
SELECT 8 note, 20000 amt FROM dual union all
SELECT 9 note, 2100 amt FROM dual union all
SELECT 10 note, 4500 amt FROM dual union all
SELECT 11 note, 1000 amt FROM dual union all
SELECT 12 note, 16000 amt FROM dual
and I need sum the amount, but for every sum <= 15000, they will be grouped together. If the amount is > 15000, they will be on their own group like this:
NOTE
AMT
group
1
1000
1
11
1000
1
5
1500
1
6
1600
1
2
2000
1
9
2100
1
4
3000
1
10
4500
2
3
8000
2
12
16000
3
7
20000
4
8
20000
5
I need the solution in oracle sql, is it possible? I'm using oracle 11g
One method is a recursive subquery:
with tt(note, amt, seqnum) as (
select t.note, t.amt, row_number() over (order by amt) as seqnum
from t
),
cte(note, amt, seqnum, grp, running_amt) as (
select note, amt, seqnum, 1, amt
from tt
where seqnum = 1
union all
select tt.note, tt.amt, tt.seqnum,
(case when tt.amt + cte.running_amt > 15000 then cte.grp + 1 else cte.grp end),
(case when tt.amt + cte.running_amt > 15000 then tt.amt else tt.amt + cte.running_amt end)
from cte join
tt
on tt.seqnum = cte.seqnum + 1
)
select *
from cte;
Here is a db<>fiddle.
Note: This is ordering by amt -- as in your sample data. You can as easily order by note (which also makes sense) just by adjusting seqnum in tt.
You can use the SQL for Pattern Matching:
WITH t AS (
SELECT 1 note, 1000 amt FROM DUAL UNION ALL
SELECT 2 note, 2000 amt FROM DUAL UNION ALL
SELECT 3 note, 8000 amt FROM DUAL UNION ALL
SELECT 4 note, 3000 amt FROM DUAL UNION ALL
SELECT 5 note, 1500 amt FROM DUAL UNION ALL
SELECT 6 note, 1600 amt FROM DUAL UNION ALL
SELECT 7 note, 20000 amt FROM DUAL UNION ALL
SELECT 8 note, 20000 amt FROM DUAL UNION ALL
SELECT 9 note, 2100 amt FROM DUAL UNION ALL
SELECT 10 note, 4500 amt FROM DUAL UNION ALL
SELECT 11 note, 1000 amt FROM DUAL UNION ALL
SELECT 12 note, 16000 amt FROM DUAL)
SELECT note, amt, amt_group, sum_amt
FROM t
MATCH_RECOGNIZE (
ORDER BY amt
MEASURES
MATCH_NUMBER() AS amt_group,
NVL(SUM(amt), amt) AS sum_amt
ALL ROWS PER MATCH
PATTERN (s*)
DEFINE
s AS SUM(amt) <= 15000
) mr
ORDER BY amt
NOTE
AMT
AMT_GROUP
SUM_AMT
1
1000
1
1000
11
1000
1
2000
5
1500
1
3500
6
1600
1
5100
2
2000
1
7100
9
2100
1
9200
4
3000
1
12200
10
4500
2
4500
3
8000
2
12500
12
16000
3
16000
7
20000
4
20000
8
20000
5
20000
RAG PCT
------ ---
GREEN 100
AMBER 50
ORANGE 20
RED 0
I need an oracle query to result like (Suggest to use Inner Join or Outer Join)
if the given PCT >100 then Green
if the given PCT >=50 and PCT < 100 then AMBER
if the given PCT >=20 and PCT < 0 then ORANGE
else RED
You can implement the logic using CASE, like :
SELECT pct, CASE
WHEN pct >= 100 THEN 'GREEN'
WHEN pct >= 50 THEN 'AMBER'
WHEN pct >= 20 then 'ORANGE'
ELSE 'RED'
END
FROM mytable
CASE stops on the first matching condition (hence no need to write WHEN pct >= 50 AND pct < 100 for example, since pct >= 100 is already caught by the previous condition.
If you are using a separate table to store the lower bound of each interval (like
myranges), as shown in your example, and you are looking to JOIN it with a table that cotains actual data (like mydata), then it is a little more tricky : you would need to ensure that you are joining with the relevant range record :
SELECT d.*, r.*
FROM mydata d
INNER JOIN myranges r
ON d.value >= r.pct
AND (
LEAD (r.pct) OVER (ORDER BY pct) IS NULL
OR d.value < LEAD (r.pct) OVER (ORDER BY pct)
)
What you're asking for doesn't entirely make sense - you've said "Use a join" but have provided nothing to join to, but never mind. The following strictly implements your spec:
WITH cteData AS (SELECT 'GREEN' AS RAG, 100 AS PCT FROM DUAL UNION ALL
SELECT 'AMBER', 50 FROM DUAL UNION ALL
SELECT 'ORANGE', 20 FROM DUAL UNION ALL
SELECT 'RED', 0 FROM DUAL)
SELECT RAG, PCT, CASE
WHEN PCT > 100 THEN 'GREEN'
WHEN PCT >= 50 AND PCT < 100 THEN 'AMBER'
WHEN PCT >= 20 AND PCT < 0 THEN 'ORANGE'
ELSE 'RED'
END AS COLOR
FROM cteData;
When executed, the above produces:
RAG PCT COLOR
GREEN 100 RED
AMBER 50 AMBER
ORANGE 20 RED
RED 0 RED
And may Codd have mercy upon your soul.
If I understand you correctly, I think this might be something like what you're after:
WITH rag_data AS (SELECT 'GREEN' AS rag, 100 AS PCT FROM DUAL UNION ALL
SELECT 'AMBER' AS rag, 50 AS PCT FROM DUAL UNION ALL
SELECT 'ORANGE' AS rag, 20 AS PCT FROM DUAL UNION ALL
SELECT 'RED' AS rag, 0 AS PCT FROM DUAL),
sample_data AS (SELECT -1 NUM FROM dual UNION ALL
SELECT 0 NUM FROM dual UNION ALL
SELECT 1 NUM FROM dual UNION ALL
SELECT 19 NUM FROM dual UNION ALL
SELECT 20 NUM FROM dual UNION ALL
SELECT 21 NUM FROM dual UNION ALL
SELECT 49 NUM FROM dual UNION ALL
SELECT 50 NUM FROM dual UNION ALL
SELECT 51 NUM FROM dual UNION ALL
SELECT 99 NUM FROM dual UNION ALL
SELECT 100 NUM FROM dual UNION ALL
SELECT 101 NUM FROM dual)
SELECT NUM,
rag,
pct,
rn
FROM (SELECT sd.num,
rd.rag,
rd.pct,
row_number() OVER (PARTITION BY sd.num ORDER BY rd.pct DESC) rn
FROM sample_data sd
INNER JOIN rag_data rd ON sd.num >= rd.pct)
WHERE rn = 1;
NUM RAG PCT RN
---------- ------ ---------- ----------
0 RED 0 1
1 RED 0 1
19 RED 0 1
20 ORANGE 20 1
21 ORANGE 20 1
49 ORANGE 20 1
50 AMBER 50 1
51 AMBER 50 1
99 AMBER 50 1
100 GREEN 100 1
101 GREEN 100 1
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.