plsql cursor iterating problem - plsql

i use oracle demo schema scott to do some plsql test ( the data in that schema are never changed ). i wrote the following program to get the employee number of each department. the problem is, there is just 4 departments but my program output 5 row. i can't find out the reason, anyone can help? great thanks.
declare
cursor employees(department_id number) is
select count(*) howmany
from scott.emp
where deptno=department_id;
employees_per_dept employees%rowtype;
cursor departments is
select *
from scott.dept;
a_department departments%rowtype;
begin
dbms_output.put_line('-----------------------------------');
open departments;
loop
exit when departments%notfound;
fetch departments into a_department;
open employees(a_department.deptno);
fetch employees into employees_per_dept;
dbms_output.put_line(employees_per_dept.howmany);
close employees;
end loop;
close departments;
dbms_output.put_line('-----------------------------------');
end;

If you output the deptno in the dbms_output you'll see the reason.
You need to switch these two lines:
fetch departments into a_department;
exit when departments%notfound;
%NOTFOUND is meaningless before the initial FETCH; your code was counting the emps in the last dept twice.

declare
cursor cl(ccode varchar2) is
Select * from employees where department_id=ccode;
z cl%rowtype;
cnt number:=0;
begin
Open cl('90');
fetch cl into Z;
while (cl%found) loop
dbms_output.put_line ( 'nsme is ' || z.last_name);
fetch cl into Z;
cnt := cnt +1;
end loop;
dbms_output.put_line (cnt);
close cl;
end;

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;

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

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.

PL/SQL Cursor just check if value is greater than a value

I want
to make a procedure check if exist empno from table employees
with emno greater than 100. If exist at least one, i want
to exit from the loop.
How can I modify the following code ?
Is it problem I don;t use %NOTFOUND , %ROWCOUNT ?
CREATE OR REPLACE procedure check_value
IS
cursor c1 is
select *
from employess;
c1_values c1%ROWTYPE;
BEGIN
open c1;
fetch c1 into c1_values;
loop
if c1_values.EMPNO > 100 then
DBMS_OUTPUT.put_line ('Found row with empno > 100');
end if;
end loop;
close c1;
END;
If you just need to check if you have a record which has empno over 100 you can use EXISTS statement e.g.
DECLARE
CURSOR c1 IS
SELECT
CASE
WHEN EXISTS (
SELECT
1
FROM
employees
WHERE
empno > 100
) THEN 1
ELSE 0
END AS empno_exists
FROM
dual;
ln_empno_exists PLS_INTEGER;
BEGIN
OPEN c1;
FETCH c1 INTO ln_empno_exists;
CLOSE c1;
DBMS_OUTPUT.PUT_LINE('Empno over 100 exists: '||CASE WHEN ln_empno_exists = 1 THEN 'TRUE' ELSE 'FALSE' END);
END;
/
EDIT: If you want to fetch the rows with some conditions, you simply need to adjust your WHERE clause in your SELECT statement. Here you have an example with some ways to iterate through the records fetched:
DECLARE
CURSOR c1 IS
SELECT
emp.*
FROM
employees emp
WHERE
empno > 100
;
lr_c1_rec c1%ROWTYPE;
BEGIN
-- Using FOR loop
DBMS_OUTPUT.PUT_LINE('START: Printing employees records where empno > 100');
FOR rec IN c1
LOOP
DBMS_OUTPUT.PUT_LINE('empno = '||rec.empno);
END LOOP;
DBMS_OUTPUT.PUT_LINE('END: Printing employees records where empno > 100');
-- Using a LOOP with EXIT clause
DBMS_OUTPUT.PUT_LINE('START: Printing employees records where empno > 100');
OPEN c1;
LOOP
FETCH c1 INTO lr_c1_rec;
-- exit the loop when your cursor doesn't have any more records to be returned
EXIT WHEN c1%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('empno = '||lr_c1_rec.empno);
END LOOP;
DBMS_OUTPUT.PUT_LINE('END: Printing employees records where empno > 100');
-- Using WHILE loop
DBMS_OUTPUT.PUT_LINE('START: Printing employees records where empno > 100');
OPEN c1;
FETCH c1 INTO lr_c1_rec;
-- As long as cursor returns any values, iterate through the records returned
WHILE c1%FOUND
LOOP
DBMS_OUTPUT.PUT_LINE('empno = '||lr_c1_rec.empno);
END LOOP;
DBMS_OUTPUT.PUT_LINE('END: Printing employees records where empno > 100');
END;
/
Just add an EXIT statement after you've found your value:
CREATE OR REPLACE procedure check_value IS
cursor c1 is
select *
from employess;
c1_values c1%ROWTYPE;
BEGIN
open c1;
loop
fetch c1 into c1_values;
IF c1%NOTFOUND THEN EXIT;
if c1_values.EMPNO > 100 then
DBMS_OUTPUT.put_line ('Found row with empno > 100');
EXIT;
end if;
end loop;
-- The EXIT statement will drop you out of the loop and leave you here
close c1;
END;
Note that I moved the fetch inside the loop and added a %NOTFOUND check. Without the %NOTFOUND the code would never know the cursor was out of data and would probably go into an infinite loop.
Why to define cursor and then open and fetch when it can be done by just
SQL But if it is required so then use FOR loop and EXIT condition
as mentioned
below. Hope it helps
CREATE OR REPLACE PROCEDURE check_value
IS
BEGIN
FOR I IN
(SELECT * FROM employess
)
LOOP
IF I.EMPNO > 5 THEN
DBMS_OUTPUT.put_line ('Found row with empno > 100');
EXIT;
END IF;
END LOOP;
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