Performance issue using while loop in insert statement - performance

I am using a try catch block to catch the data with constrain errors.
eg. if null inserted in not null column or if duplicated record inserted or the type mismatch occurs, all the source records with error should go to error log table and rest of the records should go to destination table.
for this i am using try catch so i can't use bulk insert, hence using row by row insert using While Loop, which is takes forever to run as i have to insert 3000000 records.
is there any way where i can improve performance while loop? so it can insert 3000000 records in minimum time? currently it is taking 2 hours or more :(

Try conducting your insert in batches. For instance do a loop attempting to insert 10,000/1,000/100 records at a time as a bulk insert. If there is an error in the batch, catch it and re-execute that batch as a row by row operation. You will have to play with the batch size and make it small enough so that the majority of batches are processed as bulk inserts and only have to row by row a batch occasionally.

The following demonstrates handling a pile of sample data in batches with "binary search" on the batch size in the event of an error.
set nocount on;
-- Set the processing parameters.
declare #InitialBatchSize as Int = 1024;
declare #BatchSize as Int = #InitialBatchSize;
-- Create some sample data with somewhat random Divisor values.
declare #RowsToProcess as Int = 10000;
declare #SampleData as Table ( Number Int, Divisor Int );
with Digits as ( select Digit from ( values (0), (1), (2), (3), (4), (5), (6), (7), (8), (9) ) as Digits( Digit ) ),
Numbers as (
select ( ( ( Ten_4.Digit * 10 + Ten_3.Digit ) * 10 + Ten_2.Digit ) * 10 + Ten_1.Digit ) * 10 + Ten_0.Digit + 1 as Number
from Digits as Ten_0 cross join Digits as Ten_1 cross join Digits as Ten_2 cross join
Digits as Ten_3 cross join Digits as Ten_4 )
insert into #SampleData
select Number, Abs( Checksum( NewId() ) ) % 1000 as Divisor -- Adjust "1000" to vary the chances of a zero divisor.
from Numbers
where Number < #RowsToProcess;
-- Process the data.
declare #FailedRows as Table ( Number Int, Divisor Int, ErrorMessage NVarChar(2048) );
declare #BitBucket as Table ( Number Int, Divisor Int, Quotient Int );
declare #RowCount as Int = 1; -- Force at least one loop execution.
declare #LastProcessedNumber as Int = 0;
while #RowCount > 0
begin
begin try
-- Subject-to-failure INSERT .
insert into #BitBucket
select top ( #BatchSize ) Number, Divisor, 1 / Divisor as Quotient
from #SampleData
where Number > #LastProcessedNumber
order by Number;
set #RowCount = ##RowCount;
select #LastProcessedNumber = Max( Number ) from #BitBucket;
print 'Processed ' + Cast( #RowCount as VarChar(10) ) + ' rows.';
end try
begin catch
if #BatchSize > 1
begin
-- Try a smaller batch.
set #BatchSize /= 2;
end
else
begin
-- This is a failing row. Log it with the error and reset the batch size.
set #LastProcessedNumber += 1;
print 'Row failed. Row number ' + Cast( #LastProcessedNumber as VarChar(10) ) + ', error: ' + Error_Message() + '.';
insert into #FailedRows
select Number, Divisor, Error_Message()
from #SampleData
where Number = #LastProcessedNumber;
set #BatchSize = #InitialBatchSize;
end
end catch
end;
-- Dump the results.
select * from #FailedRows order by Number;
select * from #SampleData order by Number;
select * from #BitBucket order by Number;

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

order by of nested table not taking variable value

SELECT CAST(MULTISET(SELECT *
FROM TABLE(table_a)
ORDER BY loc_sort_column DESC
) as table_a_type
)
INTO table_b
FROM dual;
I have this statement that tosses 1 collection of data into another nested table after sorting it.
The problem I am having is that it is not sorting. I have a variable loc_sort_column that in this case will be a integer/number between 1 and 11 selecting the column but it is not working.
I have stuck 1 in there and it works fine but it doesn't seem to like the variable that contains 1.
Is this an order of operation or something?
What you are doing is ordering by a constant. Following AskTom:
you cannot dynamically return a column position, you dynamically
returned A CONSTANT, A NUMBER, A CONSTANT VALUE - JUST A VALUE
order by < ordinal position> | < expression>
you are returning an expression - not an ordinal position.
What you can do is to use DECODE function to decode column position to real column. Please check my code sample:
CREATE OR REPLACE TYPE my_type FORCE AS OBJECT
(
col_1 NUMBER
,col_2 NUMBER
);
/
CREATE OR REPLACE TYPE my_nested_table AS TABLE of my_type;
/
DECLARE
l_table_a my_nested_table := my_nested_table();
l_table_b my_nested_table := my_nested_table();
l_sort_column NUMBER := 2;
BEGIN
l_table_a.extend(100);
FOR l_i IN 1..l_table_a.COUNT LOOP
l_table_a(l_i) := my_type(ROUND(DBMS_RANDOM.RANDOM,0), ROUND(DBMS_RANDOM.RANDOM,0));
END LOOP;
SELECT CAST(
MULTISET(
SELECT *
FROM TABLE(l_table_a)
ORDER BY DECODE(l_sort_column, 1, col_1
, 2, col_2) DESC
) AS my_nested_table
)
INTO l_table_b
FROM dual;
DBMS_OUTPUT.PUT_LINE('col_1 col_2');
FOR l_i IN 1..l_table_b.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(l_table_b(l_i).col_1 || ' ' || l_table_b(l_i).col_2);
END LOOP;
END;
/

Oracle - performing slow

The below procedure is taking almost5 hours to complete 130000 records. Have tried optimizing it. Is there way to optimize it more?
If i commit after each "for all" , does that increase the performance?
Also i have given a limit of 1000 records, so here it is committing for 1000 record processing or when the count becomes 0?
database version : oracle 10g
I am running the below procedure :
create or replace PROCEDURE WeeklyClearQwAcctActivityLoad
AS
v_no_of_Days_retention number;
jobname VARCHAR2 (30);
loadstartdt DATE;
rcdcnt number;
errorcode NUMBER;
errormsg VARCHAR2 (100);
/* Identify those records for which all CUST_ACCT_ID under the same Parent_id are closed (before retention days ) */
CURSOR getdataforhist
IS
select /*+ parallel(qa2,128) */ CUST_ACCT_ID from qw_account qa2
where exists
( select /*+ parallel(qaa2,128) */ 1 from QW_ACCT_ACTIVITY qaa2
where qaa2.cust_acct_id = qa2.cust_acct_id
and qaa2.actvy_end_date < sysdate - v_no_of_Days_retention
)
and not exists
(
select 1 from (
select /*+ parallel(qa,128) */ qa.PARENT_ID pidd from qw_account qa
where exists
( select /*+ parallel(qaa,128) */ 1 from QW_ACCT_ACTIVITY qaa
where qaa.cust_acct_id = qa.cust_acct_id
and qaa.actvy_end_date > sysdate - v_no_of_Days_retention
)
) pp where pp.pidd = qa2.PARENT_ID
);
TYPE t_getDataForHist IS TABLE OF qw_acct_activity.cust_acct_id%TYPE;
l_getDataForHist t_getDataForHist;
CURSOR orph_product
IS
SELECT /*+ parallel( ram , 128) */ ram.rcr_prod_acct_id
FROM rcr_acct_mapping ram
WHERE 1=1
AND ram.cust_acct_id IS NOT NULL
AND EXISTS
( SELECT /*+ parallel( rap , 128) */ 1
FROM rcr_acct_profile rap
WHERE rap.rcr_prod_acct_id = ram.rcr_prod_acct_id
AND rap.cust_acct_id = ram.cust_acct_id
AND rap.prod_acct_status in ('ACTIVE','INACTIVE','SUSPENDED')
)
AND NOT EXISTS
( SELECT /*+ parallel( qaa , 128 */ 1
FROM qw_acct_activity qaa
WHERE qaa.cust_acct_id = ram.cust_acct_id
);
TYPE t_orph_product is table of rcr_acct_mapping.rcr_prod_acct_id%TYPE;
l_orph_product t_orph_product;
cnt number default 0;
BEGIN
jobname := 'WEEKLY_CLEAN_QW_ACCT_ACTIVITY';
loadstartdt := SYSDATE;
rcdcnt := 0;
INSERT INTO rcr_stage_audit (job_name,load_start_date,load_end_date,record_cnt,processed,process_date,process_cnt,ignore_cnt)
VALUES (jobname,loadstartdt,NULL,NULL,'N',loadstartdt,NULL,NULL );
COMMIT;
BEGIN
SELECT VALUE into v_no_of_Days_retention
FROM rcr_online_svc_app_config
WHERE NAME = 'noofdaystoenddateqwacctactivity';
EXCEPTION
WHEN NO_DATA_FOUND
THEN
errorcode := SQLCODE;
errormsg := 'no of days to end date qw_accta_ctivity is not defined in rcr_code_translation table';
raise_application_error (-20101, errorcode || ' - ' || errormsg, TRUE);
END;
OPEN getDataForHist;
LOOP
FETCH getdataforhist BULK COLLECT INTO l_getdataforhist LIMIT 1000;
--EXIT WHEN getdataforhist%NOTFOUND ;
EXIT WHEN l_getdataforhist.COUNT = 0;
-- FORALL indx IN 1 .. l_getdataforhist.count
-- insert into TEMPSLOT (CUST_ACCT_ID) values ( l_getdataforhist(indx) ) ;
FORALL indx1 IN 1 .. l_getdataforhist.count
INSERT INTO qw_acct_activity_hist
SELECT qaa.*, SYSDATE
FROM qw_acct_activity qaa
WHERE CUST_ACCT_ID = ( l_getdataforhist(indx1) );
FORALL indx2 IN 1 .. l_getdataforhist.count
DELETE FROM qw_acct_activity
WHERE CUST_ACCT_ID = ( l_getdataforhist(indx2) );
rcdcnt := rcdcnt + sql%rowcount;
COMMIT;
END LOOP;
CLOSE getDataForHist;
--- Clean porduct tables for orphan CUST_ACCT_ID
OPEN orph_product;
LOOP
FETCH orph_product BULK COLLECT INTO l_orph_product LIMIT 1000;
EXIT WHEN l_orph_product.COUNT = 0;
FORALL indx10 IN 1 .. l_orph_product.COUNT
INSERT INTO rcr_acct_mapping_hist
SELECT a.*
FROM rcr_acct_mapping a
WHERE rcr_prod_acct_id = ( l_orph_product(indx10) );
FORALL indx11 IN 1 .. l_orph_product.COUNT
DELETE FROM rcr_acct_mapping WHERE rcr_prod_acct_id = ( l_orph_product(indx11) );
FORALL indx12 IN 1 .. l_orph_product.COUNT
DELETE FROM rcr_addt_acct_prof_detail WHERE rcr_prod_acct_id = ( l_orph_product(indx12) );
FORALL indx13 IN 1 .. l_orph_product.COUNT
DELETE FROM rcr_acct_profile_detail WHERE rcr_prod_acct_id = ( l_orph_product(indx13) );
FORALL indx14 IN 1 .. l_orph_product.COUNT
DELETE FROM rcr_acct_profile WHERE rcr_prod_acct_id = ( l_orph_product(indx14) );
COMMIT;
END LOOP;
close orph_product;
UPDATE rcr_stage_audit
SET load_end_date = SYSDATE,
record_cnt = rcdcnt,
processed = 'Y'
WHERE job_name = jobname
AND process_date = loadstartdt
AND load_start_date = loadstartdt;
COMMIT;
EXCEPTION
WHEN OTHERS
THEN
errorcode := SQLCODE;
errormsg := substr(sqlerrm,1,255);
raise_application_error (-20102, errorcode || ' - ' || errormsg, TRUE);
END WeeklyClearQwAcctActivityLoad;
One suggestion that I would recommend to you is to avoid explicit looping and cursors. It leads to poor performance, especially when you can directly use
insert into <table_name>(columns) select <your_query_goes_here>
This is most certainly going to perform faster than your looping constructs. In fact you can reproduce this behavior with simple benchmarks in a table generated with a million records.
So in short try to avoid looping and your code would also look more readable and less prone to errors.
I did a benchmark once where out of 10 million records only about 12,000 had to be updated and timing of explicit looping vs implicit looping lead was 1 minute 10 seconds v/s 1 second.
trace it using oracle tools like as oradebug or dbms_monitor and using tkprof or other analysis tools check the trace file.do the below:
1.extract the sid
2.in sqlplus run: oradebug setorapid xxxx
3.in sqlplus run: oradebug tracefile_name
4.after the process complete in os run tkprof on trace file.(other tools is available also).
5.in trace file check long sections and work on them

sum value in before oracle trigger

I have a table : Table1 (field1, field2, field3).
I want to validate values of updating.
If Sum(field1) group by field2 > 10 then raise error.
CREATE OR REPLACE TRIGGER HDB_TSGH_REVISE
BEFORE UPDATE OF field1
ON Table1
FOR EACH ROW
DECLARE
v_sum_amt NUMBER := 0;
BEGIN
SELECT SUM(field1)
INTO v_sum_amt
FROM Table1
WHERE field2 = 'VND';
IF v_sum_amt > 10 THEN
RAISE_APPLICATION_ERROR(-20000, 'ERROR');
END IF;
END;
Error 4091 at:
SELECT SUM(field1)
INTO v_sum_amt
FROM Table1
WHERE field2 = 'VND';
Please help me
It is caused because of ORA-04091: table name is mutating, trigger/function may not see it
As per the suggestion given, try using AFTER UPDATE trigger instead of BEFORE UPDATE
Since your case is that, you should not update the value if error, maybe you can re-update to the old value in case of error in the after update trigger.
You can also consider using AUTONOMOUS TRANSACTION.
You cannot select from the same table you are manipulating in a row level trigger.
Do NOT use an Autonomous Transaction to get round this error. Although an Autonomous Transaction would cause the error to disappear, the error indicates a fundamental mistake with your methodology and so this needs to change.
I would suggest something like:
CREATE OR REPLACE TRIGGER hdb_tsgh_revise
FOR INSERT OR UPDATE OR DELETE ON table1
COMPOUND TRIGGER
-- Flag to indicate if VND total has been increased
g_vnd_total_increased BOOLEAN;
BEFORE STATEMENT
IS
BEGIN
-- Reset VND total flag
g_vnd_total_increased := FALSE;
END BEFORE STATEMENT;
AFTER EACH ROW
IS
BEGIN
-- If inserting VND record with positive value,
-- Or updating to a VND record with a positive value,
-- Or updating from a VND record with a negative value,
-- Or updating a VND record to a larger value,
-- Or deleting VND record with negative value;
-- Then set the VND total flag
IF ( ( INSERTING
AND :new.field2 = 'VND'
AND :new.field1 > 0)
OR ( UPDATING
AND ( ( nvl(:new.field2, '?') <> nvl(:old.field2, '?')
AND ( ( :new.field2 = 'VND'
AND :new.field1 > 0)
OR ( :old.field2 = 'VND'
AND :old.filed1 < 0)))
OR ( :new.field2 = 'VND'
AND :old.field2 = 'VND'
AND nvl(:new.field1, 0) > nvl(:old.field1, 0)) ) )
OR ( DELETING
AND :old.field2 = 'VND'
AND :old.field1 < 0) )
THEN
g_vnd_total_increased := TRUE;
END IF;
END AFTER EACH ROW;
AFTER STATEMENT
IS
l_sum_field1 NUMBER;
BEGIN
-- If VND total has been increased;
-- Serialise the constraint so concurrent transactions do not affect each other
-- Ensure VND total does not exceed allowed value
IF g_vnd_total_increased THEN
dbms_lock.request
(id => 12345 -- A unique id for this constraint
,lockmode => dbms_lock.x_mode
,release_on_commit => TRUE);
SELECT sum(field1)
INTO l_sum_field1
FROM table1
WHERE field1 = 'VND';
IF l_sum_field1 > 10 THEN
raise_application_error(-20001, 'Field1 total for VND greater than 10 in Table1');
END IF;
END IF;
END AFTER STATEMENT;
END;

Resources