Oracle Trigger Syntax Using Subquery - sql

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

Related

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

How can this postgresql query return as table?

I am trying to call modules which in specific course but it returns as error: more than one row returned by a subquery used as an expression
Query:
CREATE OR REPLACE FUNCTION course_modules(_courseID integer)
RETURNS SETOF modules AS
$$
BEGIN
RETURN QUERY SELECT * FROM modules WHERE mod_id =(SELECT module_id from coursemodules WHERE course_id = _courseID);
END
$$
LANGUAGE 'plpgsql';
coursemodule table
CREATE TABLE coursemodules(
course_id integer references courses (id) ON DELETE CASCADE,
module_id integer references modules (mod_id) ON DELETE CASCADE
);
Modules Table
CREATE TABLE modules(
documents text NOT NULL,
mod_id serial primary key,
content text NOT NULL,
title varchar(50) NOT NULL
);
Course Table
CREATE TABLE courses(
finishDate Date,
description text NOT NULL,
duration varchar(50) NOT NULL,
startDate Date,
id serial primary key,
courseName varchar(50) NOT NULL
);
There is no restriction in the coursemodule table that a course could only have one module. Because of that the SELECT module_id from coursemodules WHERE course_id = _courseID subquery could return multiple lines.
If you change mod_id = (SELECT module_id from coursemodules WHERE course_id = _courseID)
to
mod_id IN (SELECT module_id from coursemodules WHERE course_id = _courseID).
It should work. Otherwise you have to add constraints to the coursemodule table.
This is a simple SQL syntax error. You're using
WHERE mod_id =
But you may have more than one row returning from the subquery. User IN:
WHERE mod_id IN

SQL Trigger Delete oldest duplicate

I have the following tables
HOLIDAY_DATE_TABLE:
USE BillingUI;
CREATE TABLE HOLIDAY_DATE_TABLE
(
HID INT IDENTITY PRIMARY KEY,
TABLE_NUMBER nchar(2) NOT NULL,
HOLIDAY_DATE nchar(8) NOT NULL,
FIELD_DESCRIPTION nVARchar(43) NULL,
);
tbl8_update_transactions:
USE BillingUI;
CREATE TABLE tbl8_update_transactions
(
TID INT IDENTITY PRIMARY KEY,
TABLE_NUMBER nchar(2) NOT NULL,
HOLIDAY_DATE nchar(8) NOT NULL,
FIELD_DESCRIPTION nVARchar(43) NULL,
HID int,
FOREIGN KEY (HID) REFERENCES HOLIDAY_DATE_TABLE (HID)
);
I'm trying to write a trigger for tbl8_update_transactions. Its goal is to identify records with duplicate foreign key values, and delete all instances of that row except for the most recent (identifiable by having the highest primary key number, as it's auto_incrementing).
What I have so far is....
CREATE TRIGGER tbl8_cleanup
ON tbl8_update_transactions
FOR INSERT
AS
BEGIN
SELECT HID, COUNT(*)
FROM tbl8_update_transactions
GROUP BY HID
HAVING COUNT(*) > 1;
DELETE FROM tbl8_update_transactions
WHERE COUNT(HID) > 1;
END;
I'm unsure as to how I can have the trigger delete all instances of a row that has duplicates except for the instance with the highest primary key number (TID).
I would use row_number() in a CTE:
with todelete as (
select t.*, row_number() over (partition by HID order by TID desc) as seqnum
from tbl8_update_transactions t
)
delete from todelete
where seqnum > 1;
Rather than scanning the entire table every time any activity occurs, why not be more targetted:
CREATE TRIGGER tbl8_cleanup
ON tbl8_update_transactions
INSTEAD OF INSERT --<-- Act before the new rows have been inserted
AS
BEGIN
DELETE FROM tbl8_update_transactions where HID in (select HID from inserted)
INSERT INTO tbl8_update_transactions (/* column list */)
SELECT /* column list */ from inserted
END;
So, before we insert any new rows, we first remove any rows that would be duplicates, and then we perform the actual insert we've been asked to do.
As a general rule, if your trigger code doesn't reference inserted and/or deleted, it's probably broken. See the documentation for more information.

On update triggering in oracle?

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;
/

Adding a nullable foreign key

I have two tables built like this (this is just a simplified and non-proprietary example):
Person Table
-----------
p_Id, f_name, l_name
Job Table
----------
job_Id, job_desc
I want to add a foreign key column, Persons.job_Id, that can be nullable that references Job.job_Id (the PK) The reason is, the job may not be known in advance, so it could be null. Having an "Other" is not an option.
I had this so far but I'm getting "could not create constraint".
ALTER TABLE dbo.Person
ADD job_Id INT FOREIGN KEY (job_Id) REFERENCES dbo.Job(job_Id)
Try it in two steps:
ALTER TABLE dbo.Person ADD job_Id INT NULL;
ALTER TABLE dbo.Person ADD CONSTRAINT FL_JOB
FOREIGN KEY (job_Id) REFERENCES dbo.Job(job_Id);
Try it like this, WITH NOCHECK:
ALTER TABLE dbo.Person ADD job_Id INT NULL;
ALTER TABLE dbo.Person WITH NOCHECK ADD CONSTRAINT FL_JOB
FOREIGN KEY (job_Id) REFERENCES dbo.Job(job_Id);
Below is my solution with creating foreign key programmatically.
TestTable1 has substitute of FK that is either NULL or matches record in TestTable2.
TestTable2 has standard FK in TestTable1.
CREATE Table TestTable1 (ID1 int IDENTITY UNIQUE, ID2 int NULL);
GO
CREATE Table TestTable2 (ID2 int IDENTITY UNIQUE, ID1 int NOT NULL foreign key references TestTable1(ID1));
GO
CREATE procedure CreateTestRecord1 #ID2 int null AS
begin
if #iD2 IS NOT NULL AND NOT EXISTS(SELECT * from TestTable2 where ID2 = #ID2)
begin
RAISERROR('Cannot insert TestTable1 record. TestTable2 record with ID %d doesnt exist', 16, 1, #ID2);
return;
end
Insert into TestTable1(ID2) OUTPUT Inserted.ID1 Values(#ID2);
end
GO
CREATE procedure LinkTable1toTable2 #ID1 int, #ID2 int NULL as
begin
if #iD2 IS NOT NULL AND NOT EXISTS(SELECT * from TestTable2 where ID2 = #ID2)
begin
RAISERROR('Cannot update ID2 in TestTable1 record. TestTable2 record with ID %d doesnt exist', 16, 1, #ID2);
return;
end
update TestTable1 Set ID2=#ID2 where ID1=#ID1;
select ##ROWCOUNT;
endGO