I'm trying to implement this constraint into my database:
(In the table Race)
CONSTRAINT (Date <= Meeting.EndDate)
Essentially the column in the Race Table needs to be Less than the EndDate column in the Meeting table.
Pretty sure I need to use a trigger to implement a foreign key however, I'm not that sure how I would go about implementing one. So Far, All I have is:
CREATE OR REPLACE TRIGGER RaceDateCheck
AFTER UPDATE OF Race ON StartDateCheck
BEGIN
INSERT INTO Race
(MeetingEndDate)
SELECT EndDate FROM Meeting
END;
Thanks,
Presumably the race date has to be between the meeting start and end date, so you could check both at once; and also presumably you want to check this for new records, not just updates. So you could use something like:
CREATE OR REPLACE TRIGGER RaceDateCheck
BEFORE INSERT OR UPDATE ON Race
FOR EACH ROW
DECLARE
meetingStart Meeting.MeetingStartDate%TYPE;
meetingEnd Meeting.MeetingEndDate%TYPE;
BEGIN
SELECT StartDate, EndDate
INTO meetingStart, meetingEnd
FROM Meeting
WHERE MeetingID = :NEW.MeetingID;
IF :NEW.RaceDate < meetingStart
OR :NEW.RaceDate > meetingEnd THEN
RAISE_APPLICATION_ERROR(-20001, 'Invalid race date');
END IF;
END;
For just the end date:
CREATE OR REPLACE TRIGGER RaceDateCheck
BEFORE INSERT OR UPDATE ON Race
FOR EACH ROW
DECLARE
meetingEnd Meeting.MeetingEndDate%TYPE;
BEGIN
SELECT EndDate
INTO meetingEnd
FROM Meeting
WHERE MeetingID = :NEW.MeetingID;
IF :NEW.RaceDate > meetingEnd THEN
RAISE_APPLICATION_ERROR(-20001, 'Invalid race date');
END IF;
END;
Related
Oracle APEX. I want to create trigger: if user deletes a row where ENDDATE is null the row won't be deleted overwise it will. This is my script:
CREATE OR REPLACE TRIGGER CHECK_NOT_NULL_
BEFORE DELETE ON CAREER
FOR EACH ROW
BEGIN
IF(OLD.ENDDATE IS NULL)
INSERT INTO CAREER VALUES (OLD.JOBNO, OLD.EMPNO, OLD.STARTDATE, OLD.ENDDATE);
END IF;
END CHECK_NOT_NULL_;
But I have ORA-24344 error. Can you explain why and what should I do to fix it?
Your trigger attempts to re-insert the row if the END_DATE is null. This won't work (you'll get the notorious mutating table error). But anyway, if you want to prevent deletion of the row it's simpler and clearer to simply do that:
CREATE OR REPLACE TRIGGER CHECK_NOT_NULL_
BEFORE DELETE ON CAREER
FOR EACH ROW
BEGIN
IF :OLD.ENDDATE IS NULL THEN
raise_application_error(-20000, 'Cannot delete a row when ENDDATE is null');
END IF;
END CHECK_NOT_NULL_;
This fails the action and tells the user why their action was refused. Silently undoing a user's action is bad practice, because it's mystifying, and mystified users are unhappy and often angry users.
Precede all olds with a colon :, i.e.
CREATE OR REPLACE TRIGGER CHECK_NOT_NULL_
BEFORE DELETE ON CAREER
FOR EACH ROW
BEGIN
IF(:OLD.ENDDATE IS NULL)
INSERT INTO CAREER VALUES (:OLD.JOBNO, :OLD.EMPNO, :OLD.STARTDATE, :OLD.ENDDATE);
END IF;
END CHECK_NOT_NULL_;
Also, I'd suggest you to name all columns you're inserting into, e.g.
insert into career (jobno, empno, startdate, enddate)
values (:old.jobno, :old.empno, :old.startdate, :old.enddate);
I am trying to implement a statement level trigger to enforce the following "An applicant cannot apply for more than two positions in one day".
I am able to enforce it using a row level trigger (as shown below) but I have no clue how to do so using a statement level trigger when I can't use :NEW or :OLD.
I know there are alternatives to using a trigger but I am revising for my exam that would have a similar question so I would appreciate any help.
CREATE TABLE APPLIES(
anumber NUMBER(6) NOT NULL, /* applicant number */
pnumber NUMBER(8) NOT NULL, /* position number */
appDate DATE NOT NULL, /* application date*/
CONSTRAINT APPLIES_pkey PRIMARY KEY(anumber, pnumber)
);
CREATE OR REPLACE TRIGGER app_trigger
BEFORE INSERT ON APPLIES
FOR EACH ROW
DECLARE
counter NUMBER;
BEGIN
SELECT COUNT(*) INTO counter
FROM APPLIES
WHERE anumber = :NEW.anumber
AND to_char(appDate, 'DD-MON-YYYY') = to_char(:NEW.appDate, 'DD-MON-YYYY');
IF counter = 2 THEN
RAISE_APPLICATION_ERROR(-20001, 'error msg');
END IF;
END;
You're correct that you don't have :OLD and :NEW values - so you need to check the entire table to see if the condition (let's not call it a "constraint", as that term has specific meaning in the sense of a relational database) has been violated:
CREATE OR REPLACE TRIGGER APPLIES_AIU
AFTER INSERT OR UPDATE ON APPLIES
BEGIN
FOR aRow IN (SELECT ANUMBER,
TRUNC(APPDATE) AS APPDATE,
COUNT(*) AS APPLICATION_COUNT
FROM APPLIES
GROUP BY ANUMBER, TRUNC(APPDATE)
HAVING COUNT(*) > 2)
LOOP
-- If we get to here it means we have at least one user who has applied
-- for more than two jobs in a single day.
RAISE_APPLICATION_ERROR(-20002, 'Applicant ' || aRow.ANUMBER ||
' applied for ' || aRow.APPLICATION_COUNT ||
' jobs on ' ||
TO_CHAR(aRow.APPDATE, 'DD-MON-YYYY'));
END LOOP;
END APPLIES_AIU;
It's a good idea to add an index to support this query so it will run efficiently:
CREATE INDEX APPLIES_BIU_INDEX
ON APPLIES(ANUMBER, TRUNC(APPDATE));
dbfiddle here
Best of luck.
Your rule involves more than one row at the same time. So you cannot use a FOR ROW LEVEL trigger: querying on APPLIES as you propose would hurl ORA-04091: table is mutating exception.
So, AFTER statement it is.
CREATE OR REPLACE TRIGGER app_trigger
AFTER INSERT OR UPDATE ON APPLIES
DECLARE
cursor c_cnt is
SELECT 1 INTO counter
FROM APPLIES
group by anumber, trunc(appDate) having count(*) > 2;
dummy number;
BEGIN
open c_cnt;
fetch c_cnt in dummy;
if c_cnt%found then
close c_cnt;
RAISE_APPLICATION_ERROR(-20001, 'error msg');
end if;
close c_cnt;
END;
Obviously, querying the whole table will be inefficient at scale. (One of the reasons why triggers are not recommended for this sort of thing). So this is a situation in which we might want to use a compound trigger (assuming we're on 11g or later).
I'm creating a update trigger where an employee can never have a salary that is greater than the president's. However I need to subquery the president's salary for comparison and the "new" updated employee's salary.
I originally had the the subquery using from the from employees table but had to make a new table because of the mutating table problem. I don't think creating a new table is plausible solution for a real implementation.
Is there a way I can save the president's salary without creating a new table?
CREATE OR REPLACE TRIGGER prevent_salary
BEFORE UPDATE ON employees
FOR EACH ROW
declare pres_sal number(8,2);
BEGIN
select salary into pres_sal from employees_salary where job_id='AD_PRES';--employees_salary was employees but that gives mutating error
IF (:new.salary > pres_sal)
THEN UPDATE employees
SET salary = :old.salary
WHERE employee_id = :old.employee_id;
END IF;
END;
One way to do it is to save off the president's salary in a BEFORE STATEMENT trigger and then use that in the FOR EACH ROW trigger.
"Compound Triggers", which have been around at least since version 11.1, offer a nice way to do that all in one place.
Here is an example:
CREATE OR REPLACE TRIGGER prevent_salary
FOR UPDATE OF salary ON employees
COMPOUND TRIGGER
pres_sal NUMBER;
BEFORE STATEMENT IS
BEGIN
select salary
into pres_sal
from employees
where job_id='AD_PRES';
END BEFORE STATEMENT;
BEFORE EACH ROW IS
BEGIN
:new.salary := least(:new.salary, pres_sal);
END BEFORE EACH ROW;
END prevent_salary;
You may try this :
CREATE OR REPLACE TRIGGER prevent_salary
BEFORE UPDATE ON employees
FOR EACH ROW
declare pres_sal number(8,2);
BEGIN
select salary into pres_sal from employees_salary where job_id='AD_PRES';--employees_salary was employees but that gives mutating error
IF (:new.salary > pres_sal)
Raise_Application_Error (-20101, 'An employee''s salary couldn''t exceed president''s !');
END IF;
END;
Suppose I have following schema :
DEPARTMENT (DepartmentName, BudgetCode, OfficeNumber, Phone)
EMPLOYEE (EmployeeNumber, FirstName, LastName, Department, Phone, Email)
Now I need to write a trigger that enforce the constraint that an employee can never change his or her department.
CREATE OR REPLACE TRIGGER department_fixed
BEFORE UPDATE ON EMPLOYEE
FOR EACH ROW
WHEN (old.Department is not null)
BEGIN
dbms_output.put('You can not change department');
END;
/
Is this right way to write this oracle trigger or am missing something ?
Also please help me to write a trigger to allow the deletion of a department if it only has one employee. Assign the last employee to the Human Resources department.
You're close. What this trigger is going to do is for every update of the EMPLOYEE table, it's going to check the original department and if it's not null, it will print something to the screen. I'm assuming you're allowing updates for other fields, right? So my trigger would look like this:
CREATE OR REPLACE TRIGGER EMPLOYEE_BUR
BEFORE UPDATE ON EMPLOYEE
FOR EACH ROW
DECLARE
DEPT_EXCEPTION EXCEPTION; --Declare the exception
PRAGMA EXCEPTION_INIT(DEPT_EXCEPTION, -20002);
L_COUNT NUMBER
BEGIN
SELECT COUNT(*) INTO L_COUNT FROM DEPARTMENT D
WHERE D.DEPARTMENT=:OLD.DEPARTMENT;
IF (:OLD.DEPARTMENT <> :NEW.DEPARTMENT) AND (L_COUNT > 0) THEN
RAISE DEPT_EXCEPTION;
END IF;
END;
.. and then I handle the DEPT_EXCEPTION in my calling procedure. You must either raise an exception -OR- do something with :NEW.DEPARTMENT or else the trigger will finish and the update statement will complete. The "EMPLOYEE_BUR" name is more for best practices, this way you know what it is. "BUR" - "Before Update for each Row"
For the second part, you could have a trigger on DEPARTMENT that fires before the delete to confirm deletion, then one after to reassign the employee:
CREATE OR REPLACE TRIGGER DEPARTMENT_BDR
BEFORE DELETE ON DEPARTMENT
FOR EACH ROW
DECLARE
DEPT_COUNT_EXCEPTION EXCEPTION;
PRAGMA EXCEPTION_INIT(DEPT_COUNT_EXCEPTION, -20003);
L_COUNT NUMBER;
BEGIN
SELECT COUNT(*) INTO L_COUNT FROM EMPLOYEE E
WHERE E.DEPARTMENT=:OLD.DEPARTMENT;
IF (L_COUNT > 1 ) THEN
RAISE DEPT_COUNT_EXCEPTION;
END IF;
END;
Then...
CREATE OR REPLACE TRIGGER DEPARTMENT_ADR
AFTER DELETE ON DEPARTMENT
FOR EACH ROW
BEGIN
UPDATE EMPLOYEE
SET DEPARTMENT='HR'
WHERE EMPLOYEE=:OLD.EMPLOYEE;
END;
So this way, if you're deleting a Department with more than one employee, trigger 2 fires and raises an exception so trigger 3 will not. If trigger 2 completes fine, then trigger 3 will fire which re-assigns the employee. If the delete statement completed, then the update will be allowed since there is no former department.
I would like to create a ORACLE plsql trigger in my booking table which will prevent user to update value where booking start date like <= sysdate.
in my booking table booking start date column name is date_from. I have tried following command but seems like I am using wrong code.
create or replace TRIGGER UPDATE_BOOKING
BEFORE UPDATE ON BOOKING
FOR EACH ROW
BEGIN
IF BOOKING.DATE_FROM <= TO_DATE(SYSDATE)
THEN
RAISE_APPLICATION_ERROR (-20001,'YOU CAN NOT UPDATE BOOKING STATUS WHERE ......');
END IF;
END;
what would be the best way to implement this trigger.
If you want to compare the existing value of the date then use :old. If you are updating the date column also and you want check it for the new value of update date then use :new. Example for :old is as follows..
create or replace TRIGGER UPDATE_BOOKING
BEFORE UPDATE ON BOOKING
FOR EACH ROW
BEGIN
IF :OLD.DATE_FROM <= TO_DATE(SYSDATE)
THEN
RAISE_APPLICATION_ERROR (-20001,'YOU CAN NOT UPDATE BOOKING STATUS WHERE ......');
END IF;
END;
No need to use to_date() function for sysdate, its already date type, check the following code:
create or replace TRIGGER UPDATE_BOOKING
BEFORE UPDATE ON BOOKING
FOR EACH ROW
BEGIN
-- used trunc function below for more accuracy
IF trunc(:OLD.DATE_FROM) <= trunc(SYSDATE)
THEN
RAISE_APPLICATION_ERROR (-20001,'YOU CAN NOT UPDATE BOOKING STATUS WHERE ......');
END IF;
END;