creating a procedure for nested tables - sql

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;
/

Related

How to write Execute immediate in cursor select Query

How to wite EXECUTE IMMEDIATE in the cursor select Query.
CREATE OR REPLACE PROCEDURE biq_attendee_report (in_from_date IN DATE)
IS
l_cur_query VARCHAR2 (5000) := 'SELECT * from table X where c1='|| in_from_date;
CURSOR cur_attendee_data
IS
EXECUTE IMMEDIATE l_cur_query;
TYPE rec_attendee_data IS TABLE OF cur_attendee_data%ROWTYPE
INDEX BY PLS_INTEGER;
l_cur_attendee_data rec_attendee_data;
BEGIN
OPEN cur_attendee_data;
LOOP
FETCH cur_attendee_data BULK COLLECT INTO l_cur_attendee_data;
EXIT WHEN l_cur_attendee_data.COUNT = 0;
DBMS_OUTPUT.put_line ('here in first insert');
lrec := return_attendee_report ();
out_attendee_tab :=
return_attendee_arr_result (return_attendee_report ());
out_attendee_tab.DELETE;
FOR i IN 1 .. l_cur_attendee_data.COUNT
LOOP
BEGIN
NULL;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line ('Error occurred : ' || SQLERRM);
END;
END LOOP;
END LOOP;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line ('HERE INSIIDE OTHERS' || SQLERRM);
END;
here I try to use initialize cursor outside begin block but it rises exception, how to write a dynamic query for the cursor,
cur_attendee_data is
EXECUTE IMMEDIATE l_cur_query;
Error(113,8): PLS-00103: Encountered the symbol "CUR_ATTENDEE_DATA" when expecting one of the following: := . ( # % ;
If you fetch all rows with BULK COLLECT INTO ... then you need only one loop, the second loop is useless.
The basic solution would be this one:
CREATE OR REPLACE PROCEDURE biq_attendee_report (in_from_date IN DATE) IS
l_cur_query VARCHAR2 (5000) := 'SELECT * from {table X} where c1=:d';
cur_attendee_data SYS_REFCURSOR;
TYPE rec_attendee_data IS TABLE OF {table X}%ROWTYPE;
l_cur_attendee_data rec_attendee_data;
BEGIN
OPEN cur_attendee_data FOR l_cur_query USING in_from_date;
FETCH cur_attendee_data BULK COLLECT INTO l_cur_attendee_data;
FOR i IN 1 .. l_cur_attendee_data.COUNT LOOP
-- do whatever you like to do with l_cur_attendee_data(i)
END LOOP;
CLOSE cur_attendee_data;
END;
However, I don't see any reason to make dynamic SQL. You can simply run
CREATE OR REPLACE PROCEDURE biq_attendee_report (in_from_date IN DATE) IS
cur_attendee_data SYS_REFCURSOR;
TYPE rec_attendee_data IS TABLE OF {table X}%ROWTYPE;
l_cur_attendee_data rec_attendee_data;
BEGIN
OPEN cur_attendee_data FOR SELECT * from {table X} where c1 = in_from_date;
FETCH cur_attendee_data BULK COLLECT INTO l_cur_attendee_data;
FOR i IN 1 .. l_cur_attendee_data.COUNT LOOP
-- do whatever you like to do with l_cur_attendee_data(i)
END LOOP;
CLOSE cur_attendee_data;
END;
You can use below code instead -
CREATE OR REPLACE PROCEDURE biq_attendee_report (in_from_date IN DATE)
IS
l_cur_query VARCHAR2 (100) := 'SELECT * from table X where c1=:in_from_date';
TYPE t_cur IS REF CURSOR;
cur_attendee_data t_cur
TYPE rec_attendee_data IS TABLE OF cur_attendee_data%ROWTYPE
INDEX BY PLS_INTEGER;
l_cur_attendee_data rec_attendee_data;
BEGIN
OPEN cur_attendee_data FOR l_cur_query USING in_from_date;
LOOP
FETCH cur_attendee_data BULK COLLECT INTO l_cur_attendee_data;
EXIT WHEN l_cur_attendee_data.COUNT = 0;
DBMS_OUTPUT.put_line ('here in first insert');
lrec := return_attendee_report ();
out_attendee_tab :=
return_attendee_arr_result (return_attendee_report ());
out_attendee_tab.DELETE;
FOR i IN 1 .. l_cur_attendee_data.COUNT
LOOP
BEGIN
NULL;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line ('Error occurred : ' || SQLERRM);
END;
END LOOP;
END LOOP;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line ('HERE INSIIDE OTHERS' || SQLERRM);
END;
You need to use open cursor for '' as following:
CREATE OR REPLACE PROCEDURE biq_attendee_report (in_from_date IN DATE)
IS
l_cur_query VARCHAR2 (5000) := 'SELECT * from table X where c1='|| in_from_date;
cur_attendee_data SYS_REFCURSOR; -- JUST DECLARED THE CURSOR
--TYPE rec_attendee_data IS TABLE OF cur_attendee_data%ROWTYPE
-- INDEX BY PLS_INTEGER; -- this declaration must be at schema level
l_cur_attendee_data rec_attendee_data;
BEGIN
OPEN cur_attendee_data for l_cur_query; -- OPEN THE CURSOR WITH DYNAMIC QUERY
..
.. -- YOUR CODE AS IT IS
..
Cheers!!

Fetch and bulk collect from sys_refcursor from another procedure and insert into another table

create or replace procedure ins_act
as
l_result sys_refcursor;
l_id1 varchar2(32);
l_id2 varchar2(32);
l_id3 varchar2(32);
l_pid varchar2(16);
l_ac varchar2(32);
l_activity_date varchar2(32);
l_file_id varchar2(64):='FILE_' ||
to_char(sysdate,'MM/DD/YYYY');
begin
-- Procedure to pull the get records
get_act(l_result);
loop
-- Bulk fetch
fetch l_result bulk collect into
l_id1,l_id2,l_id3,l_platform_id,l_ac,l_activity_date,l_file_id
limit 100;
-- I need to insert into a temp table here.
-- Using bulk collect to increase the process speed and there are no
-- tables matching the l_result output type.
-- temp table
insert into temp(
id1,id2,id3,platform_id,activity_code,update_timestamp,file_id)
values(to_char(to_date(l_id1,'mm/dd/yyyy'),'mm/dd/yyyy'),
l_id2,l_id3,0,l_activity_code,sysdate,l_file_id);
exit when l_result%notfound;
end loop;
close l_result;*/
end;
/
I need to insert the sys_refcursor l_result records into the temp table. Please suggest the best method to achieve this. There will be around 250 k - 1 million records inserted
I understand you need a BULK operation to complete your requirement. See below how you can do it. Also read the comments inline.
create or replace procedure ins_act
as
l_result sys_refcursor;
--Created a RECORD type to hold the result of the SYS_REFCURSOR
TYPE RSLT IS RECORD
(
l_id1 varchar2(32),
l_id2 varchar2(32),
l_id3 varchar2(32),
l_pid varchar2(16),
l_ac varchar2(32),
l_activity_date varchar2(32),
l_file_id varchar2(64)
) ;
--Created associative array to hold the result
Type v_reslt is table of RSLT index by PLS_INETEGER;
--Variable to Record type
var_reslt v_reslt;
begin
-- Procedure to pull the get records
get_act(l_result);
-- Bulk fetch
Loop
fetch l_result bulk collect into var_reslt limit 100;
--Bulk Insert
FORALL I INTO 1..var_reslt.count SAVE EXCEPTIONS
insert into temp(id1,
id2,
id3,
platform_id,
activity_code,
update_timestamp,
file_id)
values
(to_char(to_date(var_reslt(i).l_id1,'mm/dd/yyyy'),'mm/dd/yyyy'),
var_reslt(i).l_id2,
var_reslt(i).l_id3,
0,
var_reslt(i).l_ac,
var_reslt(i).l_activity_date,
var_reslt(i).l_file_id);
exit when l_result%notfound;
end loop;
Commit;
close l_result;
Exception
WHEN OTHERS
THEN
--Bulk Exception handling
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);
RAISE;
end;
/
PS: Not tested.

Forall inserts with dynamic sql

I have a set of insert commands stored in a table type variable (varchar2). The records are inserted into same columns of the same table.
How can I write forall loop to execute these insert queries?
Thanks
You can loop on nested tables as in the following example (from PL/SQL Collections and Records - Nested Tables)
FOR i IN names.FIRST .. names.LAST LOOP
DBMS_OUTPUT.PUT_LINE(names(i));
END LOOP;
and you can run a dynamically created query by means of the execute immediate construct.
Saying that, your problem can be resolved with the following example code.
declare
type table_varchar is table of varchar2(4000);
tv_inserts table_varchar := table_varchar();
begin
tv_inserts.extend();
tv_inserts(tv_inserts.count) := 'insert into test_table(col1, col2) values(1, 10)';
tv_inserts.extend();
tv_inserts(tv_inserts.count) := 'insert into test_table(col1, col2) values(2, 20)';
tv_inserts.extend();
tv_inserts(tv_inserts.count) := 'insert into test_table(col1, col2) values(3, 30)';
for ind in tv_inserts.first..tv_inserts.last
loop
execute immediate tv_inserts(ind);
end loop;
end;
The forall statement can be used with the execute immediate but fixing the table name in the string as in the example below
declare
type table_varchar is table of varchar2(4000);
tv_inserts table_varchar := table_varchar();
begin
tv_inserts.extend();
tv_inserts(tv_inserts.count) := '1';
tv_inserts.extend();
tv_inserts(tv_inserts.count) := '2';
tv_inserts.extend();
tv_inserts(tv_inserts.count) := '3';
for ind in tv_inserts.first..tv_inserts.last
loop
execute immediate 'insert into test_table(col1) values(:a)'
using tv_inserts(ind);
end loop;
end;
Note that this approach is not feasible without a previous processing of your input array.

How do you rollback to the next iteration in a loop?

To explain my question easier I will just paste my whole code:
drop table tst;
create table tst
(t1 number(2));
set serveroutput on
DECLARE
TYPE vltp IS TABLE OF NUMBER(3);
vl vltp := vltp(2,12,33,344,55,66,7,555,4);
NUMBER_TO_BIG EXCEPTION;
PRAGMA EXCEPTION_INIT(NUMBER_TO_BIG, -01438);
BEGIN
FOR i IN vl.FIRST .. vl.LAST LOOP
INSERT INTO tst VALUES (vl(i));
SAVEPOINT ONE;
END LOOP;
EXCEPTION
WHEN NUMBER_TO_BIG THEN
ROLLBACK TO SAVEPOINT ONE;
END;
/
select * from tst;
Basically, when I am inserting 344 into the table I get an exception (NUMBER_TO_BIG) and I want it to roll back to the loop but skip that number.
The expected output:
tst
-----
2
12
33
55
66
7
4
Actual output:
no rows selected
It is rolling back all the changes, not just that one number.
Any ideas?
You should handle the exception inside the loop itself. It will continue with the loop, once the exception is handled.
SQL> DECLARE
TYPE vltp IS TABLE OF NUMBER(3);
vl vltp := vltp(2,12,33,344,55,66,7,555,4);
NUMBER_TO_BIG EXCEPTION;
PRAGMA EXCEPTION_INIT(NUMBER_TO_BIG, -01438);
BEGIN
FOR i IN vl.FIRST .. vl.LAST LOOP
BEGIN
INSERT INTO tst VALUES (vl(i));
EXCEPTION
WHEN NUMBER_TO_BIG THEN
NULL;
END;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
/
PL/SQL procedure successfully completed.
SQL> SELECT * FROM tst;
T1
----------
2
12
33
55
66
7
4
7 rows selected.
you should try this...
drop table tst;
--create table
create table tst
(t1 number(2));
--start of code
DECLARE
TYPE vltp IS TABLE OF NUMBER(3);
vl vltp := vltp(2, 12, 33, 344, 55, 66, 7, 555, 4);
NUMBER_TO_BIG EXCEPTION;
PRAGMA EXCEPTION_INIT(NUMBER_TO_BIG, -01438);
BEGIN
FOR i IN vl.FIRST .. vl.LAST LOOP
begin
INSERT INTO tst VALUES (vl(i));
exception
when NUMBER_TO_BIG then
--log exeption into log table here
dbms_output.put_line(sqlerrm);
end;
END LOOP;
commit;
exception
when others then
--log exeption into log table here
dbms_output.put_line(sqlerrm);
END;
Hello i have illustrtaed a small snippet to replicate your scenario. Let me know if this helps.
--Check for existing table with same name and dropping if already exists
DROP TABLE tst;
--Create Table
CREATE TABLE tst
(t1 NUMBER(2)
);
--Anonymous block to perform the task
SET serveroutput ON
DECLARE
TYPE vltp
IS
TABLE OF NUMBER
(
3
)
;
vl vltp := vltp(2,12,33,344,55,66,7,555,4);
NUMBER_TO_BIG EXCEPTION;
PRAGMA EXCEPTION_INIT(NUMBER_TO_BIG, -01438);
BEGIN
FOR i IN vl.FIRST .. vl.LAST
LOOP
BEGIN
INSERT INTO tst VALUES
(vl(i)
);
EXCEPTION
WHEN NUMBER_TO_BIG THEN
NULL;
dbms_output.put_line('skipping the value');
END;
END LOOP;
COMMIT;
END;

Trigger error with column insert

I am creating a trigger for insert.If one of the value is the same as that in the old table. then I print a message. Here is my code.
Create or replace trigger TR_insert_act
After INSERT On ACTIVITIES
For each row
DECLARE
l_act varchar(30);
Begin
select Activity into l_act
From ACTIVITIES;
if(:new.Activity in l_act) then
DBMS_OUTPUT.PUT_LINE ('There is duplicate.');
end if;
end;
It is not compiled with the error on select of l_act, what to do please?
You need a BEFORE trigger, and a WHERE condition.
The DBMS_OUTPUT will not display the error though.
Create or replace trigger TR_insert_act
before INSERT On ACTIVITIES
For each row
l_act number;
Begin
select count(1) into l_act
From ACTIVITIES
WHERE ACTIVITY = :new.Activity
if(l_act > 0 ) then
RAISE_APPLICATION_ERROR (
num => -20000,
msg => 'There is Duplicate');
end if;
end;