Using two cursors in PL/SQL - plsql

DECLARE
AA NUMBER;
CURSOR S IS SELECT ENAME, SAL
FROM EMP;
CURSOR D IS SELECT ENAME, SAL
FROM EMP;
NAME EMP.ENAME%TYPE;
SALARY EMP.SAL%TYPE;
C NUMBER;
BEGIN
AA := :NUMBER_OF_EMP;
SELECT COUNT(EMPNO) INTO C FROM EMP;
OPEN S;
FOR A IN 1..AA LOOP
FETCH S INTO NAME, SALARY;
DBMS_OUTPUT.PUT_LINE('NAME : '||NAME||' SALARY :'||SALARY);
END LOOP;
CLOSE S;
DBMS_OUTPUT.PUT_LINE(' ');
OPEN D;
FOR A IN 1..AA LOOP
FETCH S INTO NAME, SALARY;
UPDATE EMP
SET SAL = SAL + 500;
DBMS_OUTPUT.PUT_LINE('NAME : '||NAME||' SALARY :'||SALARY);
END LOOP;
CLOSE D;
END;
i'm little confusing with cursor. in my code i wana display a name and salary of employees. For example, if the user put 3 in the first cursor will display just 3 employees with ( name and salary). the second cursor will do the same thing but it will change the salary for the same 3 employees and display it again.
ORA-01001: invalid cursor

The immediately cause of your error is the second FETCH statement. It fetches from cursor S but you closed that cursor above. Changing that to fetch from cursor D will correct the error condition.
But, your procedure has other problems.
The "select count(empno)..." is superfluous as you do nothing with the result.
The cursors S and D are the same and not open at the same time, so only one of them is necessary. It is fine to close then reopen the same cursor.
Whether you use 2 cursors or reuse just 1 there there is no guarantee the same rows are returned.
Your update will not do what you indicated you want. It will update the sal column of every row in the table the number of the loop is performed (in this case the number in AA). So if AA were 3 as for your example each row will have sal updated by 1500. This is because there is no where clause on the update. Therefore Oracle updates every row.

Related

Call a stored procedure in a trigger PL/SQL

Well I'm trying to call a stored procedure in my trigger.
The error I'm getting
"table %s.%s is mutating, trigger/function may not see it"
*Cause: A trigger (or a user defined plsql function that is referenced in
this statement) attempted to look at (or modify) a table that was
in the middle of being modified by the statement which fired it.
*Action: Rewrite the trigger (or function) so it does not read that table.
This is my code of the trigger:
create or replace TRIGGER check_salary_trg
AFTER INSERT OR UPDATE ON employees
FOR EACH ROW
BEGIN
DBMS_OUTPUT.PUT_LINE(:new.job_id ||' '|| :new.salary);
check_salary(:new.job_id, :new.salary);
END;
This is my stored procedure:
PROCEDURE check_salary (job_id employees.job_id%TYPE, salary employees.salary%TYPE)
IS
maxSal NUMBER;
minSal NUMBER;
BEGIN
SELECT MAX(salary) INTO maxSal FROM employees WHERE job_id = job_id;
SELECT MIN(salary) INTO minSal FROM employees WHERE job_id = job_id;
IF maxSal >= salary OR minSal <= salary THEN
RAISE_APPLICATION_ERROR(-20001,'Invalid slary '||salary||'. Salaries for job '||job_id||' must be between '||minSal||' and '||maxSal);
ELSE
DBMS_OUTPUT.PUT_LINE('Test');
END IF;
END;
This is how I try to see that the trigger is working:
UPDATE employees SET salary = 100000 WHERE employee_id = 100;
Somehow the DBMS_OUTPUT.PUT_LINE in my Trigger code is working. But the stored procedure causes the error.
You can't do SELECT or any other DML(INSERT,UPDATE,DELETE) on the table on which trigger is being fired.You have to use compound trigger to get away with mutating table error.
The procedure invoked by trigger is doing a SELECT on the employee table on which trigger is firing and that is forbidden by oracle.
A working example for same issue can be found under, refer UPDATE section Trigger selecting child records, multiplying their values and updating parent record
first of all, why do you query the same table twice in your procedures? that's a huge waste of resources... rather run
SELECT min(salary), max(salary) INTO minSal, maxSal
FROM employees
WHERE job_id = job_id
Second of all, you cannot query the same table you are update.ing !! That's a huge data (in)consistency issue. Why don't you run this in a package/procedure instead? That will give you way better control over your flow
Something like:
CREATE OR REPLACE PROCEDURE prcd_update_salary(p_emp_id INT, p_salary INT)
IS
maxSal INT;
minSal INT;
job_id INT;
BEGIN
SELECT job_id, min(salary), max(salary) INTO job_id, minSal, maxSal
FROM employees
WHERE job_id = (SELECT job_id FROM employees WHERE employee_id = p_emp_id);
IF (p_salary >= minSal AND p_salary <= maxSal) THEN
UPDATE employees SET salary = p_salary WHERE employee_id = p_emp_id;
ELSE
dbms_output.put_line('Sorry, this is out of range!')
dmbs_output.put_line('You can only use from '||minSal||' up to '||maxSal||' for a job id: '|| job_id);
END IF;
END;
This is, of course, only a sample code to give you hints on how to do that.. you have all the logic in one place and not in two diff objects (very hard to debug !!) ... in your production code you have to sanitize the input a maybe do a bit more checks and of course proper indexing is a must - but this pretty much summarizes what I would do :)

FOR UPDATE CURSOR in teradata?

This is sql block i'm using in Oracle,
Now I need to do the same way in Teradata, Is possible? I want the syntax FOR UPDATE CURSOR in Teradata!
Can you please guide me?
declare
cursor c1 is select * from Employees FOR UPDATE;
a number :=0 ;
begin
for x in c1 loop
a := a +1 ;
update employees set salary = a where current of c1;
end loop;
end;
Updateable cursors are allowed in ANSI-mode sessions only.
The syntax is quite similar:
declare c1 cursor for
select * from Employees FOR UPDATE;
a number :=0 ;
begin
for x in c1 loop
a := a +1 ;
update employees set salary = a where current of c1;
end loop;
end;
But cursors perform really bad in a parallel DBMS like Teradata as they're processed serially, one row after the other.
In almost every case cursors on data can be rewritten set-based (e.g. your example is a simple ROW_NUMBER) and then they perform several orders of magnitude faster.
Maybe you could use this instead?
UPDATE employees
FROM (
SELECT csum(1,1) new_salary, emp_id
FROM employees
) src
set salary=src.new_salary
where employees.emp_id=src.emp_id;

How to restrict the inserting a record through a oracle trigger

I want to restrict the records which are inserting into table a where i am creating trigger on that of before insert.
I have a scenario
Table A
id number
id1 number
Table B
id number
Table c
id number
Table D
id number
id1 number
My code:
create or replace trigger trg_a --trigger name
before insert on a
for each row
declare
v_id b.id%type;
v_id1 c.id%type;
pragma autonomous_transaction;
begin
select id into v_id from b where id=:NEW.id;
select id into v_id1 from c where id=:NEW.id;
if(:NEW.id=v_id or :NEW.id=v_id1) then
insert into d
(id,id1,name)
values(:NEW.id,:NEW.id1,:NEW.name);
end if;
delete from a where id =v_id or id 1=v_id1 ; --matched id
commit;
end; --end statement
It is inserting for matching records with the look up tables i.e. b, c and parallel y also inserting into table a. This record insertion need to restrict so I have kept delete statement but it is not working.
You are trying to do this in separate transaction. I don't see the reason for this and this is also causing your code doesn't work as desired.
Probably you didn't commit after each insert, so when new transaction was started inside trigger body it didn't see any records in table A to be deleted.
I left out pragma autonomous_transaction and commit; and now it deletes records from table as you expected.
(Note that I made some changes to your code, because it was unable to compile, due to non existent NAME column and id 1)
create or replace trigger trg_a --trigger name
before insert on a
for each row
declare
v_id b.id%type;
v_id1 c.id%type;
--pragma autonomous_transaction;
begin
select id into v_id from b where id=:NEW.id;
select id into v_id1 from c where id=:NEW.id;
if(:NEW.id=v_id or :NEW.id=v_id1) then
insert into d
(id,id1)
values(:NEW.id,:NEW.id1);
end if;
delete from a where id =v_id or id1=v_id1 ; --matched id
--commit;
end;
/
So either commit after each insert or ommit autonomous transaction.
UPDATE:
Now you made it clear, that you don't want to insert into table a at all. Since INSTEAD OF trigger works only on views, I fear that only way how to achieve this it to cause runtime exception, so that Insert into A will be not succesfull however INSERT into table D will succeed. Look at this, on purpose it causes on insert PL/SQL numeric or value error, when assigning varchar value to number columns
create or replace trigger trg_a --trigger name
before insert on a
for each row
declare
v_id b.id%type;
v_id1 c.id%type;
pragma autonomous_transaction;
begin
select id into v_id from b where id=:NEW.id;
select id into v_id1 from c where id=:NEW.id;
if(:NEW.id=v_id or :NEW.id=v_id1) then
insert into d
(id,id1)
values(:NEW.id,:NEW.id1);
end if;
delete from a where id =v_id or id1=v_id1 ; --matched id
commit;
:NEW.id:=null;
:NEW.id1:='a';
end;
/
If you have 11g or later, you may want to look at using a compound trigger.
Create Or Replace Trigger Trg_A
For Insert On A
Compound Trigger
Type Id_T Is Table Of A.Id%type Index By Simple_Integer;
Id_List Id_T;
Id_ndx simple_integer := 0;
Id_Count Integer;
After Each Row Is Begin
-- If the same id is in Table B or Table C
Select Count(*) Into Id_Count From B Where Id = :New.ID;
If Id_Count = 0 Then
-- Only check C if nothing in B
Select Count(*) Into Id_Count From C Where Id = :New.ID;
End If;
If Id_Count > 0 Then
-- If already in B or C, log to D
Insert Into D( Id, Id1, Name )
Values( :New.ID, :New.ID, :New.Name);
-- Then save for later deletion from A
Id_ndx := Id_ndx + 1;
Id_List( Id_ndx ) := :New.Id;
End If;
End After Each Row;
After Statement Is Begin
Forall Idx In 1 .. Id_ndx
-- Delete saved IDs (if any)
Delete From A Where Id = Id_List( Idx );
Id_List.Delete();
Id_ndx := 0;
End After Statement;
End;
The problem is that it is tricky using an autonomous transaction pragma in a compound trigger. It may not even be possible, I just don't have any experience with that pragma.
However, several people have mentioned "instead of" triggers on views. When I design a database, almost all DML goes through views, many of those views exist for no other reason. Many times a table will have multiple views so DML can take place no matter how the data is being accessed. I can't begin to tell you how many problems this solves. So you may want to take a serious look at that option.

Get two records from PLSQL cursor

My question is short. I create a cursor to get some values from my table. I want to get the current record of cursor (the fetched record) and the next record from the cursor without fetching it, because I want to make a calculation over the current record and the next record. In traditional programming it's a simple operation; you can do it with a for index by increasing it by 1.
Do you have any suggestions?
I want to make a calculation over the
current record and the next record.
Presuming you are using Oracle, this can be done quite simply in SQL using analtyical functions, specifically the lead() function. This retrieves a column's value from the next nth record (default n=1).
SQL> select empno
2 , sal as this_sal
3 , lead(sal) over (order by empno) as next_sal
4 from emp
5 order by empno
6 /
EMPNO THIS_SAL NEXT_SAL
---------- ---------- ----------
7369 800 1600
7499 1600 1250
7521 1250 2975
7566 2975 1250
7654 1250 2850
7698 2850 2450
....
This query can be using in a cursor or any other mechanism for retrieving records.
I'm not sure you can do that without moving the cursor, but you should be able to accomplish the same goal like this (psuedocode):
open cursor;
fetch cursor to rec1;
if cursor%found then
loop
fetch cursor to rec2;
exit when cursor%notfound;
perform calculations with rec1 and rec2;
rec1 := rec2;
end loop;
end if;
To answer you specific question, you could use BULK COLLECT and its limit clause into a collection (table) variable.
DECLARE
CURSOR my_cursor IS
[YOUR SQL HERE];
TYPE t_my_type IS TABLE OF my_cursor%ROWTYPE INDEX BY BINARY_INTEGER;
v_my_var t_my_type;
BEGIN
FETCH my_cursor BULK COLLECT INTO v_my_var LIMIT 2;
dbms_output.put_line('My first value: ' || v_my_var(1).some_column);
dbms_output.put_line('My second value: ' || v_my_var(2).some_column);
END;
The limit clause will cause only the first two records to be fetched and stored. The first record will have an index of 1 and the second will be 2.
You can compare two variables by saving one into local and then compare it with cursor value for example
declare
l_local1 table_name%type := null;
begin
for c1 in (select
*
from
table_name
where some clause)
loop
-- do comparation
if l_local1 is not null and c1.field_value is not null then
-- do something to compare
.
.
.
-- empty local1 variable
l_local1 := null;
end if;
-- get the value of loop in local variable
l_local1 := c1.field_value;
end loop;
end;

plsql cursor iterating problem

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;

Resources