PLSQL: edit cursor at runtime - plsql

I'm a bit confused
is it possible in a function/proc something like this?
CURSOR CR IS
SELECT
FROM
WHERE Y = X
and later in the body, change the X value into Z or something else, and then re-open the cursor so it fetches the rows with the Y = Z?

There are a couple of ways to do this without resorting to dynamic SQL. One option would be to use a parameterized cursor:
DECLARE
nSome_value NUMBER := 666;
CURSOR CR(parmSome_value NUMBER) IS
SELECT *
FROM SOME_TABLE
WHERE SOME_COLUMN = parmSome_value;
BEGIN
nSome_value := 123; -- change value of nSome_value
OPEN CR(nSome_value); -- pass nSome_value in as the value of the cursor parameter
-- Fetch from the cursor, do whatever
CLOSE CR;
END;
It's still static SQL but by passing a parameter to the cursor you increase the reusability of the cursor.
Another option is to use a cursor FOR loop, referencing the variable in the loop's SQL:
DECLARE
nSome_value NUMBER := 666;
BEGIN
nSome_value := 123; -- change value of nSome_value
FOR aRow IN (SELECT *
FROM SOME_TABLE
WHERE SOME_COLUMN = nSome_value)
BEGIN
-- Do something useful with the rows returned by the cursor
END LOOP;
END;
Note that in these cases you're not changing the SQL - you're just changing the values of the variable or parameter used in the query. One advantage of these approaches is that unlike dynamically generated SQL they're not vulnerable to SQL injection attacks.
Share and enjoy.

Yes, try something like this (typed quickly so syntax might not be exact)
sql VARCHAR2(255);
cur REF CURSOR;
val varchar2(100);
val := X;
sql := 'SELECT .. FROM .. WHERE Y = :val';
open cur for
sql
USING val;
close cur;
....
val := Z;
open cur for
sql
USING val;

This can also be done by using a subprograms like function instead of using a cursor as it
wil shorten the code :
create or replace function getashish(dept varchar2) return emp3 as
emp5 emp3 := emp3();
str varchar2(300);
begin
str := 'select emp1(e.last_name,l.city,e.salary) from employees e join departments d
on e.department_id = d.department_id join locations l on d.location_id=l.location_id where
d.department_name = :dept';
execute immediate str
bulk collect into emp5
using dept;
return emp5;
end;
here you have to create a object including the return types yoy want in the return and then create and table of that object:
create or replace type emp1 as object (lname varchar2(10),city varchar2(10),sal number(10));
and
CREATE OR REPLACE
type emp3 as table of emp1;

Related

FORALL loop - select statement from associative array

I have a function, which accepts associative array as parameters. Function - before insert/update - needs to check if records exists in table, so I need to loop over array.
Since FORALL doesn't allow SELECT statement, what is the right way to do this ?
Function (just the part I need to fix - It's an example!!):
type t_name is table of MySchema.Orders.NAME%type index by pls_integer;
type t_year is table of MySchema.Orders.YEAR%type index by pls_integer;
type t_month is table of MySchema.Orders.MONTH%type index by pls_integer;
FUNCTION Check_records (name_in IN t_name, year_in IN t_year, month_in IN t_month) RETURN INTEGER
IS
record_exists INTEGER;
BEGIN
FORALL i in name_in.FIRST..name_in.LAST
SELECT COUNT(*) INTO record_exists
FROM MySchema.Orders#link_to_table
WHERE name= name_in(i)
AND year= year_in(i)
AND month= month_in(i);
return record_exist
END Check_records;
The simplest approach would simply be to use a for loop
DECLARE
l_total_records pls_integer;
l_count pls_integer;
BEGIN
l_total_records := 0;
FOR i in name_in.FIRST..name_in.LAST
LOOP
SELECT COUNT(*)
INTO l_count
FROM MySchema.Orders#link_to_table
WHERE name= name_in(i)
AND year= year_in(i)
AND month= month_in(i);
l_total_count := l_total_count + l_count;
END LOOP;
END;
If you were querying a local table and you could use a single collection (i.e. a collection of an object type with name, year, and month fields), you could do something more elegant with a MEMBER OF function but I'm not sure that works over a database link and don't have one available to test at the moment.

Can not perform DML Operation inside a query? While trying to fetch data from collection

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).

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;

How to stop updating null values in oracle

I have a sql procedure which perfectly works. please find it below.
declare
cid number;
cadd number;
ctras number;
pr varchar(2);
vad number;
cursor c1 IS
select ac_tras, cust_id, cust_addr from customer_master;
cursor c2 IS
select pr_adr from customer_address where cust_id = cid and cust_addr = cadd;
BEGIN
open c1;
LOOP
fetch c1 into ctras, cid, cadd;
EXIT WHEN C1%NOTFOUND;
OPEN c2;
LOOP
fetch c2 into pr;
if pr='Y'
THEN EXIT ;
ELSE
UPDATE customer_master
set cust_addr = (select cust_addr from customer_address where pr_adr = 'Y' and cust_id = cid) where ac_tras = ctras;
END IF;
EXIT WHEN C2%NOTFOUND;
END LOOP;
Close C2;
END LOOP;
CLOSE C1;
END;
Everything works fine. The problem is, The update statement updates null if the sub query returns null. How to avoid this.
If the subquery doesn't find a matching row then the master table will be updated with null, because ther eis no filter to stop that. A common way to avoid that is to check that a matching row does exist:
UPDATE customer_master
set cust_addr = (
select cust_addr from customer_address
where pr_adr = 'Y' and cust_id = cid)
where ac_tras = ctras
and exists (
select cust_addr from customer_address
where pr_adr = 'Y' and cust_id = cid)
;
It doesn't really matter which column name you use in the exists clause; some people prefer to use select * or select null but it seems to be a matter of taste really (unless you specify a column you aren't going to be using later and which can't be retrieved from an index you're using anyway, which could force an otherwise unnecessary table row lookup).
You could also do a merge. And has been pointed out several times now, you don't need cursors or any PL/SQL to do this.

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

Resources