Stored procedure for money transfer in PLSQL - oracle

Hope this task find you well.
Please help me out with this,
My requirement is to transfer money from one account to another account which is in a same table.
the procedure should have 3 in parameters,
They are,
--> from_account_number
--> To_account_number
--> Credit_amount
Actual data:
acc_num acc_bal
12345 50000
67890 40000
Expected data:
eg: exec trans_sp(12345,67890,10000);
ac_num ac_bal
12345 40000
67890 60000
`trans_sp`

A simple option (with no controls at all; add them yourself):
Before:
SQL> select * from money;
ACCOUNT_NU AMOUNT
---------- ----------
12345 10000
65789 20000
86420 30000
Procedure:
SQL> create or replace procedure trans_sp (par_acc_from in varchar2, par_acc_to in varchar2, par_amount in number)
2 is
3 begin
4 update money set
5 amount = amount - par_amount
6 where account_number = par_acc_from;
7
8 update money set
9 amount = amount + par_amount
10 where account_number = par_acc_to;
11 end;
12 /
Procedure created.
Testing:
SQL> exec trans_sp('86420', '12345', 5000);
PL/SQL procedure successfully completed.
After:
SQL> select * from money;
ACCOUNT_NU AMOUNT
---------- ----------
12345 15000
65789 20000
86420 25000
SQL>

Related

HOW TO FILL A TYPE OR COLLECTION WITH NUMBERS FROM 10M TO 30M IN PLSQL ORACLE

Hello stackoverflow friends!
Please help, i have to assign a value since 100 to 300 in an cursor or a type or a table (i don't know which of them are better for this project). First i want to fill the type or cursor with those values (10M to 30M) and then choose randomly one of them but just once, i mean, it can't pick 102 twice, for example. There are millions of entries and i wouldn't want to affect the performance of the database. I tried do it with a cursor and n+1 but its so slow...
Thanks my Oracle's friends for your help and suggestions.
I believe n + 1 is slow; if you do it that way, it certainly takes time to insert 20 million rows one by one. Another option is to use row generator.
I have 21XE on laptop (nothing special; Intel i5, 8GB RAM) which isn't enough memory to do it in one go:
SQL> create table test (id number);
Table created.
SQL> insert into test
2 select 10e6 + level - 1
3 from dual
4 connect by level <= 20e6;
insert into test_20mil
*
ERROR at line 1:
ORA-30009: Not enough memory for CONNECT BY operation
SQL>
If you ask whether it really works?, the answer is yes - see for a small sample:
SQL> select level
2 from dual
3 connect by level <= 5;
LEVEL
----------
1
2
3
4
5
SQL>
Therefore, I used a loop to do it 20 times, each of them inserting 1 million rows at a time into a table (not row-by-row). Why not collection? Memory issues!
SQL> create table test (id number primary key, cb_picked number(1));
Table created.
SQL> set serveroutput on
SQL> set timing on
SQL> declare
2 l_mil number := 10e6;
3 begin
4 for i in 1 .. 20 loop
5 insert into test (id)
6 select l_mil + level - 1
7 from dual
8 connect by level <= 1e6;
9 dbms_output.put_Line('inserted ' || sql%rowcount);
10 l_mil := l_mil + 1e6;
11 end loop;
12 end;
13 /
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
PL/SQL procedure successfully completed.
Elapsed: 00:01:25.77
As you can see, it took slightly less than minute and a half.
SQL> set timing off
SQL> select count(*) from test;
COUNT(*)
----------------
20,000,000
SQL> select min(id), max(id) from test;
MIN(ID) MAX(ID)
---------------- ----------------
10,000,000 29,999,999
SQL>
That was inserting; what about fetching randomly picked rows? Use dbms_random.value function. To avoid selecting already picked values twice, update table's cb_picked column. To do that, create an autonomous transaction function (why? So that you could perform DML - update - and return value).
SQL> create or replace function f_rnd
2 return number
3 is
4 pragma autonomous_transaction;
5 retval number;
6 begin
7 select id
8 into retval
9 from test
10 where cb_picked is null
11 and id = round(dbms_random.value(10e6, 20e6));
12 update test set cb_picked = 1
13 where id = retval;
14 commit;
15 return retval;
16 end;
17 /
Function created.
Let's try it:
SQL> select f_rnd from dual;
F_RND
----------
19191411
SQL> select f_rnd from dual;
F_RND
----------
16411522
SQL> select * from test where cb_picked = 1;
ID CB_PICKED
---------- ----------
16411522 1
19191411 1
SQL>
That's it, I guess.

If-Else in PL/SQL

I want to write a procedure to perform withdraw operation that only permits a withdrawal, if there is sufficient funds in the account then update the Account table and print the message, 'Transaction successful.' else print, 'Insufficient Amount.' . The procedure should take Account_id and withdrawal amount as input.
Account:
ACCNO NUMBER PK
CUSTOMER_NAME VARCHAR2(30)
BALANCE NUMBER(15,2)
12345 Williams 23455.6
23456 Robert 43221
34521 John 23449
Functional Requirement:
procedure withdraw(ano number , amt number)
Sample input:
withdraw(12345, 2000);
Sample output:
Transaction successful.
I tried to write this code which is as follows-
set serveroutput on;
create or replace procedure withdraw(ano number, amt number) is withdraw_operation account%rowtype;
begin
select * into withdraw_operation from account
if (amt > balance)
then dbms_output.put_line('Transaction successful');
else dbms_output.put_line('Insufficient Amount');
end if;
end;
But this is not showing any output nor error, please help. Thanks in advance!
this is not showing any output nor error
This is hard to believe because what you posted isn't correct.
Anyway: from what you posted, should be something like this:
Sample data:
SQL> SET SERVEROUTPUT ON;
SQL>
SQL> SELECT * FROM account;
ACCNO CUSTOMER BALANCE
---------- -------- ----------
12345 Williams 23455,6
23456 Robert 43221
34521 John 23449
Procedure:
SQL> CREATE OR REPLACE PROCEDURE withdraw (ano NUMBER, amt NUMBER)
2 IS
3 withdraw_operation account%ROWTYPE;
4 BEGIN
5 SELECT *
6 INTO withdraw_operation
7 FROM account
8 WHERE accno = ano;
9
10 IF amt > withdraw_operation.balance
11 THEN
12 DBMS_OUTPUT.put_line ('Transaction successful');
13 ELSE
14 DBMS_OUTPUT.put_line ('Insufficient Amount');
15 END IF;
16 END;
17 /
Procedure created.
Testing:
SQL> EXEC withdraw(12345, 2000);
Insufficient Amount
PL/SQL procedure successfully completed.
SQL>

inconsistent output between sql developer and stored procedure

When I execute the below query it will return C507,
but when I use it as sub query in one of my stored procedure it only returns C.
SELECT *
FROM ( SELECT MAX (REGEXP_SUBSTR ('C507|C507',
'[^|]+',
1,
LEVEL))
serial
FROM DUAL
CONNECT BY LEVEL <=
LENGTH ('C507|C507')
- LENGTH (REPLACE ('C507|C507', '|', ''))
+ 1);
I couldn't see any problem( tell us if your way is different ) :
SQL> Create or Replace Procedure Get_Serial( o_value out varchar2 ) Is
2 Begin
3 Select *
4 Into o_value
5 From (Select Max(Regexp_Substr('C507|C507', '[^|]+', 1, Level)) serial
6 From Dual
7 Connect By Level <= Length('C507|C507') -
8 Length(Replace('C507|C507', '|', '')) + 1);
9 End;
10 /
Procedure created
SQL> var serial varchar2;
SQL> exec Get_Serial(:serial);
PL/SQL procedure successfully completed
serial
---------
C507
It is a little bit difficult to guess what you really are doing (as you posted different queries with different input data, unformatted within the comment) so - have a look a this example.
I've modified your last query so that it returns DEP_ID and MAX piece of the NAME column.
SQL> CREATE TABLE dept
2 (
3 dep_id NUMBER,
4 name VARCHAR2 (50)
5 );
Table created.
SQL> INSERT INTO dept VALUES (1, 'mirko|mirko');
1 row created.
SQL> INSERT INTO dept VALUES (2, 'angelo|angelo');
1 row created.
SQL> INSERT INTO dept VALUES (3, 'angelo|mirko');
1 row created.
SQL> SELECT * FROM dept;
DEP_ID NAME
---------- --------------------------------------------------
1 mirko|mirko
2 angelo|angelo
3 angelo|mirko
SQL>
SQL> SELECT d.dep_id,
2 MAX (REGEXP_SUBSTR (d.name,
3 '[^|]+',
4 1,
5 LEVEL))
6 serial
7 FROM dept d
8 WHERE dep_id = &par_dep_id
9 CONNECT BY LEVEL <= LENGTH (d.name) - LENGTH (REPLACE (d.name, '|', '')) + 1
10 GROUP BY d.dep_id;
Enter value for par_dep_id: 1
DEP_ID SERIAL
---------- --------------------------------------------------
1 mirko
SQL> /
Enter value for par_dep_id: 2
DEP_ID SERIAL
---------- --------------------------------------------------
2 angelo
SQL> /
Enter value for par_dep_id: 3
DEP_ID SERIAL
---------- --------------------------------------------------
3 mirko
SQL>
Saying that you have problems with a stored procedure: you should post (by editing your initial message) its code. Is it really a procedure, or a function? Query returns 2 values - does that procedure have 2 OUT parameters? If it is a function, what does it return (what datatype)?
From my point of view, query is OK as is, but - you wouldn't be posting a question here if there's no problem, but it is unclear which problem you have.
Once again: I suggest you post copy/paste of your own SQL*Plus session so that we could see what you did and how Oracle responded. Without it, we're just blindly guessing.
I created a function for this query and call it on my SP instead of using it as a sub query on my stored procedure.

Enforcing business rules using a Procedure in Oracle

How do you write a procedure which shows that one field's value cannot be higher than another field's value, in terms of numbers. Say. an employee'a salary can't be higher than his manager's salary. I've never done one before
There is no declarative way to enforce business rules like this in SQL. So it has to be done with code. There are a number of gotchas, not the least of which is identifying all the scenarios where the rule needs to be enforced..
Here are the scenarios:
When we insert an employee we need to check whether their salary is greater than 90% of their manager's salary.
When we update an employee's salary we need to check that it still isn't greater than 90% of their manager's salary.
When we update a manager's salary we need to check that it is still greater than 110% of all their subordinates' salaries.
If we insert records simultaneously for a manager and their subordinates (say using INSERT ALL) we need to make sure that rule is still enforced.
If we move an employee from one manager to another we need to make sure that rule is still enforced.
Here are the things which make all this harder:
Enforcing these rules involves selecting from the table we are manipulating so we cannot use BEFORE ... FOR EACH ROW triggers, due to the ORA-04088: mutating tables exceptions.
Also, selecting from the table means we cannot run in multi-user mode, because of read consistency (otherwise session #1 could go ahead with a pay increase to an employee oblivious to the fact that session #2 is currently applying a pay decrease to that employee's manager).
So, for all those reasons, the only way to enforce such business rules is to use an API; build a stored procedure and never let any process have naked DML access to the table.
The following chunk o' code enforces the rule just when updating an employee's salary. Points of interest include:
it has user-defined exceptions to identify rule violations. Really these should be defined in a package specification, so other program units can reference them.
the use of SELECT ... FOR UPDATE to lock the rows of interest.
the use of COMMIT and ROLLBACK to release the locks. In a real implementation this might be handled differently (i.e. by the calling program).
create or replace procedure change_emp_sal
( p_eno in emp.empno%type
, p_new_sal in emp.sal%type )
is
type emp_nt is table of emp%rowtype;
l_emp emp%rowtype;
l_mgr emp%rowtype;
l_subords emp_nt;
l_idx pls_integer;
x_mgr_not_paid_enough exception;
pragma exception_init(x_mgr_not_paid_enough, -20000);
x_sub_paid_too_much exception;
pragma exception_init(x_sub_paid_too_much, -20001);
begin
-- lock the employee record
select * into l_emp
from emp
where empno = p_eno
for update of sal;
-- lock their manager's record (if they have one)
if l_emp.mgr is not null
then
select * into l_mgr
from emp
where empno = l_emp.mgr
for update;
end if;
-- lock their subordinates' records
select * bulk collect into l_subords
from emp
where mgr = p_eno
for update;
-- compare against manager's salary
if l_mgr.sal is not null
and l_mgr.sal < ( p_new_sal * 1.1 )
then
raise x_mgr_not_paid_enough;
end if;
-- compare against subordinates' salaries
for i in 1..l_subords.count()
loop
if l_subords(i).sal > ( p_new_sal * 0.9 )
then
l_idx := i;
raise x_sub_paid_too_much;
end if;
end loop;
-- no exceptions raised so we can go ahead
update emp
set sal = p_new_sal
where empno = p_eno;
-- commit to free the locks
commit;
exception
when x_mgr_not_paid_enough then
dbms_output.put_line ('Error! manager only earns '||l_mgr.sal);
rollback;
raise;
when x_sub_paid_too_much then
dbms_output.put_line ('Error! subordinate earns '||l_subords(l_idx).sal);
rollback;
raise;
end change_emp_sal;
/
Here are the four employees of Deptarment 50:
SQL> select e.empno, e.ename, e.sal, m.ename as mgr_name, m.empno as mgr_no
2 from emp e join emp m on (e.mgr = m.empno)
3 where e.deptno = 50
4 order by sal asc
5 /
EMPNO ENAME SAL MGR_NAME MGR_NO
---------- ---------- ---------- ---------- ----------
8060 VERREYNNE 2850 FEUERSTEIN 8061
8085 TRICHLER 3500 FEUERSTEIN 8061
8100 PODER 3750 FEUERSTEIN 8061
8061 FEUERSTEIN 4750 SCHNEIDER 7839
SQL>
Let's try to give Billy a big raise, which should fail...
SQL> exec change_emp_sal (8060, 4500)
Error! manager only earns 4750
BEGIN change_emp_sal (8060, 4500); END;
*
ERROR at line 1:
ORA-20000:
ORA-06512: at "APC.CHANGE_EMP_SAL", line 67
ORA-06512: at line 1
SQL>
Okay, let's give Billy a smaller raise, which should succeed...
SQL> exec change_emp_sal (8060, 4000)
PL/SQL procedure successfully completed.
SQL>
Now let's try to give Steven a swingeing pay cut, which should fail...
SQL> exec change_emp_sal (8061, 3500)
Error! subordinate earns 3500
BEGIN change_emp_sal (8061, 3500); END;
*
ERROR at line 1:
ORA-20001:
ORA-06512: at "APC.CHANGE_EMP_SAL", line 71
ORA-06512: at line 1
SQL>
So let's give Steven a token pay cut, which should succeed ...
SQL> exec change_emp_sal (8061, 4500)
PL/SQL procedure successfully completed.
SQL>
Here is the new pay structure...
SQL> select e.empno, e.ename, e.sal, m.ename as mgr_name, m.empno as mgr_no
2 from emp e join emp m on (e.mgr = m.empno)
3 where e.deptno = 50
4 order by sal asc
5 /
EMPNO ENAME SAL MGR_NAME MGR_NO
---------- ---------- ---------- ---------- ----------
8085 TRICHLER 3500 FEUERSTEIN 8061
8100 PODER 3750 FEUERSTEIN 8061
8060 VERREYNNE 4000 FEUERSTEIN 8061
8061 FEUERSTEIN 4500 SCHNEIDER 7839
SQL>
So it works, as far as it goes. It only handles two of the five scenarios. Refactoring the code to satisfy the other three is left as an exercise for the reader.

Trying to make a job to be executed after each 1 minute,but its not working?

I have made this job,that should be executed in an interval of 1 minute,but it's not working. When I use execute dbms_job.run(2); it gets executed. printe is a procedure
Please suggest!
BEGIN
DBMS_JOB.SUBMIT (
job =>:job_no,
WHAT=>'printe;',--Procedure
next_date=>sysdate+1/24*60,
interval=>'sysdate+1/24*60'
);
commit;
END;
Try next_date = sysdate + (1/24/60) and interval = (1/24/60)...
Here is a simple job.
SQL> create table log1 (ts timestamp)
2 /
Table created.
SQL> create or replace procedure printe as
2 begin
3 insert into log1 values (systimestamp);
4 commit;
5 end;
6 /
Procedure created.
SQL>
So the first thing is to submit it with both the start time and interval correctly specified. If you cannot remember how many minutes there are in a day (1440) it is a good idea to use brackets. Let's compare submitting the job with your date specifications ...
SQL> var job_no number
SQL> BEGIN
2 DBMS_JOB.SUBMIT
3 (
4 job =>:job_no,
5 WHAT=>'printe;',--Procedure
6 next_date=>sysdate+1/24*60,
7 interval=>'sysdate+1/24*60'
8 );
9 commit;
10 END;
11 /
PL/SQL procedure successfully completed.
SQL> print job_no
JOB_NO
----------
71
SQL>
... with brackets to assert precedence ...
SQL> BEGIN
2 DBMS_JOB.SUBMIT
3 (
4 job =>:job_no,
5 WHAT=>'printe;',--Procedure
6 next_date=>sysdate+1/(24*60),
7 interval=>'sysdate+1/(24*60)'
8 );
9 commit;
10 END;
11 /
PL/SQL procedure successfully completed.
SQL> print job_no
JOB_NO
----------
72
SQL>
Clearly job 71 has not run and isn't going to run for some time yet:
SQL> select job, what, last_date, next_date, interval
2 from user_jobs
3 where job in (71,72)
4 /
JOB WHAT LAST_DATE NEXT_DATE INTERVAL
------ ------------ -------------------- -------------------- -----------------
71 printe; 05-MAY-2010 17:35:34 sysdate+1/24*60
72 printe; 03-MAY-2010 05:44:42 03-MAY-2010 05:45:34 sysdate+1/(24*60)
SQL>
Monitoring job 72 ....
SQL> select * from log1
2 /
TS
-------------------------------------------------------------------
03-MAY-10 05:43:39.250000
03-MAY-10 05:44:42.296000
SQL>
So, if this still isn't working for you, what should you be doing? The first thing is to check whether the database is configured to run jobs at all. You will need DBA access for this.
SQL> select value
2 from v$parameter
3 where name='job_queue_processes'
4 /
VALUE
-------------------------
1000
SQL>
If I remember correctly, in Oracle 9i the default value for this parameter is 0. It needs to be set to some non-zero value for jobs to run.
And if that isn't the problem you need to check for error messages in the alert log. The background_dump_dest directory may also have some .trc files produced by a failing job.

Resources