PLSQL Performance Issue - sql

This is more of a doubt than a problem. I have a requirement of just selecting some of the common fields from one table and insert it into a different table. I have done the code with 2 different styles but both with BULK COLLECT. Which is the better option to go for or is there any other way apart from this?
Please find the necessary details below.
Procedure (Way 1):
create or replace procedure a2 is
cursor c1 is select id,address from emp1;
type t is table of c1%rowtype;
c t;
begin
open c1;
loop
fetch c1 bulk collect into c;
exit when c.count=0;
forall j in 1..c.count save exceptions
insert into empl1(id,address) values (c(j).id,c(j).address);
end loop;
commit;
exception when others then
for j in 1..sql%bulk_exceptions.count loop
dbms_output.put_line('The sql error is error occured');
end loop;
end a2;
/
Running the above procedure and output:
declare
a number;
begin
dbms_output.put_line ('before procedure: ' || to_char(sysdate, 'HH24:MI:SS'));
a2;
dbms_output.put_line ('after procedure: ' || to_char(sysdate, 'HH24:MI:SS'));
end;
Output :
before procedure: 23:44:48
after procedure: 23:45:47
PL/SQL procedure successfully completed.
So the above procedure took 59 seconds to insert 34801020 records.
Now please find the second procedure.
Procedure (Way 2):
create or replace procedure a3 is
cursor c1 is select id,address from emp1;
type t is table of c1%rowtype;
c t;
begin
select id,address bulk collect into c from emp1;
forall j in 1..c.count save exceptions
insert into empl1(id,address) values (c(j).id,c(j).address);
exception when others then
for j in 1..sql%bulk_exceptions.count loop
dbms_output.put_line('The sql error is error occured');
end loop;
end a3;
/
Running the above procedure with output .
declare
a number;
begin
dbms_output.put_line ('before procedure: ' || to_char(sysdate, 'HH24:MI:SS'));
a3;
dbms_output.put_line ('after procedure: ' || to_char(sysdate, 'HH24:MI:SS'));
end;
Output :
before procedure: 23:47:57
after procedure: 23:48:53
PL/SQL procedure successfully completed.
This Procedure took 56 seconds to insert 34801020 records.
Total records in emp1 table.
SQL> select count(1) from emp1;
COUNT(1)
----------
34801020
Hence My question :
Which of the above two methods is the best way to insert 3 million records into the table and please suggest me if there is any other better way to do the above process of insertion.

I rans a test using a similar piece of code with a similar dataset size
Using a cursor loop
create or replace procedure a2 is
cursor c1 is select empno,ename from bigemp;
type t is table of c1%rowtype;
c t;
begin
open c1;
loop
fetch c1 bulk collect into c;
exit when c.count=0;
forall j in 1..c.count save exceptions
insert into bigemp2(empno,ename) values (c(j).empno,c(j).ename);
end loop;
commit;
exception when others then
for j in 1..sql%bulk_exceptions.count loop
dbms_output.put_line('The sql error is error occured');
end loop;
end a2;
SQL> exec a2
PL/SQL procedure successfully completed.
Elapsed: 00:00:56.93
Doing a regular insert statement, not using a cursor-for loop
SQL> insert into bigemp2( empno, ename )
select empno, ename from bigemp t2
29360128 rows created.
Elapsed: 00:00:11.30
Now do a direct path insert
SQL> insert /*+ append */ into bigemp2( empno, ename )
select empno, ename from bigemp t2
;
29360128 rows created.
Elapsed: 00:00:06.01
Add some parallelism
SQL> alter session enable parallel dml;
Session altered
SQL> insert /*+ append parallel(2 ) */ into bigemp2( empno, ename )
select /* parallel( t2, 2 ) */ empno, ename from bigemp t2
;
29360128 rows created.
Elapsed: 00:00:03.52
So in summary, just by using the appropriate technique, we can make the process go and order of magnitude faster (about 16x faster)

Related

How do I run a count of rows over a database link?

This code, works. It runs a row count the way you'd expect, I want to tweek it, mostly to do a count over a db_link for tables dictated as I see fit.
declare
n number;
begin
for i in (select table_name from user_tables) loop
execute immediate' select count(*) from '||i.table_name into n;
dbms_output.put_line('Table Name: '||i.table_name||' Count of Row''s: '||n);
end loop;
end;
/
So, this is the adapted code... it includes a variable with the name of the link. (The link works fine) But how to reference it is probably where I'm coming unstuck.
declare
l_dblink varchar2(100) := 'DB1';
n number;
begin
for i in (select table_name from my_tables) loop
execute immediate' select count(*) from '||i.table_name#||l_dblink into n;
dbms_output.put_line('Table Name: '||i.table_name||' Count of Row''s: '||n);
end loop;
end;
/
Can someone please have a look and tell me where I'm going wrong? I just want the SQL to pick up the table names from a local table, and then use the names to count the rows in those tables, which reside in the remote database.
Monkey is on the wrong tree and can't eat a banana.
SQL> create table my_tables (table_name varchar2(20));
Table created.
SQL> insert into my_tables values ('dual');
1 row created.
SQL> set serveroutput on
SQL> declare
2 l_dblink varchar2(100) := 'db1';
3 n number;
4 begin
5 for i in (select table_name from my_tables) -- has to be like this
6 loop -- vvv
7 execute immediate' select count(*) from '||i.table_name || '#' || l_dblink into n;
8 dbms_output.put_line('Table Name: '||i.table_name||' Count of Row''s: '||n);
9 end loop;
10 end;
11 /
Table Name: dual Count of Row's: 1
PL/SQL procedure successfully completed.

If table have 0 rows exit procedure

I have a procedure inside a package and I want to implement a logic, wich will not insert the temp table into the main table if the temp table have 0 rows, and then go to the next procedure of the package.
IF (not exists(select 1 from temp)) THEN
RETURN;
ELSE
EXECUTE IMMEDIATE 'TRUNCATE TABLE main';
INSERT --+APPEND
INTO main
Select * from temp;
EXECUTE IMMEDIATE 'TRUNCATE TABLE temp';
END IF;
With this solution, the package is compiled with error.
Can anyone give me some tips?
you can use loop, without any variables, just first iteration, something like this
FOR a in (select 1 from temp where rownum = 1) LOOP
EXECUTE IMMEDIATE 'TRUNCATE TABLE main';
INSERT --+APPEND
INTO main
Select * from temp;
EXECUTE IMMEDIATE 'TRUNCATE TABLE temp';
END LOOP;
Just count one row and then test whether the result is 0 or 1:
declare
l_row_check integer := 0;
begin
select count(*) into l_row_check from main
where rownum = 1;
if l_row_check = 0 then
execute immediate 'truncate table main';
insert --+ append
into main
select * from temp;
execute immediate 'truncate table temp';
end if;
end;
The easiest is to use a variable to check:
--- suggested edit: add condition to select 1 row at most and avoid
-- counting big table.
select count(1) into v_count from temp where rownum <=1;
IF (v_count=0) THEN
RETURN;
ELSE
EXECUTE IMMEDIATE 'TRUNCATE TABLE main';
INSERT --+APPEND
INTO main
Select * from temp;
EXECUTE IMMEDIATE 'TRUNCATE TABLE temp';
END IF;
Some answer on here use a SELECT INTO method, but I find those a bit tricky.
Since if for example SELECT ColumnA INTO vcColumnA FROM Temp will not have any records, you will end up with the error ORA-01403: no data found.
And those can be hard to find if you have a bigger database.
To loop through a table and do something with values I think cursors and records are more safe.
For example:
DECLARE
CURSOR cTemp IS
SELECT ColumnA, ColumnB
FROM Temp;
rTemp cTemp%ROWTYPE;
BEGIN
OPEN cTemp;
LOOP
FETCH cTemp INTO rTemp;
-- Exit when we read all lines in the Temp table.
EXIT WHEN cTemp%NOTFOUND;
--Do something with every row.
--For example, print ColumnB.
DBMS_OUTPUT.PUT_LINE(rTemp.ColumnB);
END LOOP;
CLOSE cTemp;
END;
/

Move large data between tables in oracle with bulk insert

I want to move 1 million rows of data to another table. Im using query:
insert into Table1
select * from Table2;
in my PL/SQL function. But this way is too slow.
How can I do this with Bulk Insert method?
Source and destination table has same structure.
Tables have hash partition and 1 index.
Forget about bulk insert. Because the insert into select is the best bulk you can load.
The fastest would be to disable the indexes (mark them unusable) and do this in a SINGLE
insert:
insert /*+ append */ into TARGET
select COLS
from SOURCE;
commit;
and rebuild the indexes using UNRECOVERABLE (and maybe even parallel).
PS: If the table is partitioned (Both source and target, you can even use parallel inserts)
FOLLOW UP:
Check the performance of the below select
SELECT /*+ PARALLEL(A 4)
USE_HASH(A) ORDERED */
YOUR_COLS
FROM
YOUR_TABLE A
WHERE
ALL_CONDITIONS;
If faster then
INSERT /*+ APPEND */
INTO
TARGET
SELECT /*+ PARALLEL(A 4)
USE_HASH(A) ORDERED */
YOUR_COLS
FROM
YOUR_TABLE A
WHERE
ALL_CONDITIONS;
USING Bulk Collect
Converting to collections and bulk processing can increase the volume and complexity of your code. If you need a serious boost in performance, however, that increase is well-justified.
Collections, an evolution of PL/SQL tables that allows us to manipulate many variables at once, as a unit. Collections, coupled with two new features introduced with Oracle 8i, BULK_COLLECT and FORALL, can dramatically increase the performance of data manipulation code within PL/SQL.
CREATE OR REPLACE PROCEDURE test_proc (p_array_size IN PLS_INTEGER DEFAULT 100)
IS
TYPE ARRAY IS TABLE OF all_objects%ROWTYPE;
l_data ARRAY;
CURSOR c IS SELECT * FROM all_objects;
BEGIN
OPEN c;
LOOP
FETCH c BULK COLLECT INTO l_data LIMIT p_array_size;
FORALL i IN 1..l_data.COUNT
INSERT INTO t1 VALUES l_data(i);
EXIT WHEN c%NOTFOUND;
END LOOP;
CLOSE c;
END test_proc;
this procedure created by rohit sahani......
using bulk collect i create this procedure
in this procedure i also did update and insert in table and
in this procedure i did collect data in bulk and then update in salary with the use of forall statement .
CREATE OR REPLACE PROCEDURE sam_proc_1(l_salary NUMBER)
IS
l_address VARCHAR(100) := 'karnatka';
a_address VARCHAR(100) := 'bihar';
c_limit PLS_INTEGER := 100;
TYPE employee_ids_t IS TABLE OF emp.emp_id%TYPE;
l_employee_ids employee_ids_t;
CURSOR employees_cur IS SELECT emp_id FROM emp;
-- CURSOR CUR1 IS SELECT EMP_ID, FNAME, ADDRESS FROM EMP;
BEGIN
OPEN employees_cur;
LOOP
FETCH employees_cur BULK COLLECT INTO l_employee_ids LIMIT c_limit;
EXIT WHEN l_employee_ids.COUNT = 0;
-----here start updating
FORALL indx IN 1 .. l_employee_ids.COUNT SAVE EXCEPTIONS
UPDATE emp a
SET a.salary = a.salary + l_salary
WHERE a.emp_id = l_employee_ids(indx);
commit;
END LOOP;
-----------
BEGIN
UPDATE emp v
SET v.address = l_address
WHERE v.address = a_address;
COMMIT;
EXCEPTION WHEN
OTHERS THEN
RAISE_APPLICATION_ERROR(-20001,'An error encountered while updating in address - '
||SQLCODE||' -ERROR- '||SQLERRM);
END;
---------
BEGIN
FOR I IN ( SELECT EMP_ID, FNAME, ADDRESS FROM EMP)
LOOP
INSERT INTO students
( stu_id, stu_name, stu_address)
VALUES
(i.emp_id, i.fname, i.address);
END LOOP;
COMMIT;
EXCEPTION WHEN
OTHERS THEN
RAISE_APPLICATION_ERROR(-20001,'An error encountered while inserting - '
||SQLCODE||' -ERROR- '||SQLERRM);
END;
------------
EXCEPTION
WHEN OTHERS
THEN
IF SQLCODE = -24381
THEN
FOR indx IN 1 .. SQL%BULK_EXCEPTIONS.COUNT
LOOP
-- Caputring errors occured during update
DBMS_OUTPUT.put_line( SQL%BULK_EXCEPTIONS (indx).ERROR_INDEX
||','||
SQL%BULK_EXCEPTIONS (indx).ERROR_CODE);
--<You can inset the error records to a table here>
END LOOP;
ELSE
RAISE;
END IF;
END sam_proc_1;
-----END PROCEDURE;c

How to select from query string in oracle

Lets assume, I have a string that holds a query string.
How can I select the rows from that query string in oracle ?
I tried execute immediate but it returns nothing.
declare
hello varchar(30000);
begin
hello:='select * from test_table';
execute immediate hello;
end;
You would use a dynamic cursor.
Here's an example with SQL*Plus:
SQL> var dyn_cur refcursor
SQL> DECLARE
2 l_sql_query VARCHAR2(1000);
3 BEGIN
4 -- complex function that returns a query:
5 l_sql_query := 'SELECT 1, dummy FROM dual';
6 OPEN :dyn_cur FOR l_sql_query;
7 END;
8 /
PL/SQL procedure successfully completed.
SQL> print dyn_cur
1 DUM
---------- ---
1 X
You can use dynamic cursors in PL/SQL procedures and packages:
SQL> CREATE OR REPLACE PROCEDURE prc_dyn_cur(p_dyn_cursor OUT SYS_REFCURSOR) IS
2 BEGIN
3 OPEN p_dyn_cursor FOR 'SELECT 1, dummy FROM dual';
4 END;
5 /
Procedure created.
SQL> exec prc_dyn_cur(:dyn_cur);
PL/SQL procedure successfully completed.
SQL> print dyn_cur
1 DUM
---------- ---
1 X
declare
hello varchar(30000);
type tb is table of test_table$rowtype;
mytb tb;
begin
hello:='select * from test_table';
execute immediate hello bulk collect into mytb;
-- now you got all og youe data in the "array" mytb
end;
notice that this solution takes into account that you know what table you are selecting from.
plus, i think you should describe what exactly it is you are trying to achieve.
CREATE OR REPLACE PROCEDURE query_executer (string_query IN VARCHAR)
IS
c1 SYS_REFCURSOR;
v_last_name employees.last_name%TYPE; -- Selecting last_name
BEGIN
OPEN c1 FOR string_query; -- Opening c1 for the select statement
LOOP
FETCH c1 INTO v_last_name;
DBMS_OUTPUT.put_line (v_last_name);
EXIT WHEN (C1%NOTFOUND);
END LOOP;
END;
SET SERVEROUTPUT ON
EXECUTE query_executer('select last_name from employees');
OUTPUT
Procedure created.
Abel
Ande
Atkinso
PL/SQL procedure successfully completed.

creating a procedure for nested tables

The following codes successfully creates a procedure using sql*plus
CREATE OR REPLACE PROCEDURE input_order (pat_id in char, vis_vdate in date, vis_act in number,
vac_vacc in char)
AS
BEGIN
DBMS_OUTPUT.PUT_LINE ('Insert attempted');
insert into vaccinations(pid,vdate,action,vaccinated) values(pat_id,vis_vdate,vis_act,vac_vacc);
DBMS_OUTPUT.PUT_LINE ('Insert succeeded');
EXCEPTION
WHEN others THEN DBMS_OUTPUT.PUT_LINE ('error');
DBMS_OUTPUT.PUT_LINE ('Insert rejected');
END;
/
However my intension is to create a similar procedure which includes populating a nested table as an attribute of a table
for example: supposing 'vis_act' is a nested table
with type vis_set_t
and attributes visname and visurname
i tried it this way but kept getting errors
CREATE OR REPLACE PROCEDURE input_order (pat_id in char, vis_vdate in date, vis_act in
vis_set_t, vac_vacc in char)
AS
BEGIN
DBMS_OUTPUT.PUT_LINE ('Insert attempted');
insert into vaccinations(pid,vdate,vis_set_t(visname,visurname),vaccinated) values
(pat_id,vis_vdate,vis_act,vac_vacc);
DBMS_OUTPUT.PUT_LINE ('Insert succeeded');
EXCEPTION
WHEN others THEN DBMS_OUTPUT.PUT_LINE ('error');
DBMS_OUTPUT.PUT_LINE ('Insert rejected');
END;
/
General insert example.
As I mentioned in comments I'm not sure what are you trying to accomplish in your procedure. This is general insert example. Feel free to ask more questions...:
DECLARE
TYPE EmpTabTyp IS TABLE OF scott.emp%ROWTYPE;
emp_tab EmpTabTyp:= EmpTabTyp();
--
PROCEDURE insert_emp(p_col_typ IN EmpTabTyp)
IS
BEGIN
SELECT * BULK COLLECT INTO emp_tab FROM scott.emp;
--
FOR i IN p_col_typ.first .. p_col_typ.last
LOOP
-- Insert your values into your table --
INSERT Into emp_test (empno, ename) VALUES (p_col_typ(i).empno, p_col_typ(i).ename);
-- Optionally display values inserted - do not do this if table has many rows --
--DBMS_OUTPUT.put_line('INSERTED :'|| p_col_typ(i).empno||chr(9)||p_col_typ(i).ename);
END LOOP;
DBMS_OUTPUT.put_line('Total rows inserted :'||p_col_typ.Count);
END insert_emp;
BEGIN
insert_emp(emp_tab);
END;
/
In addition to my comment above:
DECLARE
TYPE EmpTabTyp IS TABLE OF scott.emp%ROWTYPE;
emp_tab EmpTabTyp:= EmpTabTyp();
PROCEDURE display_emp (p_col_typ IN EmpTabTyp)
IS
BEGIN
SELECT * BULK COLLECT INTO emp_tab FROM scott.emp;
--
FOR i IN p_col_typ.first .. p_col_typ.last LOOP
DBMS_OUTPUT.put_line(p_col_typ(i).empno||chr(9)||p_col_typ(i).ename);
END LOOP;
END display_emp;
BEGIN
display_emp(emp_tab);
END;
/
DECLARE
l_names DBMS_UTILITY.maxname_array; -- or DBMS_UTILITY.name_array
PROCEDURE show_contents (names_in IN DBMS_UTILITY.maxname_array)
IS
BEGIN
FOR indx IN names_in.FIRST .. names_in.LAST
LOOP
DBMS_OUTPUT.put_line (names_in (indx));
END LOOP;
END;
BEGIN
l_names (1) := 'Picasso';
l_names (2) := 'Keefe';
l_names (3) := 'Dali';
show_contents (l_names);
END;
/
-- returning collection type example:
DECLARE
TYPE t_emptbl IS TABLE OF scott.emp%rowtype;
v_emptbl t_emptbl;
ret_val t_emptbl;
Function getEmployeeList Return t_emptbl
IS
BEGIN
SELECT * bulk collect INTO v_emptbl FROM scott.emp;
-- Print nested table of records:
FOR i IN 1 .. v_emptbl.COUNT LOOP
DBMS_OUTPUT.PUT_LINE (v_emptbl(i).empno);
END LOOP;
RETURN v_emptbl;
END;
BEGIN
ret_val:= getEmployeeList;
END;
/