In order to solve the mutating table error, I followed http://docs.oracle.com/cd/E11882_01/appdev.112/e25519/triggers.htm#LNPLS2005, but still it isn't working. Can some help to identify where is the problem of the following trigger?
Thanks,
CREATE OR REPLACE TRIGGER trg_d_inq_2 FOR
UPDATE OF hu1_dimension_lvl1
ON d_inq_dimensions
COMPOUND TRIGGER
v_exists_d NUMBER;
v_exists_c NUMBER;
TYPE process_t IS TABLE OF d_inq_dimensions.dimension_value%TYPE;
process process_t;
TYPE process_pvoc_t IS TABLE OF NUMBER
INDEX BY VARCHAR2(100 BYTE);
process_pvoc process_pvoc_t;
TYPE nprocessvoc_t IS TABLE OF NUMBER;
nprocessvoc nprocessvoc_t;
BEFORE EACH ROW
IS
BEGIN
SELECT a.hu1_dimension_lvl1, COUNT(a.hu1_dimension_lvl1)
BULK COLLECT INTO process, nprocessvoc
FROM d_inq_dimensions a
WHERE a.dimension_name = 'Processo'
GROUP BY a.hu1_dimension_lvl1;
FOR j IN 1 .. process.COUNT
LOOP
process_pvoc(process(j)) := nprocessvoc(j);
END LOOP;
END
BEFORE EACH ROW;
AFTER EACH ROW
IS
BEGIN
IF :new.hu1_dimension_lvl1 IS NOT NULL AND
:new.dimension_name = 'Processo'
THEN
IF process_pvoc(:old.hu1_dimension_lvl1) IS NULL
THEN
DELETE external.c_parameters
WHERE proj_id = 79 AND
ind_id = 53 AND
lvl_2 = :old.hu1_dimension_lvl1;
END IF;
SELECT COUNT(1)
INTO v_exists_c
FROM external.c_parameters a
WHERE proj_id = 79 AND
ind_id = 53 AND
lvl_2 = :new.hu1_dimension_lvl1;
IF v_exists_c = 0
THEN
INSERT INTO external.c_parameters
/*and something more*/
END IF;
END IF;
END
AFTER EACH ROW;
END;
Use after statement instead of BEFORE EACH ROW.
Related
Using Postgres10.
I'm having an issue where I am calling a stored procedure and expecting a specific value for my OUT param and I am not getting it. I am calling the Items stored procedure with the call code below.
PROBLEM
I expect the first time I call the Item stored procedure to get an insert with a rtn value of 1 but I get a 4... This means the IF EXISTS is finding a row in the table with the same name but my table is empty.
I am expecting there is something weird going on where the IF EXISTS statement is being re-evaluated after the INSERT statement and entering the block where rtn gets set to 4. Is this something to do with plpgsql? It's acting as if the order of the stored procedure is not going top to bottom always when I put in Raise commands to test values at certain points.
SCHEMA/TABLE
CREATE TABLE aips.Item (
ItemPk SERIAL PRIMARY KEY,
Name VARCHAR(100) NOT NULL,
CONSTRAINT UNI_Item_Name UNIQUE(Name)
);
STORED PROCEDURE
CREATE OR REPLACE FUNCTION aips.Item(
INOUT p_ItemPk INT,
INOUT p_Name VARCHAR(100),
OUT rtn INT
) AS
$$
DECLARE rowcnt INT;
BEGIN
-- Insert or Find Path
IF p_ItemPk IS NULL THEN
-- Check for Find
IF EXISTS (SELECT * FROM aips.Item where Name = p_Name) THEN
SELECT ItemPk, Name
INTO p_ItemPk, p_Name
FROM aips.Item
WHERE Name = p_Name;
rtn := 4;
RETURN;
END IF;
-- Perform insert
INSERT INTO aips.Item (Name)
VALUES (p_Name)
RETURNING ItemPk INTO p_ItemPk;
GET DIAGNOSTICS rowcnt = ROW_COUNT;
IF rowcnt = 1 THEN
rtn := 1;
ELSE
rtn := 0;
RAISE EXCEPTION 'Expecting to insert a single row and rows returned --> %', rowcnt;
END IF;
ELSE -- Update or No Operation Path
-- Check for no changes
IF EXISTS (SELECT ItemPk
FROM aips.Item
WHERE ItemPk = p_ItemPk
AND Name = p_Name) THEN
rtn := 5;
RETURN;
END IF;
-- Perform Update
UPDATE aips.Item
SET Name = p_Name
WHERE ItemPk = p_ItemPk;
GET DIAGNOSTICS rowcnt = ROW_COUNT;
IF rowcnt = 1 THEN
rtn := 2;
ELSE
rtn := 0;
RAISE EXCEPTION 'Expecting to update a single row and rows returned --> %', rowcnt;
END IF;
END IF;
RETURN;
END;
$$ LANGUAGE plpgsql;
CALL
select (aips.Item(NULL, 'Test 1')).*;
The problem is the way you call the function:
select (aips.Item(NULL, 'Test 1')).*; -- WRONG!
because it is executed three times, once for each output column. The function should be called in the FROM clause:
select * from aips.Item(NULL, 'Test 1');
I have an trigger that needs to read from a table after deleting a row. Essentially, I need to count up the remaining rows that are similar to the current row, and if that count is zero, update a field elsewhere.
After two days of hammering around, I haven't been able to figure out how to restructure my thought process to allow me to do this. Here is an example:
CREATE OR REPLACE TRIGGER Di_PatMustBeWell
AFTER DELETE
ON Diagnosis
FOR EACH ROW
Declare
--PRAGMA AUTONOMOUS_TRANSACTION;
NumDiseases Number;
BEGIN
SELECT NUMDISEASES INTO Numdiseases
FROM DiagnosisCount
where Di_Patient = :OLD.Di_Patient;
IF( NumDiseases != 1 ) THEN
UPDATE Patient SET Pat_Sick = 0 WHERE Pat_Person = :OLD.Di_Patient;
END IF;
END;
/
Short answer - no trigger, no mutating.
Yow can use the trigger with pragma autonomous_transaction for counting of remaining diagnoses for certain patient, but it's is not recommended way to do this.
Better you create new function or procedure to implement your logic on deleted diagnosis. Something like this:
create table Diagnosis as select 456 idDiseases, 123 di_patient from dual;
/
create table diagnosisCount as select 1 numDiseases, 123 di_patient from dual;
/
create table Patient as select 123 Pat_Person, 1 Pat_Sick from dual;
/
drop trigger di_patmustbewell;
create or replace function deleteDiagnosis(idDiseases number) return number is
rows_ number;
di_patient number;
Numdiseases number;
begin
<<del>> begin
delete Diagnosis where IdDiseases = deleteDiagnosis.IdDiseases
returning Diagnosis.di_patient into deleteDiagnosis.di_patient
;
rows_ := sql%rowcount;
if rows_ != 1 then raise too_many_rows; end if;
end del;
select count(1) into deleteDiagnosis.numDiseases from Diagnosis where Di_Patient = deleteDiagnosis.di_patient;
if deleteDiagnosis.numdiseases = 0 then <<upd>> begin
update Patient set Pat_Sick = 0 where Pat_Person = deleteDiagnosis.di_patient;
exception when others then
dbms_output.put_line('Cannot update Patient di_patient='||di_patient);
raise;
end upd; end if;
return rows_;
end;
/
show errors
declare rows_ number := deleteDiagnosis(456);
begin dbms_output.put_line('deleted '||rows_||' rows'); end;
/
deleted 1 rows
select * from Patient;
PAT_PERSON PAT_SICK
---------- ----------
123 0
An alternative solution, if you prefer (or must) to use a trigger in your application - declare internal function returning count of patient's diagnoses in the trigger body:
create or replace trigger di_patmustbewell
after delete on diagnosis for each row
declare
numdiseases number;
function getNumDiagnosis (di_patient number) return number is
ret number;
pragma autonomous_transaction;
begin
select count(1) into ret from diagnosis where di_patient = getNumDiagnosis.di_patient;
return ret;
end getNumDiagnosis;
begin
numDiseases := getNumDiagnosis(:old.di_patient);
if(numdiseases = 0) then
update patient set pat_sick = 0 where pat_person = :old.di_patient;
end if;
end;
/
show errors;
Trigger DI_PATMUSTBEWELL compiled
Hope it helps you a bit.
You can create a COMPOUND trigger for such cases:
create or replace TRIGGER Di_PatMustBeWell
FOR DELETE ON Diagnosis
COMPOUND TRIGGER
TYPE Di_Patient_Table_type IS TABLE OF DiagnosisCount.Di_Patient%TYPE;
Di_Patient_Table Di_Patient_Table_type;
BEFORE STATEMENT IS
BEGIN
Di_Patient_Table := Di_Patient_Table_type();
END BEFORE STATEMENT;
BEFORE EACH ROW IS
BEGIN
Di_Patient_Table.EXTEND;
Di_Patient_Table(Di_Patient_Table.LAST) := :OLD.Di_Patient;
END BEFORE EACH ROW;
AFTER STATEMENT IS
BEGIN
FOR i IN Di_Patient_Table.FIRST..Di_Patient_Table.LAST LOOP
SELECT NUMDISEASES INTO Numdiseases
FROM DiagnosisCount
where Di_Patient = Di_Patient_Table(i);
IF NumDiseases != 1 THEN
UPDATE Patient SET Pat_Sick = 0 WHERE Pat_Person = Di_Patient_Table(i);
END IF;
END LOOP;
Di_Patient_Table.DELETE;
END AFTER STATEMENT;
END;
/
The best way I can think to explain this is: I'm trying to give each robot a unique id. Then, when I run the procedure, I only have to give the robot a name and id. The status are generated for me.
However, I am reaching a total roadblock because I can't find out the proper way to do this. I've tried counting the rows and putting it into tempid. That doesn't work. A new row is made every time I run it. So, I should always get a new id. I might have done it wrong though.
I get this error:
Error(23,44): PL/SQL: ORA-00984: column not allowed here
Here is the procedure:
CREATE OR replace PROCEDURE Checkbot
(nameinput IN VARCHAR2)
IS
partfound NUMBER(4);
foundall BOOLEAN;
tempid NUMBER;
BEGIN
--Where I am having issues:
SELECT Count(*)
INTO tempid
FROM robotinventory;
INSERT INTO robotinventory VALUES (tempid, nameinput, NULL);
foundall := TRUE;
FOR i IN 1..8 LOOP
partfound := 0;
SELECT Max(prtserial)
INTO partfound
FROM partinventory
WHERE parttypeid = i;
IF partfound > 0 THEN
DELETE FROM partinventory
WHERE prtserial = partfound;
INSERT INTO robotprt VALUES (tempid, idinput, i);
ELSE
foundall := FALSE;
END IF;
END LOOP;
IF foundall THEN
UPDATE robotinventory
SET status = 'ready for assembly'
WHERE robotid = tempid;
ELSE
UPDATE robotinventory
SET status = 'waiting on parts'
WHERE robotid = tempid;
END IF;
END;/
robot inventory table:
CREATE TABLE RobotInventory
(RobotID Number(4) PRIMARY KEY,
RobotName VARCHAR2(24),
Status VARCHAR2(64));
Previous version of my code, took out what I did wrong in mine, my goal is to replace idInput with an auto incrementing number:
create or replace procedure checkbot
(idInput in number, nameInput in varchar2)
is
partFound number(4);
foundAll boolean;
begin
insert into robotInventory values (idInput, nameInput, null);
foundAll := true;
for i in 1..8 loop
partFound := 0;
select max(prtSerial)
into partFound
from partInventory
where ParttypeID = i;
if partFound > 0 then
delete from partInventory
where prtSerial = partFound;
insert into robotPrt values (partFound, idInput, i);
else
foundAll := false;
end if;
end loop;
if foundAll then
update robotInventory
set status = 'ready for assembly'
where robotID = idInput;
else
update robotInventory
set status = 'waiting on parts'
where robotID = idInput;
end if;
END;
/
Create a sequence (called seq below) and use it in your INSERT SQL to populate robot_id.
INSERT INTO robotinventory VALUES (seq.nextval, nameinput, NULL);
Look at Oracle docs on how to create a sequence in more detail. Here is an example that you can change to fit your needs.
create sequence seq
start with 1
increment by 1
maxvalue 9999;
HTH
I am trying to make a Library Information System. I have a table called Borrower(borrower_id: number, name: varchar2(30), status: varchar2(20)). 'status' can be either 'student' or 'faculty'.
I have a restriction that a maximum of 2 books can be issued to a student at any point of time, and 3 to a faculty. How do I implement it using triggers?
This is a homework question. But I've tried hard to come up with some logic. I am new to SQL so this might be easy for you lot but not for me.
I am new to stackexchange, so sorry if I've violated some rules/practices.
I expect that you would maintain a count on the borrower table of the number of books borrowed, and modify it via a trigger when a book is borrowed and when it is returned. Presumably you also have a table for the books being borrowed by the user, and the trigger would be placed on that take.
A constraint on the books_borrowed column could raise an error if the count of borrowed books exceeds 2.
This is a pretty old question, but I found it very useful as I'm also a PL/SQL beginner. There are two approaches to solve the problem and the one you want to use depends on the Oracle DB version.
For older version use a combination of a trigger and a package as below.
CREATE OR REPLACE TRIGGER trg_borrower
BEFORE INSERT OR UPDATE ON borrower
FOR EACH ROW
DECLARE
v_count NUMBER := 0;
BEGIN
v_count := borrower_pkg.count_rows(:NEW.borrower_id, :NEW.name, :NEW.status);
IF :NEW.status = 'student' AND v_count = 2 THEN
RAISE_APPLICATION_ERROR(-20000, 'Error - student');
ELSIF :NEW.status = 'faculty' AND v_count = 3 THEN
RAISE_APPLICATION_ERROR(-20001, 'Error - faculty');
END IF;
END;
/
CREATE OR REPLACE PACKAGE borrower_pkg AS
FUNCTION count_rows(p_id IN borrower.borrower_id%TYPE,
p_name IN borrower.NAME%TYPE,
p_status IN borrower.status%TYPE) RETURN NUMBER;
END;
/
CREATE OR REPLACE PACKAGE BODY borrower_pkg AS
FUNCTION count_rows(p_id IN borrower.borrower_id%TYPE,
p_name IN borrower.NAME%TYPE,
p_status IN borrower.status%TYPE) RETURN NUMBER AS
v_count NUMBER := 0;
BEGIN
SELECT COUNT(*) INTO v_count
FROM borrower
WHERE borrower_id = p_id AND NAME = p_name AND status = p_status;
RETURN v_count;
END count_rows;
END borrower_pkg;
/
For Oracle 10g and above you can use a compound trigger.
CREATE OR REPLACE TRIGGER trg_borrower_comp
FOR INSERT OR UPDATE ON borrower
COMPOUND TRIGGER
CURSOR c_borrower IS
SELECT b1.borrower_id
FROM borrower b1
WHERE EXISTS (SELECT 'x'
FROM borrower b2
WHERE b2.status = 'student' AND b1.borrower_id = b2.borrower_id
GROUP BY borrower_id HAVING COUNT(*) = 2)
OR
EXISTS (SELECT 'x'
FROM borrower b3
WHERE status = 'faculty'AND b1.borrower_id = b3.borrower_id
GROUP BY borrower_id HAVING COUNT(*) = 3);
TYPE t_borrower_count IS TABLE OF borrower.borrower_id%type;
v_borrower_count t_borrower_count;
BEFORE STATEMENT IS
BEGIN
OPEN c_borrower;
FETCH c_borrower BULK COLLECT INTO v_borrower_count;
CLOSE c_borrower;
END BEFORE STATEMENT;
BEFORE EACH ROW IS
BEGIN
IF :NEW.borrower_id MEMBER OF v_borrower_count THEN
RAISE_APPLICATION_ERROR(-20000, 'Error - ' || :NEW.status);
END IF;
END BEFORE EACH ROW;
END;
I have one table that has a code and and index attributes.
I'm trying to create a trigger that will check the values of code. If there is a duplicate in the inserted code, the index will be triggered to increase by 1. Example:
code|c_index
111 | 1
112 | 1
113 | 1
111 | 2
114 | 1
112 | 2
111 | 3
This is my code, but it doesn't work:
create trigger trg_update
after insert on trial
for each row
declare v_index;
begin
select max(nvl(trial.c_index, 0)) into v_index
from trial;
if new.code = code then
set new.c_index = v_index
else
set new.c_index = 1
end if;
end;
...............................
I've tryed to do a new better one, but still not working:
create trigger trg_update
after insert on trial
for each row
declare v_index number;
begin
if :new.code = :old.code then
select max(nvl(c_index, 0)) into v_index
from trial
where code = :new.code;
set :new.c_index = v_index + 1
else
set :new.c_index = 1
end if;
end;
What's the problem with the codes above and what is the solution for the problem?
...............................................
UPDATE:
After running this code:
create trigger trg_update
AFTER insert on trial
for each ROW
DECLARE v_index NUMBER := -1; -- "-1" is put in place so to be below the minimum value of the column
DECLARE v_cnt NUMBER := 0;
BEGIN
SELECT MAX(c_index), COUNT(*)
INTO :v_index, v_cnt
FROM trial
WHERE code = :new.code;
IF v_index <> -1 AND v_cnt > 1 THEN
--Only one update here, for the newly inserted row explicitly
UPDATE trial
SET c_index = c_index +1
WHERE code = :new.code
AND c_index = v_index
AND ROWNUM = 1;
END IF;
END;
Some problems displayed:
1- Error(2,1): PLS-00103: Encountered the symbol "DECLARE"
2- Error(7,8): PLS-00049: bad bind variable 'V_INDEX'
3- Error(9,15): PLS-00049: bad bind variable 'NEW.CODE'
This is the new code after trying to fix errors number 2 and 3:
create or replace trigger trg_update
AFTER insert on trial
for each ROW
declare vindex NUMBER := -1; -- "-1" is put in place so to be below the minimum value of the column
declare vcnt NUMBER := 0;
BEGIN
SELECT MAX(c_index), COUNT(*)
INTO vindex, vcnt
FROM trial
WHERE code = :new.code;
IF vindex <> -1 AND vcnt > 1 THEN
--Only one update here, for the newly inserted row explicitly
UPDATE trial
SET c_index = c_index +1
WHERE code = :new.code AND c_index = vindex AND ROWNUM = 1;
END IF;
END;
However, the first error still displays.
Error(2,1): PLS-00103: Encountered the symbol "DECLARE" when expecting one of the following: begin function pragma procedure subtype type current cursor delete exists prior The symbol "begin" was substituted for "DECLARE" to continue.
In addition to this error:
Error(19,4): PLS-00103: Encountered the symbol "end-of-file" when expecting one of the following: ( begin case declare end exception exit for goto if loop mod null pragma raise return select update while with << continue close current delete fetch lock insert open rollback savepoint set sql execute commit forall merge pipe purge
How can I solve these errors??
First of all, get you data set up in the database before inserting the new value, execute this:
UPDATE trial tr
SET c_index = (SELECT MAX(c_index) + 1
FROM trial
WHERE code = tr.code);
COMMIT;
After that, here's atrigger which will update the value correspondingly (the newly inserted ones):
CREATE OR REPLACE TRIGGER trg_update
AFTER INSERT ON trial
FOR EACH ROW
DECLARE
v_index NUMBER := -1; -- "-1" is put in place so to be below the minimum value of the column v_cnt NUMBER := 0;
BEGIN
SELECT MAX(c_index), COUNT(*)
INTO v_index, v_cnt
FROM trial
WHERE code = :new.code;
IF v_index <> -1 AND v_cnt > 1 THEN
--Only one update here, for the newly inserted row explicitly
UPDATE trial
SET c_index = c_index +1
WHERE code = :new.code
AND c_index = v_index
AND ROWNUM = 1;
END IF;
END;
I edited the trigger, it works well on my side, but note it will work correctly after the first update query. If the problem with the DECLARE persistes, then try removing it for instance, sometimes it's not amndatory.
You will find using triggers in this way will be painful on many levels. It's tempting at first and seems like a good idea, but it's not.
Triggers were invented before DRI was standardized. Their purpose is to enforce referential integrity. If you use DRI whenever feasible and triggers only for RI otherwise, you'll get along much better with your DBMS.
To do what you want, change your insert to use a correlated subquery that tests for the row's existence (so as not to insert duplicates), setting c_index to 0. Then update the row with set c_index =c_index +1.
create procedure tablename_add #code int
as
-- add the row if it's not already there
insert tablename values (#code, 0)
where code not in
(select code from tablename where code = #code)
-- increase the count by one
update tablename set c_index = c_index + 1
where code = #code
You don't even need to define a transaction. If there's no row, the INSERT will succeed and set c_index to zero, otherwise it will have no effect. Then the update will increment c_index1, regardless.