oracle procedure that update values based on amount - oracle

I have the following table (SEMINARS):
ID AMOUNT
6936120606 320.51
6978332460 261.05
6940456367 0
6973328053 438.68
6951690568 533.56
sum(1690.47)
I have the value 980 that I need to credit one by one the seminars amount
start from biggest amount to lowest.
for example the 980 value must will credit as following:
6951690568 533,56 (-533.56)=0 446.44 (980.00-533.56=446.44)
6973328053 438,68 (-438.68)=0 7.76 (446.44-438.68= 7.76)
6936120606 320,51 (- 7.76)=312.75 0.00 (0)
any idea?

In Oracle 11g you can use recursive query, this gave me desired output:
with
s as (select row_number() over (order by amount desc) rn, id, amount from seminars),
t(rn, id, amount, left1, left2) as (
select rn, id, amount, greatest(amount-980,0), 980-amount from s where rn = 1
union all
select s.rn, s.id, s.amount,
s.amount-least(left2, s.amount), greatest(left2-s.amount, 0)
from s join t on s.rn = t.rn+1 and left2>0)
select * from t
Output and SQLFiddle:
RN ID AMOUNT LEFT1 LEFT2
---------- --------------- ---------- ---------- ----------
1 6951690568 533.56 0 446.44
2 6973328053 438.68 0 7.76
3 6936120606 320.51 312.75 0

Related

I want to delete duplication with condition from a table in PLSQL

I want to delete the dup lines using PLSQL. The sample of the table is below
Policy #
Price
Dealno for Loan #
Price of Loan
PersonID
123
10
Loan123
1,000
abc
123
10
Loan123
3,000
abc
456
10
Loan456
500
xyz
456
10
Loan456
500
null
As you can see, in the case of Policy #123, I try to get the line with the highest amount of Price of Loan. Which mean the Price of Loan for 3,000.
For Policy #456, I want to get the one without null value.
Is there a way for me to achieve that in PLSQL.
Thank you
This query identifies if a row is OK (rn = 1) or if is is a duplicated copy (rn > 1) based on your definition
select POLICY#, PRICE, LOAN#, PRICE_LOAN, PERSON_ID,
row_number() over (partition by POLICY# order by PRICE_LOAN desc, PERSON_ID nulls last) as rn
from tab
;
POLICY# PRICE LOAN# PRICE_LOAN PER RN
---------- ---------- -------- ---------- --- ----------
123 10 loan123 3000 abc 1
123 10 loan123 1000 abc 2
456 10 loan4563 500 xyz 1
456 10 loan4563 500 2
Note that you use row_number where you partition by on the unique key and order by so that you get first the row that should be taken.
So to get the duplicates only you use this query
with rn as (
select POLICY#, PRICE, LOAN#, PRICE_LOAN, PERSON_ID,
row_number() over (partition by POLICY# order by PRICE_LOAN desc, PERSON_ID nulls last) as rn
from tab
)
select * from rn where rn > 1;
POLICY# PRICE LOAN# PRICE_LOAN PER RN
---------- ---------- -------- ---------- --- ----------
123 10 loan123 1000 abc 2
456 10 loan4563 500 2
Based on this you write the DELETE statement (enclose in BEGIN ... END if you insist in PL/SQL)
delete from tab where rowid in
(
with rn as (
select POLICY#, PRICE, LOAN#, PRICE_LOAN, PERSON_ID,
row_number() over (partition by POLICY# order by PRICE_LOAN desc, PERSON_ID nulls last) as rn
from tab
)
select rowid from rn where rn > 1
);
You may check if the delete worked fine ....
select * from tab;
POLICY# PRICE LOAN# PRICE_LOAN PER
---------- ---------- -------- ---------- ---
123 10 loan123 3000 abc
456 10 loan4563 500 xyz
... and commit

Sum 2 columns from multiple rows with same ID

Currently, I have a table MY_TABLE like below:
ID ACCT_TYPE CREDIT_AMT DEBIT_AMT
-- --------- ---------- ---------
1 CDT_01 4 (null)
1 DBT_01 (null) 6
One ID can have multiple ACCT_TYPE like above, and each type has its own amount.
I want to just select the row which has ACCT_TYPE like 'CDT_%' but also the total_amount column which is the total of credit_amt and debit_amt column for the same ID.
My expected output like below:
ID ACCT_TYPE TOTAL_AMT
-- --------- ---------
1 CDT_01 10
I tried with this select statement below but it's no use, I think it's because of different ACCT_TYPE:
Select ID, ACCT_TYPE, SUM(NVL(CREDIT_AMT, 0) + NVL(DEBIT_AMT, 0)) TOTAL_AMT
FROM MY_TABLE WHERE ACCT_TYPE LIKE 'CDT_%' GROUP BY ID, ACCT_TYPE;
Here is the output of the select statement above:
ID ACCT_TYPE TOTAL_AMT
-- --------- ---------
1 CDT_01 4
I just begin to learn some query so I don't know is it really possible to get my expected output.
One way to do it is like below:
with inputs (ID, ACCT_TYPE, CREDIT_AMT, DEBIT_AMT) as
(
select 1, 'CDT_01', 4, null from dual union all
select 1, 'DBT_01', null, 6 from dual
),
prep as
(
select t.*, sum(nvl(credit_amt,0)) over (partition by id) + sum(nvl(debit_amt,0)) over (partition by id) as sum_per_id
from inputs t
)
select id, acct_type, sum_per_id
from prep
where acct_type like 'CDT_%';
Output:
A correlated subquery might be one option; sample data (thank you, #Ranagal) in lines #1 - 5; query that does the job begins at line #6.
SQL> with inputs (ID, ACCT_TYPE, CREDIT_AMT, DEBIT_AMT) as
2 (
3 select 1, 'CDT_01', 4, null from dual union all
4 select 1, 'DBT_01', null, 6 from dual
5 )
6 select a.id,
7 a.acct_type,
8 (select sum(nvl(b.credit_amt, 0)) +
9 sum(nvl(b.debit_amt , 0))
10 from inputs b
11 where b.id = a.id
12 ) total_amt
13 from inputs a
14 where acct_type like 'CDT%';
ID ACCT_T TOTAL_AMT
---------- ------ ----------
1 CDT_01 10
SQL>

Find Maximal Value of other Rows per Group

I have a simple table with values (ID) in groups (GRP_ID).
create table tst as
select 1 grp_id, 1 id from dual union all
select 1 grp_id, 1 id from dual union all
select 1 grp_id, 2 id from dual union all
select 2 grp_id, 1 id from dual union all
select 2 grp_id, 2 id from dual union all
select 2 grp_id, 2 id from dual union all
select 3 grp_id, 3 id from dual;
It is straightforward to find a maximum value per group using analytical functions.
select grp_id, id,
max(id) over (partition by grp_id) max_grp
from tst
order by 1,2;
GRP_ID ID MAX_GRP
---------- ---------- ----------
1 1 2
1 1 2
1 2 2
2 1 2
2 2 2
2 2 2
3 3 3
But the goal is to find the maximum value excluding the value of the current row.
This is the expected result (column MAX_OTHER_ID):
GRP_ID ID MAX_GRP MAX_OTHER_ID
---------- ---------- ---------- ------------
1 1 2 2
1 1 2 2
1 2 2 1
2 1 2 2
2 2 2 2
2 2 2 2
3 3 3
Note that in the GRP_ID = 2 a tie on the MAX value exists, so the MAX_OTHER_ID remains the same.
I did manage this two step solution, but I'm wondering if there is a more straightforward and simple solution.
with max1 as (
select grp_id, id,
row_number() over (partition by grp_id order by id desc) rn
from tst
)
select GRP_ID, ID,
case when rn = 1 /* MAX row per group */ then
max(decode(rn,1,to_number(null),id)) over (partition by grp_id)
else
max(id) over (partition by grp_id)
end as max_other_id
from max1
order by 1,2
;
I wish the window functions supported multiple range specifications something like:
max(id) over (
partition by grp_id
order by id
range between unbounded preceding and 1 preceding
or range between 1 following and unbounded following
)
But unfortunately they don't.
As a workaround, you can avoid subqueries and CTEs using the function twice on the different ranges and call coalesce on that.
select grp_id,
id,
coalesce(
max(id) over (
partition by grp_id
order by id
range between 1 following and unbounded following
)
, max(id) over (
partition by grp_id
order by id
range between unbounded preceding and 1 preceding
)
) max_grp
from tst
order by 1,
2
Coalesce works out of the box because of the ordering clause as the result of the window function call will be either the max in the given window or a null value.
Demo - http://rextester.com/SDXVF13962
SELECT GRP_ID,ID, (SELECT Max(ID) FROM TEST A WHERE A.ROWID<>B.ROWID AND A.GRP_ID=B.GRP_ID) maX_ID FROM TEST B;
Got the expected result with Co-Related Query ! Hope this helps .

Oracle sql retrive records based on maximum time

i have below data.
table A
id
1
2
3
table B
id name data1 data2 datetime
1 cash 12345.00 12/12/2012 11:10:12
1 quantity 222.12 14/12/2012 11:10:12
1 date 20/12/2012 12/12/2012 11:10:12
1 date 19/12/2012 13/12/2012 11:10:12
1 date 13/12/2012 14/12/2012 11:10:12
1 quantity 330.10 17/12/2012 11:10:12
I want to retrieve data in one row like below:
tableA.id tableB.cash tableB.date tableB.quantity
1 12345.00 13/12/2012 330.10
I want to retrieve based on max(datetime).
The data model appears to be insane-- it makes no sense to join an ORDER_ID to a CUSTOMER_ID. It makes no sense to store dates in a VARCHAR2 column. It makes no sense to have no relationship between a CUSTOMER and an ORDER. It makes no sense to have two rows in the ORDER table with the same ORDER_ID. ORDER is also a reserved word so you cannot use that as a table name. My best guess is that you want something like
select *
from customer c
join (select order_id,
rank() over (partition by order_id
order by to_date( order_time, 'YYYYMMDD HH24:MI:SS' ) desc ) rnk
from order) o on (c.customer_id=o.order_id)
where o.rnk = 1
If that is not what you want, please (as I asked a few times in the comments) post the expected output.
These are the results I get with my query and your sample data (fixing the name of the ORDER table so that it is actually valid)
SQL> ed
Wrote file afiedt.buf
1 with orders as (
2 select 1 order_id, 'iphone' order_name, '20121201 12:20:23' order_time from dual union all
3 select 1, 'iphone', '20121201 12:22:23' from dual union all
4 select 2, 'nokia', '20110101 13:20:20' from dual ),
5 customer as (
6 select 1 customer_id, 'paul' customer_name from dual union all
7 select 2, 'stuart' from dual union all
8 select 3, 'mike' from dual
9 )
10 select *
11 from customer c
12 join (select order_id,
13 rank() over (partition by order_id
14 order by to_date( order_time, 'YYYYMMDD HH24:MI:SS' ) desc ) rnk
15 from orders) o on (c.customer_id=o.order_id)
16* where o.rnk = 1
SQL> /
CUSTOMER_ID CUSTOM ORDER_ID RNK
----------- ------ ---------- ----------
1 paul 1 1
2 stuart 2 1
Try something like
SELECT *
FROM CUSTOMER c
INNER JOIN ORDER o
ON (o.CUSTOMER_ID = c.CUSTOMER_ID)
WHERE TO_DATE(o.ORDER_TIME, 'YYYYMMDD HH24:MI:SS') =
(SELECT MAX(TO_DATE(o.ORDER_TIME, 'YYYYMMDD HH24:MI:SS')) FROM ORDER)
Share and enjoy.

Issue in SQL query full scanning twice?

Table A
ID EmpNo Grade
--------------------
1 100 HIGH
2 105 LOW
3 100 MEDIUM
4 100 LOW
5 105 LOW
Query:
select *
from A
where EMPNO = 100
and rownum <= 2
order by ID desc
I tried this query to retrieve max and max-1 value; I need to compare the grade from max and max-1, if equals I need to set the flag as 'Y' or 'N' without using a cursor. Also I don't want to scan the entire record twice.
Please help me.
ROWNUM is applied before ORDER BY, so you need to nest the query like this:
select * from
(select * from A where EMPNO =100 order by ID desc)
where rownum<=2
That only performs one table scan (or it may use an index on EMPNO).
select *
from (
select id, emp_no, grade
, case
when lag(grade) over (order by emp_no desc) = grade
then 'Y'
else 'N'
end
as flag
, dense_rank() over( order by emp_no desc) as rank
from t
)
where rank <=2
;

Resources