procedure using record type - plsql

I would like to create a procedure which returns a list of the first five records. I must use record type and table type. What am I doing wrong?
CREATE OR REPLACE PROCEDURE procedure_example(v_table OUT v_rec) IS
CURSOR cur1 IS
SELECT * FROM (
SELECT
type_note,
note
FROM dd_note ORDER BY type_note)
WHERE rownum < 5;
TYPE v_rec IS RECORD ( v_type_note NUMBER(2)
, v_note VARCHAR(30));
TYPE v_table IS TABLE OF v_rec INDEX BY BINARY_INTEGER;
BEGIN
OPEN cur1;
LOOP
FETCH cur1 INTO v_type_note, v_note;
dbms_output.put_line(v_type_note || '. --- ' || v_note);
EXIT WHEN cur1%NOTFOUND;
--enter code here
END LOOP;
CLOSE cur1;
END procedure_example;

The place where you declared your types is wrong. You must declare the type "v_rec" and type "v_table" in the package where this procedure belongs, NOT inside this procedure. Here's an improved version of your code. First, declare these types in your package:
TYPE v_rec IS RECORD(
v_type_note number(2),
v_note varchar(30));
TYPE v_table is table of v_rec index by pls_integer;
Then here's your function:
create or replace PROCEDURE procedure_example (out_table OUT v_table) IS
BEGIN
select type_note, note BULK COLLECT into out_table from (select type_note, note from dd_note order by type_note) where rownum < 5
FOR i in 1..out_table.COUNT LOOP
dbms_output.put_line(out_table(i).v_type_note || '. --- ' || out_table(i).v_note);
end loop;
END procedure_example;

Related

How to fetch row of unknown ROWTYPE in PL/SQL?

I have table in oracle with definitions of lists. Each definition is sql string, that returns table with 'd' and 'r' columns. I want get some 'd' value by 'r' value.
Example:
My query string is: SELECT SOME_COLUMN d, SOME_COLUMN2 r from SOME_TABLE order by 1
My PL/SQL code is:
DECLARE
query_str VARCHAR2(2000) := 'SELECT SOME_COLUMN d, SOME_COLUMN2 r from SOME_TABLE order by 1';
return_str VARCHAR2(2000);
TYPE LovCurTyp IS REF CURSOR;
v_lov_cursor LovCurTyp;
--And there is my problem:
lov_record UNKNOWN_TYPE_OF_SOME_TABLE%ROWTYPE;
BEGIN
OPEN v_lov_cursor FOR query_str;
LOOP
FETCH v_lov_cursor INTO lov_record;
EXIT WHEN v_lov_cursor%NOTFOUND;
--Get return_str here
END LOOP;
CLOSE v_lov_cursor;
return return_str;
END;
So my question is: How to fetch row of unknown ROWTYPE or maybe there is another way to do something like I describe?

Does Oracle support non-scalar cursor parameter?

This is a question about Oracle PL/SQL.
I have a procedure in which the exact WHERE clause is not known until the run time:
DECLARE
CURSOR my_cursor is
SELECT ...
FROM ...
WHERE terms in (
(SELECT future_term2 FROM term_table), -- whether this element should be included is conditional
(SELECT future_term1 FROM term_table),
(SELECT present_term FROM term_table)
);
BEGIN
(the processing)
END;
/
What the (SELECT ... FROM term_table) query returns is a 4-character string.
For a solution to this, I am thinking of using a parameterized cursor:
DECLARE
target_terms SOME_DATATYPE;
CURSOR my_cursor (pi_terms IN SOME_DATATYPE) IS
SELECT ...
FROM ...
WHERE terms in my_cursor.pi_terms;
BEGIN
target_terms := CASE term_digit
WHEN '2' THEN (
(SELECT future_term2 FROM term_table),
(SELECT future_term1 FROM term_table),
(SELECT present_term FROM term_table)
) ELSE (
(SELECT future_term1 FROM term_table),
(SELECT present_term FROM term_table)
)
END;
FOR my_record IN my_cursor (target_terms) LOOP
(the processing)
END LOOP;
END;
/
The problem is what the datatype for SOME_DATATYPE should be is not known to me, nor is it known whether Oracle supports such a cursor parameter at all. If supported, is the way shown above to fabricate the value for target_terms correct? If not, how?
Hope someone who know can advise. And thanks a lot for the help.
You can certainly pass a parameter to a cursor, just like you can to a function - but only IN parameters. However, PL/SQL is a strongly typed language, so the datatype must be specified at the time of compilation.
It looks to me like what you will need to do is construct the query dynamically and then use
OPEN cursor FOR l_query;
where l_query is the constructed string. This should give you a feel for what you can do:
CREATE OR REPLACE PACKAGE return_id_sal
AUTHID DEFINER
IS
TYPE employee_rt IS RECORD
(
employee_id employees.employee_id%TYPE,
salary employees.salary%TYPE
);
FUNCTION allrows_by (append_to_from_in IN VARCHAR2 DEFAULT NULL)
RETURN SYS_REFCURSOR;
END return_id_sal;
/
CREATE OR REPLACE PACKAGE BODY return_id_sal
IS
FUNCTION allrows_by (append_to_from_in IN VARCHAR2 DEFAULT NULL)
RETURN SYS_REFCURSOR
IS
l_return SYS_REFCURSOR;
BEGIN
OPEN l_return FOR
'SELECT employee_id, salary FROM employees ' || append_to_from_in;
RETURN l_return;
END allrows_by;
END return_id_sal;
/
DECLARE
l_cursor SYS_REFCURSOR;
l_row return_id_sal.employee_rt;
BEGIN
l_cursor := return_id_sal.allrows_by ('WHERE department_id = 10');
LOOP
FETCH l_cursor INTO l_row;
EXIT WHEN l_cursor%NOTFOUND;
END LOOP;
END;
/
You will need to take precautions against SQL injection with this sort of code. Certainly a user should never be able to pass SQL text directly to such a function!
You can use also some built-in VARRAY SQL types like SYS.ODCIVARCHAR2LIST or create your own :
CREATE OR REPLACE NONEDITIONABLE TYPE VARCHARLIST
AS VARRAY(32767) OF VARCHAR2(4000);
Then you can use it with SELECT COLUMN_VALUE FROM TABLE(COLLECTION) statement in your cursor:
DECLARE
l_terms SYS.ODCIVARCHAR2LIS; --or VARCHARLIST
CURSOR my_cursor (p_terms IN SYS.ODCIVARCHAR2LIS) IS
SELECT your_column
FROM your_table
WHERE terms in (select COLUMN_VALUE from table (p_terms));
BEGIN
select term
bulk collect into l_terms
from (
select 'term1' term from dual
union all
select 'term2' term from dual
);
FOR my_record IN my_cursor (l_terms) LOOP
--process data from your cursor...
END LOOP;
END;

Assigning object type in plsql

I need your help to know how to assign the object type through a string in PLSQL
Below is the problem description:
I first created the object types as below:
create or replace type picu_obj is object(Customer_ID varchar2(32767),Customer_Name varchar2(32767),Server_Name varchar2(32767),Time_stamp varchar2(32767));
create or replace type picu_obj_tab is table of picu_obj;
and I have a PLSQL block as below:
declare
l_str1 varchar2(1000);
l_str2 varchar2(10000);
l_newstr1_1 varchar2(10000);
picu_var picu_obj_tab;
cursor c1cudetails
is
select item,current_value
from
(select rownum,
last_value(category ignore nulls) over (order by rownum) category ,
last_value(item ignore nulls) over (order by rownum) item,
current_value
from pi_perfdata_new
order by rownum
)
where upper(category) like '%CUSTOMER%DETAILS%' ;
type cudet is table of c1cudetails%rowtype index by pls_integer;
l_cudet cudet;
begin
/* create dynamic string for items */
open c1cudetails;
fetch c1cudetails bulk collect into l_cudet limit 50;
for i in l_cudet.first..l_cudet.last loop
l_str1:=l_str1||','||''''||l_cudet(i).current_value||'''';
l_str2:=trim(leading ',' from l_str1);
l_newstr1_1:='picu_obj_tab(picu_obj('||l_str2||'))';
end loop;
-- dbms_output.put_line(''||l_newstr1_1||'');
-- picu_var := l_newstr1_1;
close c1cudetails;
end;
For the string "l_newstr1_1" following value is retruned from above PLSQL block
picu_obj_tab(picu_obj('CSCO5','DXRTYE','PI22-pro-333','2015-07-22-22:48:56'))
Now I want to assign the above result to variable "picu_var" which I have declared.
Basically I need to convert to the following during runtime.
picu_var := picu_obj_tab(picu_obj('CSCO5','DXRTYE','PI22-pro-333','2015-07-22-22:48:56'))
How to achieve the same?
Please suggest how to initialize the object type variable to the string values.
Use dynamic PL/SQL like this:
execute immediate 'begin :x := ' || l_newstr1_1|| '; end;'
using out picu_var;

Can I pass an explicit cursor to a function/procedure for use in FOR loop?

I have a procedure that performs some calculations on all records returned by a cursor. It looks a bit like this:
PROCEDURE do_calc(id table.id_column%TYPE)
IS
CURSOR c IS
SELECT col1, col2, col3
FROM table
WHERE ...;
BEGIN
FOR r IN c LOOP
-- do some complicated calculations using r.col1, r.col2, r.col3 etc.
END LOOP;
END;
Now I have the case where I need to perform the exact same calculation on a different set of records that come from a different table. However, these have the same "shape" as in the above in example.
Is it possible to write a procedure that looks like this:
PROCEDURE do_calc2(c some_cursor_type)
IS
BEGIN
FOR r IN c LOOP
-- do the calc, knowing we have r.col1, r.col2, r.col3, etc.
END LOOP;
END;
I know about SYS_REFCURSOR, but I was wondering if it was possible to use the much more convenient FOR ... LOOP syntax and implicit record type.
Create a package.
Declare your cursor as package variable.
Use %rowtype to set function parameter type.
create or replace package test is
cursor c is select 1 as one, 2 as two from dual;
procedure test1;
function test2(test_record c%ROWTYPE) return number;
end test;
create or replace package body test is
procedure test1 is
begin
for r in c loop
dbms_output.put_line(test2(r));
end loop;
end;
function test2(test_record c%ROWTYPE) return number is
l_summ number;
begin
l_summ := test_record.one + test_record.two;
return l_summ;
end;
end test;
I had a similar problem, where I had two cursors that needed to be processed the same way, so this is how I figured it out.
DECLARE
--Define our own rowType
TYPE employeeRowType IS RECORD (
f_name VARCHAR2(30),
l_name VARCHAR2(30));
--Define our ref cursor type
--If we didn't need our own rowType, we could have this: RETURN employees%ROWTYPE
TYPE empcurtyp IS REF CURSOR RETURN employeeRowType;
--Processes the cursors
PROCEDURE process_emp_cv (emp_cv IN empcurtyp) IS
person employeeRowType;
BEGIN
LOOP
FETCH emp_cv INTO person;
EXIT WHEN emp_cv%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('Name = ' || person.f_name ||
' ' || person.l_name);
END LOOP;
END;
--Defines the cursors
PROCEDURE mainProcedure IS
emp empcurtyp;
BEGIN
OPEN emp FOR SELECT first_name, last_name FROM employees WHERE salary > 50000;
process_emp_cv(emp);
CLOSE emp;
OPEN emp FOR SELECT first_name, last_name FROM kuren WHERE first_name LIKE 'J%';
process_emp_cv(emp);
CLOSE emp;
END;
BEGIN
mainProcedure;
END;
/
You can also use this if you want to bulk collect your cursors. You just need to change your helper procedure process_emp_cv; the rest can stay the same.
Using BULK COLLECT
--Processes the cursors
PROCEDURE process_emp_cv (emp_cv IN empcurtyp) IS
TYPE t_employeeRowTable IS TABLE OF employeeRowType;
employeeTable t_employeeRowTable;
BEGIN
LOOP
FETCH emp_cv BULK COLLECT INTO employeeTable LIMIT 50;
FOR indx IN 1 .. employeeTable.Count
LOOP
DBMS_OUTPUT.PUT_LINE('Name = ' || employeeTable(indx).f_name ||
' ' || employeeTable(indx).l_name);
END LOOP;
EXIT WHEN emp_cv%NOTFOUND;
END LOOP;
END;
Try this one, Usong ref cursor.
declare
type c is ref cursor;
c2 c;
type rec is record(
id number,
name varchar(20)
);
r rec;
procedure p1(c1 in out c,r1 in out rec)is begin
loop
fetch c1 into r1;
exit when c1%notfound;
dbms_output.put_line(r1.id || ' ' ||r1.name);
end loop;
end;
begin
open c2 for select id, name from student;
p1(c2,r);
end;
Yes you can use Cursor explicitly into procedure and function,for that cursor need to declare into package as variable

How to insert into a table correctly using table of records and forall in pl/sql

I want to insert records into MY_TABLE using forall. But the no. of records dat gets inserted keeps on changing with each test run! I think it has something to do with loop counter but I am not able to figure out. Here's the code snippet.
DECLARE
TYPE l_rec_type IS RECORD (
datakey SOURCE_TABLE.datakey%TYPE,
sourcekey SOURCE_TABLE.sourcekey%TYPE,
DESCRIPTION SOURCE_TABLE.DESCRIPTION%TYPE,
dimension_name SOURCE_TABLE.dimension_name%TYPE ,
data_type SOURCE_TABLE.data_type%TYPE
);
TYPE l_table_type IS TABLE OF l_rec_typeINDEX BY PLS_INTEGER;
l_table l_table_type;
l_cntr NUMBER;
BEGIN
FOR rec_dimname IN (SELECT dimension_name FROM dimension_table) LOOP
l_cntr1 := 1
FOR rec_source IN (SELECT * FROM source_table WHERE data_type IS NOT NULL) LOOP
l_table(l_ctr1).datakey := rec_source.datakey;
l_table(l_ctr1).sourcekey := rec_source.sourcekey;
l_table(l_ctr1).DESCRIPTION := rec_source.DESCRIPTION;
l_table(l_ctr1).dimension_name := rec_source.dimension_name;
l_table(l_ctr1).data_type := rec_source.data_type;
l_cntr1 := l_cntr1+1;
END LOOP
FORALL j IN l_table.FIRST..l_table.LAST
INSERT INTO my_table VALUES(l_table(j).datakey,
l_table(j).sourcekey,
l_table(j).DESCRIPTION,
l_table(j).dimension_name,
l_table(j).data_type,
1,
SYSDATE,
login_id
);
END LOOP;
END;
What am I doing wrong? Normal insert using for loop is inserting 5000 records. Another problem that I am facing is how to handle WHEN DUP_VAL_ON_INDEX and WHEN OTHERS exception using forall. In nornal for loop its easy. But I have to use FORALL for fast inserts. Please help!
Looking at your code I can see that you not delete the data stored in the pl/table inside your loop and you don't have a order by to your query's. So if the first iteration have more data then the second you will have duplicate data.
So after initializing your l_cntr1 var (l_cntr1 := 1) you must clear your pl/table:
l_table.delete;
Hope that helps.
Here's the fixed code. Plus SAVE EXCEPTIONS really saved my day!. Here is how I implemented the solution. Thank you all for your valuable time and suggestions.
DECLARE
TYPE l_rec_type IS RECORD (
datakey SOURCE_TABLE.datakey%TYPE,
sourcekey SOURCE_TABLE.sourcekey%TYPE,
DESCRIPTION SOURCE_TABLE.DESCRIPTION%TYPE,
dimension_name SOURCE_TABLE.dimension_name%TYPE ,
data_type SOURCE_TABLE.data_type%TYPE
);
TYPE l_table_type IS TABLE OF l_rec_typeINDEX BY PLS_INTEGER;
l_table l_table_type;
l_cntr NUMBER;
ex_dml_errors EXCEPTION;
PRAGMA EXCEPTION_INIT(ex_dml_errors, -24381);
login_id NUMBER := -1;
errm VARCHAR2(512);
err_indx NUMBER
BEGIN
FOR rec_dimname IN (SELECT dimension_name FROM dimension_table) LOOP
l_cntr1 := 1;
l_table.DELETE; -- Added
FOR rec_source IN (SELECT * FROM source_table WHERE data_type IS NOT NULL) LOOP
l_table(l_ctr1).datakey := rec_source.datakey;
l_table(l_ctr1).sourcekey := rec_source.sourcekey;
l_table(l_ctr1).DESCRIPTION := rec_source.DESCRIPTION;
l_table(l_ctr1).dimension_name := rec_source.dimension_name;
l_table(l_ctr1).data_type := rec_source.data_type;
l_cntr1 := l_cntr1+1;
END LOOP
FORALL j IN l_table.FIRST..l_table.LAST SAVE EXCEPTIONS
INSERT INTO my_table VALUES(l_table(j).datakey,
l_table(j).sourcekey,
l_table(j).DESCRIPTION,
l_table(j).dimension_name,
l_table(j).data_type,
1,
SYSDATE,
login_id
);
END LOOP;
END LOOP;
EXCEPTION
WHEN ex_dml_errors THEN
l_error_count := SQL%BULK_EXCEPTIONS.count;
DBMS_OUTPUT.put_line('Number of failures: ' || l_error_count);
errm := SQLERRM(-SQL%BULK_EXCEPTIONS(i).ERROR_CODE);
err_indx := SQL%BULK_EXCEPTIONS(i).error_index
FOR i IN 1 .. l_error_count LOOP
DBMS_OUTPUT.put_line('Error: ' || i ||
' Array Index: ' || SQL%BULK_EXCEPTIONS(i).error_index ||
' Message: ' || SQLERRM(-SQL%BULK_EXCEPTIONS(i).ERROR_CODE));
IF errm LIKE '%unique%constraint%violated' THEN -- Insert into my_multiple_entries_tbl on duplicate value on index DATAKEY
INSERT INTO my_multiple_entries_tbl(my_multiple_entries_tbl_seq.NEXTVAL,
l_table(err_indx).datakey,
l_table(err_indx).sourcekey,
l_table(err_indx).data_type,
SYSDATE,
login_id );
ELSE -- Insert into my_other_errors_tbl on other errors
INSERT INTO my_other_errors_tbl ( my_other_errors_tbl_seq.NEXTVAL,
l_table(err_indx).datakey,
l_table(err_indx).sourcekey,
l_table(err_indx).data_type,
SYSDATE,
login_id );
END IF;
END;
Your seem to be inserting exactly the same thing multiple times - you're solely looping through the count of dimension_table, which means it can be simplified to the following, which will be faster. At the bottom is a forall version.
You can't use exception when dup_val_on_index with either version, you have to do it row by row. Judging solely by what you've posted I suspect that you can actually achieve what you're trying to do in a single query and save all this problem completely ( including dealing with duplicate values ).
declare
i integer;
begin
select count(*)
into i
from dimension_table;
for j in 1 .. i loop
insert into my_table (datakey, sourcekey, description
, dimension_name, someother_column
, some_date_column, login_id
select datakey, sourcekey, description, dimension_name
, data_type, 1, sysdate, login_id -- previously missing
from source_table
where data_type is not null;
end loop;
commit;
end;
/
If, however, you really want to use forall you can do something like this:
declare
cursor c_src is
select datakey, sourcekey, description, dimension_name
, data_type, 1, sysdate, login_id -- previously missing
from source_table
where data_type is not null;
type t__src is table of c_src%rowtype index by binary_integer;
t_src t__src;
i integer;
begin
select count(*)
into i
from dimension_table;
for j in 1 .. i loop
open c_src;
loop
fetch c_src bulk collect into t_src;
forall k in t_src.first .. t_src.last
insert into my_table (datakey, sourcekey, description
, dimension_name, someother_column
, some_date_column, login_id
values t_src;
end loop;
close c_src;
end loop;
commit;
end;
/

Resources