I am having trouble with an SQL Trigger on a View. I have a table called Absence like so:
CREATE TABLE Absence
(
absence_id_pk varchar(6) NOT NULL,
staff_id_fk varchar(6),
start_date date,
end_date date,
reason varchar(30),
PRIMARY KEY (absence_id_pk),
FOREIGN KEY (staff_id_fk) REFERENCES Full_Time_Employee(staff_id_fk)
);
which records periods of staff absence from work.
Here is the problem! I would like to create a trigger that sends a message to the DBMS when a member of staff's total number of absent days is greater than 20 e.g. Eek! This staff is taking too much sick leave. In reality, it would probably be checked against a period of time e.g. in the last 6 months but this doesn't need to be that complex. Simply when the total of periods of absence is above 20 days on insert of a new record into the Absence table.
After reading some of the comments I have made this new trigger:
create or replace TRIGGER absence_check
BEFORE INSERT
ON absence
FOR EACH ROW
DECLARE
staffid absence.staff_id_fk%TYPE;
days number;
BEGIN
SELECT SUM(end_date - start_date) INTO days
FROM absence
WHERE staff_id_fk = staffid;
IF days > 20
THEN
DBMS_OUTPUT.PUT_LINE('Total days absent are more than 20' || staffid);
END IF;
END;
Any advice/guidance/solutions would be greatly appreciated! It would be a bonus if the message could print out the staff_id_fk that has just violated the > 20 days absent rule.
P.S. I am a University student and although this may be implemented in other ways, we have been asked to try and create triggers for our database scenario!
create or replace TRIGGER absence_check
BEFORE INSERT
ON absence
FOR EACH ROW
DECLARE
staffid absence.staff_id_fk%TYPE := :NEW.staff_id_fk;
days number;
BEGIN
SELECT SUM(end_date - start_date) INTO days
FROM Absence, Staff
WHERE Absence.staff_id_fk = staffid;
IF days > 20
THEN
DBMS_OUTPUT.PUT_LINE('Warning: Total number of days absent is more than 20 for staff member: ' || staffid);
END IF;
END;
Thanks for everyone's help and suggestions. It feels great when you finally crack a problem and I have learnt so much about Triggers by exploring this problem with your help.
Related
I have the following schema:
CREATE TABLE EPOCA
(
ID INT
CONSTRAINT PK_EPOCA PRIMARY KEY,
NOME VARCHAR(250),
DATA_INI DATE
CONSTRAINT NN_EPOCA_DATA_INI NOT NULL,
DATA_FIM DATE,
CONSTRAINT CK_EPOCA_DATAS CHECK (DATA_INI < DATA_FIM)
);
And the following trigger, that is supposed to raise an error whenever an EPOCA is inserted into the database and the period between DATA_FIM and DATA_INI is overlapped with other periods of other EPOCAS.
CREATE OR REPLACE TRIGGER TRGEPOCASNAOSOBREPOSTAS
AFTER INSERT OR UPDATE
ON EPOCA
FOR EACH ROW
BEGIN
IF INSERTING THEN
IF :OLD.DATA_INI <= :NEW.DATA_INI AND :OLD.DATA_FIM >= :NEW.DATA_FIM THEN
RAISE_APPLICATION_ERROR(-20021, 'INSERT FAILED BECAUSE SELECTED DATES OVERLAP EXISTENT ONES');
END IF;
ELSIF UPDATING THEN
IF :OLD.DATA_INI <= :NEW.DATA_INI AND :OLD.DATA_FIM >= :NEW.DATA_FIM THEN
RAISE_APPLICATION_ERROR(-20022, 'UPDATE FAILED BECAUSE SELECTED DATES OVERLAP EXISTENT ONES');
END IF;
END IF;
END;
Think of it as: if I define the summer between june 1st and 30th of august I cannot define anyother period of the year with that period of time, nor can I update an existing period with those dates or nothing between june 1st and 30th august.
Right now I can insert any EPOCA with the same date as any other present in the table and I can update the date of any EPOCA with the dates of other EPOCAS and it allows me. What can I change?
I guess you would need a trigger like this one:
CREATE OR REPLACE TRIGGER TRGEPOCASNAOSOBREPOSTAS
AFTER INSERT OR UPDATE
ON EPOCA
c INTEGER;
BEGIN
SELECT COUNT(*)
INTO c
FROM EPOCA e
WHERE EXISTS (
SELECT 1
FROM EPOCA ee
WHERE (e.DATA_INI BETWEEN ee.DATA_INI AND ee.DATA_FIM
OR e.DATA_FIM BETWEEN ee.DATA_INI AND ee.DATA_FIM)
AND ee.ROWID <> e.ROWID);
IF c > 0 THEN
RAISE_APPLICATION_ERROR(-20021, 'INSERT FAILED BECAUSE SELECTED DATES OVERLAP EXISTENT ONES');
END IF;
END;
Note, the FOR EACH ROW clause is not given!
Otherwise the trigger performs only the currently inserted/updated row but does not compare to any existing data.
Consider also cases like this:
In the table you have a period from 1st to 30th of August, then you try to add period for 1st of May to 31th of December. Of course, such situations should be also blocked by the trigger. Thus you need only a statement-level trigger, i.e. a row level trigger which checks only the inserted/updated row is not sufficient.
I have the following tables:
CREATE TABLE review
(
review_id NUMBER(2) NOT NULL,
review_date DATE NOT NULL,
review_rating NUMBER(1) NOT NULL,
driver_no NUMBER(2) NOT NULL,
vehicle_id NUMBER(3) NOT NULL
);
CREATE TABLE testing
(
testing_id NUMBER(2) NOT NULL,
testing_start DATE NOT NULL,
testing_end DATE NOT NULL
driver_no NUMBER(2) NOT NULL,
vehicle_id NUMBER(3) NOT NULL
);
Basically vehicles are tested by drivers between two dates. After the testing is complete, the driver reviews the vehicle.
I want to create a trigger which will prevent an invalid review from being added. The review is invalid if the driver reviews the vehicle before the testing end date. The review is also invalid if the driver reviews a vehicle that he has not driven.
For example, driver 1 tests vehicle 7 from 01 Feb 2019 to 07 Feb 2019. If a review is added for 05 Feb 2019, I want the trigger to prevent this from being inserted. Also, if a review is added for vehicle 5 (when vehicle 7 was the one being tested), I want the trigger to prevent this from being inserted.
This is what I have so far:
CREATE OR REPLACE TRIGGER review_check_validity
AFTER INSERT ON review
FOR EACH ROW
BEGIN
SELECT testing_start
FROM testing
WHERE driver_no = :new.driver_no;
SELECT vehicle_id
FROM testing
WHERE driver_no = :new.driver_no;
IF :new.review_date < testing_end THEN
raise_application_error(-20000, 'Review date cannot be before
testing end date');
END IF;
IF :new.vehicle_id != vehicle_id THEN
raise_application_error(-20000, 'Driver has never driven this
vehicle');
END IF;
END;
/
The trigger compiles without any errors - however when I try to test it by inserting an invalid row into the REVIEW table, I get an error message stating
Exact fetch returns more than requested number of rows
Could somebody please point out what changes I need to make to my code to achieve the desired result?
I'd rearrange the logic here and:
Check if the driver has tested the vehicle, then
Check if the review is attempted before the testing end date for the vehicle (something you've left out).
In Oracle PL/SQL, which includes trigger code, you can't just SELECT. You have to SELECT INTO a variable. Then you can use the variable in your logic.
Equally important, when you SELECT INTO a variable the query can only return one result. Multiple rows will trigger the error you've encountered.
CREATE OR REPLACE TRIGGER review_check_validity
AFTER INSERT ON review
FOR EACH ROW
DECLARE
testEnd DATE;
vehicleTestCount NUMBER;
BEGIN
SELECT COUNT(*)
INTO vehicleTestCount
FROM testing
WHERE vehicle_id = :new.vehicle_id;
IF vehicleTestCount = 0 THEN
raise_application_error(-20000, 'Driver has never driven this vehicle');
END IF;
-- Assumes one test per driver per vehicle
SELECT testing_end
INTO testEnd
FROM testing
WHERE driver_no = :new.driver_no
AND vehicle_id = :new.vehicle_id;
IF :new.review_date < testEnd THEN
raise_application_error(-20000, 'Review date cannot be before
testing end date');
END IF;
END;
/
Finally, your table structure allows multiple tests of the same vehicle by the same driver. If it should allow this, then the review table should link to the testing table by testing_id rather than driver_no and vehicle_id.
I do think you should have an INTO clause in your SELECT fetches. I added some notes in your query to try to help you clearing it.
CREATE OR REPLACE TRIGGER review_check_validity
AFTER INSERT ON review
FOR EACH ROW
-- Need to declare variables for usage in your SELECT fetches.
DECLARE
var_revdate number; -- or date, if applicable
var_revvehi number; -- or varchar(n), if applicable
BEGIN
-- In here you should have an INTO clause to assign your date parameter into
-- the predefined variable. As well, you need to ensure this fetch will provide
-- only one row.
SELECT testing_start INTO var_revdate -- why are you fetching "testing_start"?
FROM testing
WHERE driver_no = :new.driver_no;
-- Same case, it can only retrieve one row. If you need to do more than one row,
-- you may need to use a BULK & a LOOP.
SELECT vehicle_id INTO var_vehicid
FROM testing
WHERE driver_no = :new.driver_no;
IF :new.review_date < testing_end THEN
raise_application_error(-20000, 'Review date cannot be before testing end date');
END IF;
IF :new.vehicle_id != var_vehicid THEN
raise_application_error(-20000, 'Driver has never driven this vehicle');
END IF;
END;
/
Hope that helps.
There are a few mistakes in your code:
In PL/SQL select queries must have an INTO clause where the data fetched from the SELECT query is stored into some variables. -- That part is missing in your code
You are searching in the testing table with the only driverno, which will give you all the records of that driverno(all the vehicles tested by that driveno). You need to include vehicleid along with driverno to fetch the details of testing relevant to the current review.
So Your trigger code can be re-written as follows:
CREATE OR REPLACE TRIGGER REVIEW_CHECK_VALIDITY
BEFORE INSERT ON REVIEW -- USING BEFORE INSERT TRIGGER TO AVOID ANY UNDOs
FOR EACH ROW
DECLARE
LV_TEST_END_DATE DATE;
LV_TEST_COUNT NUMBER;
BEGIN
-- FETCHING RELEVANT DATA USING SINGLE QUERY
SELECT
COUNT(1),
MAX(TESTING_END) -- PLEASE HANDLE THE SCENARIO WHERE THE TESTING END DATE IS NULL
INTO
LV_TEST_COUNT,
LV_TEST_END_DATE
FROM
TESTING
WHERE
VEHICLE_ID = :NEW.VEHICLE_ID
AND DRIVER_NO = :NEW.DRIVER_NO;
IF LV_TEST_COUNT = 0 THEN
RAISE_APPLICATION_ERROR(-20000, 'Driver has never driven this vehicle');
ELSIF :NEW.REVIEW_DATE < LV_TEST_END_DATE THEN
RAISE_APPLICATION_ERROR(-20000, 'Review date cannot be before testing end date');
END IF;
END;
/
Cheers!!
i'm a university student working on a simple database project for our DB exam. I've reached the stage of developing the dynamic constraint for the DB via PL/SQL trigger; the database applies to a oil/gas refinirey enviroment.
Here is table on which the trigger should work:
CREATE TABLE FEED(
NUMBER_STOCK NUMBER(2) CHECK(NUMBER_STOCK>0),
REACT_NAME VARCHAR(20),
PROD_LANE_NUMB NUMBER(2),
DATE_FEED DATE PRIMARY KEY,
QUANTITY NUMBER(5) NOT NULL CHECK (QUANTITY>0),
CONSTRAINT FKA FOREIGN KEY (NUMBER_STOCK) REFERENCES STOCKS(NUMBER_STOCK) ON DELETE CASCADE,
CONSTRAINT FKA2 FOREIGN KEY (REACT_NAME,PROD_LANE_NUMB) REFERENCES PRODUCTION_PLANTS(REACT_NAME,PROD_LANE_NUMB) ON DELETE CASCADE
);
The trigger i am trying to develop has the following purpose:
During the winter and summer months(December-February and June-August) the production plants cannot work at full load due to many weather related factors, such corrosion, pipe dilation/constriction and higher/lower temepratures. During these months the QUANTITY of raw materials sent daily to the plants, must be less than the average of total QUANTITY sent in the months preeciding those periods( November and May).
Now here's how i developed my trigger(Note: there is already another AFTER INSERT OR UPDATE trigger active on the table):
CREATE OR REPLACE TRIGGER METEO
AFTER INSERT OR UPDATE ON FEED
FOR EACH ROW
DECLARE
ACTL_MONTH CHAR(3); --ACTUAL MONTH WITHIN 3 LETTER FORMAT --
MONTH_AVG NUMBER(8) := 0; --PREECIDING MONTHS AVARAGE--
FEED_QUAN NUMBER(8) := 0; --ACTUAL FEED INSERTED/UPDATED--
BEGIN
--GETTING DATE FROM DUAL IN 3 LETTER FORMAT--
SELECT TO_CHAR(TRUNC(SYSDATE,'MONTH'),'MON') INTO ACTL_MONTH FROM DUAL;
--CHECKING DATE--
IF ACTL_MONTH='MAY' THEN
SELECT AVG(QUANTITY) INTO MONTH_AVG FROM FEED WHERE TO_CHAR(TRUNC(DATE_FEED,'MONTH'),'MON')='MAY';
END IF;
IF ACTL_MONTH='NOV' THEN
SELECT AVG(QUANTITY) INTO MONTH_AVG FROM FEED WHERE TO_CHAR(TRUNC(DATE_FEED,'MONTH'),'MON')='NOV';
END IF;
--SELECTING THE QUANTITY FEEDED--
SELECT :NEW.QUANTITY INTO FEED_QUAN FROM FEED;
IF MONTH_AVG<FEED_QUAN THEN
RAISE_APPLICATION_ERROR(-20008,'EXCEEDING FEED QUANTITY DUE TO WEATHER LIMITATIONS.ROLLBACK');
END IF;
END;
/
But every time i insert a value the trigger does not fire, and allows me to insert/update rows with not allowed values.
So:
Did i made mistakes in PL/SQL code?(It compiled with no errors)
May trigger cannot be fired upon dates?
Should i use another type of trigger structure?(statement?before?)
This is my first question on stack overflow so have mercy on me, and yes i have used google and used stack search for similar question but did not find anything like my problem, if something is not clear point it to me and i will adjust/explain; besisde consdier that im a foreign student so expect LOTS of english grammar errors.
There are a couple of different ways to solve this problem. First, create a second table to hold your monthly averages:
CREATE TABLE MONTHLY_AVERAGE_FEED
(MONTH_YEAR DATE -- 01-MM-YYYY
PRIMARY KEY,
TOTAL_QUANTITY NUMBER,
TRANSACTIONS NUMBER,
AVERAGE_QUANTITY NUMBER
GENERATED ALWAYS AS (CASE
WHEN TRANSACTIONS > 0 THEN
TOTAL_QUANTITY / TRANSACTIONS
ELSE 0
END));
and maintained by a trigger:
CREATE TRIGGER FEED_AVERAGE_AIUD
AFTER INSERT OR UPDATE OR DELETE ON FEED
FOR EACH ROW
BEGIN
IF UPDATING OR DELETING THEN
-- Back out the 'old' values
UPDATE MONTHLY_AVERAGE_FEED
SET TOTAL_QUANTITY = GREATEST(TOTAL_QUANTITY - :OLD.QUANTITY, 0),
TRANSACTIONS = GREATEST(TRANSACTIONS - 1, 0)
WHERE MONTH_YEAR = TRUNC(:OLD.DATE_FEED, 'MONTH');
END IF;
IF INSERTING OR UPDATING THEN
MERGE INTO MONTHLY_AVERAGE_FEED maf
USING (SELECT TRUNC(:NEW.DATE_FEED, 'MONTH') AS MONTH_YEAR
FROM DUAL) d
ON (maf.MONTH_YEAR = d.MONTH_YEAR)
WHEN MATCHED THEN
UPDATE
SET TOTAL_QUANTITY := TOTAL_QUANTITY +
(:NEW.QUANTITY *
CASE
WHEN INSERTING OR UPDATING THEN 1
ELSE -1
END),
TRANSACTIONS = TRANSACTIONS + 1
WHEN NOT MATCHED THEN
INSERT (MONTH_YEAR, TRANSACTIONS, TOTAL_QUANTITY)
VALUES (TRUNC(:NEW.DATE_FEED, 'MONTH'), 1, :NEW.QUANTITY);
END IF;
END FEED_AVERAGE_AIUD;
Then in your trigger you can simply query the MONTHLY_AVERAGE_FEED table to get the average feed for whatever month you want.
The second option would be to rewrite #MT0's trigger as a compound trigger. The "BEFORE STATEMENT" section of the trigger would handle computing the average feed for whatever month(s) you want, while the "BEFORE EACH ROW" section would contain the rest of the work.
Thanks to the answers and the help given by Bob Jarvis and MT0 i have finally solved my problem.
So i added a support table for the averages like Bob Jarvis suggested, and then added a specific trigger to populate it when any row is inserted into FEED table of my first post; here's the trigger modified which compile properly:
CREATE OR REPLACE TRIGGER FEED_AVERAGE_AIUD
AFTER INSERT OR UPDATE OR DELETE ON FEED
FOR EACH ROW
BEGIN
IF UPDATING OR DELETING THEN
-- Back out the 'old' values
UPDATE MONTHLY_AVERAGE_FEED
SET TOTAL_QUANTITY = GREATEST(TOTAL_QUANTITY - :OLD.QUANTITY, 0),
TRANSACTIONS = GREATEST(TRANSACTIONS - 1, 0)
WHERE MONTH_YEAR = TRUNC(:OLD.DATE_FEED, 'MONTH');
END IF;
IF INSERTING OR UPDATING THEN
MERGE INTO MONTHLY_AVERAGE_FEED maf
USING (SELECT TRUNC(:NEW.DATE_FEED, 'MONTH') AS MONTH_YEAR
FROM DUAL) d
ON (maf.MONTH_YEAR = d.MONTH_YEAR)
WHEN MATCHED THEN
UPDATE
SET TOTAL_QUANTITY = TOTAL_QUANTITY +:NEW.QUANTITY,
TRANSACTIONS = TRANSACTIONS + 1
WHEN NOT MATCHED THEN
INSERT (MONTH_YEAR, TRANSACTIONS, TOTAL_QUANTITY)
VALUES (TRUNC(:NEW.DATE_FEED, 'MONTH'), 1, :NEW.QUANTITY);
END IF;
END FEED_AVERAGE_AIUD;
/
So with the average table in place and running i created the following trigger to check the consitency beetwen the new inserted/updated values and the averages in specified months:
CREATE OR REPLACE TRIGGER METEO
AFTER INSERT OR UPDATE ON FEED
FOR EACH ROW
DECLARE
ACT_QUANT NUMBER; --ACTUAL INSERTED/UPDATED QUANTITY---
ACT_MONTH NUMBER; --MONTH AT WHICH THE QUANTITY WAS INSERTED/UPDATED--
REF_AVERG NUMBER; --THE AVERAGE IN THE AVERAGES TABLE REFEERING TO THE ISNERTED/UPDATED MONTH--
BEGIN
ACT_MONTH:= EXTRACT(MONTH FROM :NEW.DATE_FEED);
ACT_QUANT:= :NEW.QUANTITY;
-- SO IF I AM INSERTING/UPDATING VALUES IN JUNE/JULY/AUGUST
-- I SIMPLY SEARCH THE AVERAGE TABLE WITH A QUERY WITH MONTH AND YEAR TAKEN
-- BY THE DATE_FEED
IF ACT_MONTH IN(6,7,8) THEN
SELECT AVERAGE_QUANTITY
INTO REF_AVERG
FROM MONTHLY_AVERAGE_FEED
WHERE EXTRACT(MONTH FROM MONTH_YEAR)=5 AND
EXTRACT(YEAR FROM MONTH_YEAR)=EXTRACT(YEAR FROM :NEW.DATE_FEED);
ELSIF ACT_MONTH=12 THEN --FOR DECEMBER I TAKE THE SAME YEAR NOVEMBER AVG--
SELECT AVERAGE_QUANTITY
INTO REF_AVERG
FROM MONTHLY_AVERAGE_FEED
WHERE EXTRACT(MONTH FROM MONTH_YEAR)=11 AND
EXTRACT(YEAR FROM MONTH_YEAR)=EXTRACT(YEAR FROM :NEW.DATE_FEED);
ELSIF ACT_MONTH IN (1,2) THEN --FOR JANUARY AND FEBRUARY I TAKE THE AVG OF THE PREVIOUS YEAR --
SELECT AVERAGE_QUANTITY
INTO REF_AVERG
FROM MONTHLY_AVERAGE_FEED
WHERE EXTRACT(MONTH FROM MONTH_YEAR)=11 AND
EXTRACT(YEAR FROM MONTH_YEAR)=EXTRACT(YEAR FROM :NEW.DATE_ALIM)-1;
END IF;
IF ACT_QUANT>REF_AVERG THEN
RAISE_APPLICATION_ERROR(
-20008,
'EXCEEDING FEED QUANTITY DUE TO WEATHER LIMITATIONS.ROLLBACK'
);
END IF;
END;
/
The trigger compiled and worked as it was intended, it's not elegant for sure, but it does his job. As ever I had to translate every table/attributes names from my original language to english so it's possibile that i forgot something or mispelled word,verbs etc.
Anyway thanks to everyone who replied and helped,hoping this will help somebody else someday, thank you guys.
Change the trigger from AFTER to BEFORE.
You don't need to use SELECT ... INTO to assign variables.
You aren't answering the question. You need to check if the month is Dec-Feb or Jun-Aug and then find the daily average for Nov or May (respectively).
Rather than getting the average for all Mays (or Novembers), you need to get the average for only the preceding May (or November).
Like this:
SET DEFINE OFF;
CREATE OR REPLACE TRIGGER METEO
BEFORE INSERT OR UPDATE ON FEED
FOR EACH ROW
DECLARE
MONTH_START DATE;
MONTH_AVG NUMBER(8);
BEGIN
MONTH_START := CASE EXTRACT( MONTH FROM :NEW.DATE_FEED )
WHEN 12 THEN ADD_MONTHS( TRUNC( :NEW.DATE_FEED, 'MM' ), -1 )
WHEN 1 THEN ADD_MONTHS( TRUNC( :NEW.DATE_FEED, 'MM' ), -2 )
WHEN 2 THEN ADD_MONTHS( TRUNC( :NEW.DATE_FEED, 'MM' ), -3 )
WHEN 6 THEN ADD_MONTHS( TRUNC( :NEW.DATE_FEED, 'MM' ), -1 )
WHEN 7 THEN ADD_MONTHS( TRUNC( :NEW.DATE_FEED, 'MM' ), -2 )
WHEN 8 THEN ADD_MONTHS( TRUNC( :NEW.DATE_FEED, 'MM' ), -3 )
ELSE NULL
END;
IF MONTH_START IS NULL THEN
RETURN;
END IF;
SELECT AVG( QUANTITY )
INTO MONTH_AVG
FROM FEED
WHERE DATE_FEED >= MONTH_START
AND DATE_FEED < ADD_MONTHS( MONTH_START, 1 );
IF MONTH_AVG <= :NEW.QUANTITY THEN
RAISE_APPLICATION_ERROR(-20008,'EXCEEDING FEED QUANTITY DUE TO WEATHER LIMITATIONS.ROLLBACK');
END IF;
END;
/
I have two tables:
Assessment and Assessment_Announcement
CREATE TABLE "ASSESSMENT"
( "ASSESSMENT_NAME" VARCHAR2(50) NOT NULL ENABLE,
"DEADLINE_DATE" DATE NOT NULL ENABLE,
CONSTRAINT "ASSESSMENT_PK" PRIMARY KEY ("ASSESSMENT_NAME") ENABLE
)
CREATE TABLE "ASSESSMENT_ANNOUNCEMENT"
( "ASSESSMENT_NAME" VARCHAR2(50) NOT NULL ENABLE,
"DEADLINE_DATE" DATE NOT NULL ENABLE,
"ATTENTION" VARCHAR2(500) NOT NULL ENABLE,
CONSTRAINT "ASSESSMENT_ANNOUNCEMENT_PK" PRIMARY KEY ("ASSESSMENT_NAME") ENABLE
)
I am looking at implementing a trigger which updates to the 'ASSESSMENT_ANNOUNCEMENT' table when the date in the DEADLINE_DATE column on the ASSESSMENT table is within 7 days. The data might have been stored for 14 days, but when it is within 7 days of the deadline - it triggers. This shouldn't trigger on insert unless the insert date is within 7 days of the deadline.
So far I have the following code:
CREATE OR REPLACE TRIGGER "TEST"
AFTER INSERT ON ASSESSMENT
FOR EACH ROW
BEGIN
insert into ASSESSMENT_ANNOUNCEMENT(ASSESSMENT_NAME, DEADLINE_DATE, ATTENTION)
values (:new.ASSESSMENT_NAME, :new.DEADLINE_DATE, 'DEADLINE IS 7 DAYS OR LESS');
WHERE DEADLINE_DATE >= (SYSDATE) - 7
Any help and guidance would be greatly appreciated!
Thanks!
It sounds like you want
BEGIN
IF :new.deadline_date >= sysdate + 7
THEN
INSERT INTO assessment_announcement( assessment_name, deadline_date, attention )
VALUES( :new.assessment_name, :new.deadline_date, 'Deadline is 7 days or less' );
END IF;
END;
Note that an Oracle DATE always contains a day component and a time component. So sysdate + 7 returns exactly 24*7 = 168 hours ago. If it is currently 4:00 PM on Feb 26, it would return 4:00 PM on March 5. If you want the trigger to insert a row in assessment_announcement if the deadline_date is any time on March 5, you'd need trunc(sysdate) + 7.
I don't believe you can accomplish what you want with a trigger as you have no guarantee that the table will be updated within the timeframe you want. Instead you should be looking to run a stored procedure to do this that can be scheduled on an appropriate interval; say every day or every 12 hours. I've done this with MSSQL and not oracle, but this article should help you get started on your way.
The PL-SQL BLock you want to schedule is your query above. You could modify it to be
insert into ASSESSMENT_ANNOUNCEMENT(ASSESSMENT_NAME, DEADLINE_DATE, ATTENTION)
Select ASSESSMENT_NAME, DEADLINE_DATE, 'Deadline is 7 days or less'
FROM ASSESSMENT
WHERE DEADLINE_DATE >= (SYSDATE) - 7
I need a trigger to check over-dues.
If it is over due it should put some details from a table called loan to a table called fine with a fine amount but, the trigger I created is giving compilation errors.
SQL> CREATE OR REPLACE TRIGGER Over_Due
2 AFTER INSERT OR UPDATE ON loan_table
3 FOR EACH ROW
4 DECLARE due_date DATE;
5 BEGIN
6 SELECT COUNT(*) INTO due_date FROM loan_table l
7 WHERE l.date_due = :new.date_due;
8 IF(date_due > SYSDATE)
9 THEN
10 INSERT INTO fine_table VALUES(fine_id, :old.loan_id,:old.book_id,:old.student_id,amount);
11 END IF;
END; 12
13 /
Warning: Trigger created with compilation errors.
SQL> show errors;
Errors for TRIGGER OVER_DUE:
LINE/COL ERROR
-------- -----------------------------------------------------------------
3/1 PL/SQL: SQL Statement ignored
3/8 PL/SQL: ORA-00932: inconsistent datatypes: expected DATE got
NUMBER
There is a big problem with your design: Triggers are event based - yours only fires when a new loan is made. What happens if a book is overdue and no one loans a book for a while? The answer is, nothing! The overdue detection is not made until a book is borrowed. Also, the same check is made every time a book is borrowed, which is too often.
Instead, what you need is a periodic check of all records - I would recommend once per day, timed to run after the library has closed (so your processing does not affect transaction performance) that checks for the existence of overdue books.
You are assigning the value of Count(*) (a number) into a DATE field (due_date).
I suspect what you intend to do here is:
select l.due_date into due_date from ...
Your problem seems to be
SELECT COUNT(*) INTO due_date
COUNT(*) will never be of a DATE type.
You probably want to modify it like
...
DECLARE due_date DATE;
BEGIN
SELECT l.date_due INTO due_date FROM loan_table l
WHERE l.date_due = :new.date_due ORDER BY l.date_due ASC LIMIT 1;
IF(due_date > SYSDATE)
THEN
...
which will check the oldest l.date_due against SYSDATE and do the insert accordingly. If you need something else, you need to be more specific.