how to Pass table name to PL SQL cursor dynamically? - plsql

I have written one SQL Procedure where I have written one cursor and every time i have to pass table name to cursor query dynamically .
create or replace
PROCEDURE Add_DEN as
v_TableName VARCHAR2(4000) := 'BO_USER_DATA';
cursor c_DEN is select * from BO_USER_DATA; // Want to pass dynamically ,now hardcoded
r_DEN c_DEN%ROWTYPE;
fetch c_DEN into r_DEN;
v_Name := r_DEN."Name";
Can i write something like this
cursor c_DEN is "select * from " || v_TableName;
Any Help ?

here an example:
declare
TYPE curtype IS REF CURSOR;
l_cursor curtype;
l_param number;
l_key number;
l_value number;
l_sql varchar2(200);
begin
/* build your sql... */
l_sql := 'with data as (select 1 key, 100 value from dual union select 2, 200 from dual union select 3, 300 from dual union select 3, 301 from dual)' ||
' select key, value from data where key = :1';
l_param := 3;
open l_cursor for l_sql
using l_param;
loop
fetch l_cursor
into l_key, l_value;
exit when l_cursor%notfound;
dbms_output.put_line(l_key||' = '||l_value);
end loop;
close l_cursor;
end;
Result:
3 = 300
3 = 301

The basic answer is yes, you can and Given your example I would recommend you to use execute immediate to execute an arbitrary SQL string and bind the variables.
I would nevertheless reconsider if you really needed to dynamically set the table as this is not very often really needed.
Example:
DECLARE
sql_stmt VARCHAR2(200);
emp_id NUMBER(4) := 7566;
emp_rec emp%ROWTYPE;
BEGIN
sql_stmt := 'SELECT * FROM emp WHERE empno = :id';
EXECUTE IMMEDIATE sql_stmt INTO emp_rec USING emp_id;
END;

Related

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;

fetching a string and erase duplicates

I have a cursor called cACLsonDistribution that I fetch into the variable called ACLs.
The cursor is populated with a list of emails and sometimes those emails are duplicated.
LOOP
FETCH cACLsonDistribution INTO ACLs;
EXIT WHEN cACLsonDistribution%NOTFOUND;
ACLsonDistribution := ACLsonDistribution || ','|| ACLs.ENGINEER_EMAIL;
END LOOP;
This returns a string like this:
,bernardo.t#example.com,bernardo.t#example.com,felipe.t#example.com,jose.t#example.com
As you can see there 2 duplicates emails there. Sometimes there are 3 duplicates emails. What I would like to return the string without the duplicates. Like this:
,bernardo.t#example.com,felipe.t#example.com,jose.t#example.com
Try this simple illustration. Hope this helps.
SET SQLBL ON;
SET serveroutput ON;
DECLARE
PLST SYS_REFCURSOR;
AV VARCHAR2(100);
LV_VAR VARCHAR2(100);
VAR_TAB VARCHAR2(100);
BEGIN
OPEN PLST FOR SELECT 'avrajit.roy#abc.com' nm FROM DUAL
UNION ALL
SELECT 'avrajit.roy#abc.com' nm FROM DUAL
UNION ALL
SELECT 'shubhojit.roy#abc.com' nm FROM DUAL;
LOOP
FETCH plst INTO AV;
EXIT
WHEN PLST%NOTFOUND;
-- NULL;
lv_var:=lv_var||','||av;
END LOOP;
-- close
DBMS_OUTPUT.PUT_LINE(SUBSTR(LV_VAR,2,LENGTH(LV_VAR)));
SELECT WMSYS.WM_CONCAT(a.txt)
INTO var_tab
FROM
( SELECT DISTINCT(REGEXP_SUBSTR(SUBSTR(LV_VAR,2,LENGTH(LV_VAR)),'[^,]+', 1, level)) txt
FROM DUAL
CONNECT BY regexp_substr(SUBSTR(LV_VAR,2,LENGTH(LV_VAR)), '[^,]+', 1, level) IS NOT NULL
)a;
DBMS_OUTPUT.PUT_LINE(var_tab);
END;
----------------------------------OUTPUT----------------------------------------
avrajit.roy#abc.com,shubhojit.roy#abc.com
----------------------------------OUTPUT----------------------------------------

TOAD displaying cursor recordset returned by stored procedure

How I could print recorset result from the returning cursor, please.
Down below executes fine but I need to see result.
This is block in TOAD, calling package sp AMD_NEEDMSG:
DECLARE
RETURN_RECORDSET CTI_MATRIX.AMD.REF_CURSOR;
BEGIN
CTI_MATRIX.AMD.AMD_NEEDMSG ( '88888888885', RETURN_RECORDSET );
END;
This package spec:
CREATE OR REPLACE PACKAGE CTI_MATRIX.AMD AS
TYPE REF_CURSOR IS REF CURSOR;
PROCEDURE AMD_NEEDMSG (v_CRN IN VARCHAR2, return_recordset OUT REF_CURSOR);
END AMD;
This is package body:
CREATE OR REPLACE PACKAGE BODY CTI_MATRIX.AMD AS
PROCEDURE AMD_NEEDMSG (v_CRN IN VARCHAR2, return_recordset OUT REF_CURSOR) IS
return_flag INTEGER;
row_cnt INTEGER;
number_of_days INTEGER;
var_DATE DATE;
CURSOR ACCNTSEARCH (P_CRN IN VARCHAR2) IS
SELECT DISTINCT COUNT(*) , AMD.MSG_DATE
FROM TBL_AMD_NEEDMSG AMD
WHERE AMD.PHONE_NUMBER = P_CRN AND ROWNUM = 1;
BEGIN
OPEN ACCNTSEARCH(v_CRN);
FETCH ACCNTSEARCH INTO row_cnt, var_DATE;
CLOSE ACCNTSEARCH;
IF (row_cnt = 0)
THEN
INSERT INTO TBL_AMD_NEEDMSG (PHONE_NUMBER, MSG_DATE) VALUES (v_CRN , SYSDATE);
return_flag := 1;
ELSE
SELECT SYSDATE-var_DATE INTO number_of_days FROM dual;
IF (number_of_days>7)
THEN
UPDATE TBL_AMD_NEEDMSG SET MSG_DATE = SYSDATE WHERE PHONE_NUMBER = v_CRN;
return_flag := 1;
ELSE
return_flag := 0;
END IF;
END IF;
COMMIT;
OPEN return_recordset FOR
SELECT return_flag AS ReturnFLag FROM DUAL;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
END AMD_NEEDMSG;
END AMD;
/
Bottom line is to return to client a value of return_flag in the form of record set.
To return the return_flag, which is int=1 or 0 you need function that returns a single value, not recordset. Below are general recordset examples - hope this helps:
DECLARE
TYPE empcurtyp IS REF CURSOR;
emp_cv empcurtyp;
--
TYPE namelist IS TABLE OF scott.emp.ename%TYPE;
TYPE sallist IS TABLE OF scott.emp.sal%TYPE;
names namelist;
sals sallist;
BEGIN
OPEN emp_cv FOR
SELECT ename, sal FROM scott.emp
WHERE job = 'MANAGER' ORDER BY sal DESC;
--
FETCH emp_cv BULK COLLECT INTO names, sals;
CLOSE emp_cv;
-- loop through the names and sals collections
FOR i IN names.FIRST .. names.LAST LOOP
DBMS_OUTPUT.PUT_LINE
('Name = ' || names(i) || ', salary = ' || sals(i));
END LOOP;
END;
/
-- SYS_REFCURSOR example --
DECLARE
p_rc_type SYS_REFCURSOR;
emp_rec scott.emp%ROWTYPE;
--
PROCEDURE p_Emp_Info (p_cur_var OUT SYS_REFCURSOR)
IS
BEGIN
OPEN p_cur_var FOR
SELECT ename, job FROM scott.emp WHERE job = 'MANAGER';
LOOP
FETCH p_cur_var INTO emp_rec.ename, emp_rec.job;
EXIT WHEN p_cur_var%NOTFOUND;
dbms_output.put_line(emp_rec.ename ||' '|| emp_rec.job);
END LOOP;
CLOSE p_cur_var;
END p_Emp_Info;
--
BEGIN
p_Emp_Info(p_rc_type);
END;
/

opening implicit cursor with for loop

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.

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