i have a probelm with a little project.
I have these tables:
USER (TAG VARCHAR, NICKNAME VARCHAR, TAG_CLAN VARCHAR)
DONATION(DATE_DON DATE, DON_SEND VARCHAR, DON_REIC VARCHAR)
THE ELEMENTS: DON_SEND AND DON_REICV ARE THE FOREIGN KEY THAT POINTS TO THE MANI (TAG) OF THE TABLE USER.
I'm trying to make a trigger that allows users to make and receive donations only if the TAG_CLAN is the same
I tried in this way but it doesn't work:
CREATE OR REPLACE TRIGGER CONTROLLO_USER_DONAZIONE
BEFORE INSERT ON DONAZIONE
FOR EACH ROW
DECLARE
TAG_C1 UTENTE.NUM_CLAN%TYPE;
TAG_C2 UTENTE.NUM_CLAN%TYPE;
CLAN_DIFF EXCEPTION;
BEGIN
SELECT U.NUM_CLAN INTO TAG_C1
FROM DONAZIONE D JOIN UTENTE U ON D.COD_UTENTE_EFFETTUA=U.TAG_USER
WHERE D.COD_UTENTE_EFFETTUA=(:NEW.COD_UTENTE_EFFETTUA);
SELECT U.NUM_CLAN INTO TAG_C2
FROM DONAZIONE D JOIN UTENTE U ON D.COD_UTENTE_RICEVE=U.TAG_USER
WHERE D.COD_UTENTE_RICEVE=(:NEW.COD_UTENTE_RICEVE);
IF TAG_C1<>TAG_C2 THEN
RAISE CLAN_DIFF;
END IF;
EXCEPTION
WHEN CLAN_DIFF THEN
RAISE_APPLICATION_ERROR(-20003,'NON SIETE NELLO STESSO CLAN, QUINDI NON PUOI RICEVERE/DONARE CARTE!');
END;
Can you help me please?
There are some differences between the tables you posted and the code of your trigger. With tables like these:
CREATE TABLE utente
(
TAG_USER VARCHAR2(10) PRIMARY KEY,
NICKNAME VARCHAR2(10),
NUM_CLAN VARCHAR2(10)
);
CREATE TABLE DONAZIONE
(
DATE_DON DATE,
COD_UTENTE_EFFETTUA VARCHAR2(10) REFERENCES utente(TAG_USER),
COD_UTENTE_RICEVE VARCHAR2(10) REFERENCES utente(TAG_USER)
);
This could be your trigger:
CREATE OR REPLACE TRIGGER CONTROLLO_USER_DONAZIONE
BEFORE INSERT
ON DONAZIONE
FOR EACH ROW
DECLARE
TAG_C1 UTENTE.NUM_CLAN%TYPE;
TAG_C2 UTENTE.NUM_CLAN%TYPE;
CLAN_DIFF EXCEPTION;
BEGIN
SELECT U.NUM_CLAN
INTO TAG_C1
FROM UTENTE U
WHERE U.TAG_USER = :NEW.COD_UTENTE_EFFETTUA;
SELECT U.NUM_CLAN
INTO TAG_C2
FROM UTENTE U
WHERE U.TAG_USER = :NEW.COD_UTENTE_RICEVE;
IF TAG_C1 <> TAG_C2
THEN
RAISE CLAN_DIFF;
END IF;
EXCEPTION
WHEN CLAN_DIFF
THEN
RAISE_APPLICATION_ERROR(-20003, 'NON SIETE NELLO STESSO CLAN, QUINDI NON PUOI RICEVERE/DONARE CARTE!');
END;
With data like the following:
insert into utente(TAG_USER, NICKNAME, NUM_CLAN) values ('one', 'User one', 'Numbers');
insert into utente(TAG_USER, NICKNAME, NUM_CLAN) values ('two', 'User two', 'Numbers');
insert into utente(TAG_USER, NICKNAME, NUM_CLAN) values ('a', 'User a', 'Letters');
it works like:
SQL> insert into donazione(DATE_DON, COD_UTENTE_EFFETTUA, COD_UTENTE_RICEVE) values (sysdate, 'one', 'two');
1 row created.
SQL> insert into donazione(DATE_DON, COD_UTENTE_EFFETTUA, COD_UTENTE_RICEVE) values (sysdate, 'a', 'two');
insert into donazione(DATE_DON, COD_UTENTE_EFFETTUA, COD_UTENTE_RICEVE) values (sysdate, 'a', 'two')
*
ERROR at line 1:
ORA-20003: NON SIETE NELLO STESSO CLAN, QUINDI NON PUOI RICEVERE/DONARE CARTE!
ORA-06512: at "ALEK.CONTROLLO_USER_DONAZIONE", line 23
ORA-04088: error during execution of trigger 'ALEK.CONTROLLO_USER_DONAZIONE'
The issue in your code is that you do a join, assuming that the record you're inserting already exists in the table DONAZIONE, this getting a no_data_found exception.
As an aside, I changed varchar into varchar2; have a look at the difference between them.
Also, this assumes that NUM_CLAN always is NOT NULL.
EDIT:
Given the way you need to handle null values, you can edit the IF
IF TAG_C1 <> TAG_C2
into
IF TAG_C1 <> TAG_C2 or TAG_C1 is null or TAG_C2 is null
or, more compact, but less readable:
IF nvl(TAG_C1, 'a value that a clan can never have') <> nvl(TAG_C2 , 'some other impossible value')
Related
It give me error example image at below:
Trigger code:
CREATE OR REPLACE TRIGGER InsertNewStaffs
BEFORE INSERT ON Staffs
FOR EACH ROW
ENABLE
DECLARE
v_user varchar(255);
v_date varchar(255);
v_Staffs_ID Staffs.Staffs_ID%TYPE;
v_Staffs_Name Staffs.Staffs_Name%TYPE;
v_Staffs_Contact_Number Staffs.Staffs_Contact_Number%TYPE;
v_Staffs_Email Staffs.Staffs_Email%TYPE;
v_Orders_ID Staffs.Orders_ID%TYPE;
v_count INTEGER;
BEGIN
SELECT count(*) INTO v_count FROM Staffs
WHERE Staffs_ID = v_Staffs_ID OR
Staffs_Name = v_Staffs_Name OR
Staffs_Contact_Number = v_Staffs_Contact_Number OR
Staffs_Email = v_Staffs_Email;
IF v_count > 0 THEN
RAISE_APPLICATION_ERROR(-20000, 'Oops, some data is already exists. Please try again...');
DBMS_OUTPUT.PUT_LINE('Oops, some data is already exists. Please try again...');
SELECT user, TO_CHAR(sysdate, 'DD/MON/YYYY HH24:MI:SS') INTO v_user, v_date FROM dual;
ELSE
INSERT INTO Staffs(Staffs_ID, Staffs_Name, Staffs_Contact_Number, Staffs_Email, Orders_ID)
VALUES(v_Staffs_ID, v_Staffs_Name, v_Staffs_Contact_Number, v_Staffs_Email, v_Orders_ID);
DBMS_OUTPUT.PUT_LINE('One Row Inserted By ' || v_user || CHR(10));
DBMS_OUTPUT.PUT_LINE('Inserted data at ' || v_date);
INSERT INTO monitorInsertStaffs(user_name, entry_date, operation)
VALUES(v_user, v_date, 'Insert');
END IF;
END;
/
My Table:
CREATE TABLE Staffs(
Staffs_ID char(20) NOT NULL,
Staffs_Name varchar(255) NOT NULL,
Staffs_Contact_Number varchar(50) NOT NULL,
Staffs_Email varchar(255) NOT NULL,
Orders_ID char(20),
PRIMARY KEY (Staffs_ID),
FOREIGN KEY (Orders_ID) REFERENCES Orders(Orders_ID)
);
CREATE TABLE Orders(
Orders_ID char(20) NOT NULL,
Order_Date DATE NOT NULL,
Order_Status varchar(255) NOT NULL,
Order_Quantity int NOT NULL,
Order_TotalAmount NUMERIC(10,2) NOT NULL,
Order_TotalPrice NUMERIC(10,2) NOT NULL,
PRIMARY KEY (Orders_ID),
Pets_Products_ID char(20),
CustomerID char(20),
FOREIGN KEY (Pets_Products_ID) REFERENCES Pets_Products(Pets_Products_ID),
FOREIGN KEY (CustomerID) REFERENCES Customers(CustomerID)
);
I try to insert data and if the data has existed it will show RAISE_APPLICATION_ERROR(-20000, 'Oops, some data is already exists. Please try again...'); but it didn't show the message and also cannot insert data when no exists the data.
I don't know where is error code that I find.
The whole concept is just wrong.
you've based trigger on a table into which you're just inserting a row (staffs)
then you're selecting from the same table (it'll raise the mutating table error if you try to insert more than a single row)
the where clause uses local variables that have no values
insert into staffs cause the same trigger to fire over and over again, until Oracle concludes that that's enough and raises the error
Don't use a trigger. Use UNIQUE constraints:
CREATE TABLE Staffs(
Staffs_ID char(20) NOT NULL,
Staffs_Name varchar(255) NOT NULL,
Staffs_Contact_Number varchar(50) NOT NULL,
Staffs_Email varchar(255) NOT NULL,
Orders_ID char(20),
PRIMARY KEY (Staffs_ID),
UNIQUE (Staffs_Name),
UNIQUE (Staffs_Contact_Number),
UNIQUE (Staffs_Email),
FOREIGN KEY (Orders_ID) REFERENCES Orders(Orders_ID)
);
(However, you should also consider whether your business requirements make sense or if you can have multiple staff members called Jane Smith or if you can have two staff members who share an office with the same telephone number?)
If you want to use a logging table then use an autonomous transaction to just insert into that table:
CREATE OR REPLACE TRIGGER InsertNewStaffs
BEFORE INSERT ON Staffs
FOR EACH ROW
ENABLE
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO monitorInsertStaffs(
user_name, entry_date, operation
) VALUES(
:NEW.Staffs_ID, SYSDATE, 'Insert'
);
COMMIT;
END;
/
db<>fiddle here
I'm working on Oracle 11g 64bit.
Say I have a table named "MyTable", I'm trying to monitoring a column named "My_Name".
When "My_Name" is going to be changed to '' (before update), I want to stop it and change "My_Name" back to old value. In other word, '' isn't a legal value for the "My_Name" column.
Here's what I did so far, no compilation error, but no effect, I can still write the '' value into "My_Name" column.
CREATE OR REPLACE TRIGGER MyTable_tracking
BEFORE INSERT OR UPDATE ON MyDB.MyTable REFERENCING NEW AS newValue OLD AS oldValue
FOR EACH ROW
DECLARE
v_old VARCHAR(20);
v_new VARCHAR(20);
BEGIN
IF INSERTING THEN
v_new:=:newValue.My_Name; --Trigger checks column 'My_Name' only
ELSIF UPDATING THEN
v_old:=:oldValue.My_Name; --Trigger checks column 'My_Name' only
v_new:=:newValue.My_Name; --Trigger checks column 'My_Name' only
--IF :newValue.My_Name='' THEN
IF LENGTH(TRIM(:newValue.My_Name))=0 THEN
:newValue.My_Name:=:oldValue.My_Name;
END IF;
END IF;
END;
How can I do this?
No need for a trigger. In Oracle an empty string '' and null are the same thing. So just define my_name as NOT NULL and you can't put null or '' in it.
SQL> create table my_table (id integer primary key, my_name varchar(20) not null);
Table created.
SQL> insert into my_table values (1, 'Arthur');
1 row created.
SQL> update my_table set my_name = '' where id = 1;
update my_table set my_name = '' where id = 1
*
ERROR at line 1:
ORA-01407: cannot update ("ARTHUR"."MY_TABLE"."MY_NAME") to NULL
SQL> insert into my_table values (2, '');
insert into my_table values (2, '')
*
ERROR at line 1:
ORA-01400: cannot insert NULL into ("ARTHUR"."MY_TABLE"."MY_NAME")
SQL>
If you can't use the most efficient solution because of external restrictions (which I find highly questionable), you can use something like this:
create or replace trigger slow_not_null_check
before insert or update on my_table
for each row
begin
if inserting and :new.my_name is null then
:new.my_name := 'No NULL allowed';
end if;
if updating and :new.my_name is null then
:new.my_name := :old.my_name;
end if;
end;
/
This will silently convert, '' to 'No NULL allowed' when inserting and will restore the previous value when updating:
insert into my_table values (1, '');
insert into my_table values (2, 'Arthur');
select * from my_table;
ID | MY_NAME
---+----------------
1 | No NULL allowed
2 | Arthur
update my_table
set my_name = ''
where id = 2;
select *
from my_table;
ID | MY_NAME
---+----------------
1 | No NULL allowed
2 | Arthur
I fixed my problem whit following code
IF INSERTING THEN
v_new:=:newValue.My_Name;
ELSIF UPDATING THEN
v_old:=:oldValue.My_Name;
v_new:=:newValue.My_Name;
--IF :newValue.My_Name='some specified value' THEN --not allow some value
--IF :newValue.My_Name='' --not working
IF :newValue.My_Name='' OR :newValue.My_Name IS NULL THEN --not allow '' or null
:newValue.My_Name:=v_old; --works
--:newValue.My_Name:=:oldValue.My_Name; --not working, use variable instead
END IF;
END IF;
DROP TRIGGER EPI_BOREHOLE_INI;
CREATE OR REPLACE TRIGGER EPI_BOREHOLE_INI
INSTEAD of Insert ON EPI_BOREHOLE for each row
DECLARE
V_ID number(10);
V_USER varchar2(100);
BEGIN
if (:new.UBHI is null or :new.NAME is null or) then
Raise_Application_Error(-20101, 'Insert failed. The key values of BOREHOLE(UBHI, NAME) cannot be null');
end if;
begin
select BOREHOLE_ID.nextval into V_ID from dual;
SELECT USER INTO V_USER FROM DUAL;
INSERT INTO EPI_BOREHOLE (ID, UBHI, NAME, INSERT_DATE ,INSERT_NAME, UPDATE_DATE,UPDATE_NAME) VALUES (V_ID,:NEW.UBHI, :NEW.NAME, SYSDATE, V_USER, SYSDATE, V_USER);
end;
END;
I see little error in your code: Please see below:
if (:new.UBHI is null or :new.NAME is null or) then <-- An additional OR written.
remove it and try or put one more condition.
I dont see any issue with the code.
Try doing
SET DEFINE OFF
Lets say I have a table as follows--
create table employees
(
eno number(4) not null primary key,
ename varchar2(30),
zip number(5) references zipcodes,
hdate date
);
And I'm trying to create a trigger with--
CREATE OR REPLACE TRIGGER TWELVE_ONE
BEFORE INSERT OR UPDATE
ON EMPLOYEES
FOR EACH ROW
DECLARE
V_DATE VARCHAR2 (10);
BEGIN
SELECT TO_CHAR (SYSDATE, 'hh24:mi:ss') INTO V_DATE FROM DUAL;
IF (V_DATE >= '12:00:01' AND V_DATE < '13:00:00')
THEN
INSERT INTO TABLE ?????
ELSE
ROLLBACK? TERMINATE TRANSACTION?
END IF;
END;
Purpose of the trigger is to allow an insertion/update during 12:00-13:00 and prevent the insertion at any other time. The trigger construction (thanks to #Melkikun) is seems ok. However now I'm facing the following issues--
How is it possible to pass the values here? I mean lets say my create statement is:
Insert into employees Values (1, 'someone', 11111, '17-12-2015')
And lets say the time is 12:30:01 now. How would the trigger perform the insertion without knowing the values?
And lets say the time is now 13:00:1 now. How would the trigger stop/prevent the insertion?
I'm using Oracle SQL Developer 4.02.15
Many Thanks
You just have to do it the other way.
If the time is not correct,then you raise an exception, so the insert won't be done.
CREATE OR REPLACE TRIGGER TWELVE_ONE
BEFORE INSERT OR UPDATE
ON EMPLOYEES
FOR EACH ROW
DECLARE
V_DATE VARCHAR2 (10);
MyException exception;
BEGIN
SELECT TO_CHAR (SYSDATE, 'hh24:mi:ss') INTO V_DATE FROM DUAL;
IF (V_DATE < '12:00:01' OR V_DATE > '13:00:00')
THEN
raise MyException;
END IF;
EXCEPTION
When MyException then
ROLLBACK;
//output message ...
END;
How would the trigger perform the insertion without knowing the values?
The trigger knows the value thanks to :NEW and :OLD.
You normally use the terms in a trigger using :old to reference the old value and :new to reference the new value.So you will have :NEW.eno ,:NEW.ename ...
Here is an example from the Oracle documentation :
CREATE OR REPLACE TRIGGER Print_salary_changes
BEFORE DELETE OR INSERT OR UPDATE ON Emp_tab
FOR EACH ROW
WHEN (new.Empno > 0)
DECLARE
sal_diff number;
BEGIN
sal_diff := :new.sal - :old.sal;
dbms_output.put('Old salary: ' || :old.sal);
dbms_output.put(' New salary: ' || :new.sal);
dbms_output.put_line(' Difference ' || sal_diff);
END;
My trigger wants to check if a 'new' manager supervises no more than 5 employees.
Manager supervising only 5 people are in BLOCKED_MANAGER table(ssn,numberofemployees).
Finally, every update is recorded in SUPERLOG table(date,user,old_manager,new_manager).
I get no compiling error about the trigger, but when I update a superssn I get this error:
SQL> update employee set superssn='666666607' where ssn='111111100';
update employee set superssn='666666607' where ssn='111111100'
*
ERROR at line 1:
ORA-04091: Table FRANK.EMPLOYEE is mutating, the trigger/function
can't read it
ORA-06512: a "FRANK.TLOG", line 20
ORA-04088: error during execution of trigger 'FRANK.TLOG'
How can I solve this trigger? Thank you
create or replace trigger tlog
before update of superssn on employee
for each row
declare
t1 exception;
n number:=0;
cont number:=0;
empl varchar2(16);
cursor cur is (select ssn from blocked_manager where ssn is not null);
begin
open cur;
loop
fetch cur into empl;
exit when cur%notfound;
if(:new.superssn = empl) then
n:=1;
end if;
end loop;
close cur;
if n=1 then
raise t1;
end if;
select count(*) into cont from employee group by superssn having superssn=:new.superssn;
if(cont=4) then
insert into blocked_manager values(:new.superssn,5);
end if;
insert into superlog values(sysdate,user,:old.superssn, :new.superssn );
exception
when t1 then
raise_application_error(-20003,'Manager '||:new.superssn||' has already 5 employees');
end;
Probably the quickest way around this is to use a carefully constructed statement trigger instead of a row trigger. Row triggers have the phrase FOR EACH ROW in them, are invoked for each row which is modified (based on the BEFORE/AFTER INSERT, BEFORE/AFTER UPDATE, and BEFORE/AFTER DELETE constraints on the trigger), can see the appropriate :NEW and :OLD values, and are subject to the "can't look at the table on which the trigger is defined" rule. Statement triggers are invoked at the appropriate time for each statement which is executed, can't see row values, but aren't subject to the limits on looking at the particular table on which they're defined. So for the portions of your logic which don't need to work with :NEW or :OLD values a trigger such as this might prove useful:
CREATE OR REPLACE TRIGGER EMPLOYEE_S_BU
BEFORE UPDATE ON EMPLOYEE
-- Note: no BEFORE EACH ROW phrase, so this is a statement trigger
BEGIN
-- The following FOR loop should insert rows into BLOCKED_MANAGER for all
-- supervisors which have four or more employees under them and who are not
-- already in BLOCKED_MANAGER.
FOR aRow IN (SELECT e.SUPERSSN, COUNT(e.SUPERSSN) AS EMP_COUNT
FROM EMPLOYEE e
LEFT OUTER JOIN BLOCKED_MANAGER b
ON b.SSN = e.SUPERSSN
WHERE b.SSN IS NULL
GROUP BY e.SUPERSSN
HAVING COUNT(e.SUPERSSN) >= 4)
LOOP
INSERT INTO BLOCKED_MANAGER
(SSN, EMPLOYEE_COUNT)
VALUES
(aRow.SUPERSSN, aRow.EMP_COUNT);
END LOOP;
-- Remove rows from BLOCKED_MANAGER for managers who supervise fewer
-- than four employees.
FOR aRow IN (SELECT e.SUPERSSN, COUNT(e.SUPERSSN) AS EMP_COUNT
FROM EMPLOYEE e
INNER JOIN BLOCKED_MANAGER b
ON b.SSN = e.SUPERSSN
GROUP BY e.SUPERSSN
HAVING COUNT(e.SUPERSSN) <= 3)
LOOP
DELETE FROM BLOCKED_MANAGER
WHERE SSN = aRow.SUPERSSN;
END LOOP;
-- Finally, if any supervisor has five or more employees under them,
-- raise an exception. Note that we go directly to EMPLOYEE to determine
-- the number of employees supervised.
FOR aRow IN (SELECT SUPERSSN, COUNT(*) AS EMP_COUNT
FROM EMPLOYEE
GROUP BY SUPERSSN
HAVING COUNT(*) >= 5)
LOOP
-- If we get here we've found a supervisor with 5 (or more) employees.
-- Raise an exception
RAISE_APPLICATION_ERROR(-20000, 'Found supervisor ' || aRow.SUPERSSN ||
' supervising ' || aRow.EMP_COUNT ||
' employees');
END LOOP;
END EMPLOYEE_S_BU;
Note that if you get rid of the BLOCKED_MANAGER table (which this trigger still maintains, although I don't know if it's truly necessary) the logic gets cut down considerably.
You'll still need a row trigger to handle the logging, but as that's just a matter of cutting down your existing trigger I'll leave that to you. :-)
Share and enjoy.
As you have discovered, you cannot select from the same table that a row-level trigger is defined against; it causes a table mutating exception.
In order to properly create this validation using a trigger a procedure should be created to obtain user-specified locks so the validation can be correctly serialized in a multi-user environment.
PROCEDURE request_lock
(p_lockname IN VARCHAR2
,p_lockmode IN INTEGER DEFAULT dbms_lock.x_mode
,p_timeout IN INTEGER DEFAULT 60
,p_release_on_commit IN BOOLEAN DEFAULT TRUE
,p_expiration_secs IN INTEGER DEFAULT 600)
IS
-- dbms_lock.allocate_unique issues implicit commit, so place in its own
-- transaction so it does not affect the caller
PRAGMA AUTONOMOUS_TRANSACTION;
l_lockhandle VARCHAR2(128);
l_return NUMBER;
BEGIN
dbms_lock.allocate_unique
(lockname => p_lockname
,lockhandle => p_lockhandle
,expiration_secs => p_expiration_secs);
l_return := dbms_lock.request
(lockhandle => l_lockhandle
,lockmode => p_lockmode
,timeout => p_timeout
,release_on_commit => p_release_on_commit);
IF (l_return not in (0,4)) THEN
raise_application_error(-20001, 'dbms_lock.request Return Value ' || l_return);
END IF;
-- Must COMMIT an autonomous transaction
COMMIT;
END request_lock;
This procedure can then be used in a compound trigger (assuming at least Oracle 11, this will need to be split into individual triggers in earlier versions)
CREATE OR REPLACE TRIGGER too_many_employees
FOR INSERT OR UPDATE ON employee
COMPOUND TRIGGER
-- Table to hold identifiers of inserted/updated employee supervisors
g_superssns sys.odcivarchar2list;
BEFORE STATEMENT
IS
BEGIN
-- Reset the internal employee supervisor table
g_superssns := sys.odcivarchar2list();
END BEFORE STATEMENT;
AFTER EACH ROW
IS
BEGIN
-- Store the inserted/updated supervisors of employees
IF ( ( INSERTING
AND :new.superssn IS NOT NULL)
OR ( UPDATING
AND ( :new.superssn <> :old.superssn
OR :new.superssn IS NOT NULL AND :old.superssn IS NULL) ) )
THEN
g_superssns.EXTEND;
g_superssns(g_superssns.LAST) := :new.superssn;
END IF;
END AFTER EACH ROW;
AFTER STATEMENT
IS
CURSOR csr_supervisors
IS
SELECT DISTINCT
sup.column_value superssn
FROM TABLE(g_superssns) sup
ORDER BY sup.column_value;
CURSOR csr_constraint_violations
(p_superssn employee.superssn%TYPE)
IS
SELECT count(*) employees
FROM employees
WHERE pch.superssn = p_superssn
HAVING count(*) > 5;
r_constraint_violation csr_constraint_violations%ROWTYPE;
BEGIN
-- Check if for any inserted/updated employee there exists more than
-- 5 employees for the same supervisor. Serialise the constraint for each
-- superssn so concurrent transactions do not affect each other
FOR r_supervisor IN csr_supervisors LOOP
request_lock('TOO_MANY_EMPLOYEES_' || r_supervisor.superssn);
OPEN csr_constraint_violations(r_supervisor.superssn);
FETCH csr_constraint_violations INTO r_constraint_violation;
IF csr_constraint_violations%FOUND THEN
CLOSE csr_constraint_violations;
raise_application_error(-20001, 'Supervisor ' || r_supervisor.superssn || ' now has ' || r_constraint_violation.employees || ' employees');
ELSE
CLOSE csr_constraint_violations;
END IF;
END LOOP;
END AFTER STATEMENT;
END;
You do not need the blocked_manager table to manage this constraint. This information can be derived from the employee table.
Or in versions earlier than Oracle 11i:
CREATE OR REPLACE PACKAGE employees_trg
AS
-- Table to hold identifiers of inserted/updated employee supervisors
g_superssns sys.odcivarchar2list;
END employees_trg;
CREATE OR REPLACE TRIGGER employee_biu
BEFORE INSERT OR UPDATE ON employee
IS
BEGIN
-- Reset the internal employee supervisor table
employees_trg.g_superssns := sys.odcivarchar2list();
END;
CREATE OR REPLACE TRIGGER employee_aiur
AFTER INSERT OR UPDATE ON employee
FOR EACH ROW
IS
BEGIN
-- Store the inserted/updated supervisors of employees
IF ( ( INSERTING
AND :new.superssn IS NOT NULL)
OR ( UPDATING
AND ( :new.superssn <> :old.superssn
OR :new.superssn IS NOT NULL AND :old.superssn IS NULL) ) )
THEN
employees_trg.g_superssns.EXTEND;
employees_trg.g_superssns(employees_trg.g_superssns.LAST) := :new.superssn;
END IF;
END;
CREATE OR REPLACE TRIGGER employee_aiu
AFTER INSERT OR UPDATE ON employee
IS
DECLARE
CURSOR csr_supervisors
IS
SELECT DISTINCT
sup.column_value superssn
FROM TABLE(employees_trg.g_superssns) sup
ORDER BY sup.column_value;
CURSOR csr_constraint_violations
(p_superssn employee.superssn%TYPE)
IS
SELECT count(*) employees
FROM employees
WHERE pch.superssn = p_superssn
HAVING count(*) > 5;
r_constraint_violation csr_constraint_violations%ROWTYPE;
BEGIN
-- Check if for any inserted/updated employee there exists more than
-- 5 employees for the same supervisor. Serialise the constraint for each
-- superssn so concurrent transactions do not affect each other
FOR r_supervisor IN csr_supervisors LOOP
request_lock('TOO_MANY_EMPLOYEES_' || r_supervisor.superssn);
OPEN csr_constraint_violations(r_supervisor.superssn);
FETCH csr_constraint_violations INTO r_constraint_violation;
IF csr_constraint_violations%FOUND THEN
CLOSE csr_constraint_violations;
raise_application_error(-20001, 'Supervisor ' || r_supervisor.superssn || ' now has ' || r_constraint_violation.employees || ' employees');
ELSE
CLOSE csr_constraint_violations;
END IF;
END LOOP;
END;