opening implicit cursor with for loop - plsql

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.

Related

How to use the for loop in fetching Id's from a rows in a table to be used by a procedure in PLSQL?

This is my code below I get this error(Error at line 24/8: ORA-06550: line 20, column 12:PLS-00201: identifier 'A.ID' must be declared) as shown in the image below when I try running the code. Please how can I write the plsql code properly(using for loop) to fetch each row ID and pass them to the procedure?
BEGIN
DECLARE
p_id number(30);
p_status varchar(20);
BEGIN
for c in (
SELECT
a.ID,
a.STATUS
INTO
p_id,
p_status
from USER_COMMISSIONS a,
order_line b where a.order_line_id=b.id and a.status= 'unconfirmed'
)
LOOP
begin
p_id := a.ID;
p_status := a.STATUS;
EXCEPTION
WHEN NO_DATA_FOUND THEN
NULL;
end;
-- update pstk_payload set status = 'done' where id = pyld_id;
dbms_output.put_line(p_id);
-- PSTK_PAYMENT_PACKAGE.add_payment(p_amt, p_user_id, p_reference, p_name, p_narration, p_payment_date, p_net_amt, p_payment_type_id, p_transaction_type_id, p_payment_id, p_status);
END LOOP;
end;
END;
There's nothing to declare, actually - everything you need (at least, in code you posted and that's not commented) is contained in cursor itself.
As William commented, you need to reference columns with the cursor name (not tables that are their source).
Also, no need for any exception handler; cursor certainly won't return no_data_found; if its select doesn't return anything the only "consequence" will be that none of commands within the loop will be executed.
If you're joining tables, then use JOIN; leave where clause for conditions (if any).
Therefore:
begin
for c in (select a.id,
a.status
from user_commissions a join order_line b on a.order_line_id = b.id
where a.status= 'unconfirmed'
)
loop
dbms_output.put_line(c.id ||', '|| c.status);
end loop;
end;

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;

oracle plsql procedure dynamic count of the tables in cursor loop

I want to update all the tables having ABC column.Need to skip the tables which doesn't have data.I am having problem in checking the count of the table in a cursor loop.
PLSQL code
create or replace procedure testp is
CURSOR c_testp
IS
SELECT table_name,
column_name
FROM all_tab_columns
WHERE column_name IN('ABC')
ORDER BY table_name;
c int;
BEGIN
FOR table_rec IN c_testp
LOOP
BEGIN
SELECT COUNT(*)
INTO c
FROM table_rec.table_name;
IF(c>0) THEN
query := 'update '||table_rec.table_name||' set '||table_rec.column_name ||'= xyz';
EXECUTE IMMEDIATE query;
COMMIT;
END IF;
EXCEPTION
WHEN no_data_found THEN
dbms_output.put_line('data not found');
WHEN OTHERS THEN
dbms_output.put_line('others');
END;
END LOOP;
END;
In your code, use this:
EXECUTE IMMEDIATE 'SELECT count(*) FROM ' || table_rec.table_name INTO c;
instead of this:
SELECT COUNT(*)
INTO c
FROM table_rec.table_name;
However, as mentioned in comments - there is actually no need to perform that condition check, as no update will be performed when table is empty.

how to Pass table name to PL SQL cursor dynamically?

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;

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