Insert within FORALL loop in PL/SQL - performance

Is it possible to do something like this in pl/sql for bulk insert using FORALL?
TYPE c_type1 IS RECORD
(
column1 table1.column1%TYPE,
column2 table1.column2%TYPE,
client table2.client%TYPE
);
type1 c_type1;
CURSOR cur_t IS select * BULK COLLECT INTO recs from table3 ;
begin
FOR recs IN cur_t
LOOP
SELECT * INTO type1 FROM (select a.column1, a.column2,imm.client
...
from table1 a, table2 imm
WHERE
a.column1 = recs.column1
) WHERE ROWNUM=1;
INSERT INTO table2 values (recs.column1,type1.column2);
...
P.S : There are more 80 columns to be inserted.

Your question is not pretty clear but looking at your code I have the following. Check if this is what you were looking for.
declare
CURSOR cur_t IS
select t3.column1 , t1.column2
from table3 t3
inner join table1 t1
on t3.column1 = t1.column1;
type var_cur is table of cur_t%rowtype;
var var_cur;
begin
open cur_t;
LOOP
FETCH cur_t bulk collect into var limit 100;
EXIT WHEN cur_t%NOTFOUND;
FORALL i IN 1 .. var.count SAVE EXCEPTIONS
INSERT INTO TABLE2
VALUES var(i);
END LOOP;
CLOSE distinctUserIdCursor;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line('Error in Insertion of record' || '~~~~' || SQLERRM);
FOR indx IN 1 .. SQL%BULK_EXCEPTIONS.COUNT
LOOP
DBMS_OUTPUT.put_line (SQL%BULK_EXCEPTIONS (indx).ERROR_INDEX|| ': '
|| SQL%BULK_EXCEPTIONS (indx).ERROR_CODE);
END LOOP;
end;

Related

How to get the number of rows in ROWTYPE variable

I have two tables Table1 an dTable2 that have identical columns. I need to check if a particular id is in one of them and return the row of data from whichever table.
I have the following PL/SQL code:
v_result Table1%ROWTYPE;
BEGIN
SELECT a.*
INTO v_result
FROM Table1 a
WHERE a.id = 123;
EXCEPTION
WHEN NO_DATA_FOUND THEN -- when record not found
SELECT b.*
INTO v_result
FROM Table2 b
WHERE b.id = 123;
END;
The issue is that the exception does not get thrown, so v_result returns no data. How can I check v_result for the number of rows?
For cursor I can use ROWCOUNT but v_result is not a cursor.
I also tried using count property but it errored out.
I changed my code to:
v_result Table1%ROWTYPE;
BEGIN
SELECT a.*
INTO v_result
FROM Table1 a
WHERE a.id = 123;
if v_result.count =0 then
SELECT b.*
INTO v_result
FROM Table2 b
WHERE b.id = 123;
end if;
EXCEPTION
WHEN NO_DATA_FOUND THEN -- when record not found
SELECT b.*
INTO v_result
FROM Table2 b
WHERE b.id = 123;
END;
And got an error component 'count' must be declared
What am I doing wrong?
You may use only a single row in a record variable. If you want to store and count multiple rows, you may define a collection of records and use BULK COLLECT INTO to load all of them at once and it won't raise a NO_DATA_FOUND. The count function works on collections.
DECLARE
TYPE type_tab1 IS TABLE OF Table1%ROWTYPE;
TYPE type_tab2 IS TABLE OF Table2%ROWTYPE;
v_result1 type_tab1;
v_result2 type_tab2;
BEGIN
SELECT a.*
BULK COLLECT INTO v_result1
FROM Table1 a
WHERE a.id = 123;
if v_result1.count = 0 then
SELECT b.* BULK COLLECT
INTO v_result2
FROM Table2 b
WHERE b.id = 123;
end if;
DBMS_OUTPUT.PUT_LINE('v_result1 ='|| v_result1.count);
DBMS_OUTPUT.PUT_LINE('v_result2 ='|| v_result2.count);
END;
/
Output for the Demo
v_result1 =0
v_result2 =1
If your intention is to simply check if a row exists, then a simpler and efficient approach would be to use EXISTS
SELECT
CASE WHEN
EXISTS (
SELECT 1
FROM table1
WHERE id = 123
) THEN 1
ELSE 0
END
INTO v_count
FROM dual;
IF v_count = 0
THEN
..
..

How to store the number of affected records and return it as a parameter?

I have an in parameter (set = 0), to keep track of the count of entries I will be modifying. I am trying to merge data into a table called Table1, the records that have been updated in a different table (Table2) since the last time Table1 has been updated. The conditional statement will compare the Table1.LastUpdate column to the max(Modified_date) column of Table2 and only insert entries where the table1.last_update column is greater than the table2.max(modified_date) column. Then I will need to store this number and return it as an out parameter. What I have is follows:
create or replace procedure test_proc (rUpdated_Row_Count IN NUMBER, rUpdated_Row_Count_2 OUT NUMBER) is
CURSOR c1 is
select max(modified_date) as max_modified_date
from table1;
l_var c1%ROWTYPE;
-----------
CURSOR c2 is
select table2_id
, last_update
from table2;
k_var c2%ROWTYPE;
BEGIN
LOOP
Open c1;
Fetch c1 into l_var;
Open c2;
Fetch c2 into k_var;
EXIT WHEN c1%NOTFOUND;
IF k_var.last_update > l_var.max_modified_date THEN
Insert into table2(table2_id, last_update)
values(null, k_var.last_update);
commit;
rUpdated_Row_Count_2 := rUpdated_Row_Count + 1;
END IF;
END LOOP;
Close c1;
Close c2;
END test_proc;
Thanks in advance!
Modified my code (after doing further research):
create or replace procedure test_proc (rUpdated_Row_Count IN NUMBER, rUpdated_Row_Count_2 OUT NUMBER) is
CURSOR c1 is
select max(modified_date) as max_modified_date
from table1;
l_var c1%ROWTYPE;
-----------
CURSOR c2 is
select table2_id
, last_update
from table2;
k_var c2%ROWTYPE;
BEGIN
Open c1;
Open c2;
LOOP
Fetch c1 into l_var;
Fetch c2 into k_var;
EXIT WHEN c2%NOTFOUND;
IF k_var.last_update > l_var.max_modified_date THEN
Insert into table2(table2_id, last_update)
values(null, k_var.last_update);
commit;
rUpdated_Row_Count_2 := rUpdated_Row_Count + 1;
END IF;
END LOOP;
Close c1;
Close c2;
END test_proc;
Reproducable data / Code is below:
Create table1
(
table1_id number,
modified_date date
);
Create table2
(
table2_id number,
last_update date
);
insert into table1(table1_id, modified_date) values(1, sysdate);
insert into table1(table1_id, modified_date) values(2, sysdate);
insert into table1(table1_id, modified_date) values(3, sysdate -1);
insert into table2(table2_id, last_update) values(1, sysdate + 1);
insert into table2(table2_id, last_update) values(2, sysdate + 2);
Not quite sure what the "IN" parameter is for. Also not quite sure about the overall rationale. However, here's how I'd write a first version of your procedure:
create or replace procedure test_proc2 (
rUpdated_Row_Count IN NUMBER
, rUpdated_Row_Count_2 IN OUT NUMBER )
is
max_modified_date date ;
begin
select max( modified_date ) into max_modified_date from table1 ;
for rec_ in (
select table2_id, last_update
from table2
) loop
if rec_.last_update > max_modified_date then
insert into table2( table2_id, last_update )
values( null, rec_.last_update ) ;
rUpdated_Row_Count_2 := rUpdated_Row_Count_2 + 1 ;
end if ;
end loop;
end ;
/
Using your test tables (your DDL code should be: CREATE TABLE table1 ... by the way), we can use the following anonymous block for executing the procedure.
-- not sure what the "IN" parameter is used for
declare
rowcount_in number := 0 ; -- not needed
rowcount_out number := 0 ;
begin
test_proc2( rowcount_in, rowcount_out ) ;
dbms_output.put_line( 'updated rows: ' || rowcount_out ) ;
end;
/
updated rows: 2
After executing the anonymous block the tables contain ...
SQL> select * from table1 ;
TABLE1_ID MODIFIED_DATE
1 15-MAY-18
2 15-MAY-18
3 14-MAY-18
SQL> select * from table2 ;
TABLE2_ID LAST_UPDATE
1 16-MAY-18
2 17-MAY-18
NULL 16-MAY-18
NULL 17-MAY-18
Many people will tell you that you should use BULK operations (BULK COLLECT, FORALL etc) whenever possible. Does all that help you?

Check if Exists PLS-00405: subquery not allowed in this context

I have cursor it selects from TableA then Fetch Loop that inserts into TableB.
I want to check if the value already exists in the TableB.
If it exists then I want to skip the insert.
create or replace
PROCEDURE DAILY_RPT (
v_start IN DATE,
v_end IN DATE)
IS
ao_out_no out_pair.out_no%type;
cursor get is
SELECT ao_out_no from tableA;
BEGIN
open get;
LOOP
fetch get into ao_out_no;
EXIT WHEN get%NOTFOUND;
if (ao_out_no = (select out_no from TableA where out_no = ao_out_no) THEN
--DO NOTHING
else
INSERT INTO TABLEB(OUT_NO) VALUES (ao_out_no);
end if;
END LOOP;
close get;
END;
I used IF CONDITION however, I used variable into if condition & I am getting below.
PLS-00405: subquery not allowed in this context
if (ao_out_no = (select out_no from TableA where out_no = ao_out_no) THEN
You don't need cursor or PL/SQL at all:
INSERT INTO TABLEB(OUT_NO)
SELECT ao_out_no
FROM tableA ta
WHERE ... -- filtering rows
AND NOT EXISTS (SELECT * From TableB tb WHERE tb.OUT_NO = ta.ao_out_no);
Use the following :
for i in (
select out_no from TableA where out_no
)
loop
if i.out_no = ao_out_no
then
-- DO NOTHING
else
...
or
create a new variable named x, and then assign a value to it by
select out_no into x from TableA where out_no = ao_out_no;
and check returning value for x.
With corrected syntax, it would be something like this:
create or replace procedure daily_rpt
( v_start in date
, v_end in date )
as
begin
for r in (
select ao_out_no, 0 as exists_check
from tablea
)
loop
select count(*) into exists_check
from tablea
where out_no = r.ao_out_no
and rownum = 1;
if r.exists_check > 0 then
--DO NOTHING
else
insert into tableb (out_no) values (r.ao_out_no);
end if;
end loop;
end;
However, it's inefficient to query all of the rows and then do an additional lookup for each row to decide whether you want to use it, as SQL can do that kind of thing for you. So version 2 might be something like:
create or replace procedure daily_rpt
( v_start in date
, v_end in date )
as
begin
for r in (
select ao_out_no
from tablea
where not exists
( select count(*)
from tablea
where out_no = r.ao_out_no
and rownum = 1 )
)
loop
insert into tableb (out_no) values (r.ao_out_no);
end loop;
end;
at which point you might replace the whole loop with an insert ... where not exists (...) statement.

PL/SQL nested loop (loop within a loop)

Below is a PL/SQL I'm working on
declare
v_sql varchar2(500);
BEGIN
for t in (
SELECT distinct ID
FROM TABLEB
) loop
for c in (
select * from (
select 'delete from ' as test
from dual
union all
select 'TABLEA'||' where ' as test
from dual
union all
select 'ID='||t.ID
from dual
)
) loop
v_sql := v_sql || c.test;
end loop;
dbms_output.put_line(v_sql);
end loop;
END;
/
The result I'm getting is this
delete from TABLEA where ID=1
delete from TABLEA where ID=1delete from TABLEA where ID=2
I want
delete from TABLEA where ID=1
delete from TABLEA where ID=2
Any help on the PLSQL will be appreciated
What is the purpose of the inner FOR loop? It does nothing that requires a loop, and can be simply rewritten like this:
declare
v_sql varchar2(500);
begin
for t in (select distinct id from tableb) loop
v_sql := 'delete from tablea where id = ' || t.id ||';';
dbms_output.put_line(v_sql);
end loop;
end;
/
BTW, it seems that you're missing the terminating semicolon in line v_sql := ...
Demonstration on HR's DEPARTMENTS table:
SQL> declare
2 v_sql varchar2(500);
3 begin
4 for t in (select distinct department_id id from departments) loop
5 v_sql := 'delete from tablea where id = ' || t.id ||';';
6 dbms_output.put_line(v_sql);
7 end loop;
8 end;
9 /
delete from tablea where id = 10;
delete from tablea where id = 20;
delete from tablea where id = 30;
delete from tablea where id = 40;
delete from tablea where id = 50;
delete from tablea where id = 60;
<snip>
You're not clearing the buffer after you've printed the statement, so you're appending the next statement to the first one. To clear the buffer, add
v_sql := NULL;
after the line which reads
dbms_output.put_line(v_sql);
Best of luck.

oracle select from table if values not exists

I need to write oracle query (Just query)
to select values from table, and if not found select from another table.
any help to do this in pl/sql?
SELECT * FROM firstTable
UNION ALL
SELECT * FROM secondTable WHERE (SELECT count(*) FROM FIRST_TABLE ) = 0
You can enclose the SELECT statement within a block and add an exception handler to it.
So, if there are no rows selected from the first table, then you select from the second table. The structure would be something like below:
Begin
select <many columns> into <many variables or row type>
From Table1
where <conditions>;
EXCEPTION
WHEN NO_DATA_FOUND THEN
select <many columns> into <many variables or row type>
From Table2
Where <Conditions>;
End;
References:
Another related SO question
Exception Handlers
Documentation for the SELECT INTO statement
Here is an example of a PL/SQL function that will perform a test, and then execute a secondary query based upon the results of the test. You can adjust it to fit your needs:
set serveroutput on;
declare
row_count number;
column1 varchar(10);
column2 varchar(10);
column3 number;
begin
/*Perform your test*/
select count(target_column) into row_count
from my_table
where condition_column = 'x';
/*Run your secondary query based on the output of the first*/
if row_count > 0 then
select
col_x into column1,
col_y into column2,
col_z into column3
from my_other_table_A;
else
select
col_a into column1,
col_b into column2,
col_c into column3
from my_other_table_B;
end if;
/*Show the results*/
dbms_output.put_line('column1: ' || column1);
dbms_output.put_line('column2: ' || column2);
dbms_output.put_line('column3: ' || column3);
end;
/

Resources