I have a function like this (unfortunately, I cannot change it to send one array instead of multiple parameters):
CREATE FUNCTION (p1_ IN OUT VARCHAR2(100), p2_ IN OUT VARCHAR2(100), <...>, p10_ IN OUT VARCHAR2(100)
IS
BEGIN
gather_value(p1_);
gather_value(p2_);
<...>
gather_value(p8_);
END;
To clean it up a little bit, I would prefer to change the body as follows:
IS
stmt VARCHAR2(1000);
BEGIN
FOR i IN 1 .. 8
LOOP
stmt := 'gather_value(p:1_);'
EXECUTE IMMEDIATE stmt USING i;
END LOOP;
END;
However, in that case no such parameters are found.
Is there a way to work it around?
Related
here is my PLSQL code:
declare
headerStr varchar2(1000):='C1~C2~C3~C5~C6~C7~C8~C9~C10~C11~C12~C16~C17~C18~C19~RN';
mainValStr varchar2(32000):='1327~C010802~9958756666~05:06AM~NO~DISPOSAL~NDL~4~P32~HELLO~U~28-OCT-2017~28-OCT-2017~Reject~C010741~1;1328~C010802~9958756666~06:07AM~MH~DROP~NDL~1~P32~~U~28-OCT-2017~28-OCT-2017~Reject~C010741~2;1329~C010802~9999600785~01:08AM~BV~DROP~NDL~2~P32~MDFG~U~28-OCT-2017~28-OCT-2017~Reject~C010741~3';
valStr varchar2(4000);
headerCur sys_refcursor;
mainValCur sys_refcursor;
valCur sys_refcursor;
header varchar2(1000);
val varchar2(1000);
iterator number:=1000;
strIdx number;
strLen number;
idx number;
TYPE T_APPROVAL_RECORD IS TABLE OF VARCHAR2(4000) INDEX BY VARCHAR2(1000);
headerTable T_APPROVAL_RECORD;
cnt number;
begin
open headerCur for select * from table(split_str(headerStr,'~'));
open mainValCur for select * from table(split_str(mainValStr,';'));
loop
fetch mainValCur into valStr;
exit when mainValCur%notfound;
insert into header_test values(cnt, valStr); -- for testing purpose
open valCur for select * from table(split_str(valStr,'~'));
loop
fetch valCur into val;
fetch headerCur into header;
exit when valCur%notfound;
exit when headerCur%notfound;
insert into header_test values(header, val);
headerTable(header):= val;
end loop;
idx := headerTable.FIRST; -- Get first element of array
WHILE idx IS NOT NULL LOOP
insert into header_test values (idx, headerTable(idx));
idx := headerTable.NEXT(idx); -- Get next element of array
END LOOP;
headerTable.delete;
end loop;
commit;
end;
c1 c2 ..... c19 are column name and RN is rownumber,
data for the columns of each will be in mainValString seperated by ;
Why i am getting ORA-14551 when i am trying to access collection "headerTable"?
Please help.
Problem is with this line.
idx := headerTable.FIRST;
The index of headertable is of TYPE VARCHAR2 whereas idx is defined as NUMBER.
declare idx as VARCHAR2(1000), it should work.
Having said that, ORA-14551 - Cannot perform DML ... is not related to this error. It is unclear to me why should you encounter this error.
Oh but it does:
EXCEPTION WHEN OTHERS THEN
v_msg:=sqlcode||sqlerrm;
insert into err_data_transfer values('SPLIT_STR',v_msg,sysdate,null);
It may only be during an exception, but it's still DML during a select statement. You may be able to create another procedure as an AUTONOMOUS_TRANSACTION to create the error log. Also, you should either re-raise or raise_application_error afterward. If not your procedure will continue as though the error did not occur; which leads to more problems as to why your main process does not work (including running to completion but doing the wrong thing).
I am trying to use Dynamic query in my code below but getting error (00900. 00000 - "invalid SQL statement"). Kindly suggest where i am mistaking in the code.
create or replace PROCEDURE CreateInsertTmpTable
AS
crttmp VARCHAR2(200);
intrtmp VARCHAR2(200);
printTableValues VARCHAR2(1000);
BEGIN
crttmp := 'CREATE GLOBAL TEMPORARY TABLE my_temp_table ON COMMIT PRESERVE ROWS AS SELECT * FROM VWBLKDATA WHERE 1=0';
EXECUTE IMMEDIATE crttmp;
intrtmp := 'INSERT INTO my_temp_table SELECT * FROM VWBLKDATA';
EXECUTE IMMEDIATE intrtmp;
printTableValues := ' for data in(SELECT * from my_temp_table)
loop
dbms_output.put_line(data.ID);
end loop';
EXECUTE IMMEDIATE printTableValues;
COMMIT;
END CreateInsertTmpTable;
I think you're overdoing the EXECUTE IMMEDIATE; you can run INSERT statements and PL/SQL without them like:
begin
for i in 1..10 loop
insert into test (some_column) values (to_char(i));
end loop;
end;
But anyways, it looks like you're last EXECUTE IMMEDIATE is trying to execute a partial PL/SQL anonymous block; it's missing a "begin" and "end;"
I would suggest just executing the for loop like so:
for data in (SELECT * from my_temp_table)
loop
dbms_output.put_line(data.ID);
end loop;
or else you'll need to add a begin/end around it in the text (and the "end loop" needs a trailing ";"):
printTableValues := 'begin
for data in (SELECT * from my_temp_table)
loop
dbms_output.put_line(data.ID);
end loop;
end;';
How to read array of n elements dynamically and display the elements in plsql.
Below is my code (i am new to plsql programming)
set serveroutput on
set verify on
declare
type myarray is table of number index by binary_integer;
x myarray;
i pls_integer;
n number;
begin
-- populate array
dbms_output.put_line('Enter number of array elements');
n := &n;
dbms_output.put_line('Enter elements one by one');
for i in 1..n loop
dbms_output.get_lines(&&x(i),n);
end loop;
i :=0;
-- print array
loop
i := i + 1;
begin
dbms_output.put_line(x(i));
exception
when no_data_found then exit;
end;
end loop;
end;
/
quit;
I stumble into the same issue and wanted to try something.
You could do this with a brutal approach that consists in using you shell to call the client (i.e. sqlplus) any time you want to add a value to your array. I wrote a little package that stores the values of the array in a table. Then you can change the way you store the values, but principle would remain the same:
-- this package uses "execute immediate" statements for everything
-- because it assumes the temporary table T_MY_ARRAY can be non-existent.
create or replace package my_array
authid current_user
is
TYPE a_TBL_Number IS TABLE OF Number INDEX BY BINARY_INTEGER;
procedure init;
procedure add(v in number);
procedure display;
function to_var return a_TBL_Number;
end my_array;
/
create or replace package body my_array
is
procedure init is
-- create table if needed, then make sure its empty;
begin
begin
execute immediate 'create table T_MY_ARRAY(c1 number)';
exception when others then
-- dbms_output.put_line(sqlerrm);
-- we're never sure the temp. table already exists. So create it and catch if existing.
null;
end;
execute immediate 'truncate table T_MY_ARRAY';
end init;
procedure add(v in number) is
-- add new value
begin
execute immediate 'insert into T_MY_ARRAY (c1) values ('||v||')';
end add;
function to_var return a_TBL_Number is
-- hand out an array with the values
t_TBL_n a_TBL_Number;
begin
execute immediate 'select c1 from T_MY_ARRAY ' bulk collect into t_TBL_n;
return t_TBL_n;
end to_var;
procedure display is
t_TBL_n a_TBL_Number;
begin
t_TBL_n:=my_array.to_var();
for i in 1..t_TBL_n.count loop
dbms_output.put_line(t_TBL_n(i));
end loop;
end display;
end my_array;
/
Then here is how you could call this from a shell (here it is for an old ksh):
read SQL_CONNECT?"Enter your SQL connection string:"
read n?"Enter number of array elements:"
# first initialize the array:
sqlplus -s ${SQL_CONNECT} <<_EOF
set serveroutput off
set termout off
set feedback off
begin
my_array.init;
end;
/
_EOF
# then loop on the given number of elements
typeset -i10 i=0
while [[ ${i} -lt ${n} ]]
do
i=i+1
echo iter:${i}
read MY_VALUE?"Enter elements one by one:"
sqlplus -s ${SQL_CONNECT} <<_EOF
set serveroutput off
set termout off
set feedback off
begin
my_array.add(${MY_VALUE});
end;
/
commit;
_EOF
done
# at the end, use stored values to display result:
sqlplus -s ${SQL_CONNECT} <<_EOF
set serveroutput on
set feedback off
begin
dbms_output.put_line('---------------');
dbms_output.put_line('your input was:');
my_array.display;
end;
/
_EOF
I have a stored procedure that has following pl/sql block. This block was using select query in for statement but i need to change that static variable to dynamic query. As I changed that it has error. Is there any way to use variable with FOR LOOP in implicit cursor.
declare
sql_query varchar2(32767) := 'select ctlchar ';
kpiNameQuery varchar2(600);
isWg boolean := true;
begin
IF isWG then
kpiNameQuery := 'select distinct KPI_NAME from weeklykpi where kpi_name in (select kpi_wg from auxillary.kpi_types) order by 1';
Else
kpiNameQuery := 'select distinct KPI_NAME from weeklykpi where kpi_name in (select kpi_wg1 from auxillary.kpi_types) order by 1';
End IF;
for KPI_NAME in kpiNameQuery
loop
sql_query := sql_query || ' , min(case when KPI_NAME = '''||x.KPI_NAME||''' then KPI_VALUE end) as '||x.KPI_NAME;
dbms_output.put_line(sql_query);
end loop;
end;
You can achieve similar functionality with the following using cursor
declare
type t_cursor is ref cursor;
c_cursor t_cursor;
l_sql varchar2(512);
l_var number;
begin
l_sql := 'select count(*) from emp'; -- do dynamic check before here for
-- correct sql
open c_cursor for l_sql;
loop
fetch c_cursor
into l_var;
exit When c_cursor%notfound;
DBMS_OUTPUT.put_line ('val '||l_var);
end loop;
close c_cursor;
end;
Unfotunately no, the doc states:
If the dynamic SQL statement is a SELECT statement that returns multiple rows, native dynamic SQL gives you these choices:
Use the EXECUTE IMMEDIATE statement with the BULK COLLECT INTO clause.
Use the OPEN FOR, FETCH, and CLOSE statements.
So you will have to use a REF cursor (or EXECUTE IMMEDIATE and loop over the results).
Incidentally, in your case you could go for static SQL and have comparable performance:
BEGIN
FOR cc IN (SELECT DISTINCT KPI_NAME
FROM weeklykpi
WHERE kpi_name IN (SELECT CASE WHEN l_variable = 1
THEN kpi_wg
ELSE kpi_wg1
END
FROM auxillary.kpi_types) LOOP
ORDER BY 1
-- do something
END LOOP;
END;
You'll have to use some other type than boolean though since it's unknown to SQL.
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