How to make a PL/SQL perform faster - oracle

I've got this PL/SQL procedure which runs for about 4-6 minutes:
DECLARE
i NUMBER := 0;
begin
for x in (select anumber
, position
, character
from sdc_positions_cip
where kind = 'Name')
loop
update sdc_compare_person dcip
set dcip.GESNAM_D = substr(dcip.GESNAM_D, 1, x.position - 1) || x.character ||
substr(dcip.GESNAM_D, x.position + 1, length(dcip.GESNAM_D) - x.position)
where dcip.sourcekey = x.anumber;
i := i + 1;
IF i > 100 THEN COMMIT;
i := 0;
END IF;
end loop;
commit;
end;
/
I'v placed an index on dcip.sourcekey and x.anumber.
The tablespace that it's using is 10GB.
Is there a way to make this procedure (much) faster?

Your performance bottleneck is the loop. It forces your code to switch between PLSQL and Oracle SQL for every single UPDATE-Statement.
In order to eliminate these context switches, you could probably use an UPDATE-Statement containing a subselect, but I more like MERGE, for example like in the following way:
merge into sdc_compare_person dcip
using (
select anumber, position, character
from sdc_positions_cip
where kind = 'Name'
) x
on (dcip.sourcekey = x.anumber)
when matched then update set
dcip.GESNAM_D = substr(dcip.GESNAM_D, 1, x.position - 1) ||
x.character ||
substr(dcip.GESNAM_D, x.position + 1, length(dcip.GESNAM_D) - x.position);
Another option would be to use BULK COLLECT INTO and FORALL to perform bulk selects and bulk inserts. Due to the limited complexity of your procedure, I strongly recommend using a single statement like mine.

You can also try this version:
update
(select dcip.GESNAM_D, x.position, x.character, dcip.sourcekey, anumber
from sdc_compare_person dcip
join sdc_positions_cip on dcip.sourcekey = x.anumber)
set GESNAM_D = substr(GESNAM_D, 1, position - 1) || character || substr(GESNAM_D, position + 1, length(GESNAM_D) - position);

Related

Oracle: Update Every Row in a Table based off an Array

So i'm trying to create some seed data for a database that uses zip codes. I've created an array of 22 arbitrary zip code strings, and i'm trying to loop through the array and update one of the zips to every row in a table. Based on what I read and tried (I'm a 1st year, so I'm probably missing something), this should work, and does when I just output the array value based on the count of the table. this issue is in the row id subquery. When I run it in my console, it doesn't throw any errors, but it never completes and I think it's stuck in an infinite loop. How can I adjust this so that it will update the field and not get stuck?
declare
t_count NUMBER;
TYPE zips IS VARRAY(22) OF CHAR(5);
set_of_zips zips;
i NUMBER;
j NUMBER :=1;
BEGIN
SELECT count(*) INTO t_count FROM T_DATA;
set_of_zips:= zips('72550', '71601', '85920', '85135', '95451', '90021', '99611', '99928', '35213', '60475', '80451', '80023', '59330', '62226', '27127', '28006', '66515', '27620', '66527', '15438', '32601', '00000');
FOR i IN 1 .. t_count LOOP
UPDATE T_DATA
SET T_ZIP=set_of_zips(j)
---
WHERE rowid IN (
SELECT ri FROM (
SELECT rowid AS ri
FROM T_DATA
ORDER BY T_ZIP
)
) = i;
---
j := j + 1;
IF j > 22
THEN
j := 1;
END IF;
END LOOP;
COMMIT;
end;
You don't need PL/SQL for this.
UPDATE t_data
SET t_zip = DECODE(MOD(ROWNUM,22)+1,
1,'72550',
2,'71601',
3,'85920',
4,'85135',
5,'95451',
6,'90021',
7,'99611',
8,'99928',
9,'35213',
10,'60475',
11,'80451',
12,'80023',
13,'59330',
14,'62226',
15,'27127',
16,'28006',
17,'66515',
18,'27620',
19,'66527',
20,'15438',
21,'32601',
22,'00000')

In below program only for first two rows it is working and for other rows it is showing incorrect values in plsql

Here for the below program i need to print the amt_running_bal from the previous value. but it is not working and showing error. what is the error in the below program.Please provide any solution for this.
DECLARE
total Number := 1000000;
c_cod_acct_no Char;
c_amt_txn Number;
c_cod_drcr Char;
c_amt_running_bal Number;
amt_running_bal Number;
CURSOR c_chnos1 is
SELECT cod_drcr, amt_txn,amt_running_bal FROM chnos1;
BEGIN
OPEN c_chnos1;
FOR k IN 1..2 LOOP
FETCH c_chnos1 into c_cod_drcr,c_amt_txn,c_amt_running_bal;
if c_cod_drcr = 'C' then
total := total + c_amt_txn;
Update chnos1 SET amt_running_bal = total where cod_drcr='C' ;
elsif
c_cod_drcr = 'D' then
total := total - c_amt_txn;
Update chnos1 SET amt_running_bal = total where cod_drcr='D';
else
total := total + c_amt_txn;
Update chnos1 SET amt_running_bal = total where cod_drcr='C';
end if;
END LOOP;
CLOSE c_chnos1;
END;
/
Your query does not work as you limit the loop to k IN 1..2 so it will only read two rows from the cursor and there is no correlation between the row you are reading from the cursor and what you are updating; in fact, you are updating all the rows WHERE cod_drcr = 'C' or WHERE cod_drcr = 'D' and not just the current row. You could fix it by correlating the updates to the current row using the ROWID pseudo-column but it is an inefficient solution to use cursors as it will be slow and generate redo/undo log entries for each iteration of the cursor loop.
Instead, do it all in a single MERGE statement using an analytic SUM and a CASE expression:
MERGE INTO chnos1 dst
USING (
SELECT rowid AS rid,
1000000
+ SUM(
CASE cod_drcr
WHEN 'C' THEN +amt_txn
WHEN 'D' THEN -amt_txn
ELSE 0
END
)
OVER (
-- Use something like this to update each account
-- PARTITION BY cod_acct_no ORDER BY payment_date
-- However, you haven't said how to partition or order the rows so use this
ORDER BY ROWNUM
) AS total
FROM chnos1
) src
ON (dst.ROWID = src.rid)
WHEN MATCHED THEN
UPDATE SET amt_running_bal = src.total;
fiddle

PLSQL script optimisation / tuning

I have this plsql script. I was able to test this on a test table with around 300 rows and it is working perfectly fine. However when I tried to run this using the actual table which is around 1M rows, it doesn't complete. I would like seek for your suggestion on how can I optimise my script, I am new to plsql so any ideas/suggestions are a great help. :)
DECLARE
c_BGROUP PP_TRANCHE_RBS.BGROUP%TYPE := 'RBS';
l_start NUMBER;
/* Check for all entries where pt03d = pt04d+1. */
CURSOR c_pp_tranche IS
SELECT
refno,
pt04d,
seqno
FROM PP_TRANCHE_RBS a
WHERE a.BGROUP = c_BGROUP
AND a.pt03d = (SELECT (pt04d + 1)
FROM PP_TRANCHE_RBS
WHERE bgroup = a.bgroup
AND refno = a.refno
and seqno = a.seqno)
;
TYPE c_refno IS TABLE OF PP_TRANCHE_RBS.REFNO%TYPE;
TYPE c_pt04d IS TABLE OF PP_TRANCHE_RBS.PT04D%TYPE;
TYPE c_seqno IS TABLE OF PP_TRANCHE_RBS.SEQNO%TYPE;
t_refno c_refno;
t_pt04d c_pt04d;
t_seqno c_seqno;
BEGIN
DBMS_OUTPUT.put_line('Updating rows... ');
l_start := DBMS_UTILITY.get_time;
OPEN c_pp_tranche;
LOOP
FETCH c_pp_tranche BULK COLLECT INTO t_refno, t_pt04d, t_seqno LIMIT 10000; -- break the data into chucks of 10000 rows
EXIT WHEN t_refno.COUNT() = 0; -- cursor attribute to exit when 0.
FORALL i IN t_refno.FIRST .. t_refno.LAST
/* Update pt03d = pt04d */
UPDATE PP_TRANCHE_RBS
SET pt03d = t_pt04d(i)
WHERE
bgroup = c_BGROUP
AND refno = t_refno(i)
AND seqno = t_seqno(i)
;
-- Process contents of collection here.
DBMS_OUTPUT.put_line(t_refno.count || ' rows was updated');
END LOOP;
DBMS_OUTPUT.put_line('Bulk Updates Time: ' || (DBMS_UTILITY.get_time - l_start));
CLOSE c_pp_tranche;
END;
/
exit;
Equivalent pure SQL statement:
UPDATE PP_TRANCHE_RBS
SET pt03d = pt04d
WHERE bgroup = 'RBS'
and pt03d = pt04d + 1;
This will probably run faster than your procedural version. PL/SQL bulk processing is faster than row-by-row but it's usually slower than a single set-based operation. So save it for those times when you have complicated transformation logic which can only be handled procedurally.

FOR r in (SELECT ... INTO ...)

Today, I came across a funny piece of code that I think should not compile. It uses an SELECT ... INTO clause within a FOR r IN ... LOOP. Here's a script that compiles on Oracle 11i. The script is a shortened version of actual PL/SQL code compiled in a package, runing in production.
create table tq84_foo (
i number,
t varchar2(10)
);
insert into tq84_foo values (1, 'abc');
insert into tq84_foo values (2, 'def');
declare
rec tq84_foo%rowtype;
begin
for r in (
select i, t
into rec.i, rec.t -- Hmm???
from tq84_foo
)
loop
dbms_output.put_line('rec: i= ' || rec.i || ', t=' || rec.t);
end loop;
end;
/
drop table tq84_foo purge;
The output, when run, is:
rec: i= , t=
rec: i= , t=
I believe 1) I can safely remove the INTO part of the select statement and 2) that this construct should either be invalid or exhibits at least undefined behaviour.
Are my two assumptions right?
Your assumptions are partly right:
1) Yes, you can safely remove the INTO part of the SELECT statement. But you should change the line in the loop to this format:
dbms_output.put_line('rec: i= ' || r.i || ', t=' || r.t);
That way it will get the data out of the r variable
2) The problem with this code is that the syntax of the SELECT ... INTO should fail if the query return more than one row. If it does not fail so it might be a bug and will have unexpected behaviour.

wrong updation of data using collection but insertion is possible

create or replace function get_ware_house_branch(p_WAREHOUSE_IDS in varchar2,
p_PLACE_ID in varchar2)
return id_warehouse_list
is
l_warehouse_list id_warehouse_list := id_warehouse_list();
str varchar2(300);
begin
str := 'SELECT BRANCH_WAREHOUSE(w.wh_id, w.wh_name)
FROM POD_WAREHOUSE_MASTER W
where ( W.wh_id IN (' ||p_WAREHOUSE_IDS || '))';
execute immediate str bulk collect into l_warehouse_list;
for i in l_warehouse_list.first..l_warehouse_list.last loop
dbms_output.put_line(l_warehouse_list(i).wh_id || ', ' || l_warehouse_list(i).wh_name);
/*update pod_place_warehouse_mapping_tb
set wh_id = l_warehouse_list(i).wh_id
where place_id = p_PLACE_ID ;*/
insert into pod_place_warehouse_mapping_tb (id,place_id ,wh_id )
values
(POD_UNIQUE_VAL_SEQ.NEXTVAL ,p_PLACE_ID,l_warehouse_list(i).wh_id);
end loop;
commit;
return l_warehouse_list;
end;
The result you're seeing is exactly what the commented-out UPDATE statement says you want to do. In the update case you could replace the loop with a single update statement:
UPDATE POD_PLACE_WAREHOUSE_MAPPING_TB
SET WH_ID = l_warehouse_list(l_warehouse_list.LAST).WH_ID
WHERE PLACE_ID = p_PLACE_ID
The loop and the UPDATE statement above will produce the exact same results. In the loop case, you're updating all rows in POD_PLACE_WAREHOUSE_MAPPING_TB with each value from l_warehouse_list, one after the other. When the loop is complete all rows in POD_PLACE_WAREHOUSE_MAPPING_TB which have PLACE_ID = p_PLACE_ID will have their WH_ID column set to the WH_ID value from the l_warehouse_list element with the highest index.
I have no idea what other result you're expecting. I think you need to re-think what it is you're trying to do.
Best of luck.

Resources