On update triggering in oracle? - sql

I create 2 tables employees and customers.The employees table code is here:
create table employees(
employeeNumber number not null,
lastName varchar2(30) not null,
firstName varchar2(30) not null,
email varchar2(50) not null,
officeCode varchar2(10) not null,
assignTo number default null,
jobTitle varchar2(100) not null,
primary key (employeeNumber),
foreign key (officeCode) references offices(officeCode),
foreign key (assignTo) references employees(employeeNumber)
);
here assignTo is a foreign key of his own table employees and employeeNumber is auto increment.Some insertion sample is here:
insert into employees (lastName,firstName,email,officeCode,jobTitle)
values ('hasan','rumy','md.rejaulhasanrumy#gmail.com','123','manager');
insert into employees (lastName,firstName,email,officeCode,assignTo,jobTitle)
values ('hasan','rakib','kalorakib#gmail.com','123', 1 ,'assistant manager');
The customer table code is here:
create table customers (
customerNumber number not null,
customerName varchar2(50) not null,
phone varchar2(20) not null,
address varchar2(70) not null,
city varchar2(50) not null,
postalCode varchar2(15) not null,
country varchar2(40) not null,
salesRepEmployeeNumber number default null,
primary key(customerNumber),
foreign key (salesRepEmployeeNumber) references employees (employeeNumber)
);
customerNumber is auto increment.some sample insertion is here:
insert into customers
(customerName,phone,address,city,postalCode,country,salesRepEmployeeNumber)
values ('roxy','017456','holy park','kolia','Z143','something',1);
Now I create a trigger which execute before update employeeNumber column of employees table for on update cascade and the code is here:
create or replace trigger employees_update
before update of employeeNumber on employees
for each row
begin
update employees
set
assignTo = :new.employeeNumber
where assignTo = :old.employeeNumber;
update customers set
salesRepEmployeeNumber = :new.employeeNumber
where salesRepEmployeeNumber = :old.employeeNumber;
end;
/
above all is right in oracle but the problem is when I update employees table.The update code is here:
update employees set employeeNumber = 134 where employeeNumber = 1;
the problem is here:
ORA-04091: table RUMY.EMPLOYEES is mutating, trigger/function may not see it
ORA-06512: at "RUMY.EMPLOYEES_UPDATE", line 2
ORA-04088: error during execution of trigger 'RUMY.EMPLOYEES_UPDATE'
1. update employees set employeeNumber = 134 where employeeNumber = 1;
As far I know it's a system problem so where I make mistake?Can not I make foreign key assignTo of employees table?Also notice that same thing work properly in mysql.Advance thanks for answering this long question.

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.
Then - assuming at least Oracle 11, this will need to be split into individual triggers in earlier versions
CREATE OR REPLACE TRIGGER employees_update
FOR UPDATE ON employees
COMPOUND TRIGGER
TYPE employeeNumberRec IS RECORD
(oldEmployeeNumber employees.employeeNumber%TYPE
,newEmployeeNumber employees.employeeNumber%TYPE);
TYPE employeeNumbersTbl IS TABLE OF employeeNumberRec;
g_employeeNumbers employeeNumbersTbl;
BEFORE STATEMENT
IS
BEGIN
-- Reset the internal employees table
g_employeeNumbers := employeeNumbersTbl();
END BEFORE STATEMENT;
AFTER EACH ROW
IS
BEGIN
-- Store the updated employees
IF :new.employeeNumber <> :old.employeeNumber THEN
g_employeeNumbers.EXTEND;
g_employeeNumbers(g_employeeNumbers.LAST).oldEmployeeNumber := :old.employeeNumber;
g_employeeNumbers(g_employeeNumbers.LAST).newEmployeeNumber := :new.employeeNumber;
END IF;
END AFTER EACH ROW;
AFTER STATEMENT
IS
BEGIN
-- Now update the child tables
FORALL l_index IN 1..g_employeeNumbers.COUNT
UPDATE employees
SET assignTo = g_employeeNumbers(l_index).newEmployeeNumber
WHERE assignTo = g_employeeNumbers(l_index).oldEmployeeNumber;
FORALL l_index IN 1..g_employeeNumbers.COUNT
UPDATE customers
SET salesRepEmployeeNumber = g_employeeNumbers(l_index).newEmployeeNumber
WHERE salesRepEmployeeNumber = g_employeeNumbers(l_index).oldEmployeeNumber;
END AFTER STATEMENT;
END;
EDIT
In addition you will need to make the foreign key constraints that reference this table deferred e.g.
CREATE TABLE employees
(employeeNumber NUMBER NOT NULL
,lastName VARCHAR2(30) NOT NULL
,firstName VARCHAR2(30) NOT NULL
,email VARCHAR2(50) NOT NULL
,officeCode VARCHAR2(10) NOT NULL
,assignTo NUMBER DEFAULT NULL
,jobTitle VARCHAR2(100) NOT NULL
,PRIMARY KEY (employeeNumber)
,FOREIGN KEY (officeCode)
REFERENCES offices (officeCode)
,FOREIGN KEY (assignTo)
REFERENCES employees (employeeNumber)
DEFERRABLE
INITIALLY DEFERRED
)
and
CREATE TABLE customers
(customerNumber NUMBER NOT NULL
,customerName VARCHAR2(50) NOT NULL
,phone VARCHAR2(20) NOT NULL
,address VARCHAR2(70) NOT NULL
,city VARCHAR2(50) NOT NULL
,postalCode VARCHAR2(15) NOT NULL
,country VARCHAR2(40) NOT NULL
,salesRepEmployeeNumber NUMBER DEFAULT NULL
,PRIMARY KEY (customerNumber)
,FOREIGN KEY (salesRepEmployeeNumber)
REFERENCES employees (employeeNumber)
DEFERRABLE
INITIALLY DEFERRED
)
Note: if the constraint is violated this will cause an error at COMMIT not after an individual DML statement.

You can re-write the above trigger in following ways to avoid the issues:
create or replace trigger employees_update
before update of employeeNumber on employees
for each row
begin
emp_upd_trg_proc(:new.employeeNumber,:old.employeeNumber);
update customers set
salesRepEmployeeNumber = :new.employeeNumber
where salesRepEmployeeNumber = :old.employeeNumber;
end;
/
create or replace procedure emp_upd_trg_proc
(new number, old number)
is
pragma autonomous_transaction;
begin
update employees
set
assignTo = new
where assignTo = old;
commit;
end;
/

Related

Can I create a trigger with a set of instruction with DB2?

I'm working with DB2 and I have to make a trigger that after a certain update on 'Disponibilita' has to do two differentes operation with the table 'Promozioni'
here the schemas:
create table PROMOZIONI (
PID char(5) not null primary key,
Valore DEC(4,2) not null,
NumProdotti INT not null DEFAULT 0 );
create table DISPONIBILITA (
CodProdotto char(5) not null,
CodNegozio char(5) not null,
Quantita INT not null,
PID char(5) references PROMOZIONI,
primary key (CodProdotto, CodNegozio));
and this is the trigger that obviously doesn't work:
Create or replace trigger AggiornaNumProdotti
After Update on Disponibilita
referencing old as O new as N
for each row
update Promozioni p
SET NumProdotti=NumProdotti+1
Where N.PID is not null and N.PID=p.PID;
UPDATE Promozioni p2
SET NumProdotti=NumProdotti-1
WHERE O.PID is not null and O.PID=p2.PID;
is there any way to make a single trigger or i'm force to create two differentes ones for each specific instruction? Thanks a lot
For more than one query you need a BEGIN and END
create table PROMOZIONI (
PID char(5) not null primary key,
Valore DEC(4,2) not null,
NumProdotti INT not null DEFAULT 0 );
INSERT INTO PROMOZIONI VALUES ('1',1.2,0),
('2',1.2,0)
create table DISPONIBILITA (
CodProdotto char(5) not null,
CodNegozio char(5) not null,
Quantita INT not null,
PID char(5) references PROMOZIONI,
primary key (CodProdotto, CodNegozio));
INSERT INTO DISPONIBILITA VALUES ('1','1',1,'1')
Create or replace trigger AggiornaNumProdotti
After Update on Disponibilita
referencing old as O new as N
for each row
BEGIN
update Promozioni p
SET NumProdotti=NumProdotti+1
Where N.PID is not null and N.PID=p.PID;
UPDATE Promozioni p2
SET NumProdotti=NumProdotti-1
WHERE O.PID is not null and O.PID=p2.PID;
END;
UPDATE DISPONIBILITA SET PID = '2' WHERE PID = '1'
SELECT * FROM PROMOZIONI
PID
VALORE
NUMPRODOTTI
1
1.20
-1
2
1.20
1
fiddle

trigger for not inserting members doesnt work

I have this table
CREATE TABLE members
(
member_id INT PRIMARY KEY NOT NULL,
first_name VARCHAR(20),
last_name VARCHAR(20),
web_page VARCHAR(200),
e_mail VARCHAR(200),
cv VARCHAR(800),
dep_id INT,
teacher_id INT
);
and I want to create a trigger that if someone wants to insert a member which has a dep_id of 1 or 2 or 3.
And the teacher_id is different than NULL (as the teacher_id column is filled with either NULL or an id of another member)
I came up with this
CREATE TRIGGER employee_insup1
ON members
FOR INSERT, UPDATE
AS
DECLARE #dep_id INT, #teacher_id INT
SELECT #dep_id = i.dep_id, #teacher_id = i.teacher_id
FROM inserted i
IF ((#dep_id = 1) AND (#teacher_id != NULL))
BEGIN
RAISERROR('Teacher_id expects NULL',16,1)
ROLLBACK TRANSACTION
END
but after all if I try to insert a member with dep_id 1 and teacher_id 7(for example) it will be registered
You don't need a trigger for this. A check constraint is sufficient:
alter table members add constraint chk_members_dep_teacher
check (dep_id not in (1, 2, 3) or teacher_id is not null);
Specifically, this ensures that when dep_id is in one of those departments, then the teacher_id is not null. You might find the logic easier to follow as:
alter table members add constraint chk_members_dep_teacher
check (not (dep_id nt in (1, 2, 3) and teacher_id is null) );

Oracle Trigger Syntax Using Subquery

I'm trying to write a Trigger in Oracle Syntax which, upon entering a line into a particular table, checks that both values entered belong to some classification that is held in another table. My initial thought was to have a constraint on the table that included a subquery but Oracle doesn't seem to like that.
The select query I have written in the below works - but I'm not sure how to put it into a trigger - but essentially I need the trigger to ensure that EW1.OrgId and EW2.OrgId are the same. Any help is appreciated!
CREATE TABLE Organisation (
OrgId INTEGER PRIMARY KEY,
Name VARCHAR(40) NOT NULL
);
CREATE TABLE Person (
PersonId INTEGER PRIMARY KEY,
FirstName VARCHAR(45) NOT NULL,
LastName VARCHAR(45) NOT NULL
);
CREATE TABLE Employee (
PersonId INTEGER PRIMARY KEY REFERENCES PERSON (PersonId) ON DELETE CASCADE
);
CREATE TABLE Manager (
PersonId INTEGER PRIMARY KEY REFERENCES PERSON (PersonId) ON DELETE CASCADE
);
CREATE TABLE EnlistedWith (
OrgId INTEGER REFERENCES ORGANISATION (OrgId) ON DELETE CASCADE,
PersonId INTEGER REFERENCES PERSON (PersonId) ON DELETE CASCADE,
PRIMARY KEY(OrgId,PersonId)
);
CREATE TABLE SupervisedBy (
EmployeeId INTEGER REFERENCES Employee (PersonId) ON DELETE CASCADE,
ManagerId INTEGER REFERENCES Manager (PersonId) ON DELETE CASCADE,
CONSTRAINT PK_SupervisedBy PRIMARY KEY (EmployeeId, ManagerId)
);
CREATE TRIGGER SupervisorCompany
AFTER INSERT ON SupervisedBy
FOR EACH ROW
BEGIN
declare qty INTEGER := 0;
BEGIN
SELECT COUNT (*) into qty
FROM SupervisedBy SB
INNER JOIN EnlistedWith EW1 ON SB.ManagerId = EW1.PersonId
INNER JOIN EnlistedWith EW2 ON SB.EmployeeId = EW2.PersonId
and EW1.OrgId <> EW2.OrgId
IF qty <> 0
then Raise_Error (1234567, 'Manager and Employee are not Enlisted with same Organisation');
END IF;
END;
END;
You can refer to the columns of the owner of a Trigger using :NEW / :OLD. So, your Trigger could be re-written as
CREATE OR REPLACE TRIGGER supervisorcompany AFTER
INSERT
ON supervisedby FOR EACH ROW
DECLARE qty INTEGER := 0;
BEGIN
SELECT count (*)
INTO qty
FROM enlistedwith ew1
WHERE ew1.personid = :NEW.managerid
AND EXISTS
(
SELECT 1
FROM enlistedwith ew2
WHERE ew2.personid = :NEW.employeeid
AND ew1.orgid <> ew2.orgid ) ;
IF qty <> 0 THEN
raise_application_error (1234567, 'Manager and Employee are not Enlisted with same Organisation');
END IF;
END;
/
There's some guessing involved... Maybe something like this
CREATE TRIGGER SupervisorCompany
AFTER INSERT
ON SupervisedBy
FOR EACH ROW
BEGIN
IF (SELECT OrgId
FROM EnlistedWith
WHERE PersonId = New.ManagerId)
<>
(SELECT OrgId
FROM EnlistedWith
WHERE PersonId = New.EmployeeId) THEN
RAISE_ERROR(1234567, 'Manager and Employee are not Enlisted with same Organisation');
END IF;
END;
is what you want? It checks if the OrgId of the row where the PersonId equals the newly entered ManagerId is the same as the one for the newly entered EmployeeId. If not, your error is raised.
(Untested, as no DDL for the tables were provided.)

Calculating overtime using rows from 3 different tables

I'm fairly new to SQL and I am trying to create a trigger to calculate overtime worked by subtracting hours contracted (GRADE_HOURS) in the COMPANY_GRADE table from hours worked (TIMESHEET_HOURS) in the COMPANY_TIMESHEET table. This will then go into the TIMESHEET_OT column of the COMPANY_TIMESHEET table whenever a row is inserted into the COMPANY_TIMESHEET table.
The tables concerned are as follows:
CREATE TABLE COMPANY_TIMESHEET
(
timesheet_ID number(3) constraint timesheet_pk primary key,
Timesheet_emp number(3) constraint timesheet_empnotnull not null references company_employee,
Timesheet_wc date constraint timesheet_wcnotnull not null,
Timesheet_hours number(2),
Timesheet_OT number(2) default 0,
Timesheet_approved number(3) references company_employee
);
CREATE TABLE COMPANY_GRADE
(
grade_ID number(3) constraint grade_pk primary key,
Grade_rate number(5,2) constraint grade_ratenotnull not null,
Grade_hours number(2)
) ;
CREATE TABLE COMPANY_EMPLOYEE
(
emp_ID number(3) constraint emp_pk primary key,
Emp_firstname varchar2(50) constraint emp_firstnamenotnull not null,
Emp_surname varchar2(50),
Emp_department number(2) constraint employeeFKdepartment references company_department,
emp_street varchar2(50),
emp_town varchar2(50),
emp_district varchar2(50),
Emp_grade number(3) default 4 constraint checkempgrade check (Emp_grade between 1 and 9) references company_grade,
Emp_site varchar2(30) default 'LONDON'
);
I would appreciate any help as I have been trying for many hours now only to be met by error after error.
I have tried numerous variations of this as a starting block to try and pass the hours contracted into a variable to then subtract from another variable:
CREATE OR REPLACE TRIGGER trg_ot
BEFORE INSERT ON company_timesheet
FOR EACH ROW
DECLARE t_contracted NUMBER;
BEGIN
SELECT grade_hours INTO t_contracted
FROM company_grade
WHERE company_employee.emp_id = :new.timesheet_emp;
END;
/
CREATE OR REPLACE TRIGGER trg_ot
BEFORE INSERT ON company_timesheet
FOR EACH ROW
DECLARE t_contracted NUMBER;
BEGIN
SELECT g.grade_hours
INTO t_contracted
FROM company_grade g
INNER JOIN
company_employee e
ON ( e.emp_grade = g.grade_id )
WHERE e.emp_id = :new.timesheet_emp;
:new.timesheet_ot := :new.timesheet_hours - t_contracted;
END;
/

Constrain number of rows in table, by value in another table

I have a table called ward (dealing with hospitals):
Create table ward(
Wno varchar(15) Primary Key,
Name varchar(20) Not Null,
Number_of_beds integer Not Null
);
And a table for patients:
Create table patient(
Pid varchar(15) Primary Key,
Name varchar(20) Not Null,
Address varchar(50) Not Null,
Date_of_birth date Not Null
);
I need to constrain the patients somehow so that if a patient is assigned to a specific ward then the number of patients can't exceed the number of beds in the ward.
I thought of adding the Wno as a foreign key to the patient table, but don't really know where to go from there.
You may add the foreign key into patient table as following. Apology this query is in MYSQL. I noticed that you need Oracle though. However the logic is similar :) The syntax needs changes.
Create table ward(
Wno varchar(15) Not null Primary Key,
Name varchar(20) Not Null,
Number_of_beds integer Not Null
);
And a table for patients:
Create table patient(
Pid varchar(15) Primary Key,
Name varchar(20) Not Null,
Address varchar(50) Not Null,
Date_of_birth date Not Null,
WardNo varchar(15),
foreign key (wardno) references ward (wno) ' -- adds the foreign key relation
);
In order to check if ward is full, you can have an insert or update trigger
Free bed count can be obtained by following query:
SELECT p.wardno, (w.number_of_beds - count(pid)) as freebeds
from patient as p
left join ward as w
on p.wardno = w.wno
group by wardno
Now we create a trigger to check if any patient is going into a ward whre freebed count = 0.
Updated to Oracle Version
CREATE OR REPLACE TRIGGER FreeBedsWardTrigger
BEFORE UPDATE OR INSERT
ON patient
FOR EACH ROW
DECLARE
max_beds INTEGER; -- max number of beds for the ward
used_beds INTEGER; -- used beds for the ward
BEGIN
SELECT COUNT (pid)
INTO used_beds
FROM patient
WHERE wardno = :NEW.wardno;
SELECT number_of_beds
INTO max_beds
FROM ward
WHERE wno = :NEW.wardno;
IF (max_beds - used_beds) > 0
THEN
RETURN;
ELSE
RAISE_APPLICATION_ERROR (-100100,
'No more beds available in this ward.');
END IF;
END;