Create Trigger based on Status change in column and insert value in another column for a table - sql

I have a table named as Employee which has columns like Status and Expected_Promotion_Date
The Status column is populated from StatusEnum which has values like Level 1, Level 2, and Level 3.
When the Status of Employee changes from Level 1 to Level 2, I need a trigger that populates a value in the Expected_Promotion_Date column i.e. current date plus 3 years.
I tried making the trigger, please find the reference code for the same:
CREATE or REPLACE TRIGGER Expected_Promotion_Date
AFTER
UPDATE OF STATUS FOR EACH ROW
DECLARE NEXT_MAINTENANCE_CONTROL DATE;
BEGIN
IF(:OLD.STATUS<>:NEW.STATUS) THEN
BEGIN
IF(:OLD.STATUS=-10 AND :NEW.STATUS=00)THEN
BEGIN
END
END
END
Can someone please help me with the trigger as I am new to it and learning it from the first time. If someone can help me with it or guide me for the same, it would be really great.
Thanks in advance.

Something like this:
create or replace trigger expected_promotion_date
before update on employee for each row
begin
if :old.status != :new.status then
:new.expected_promotion_date := trunc(sysdate) + interval '3' year;
end if;
end;
/
This code checks whether the value of STATUS has changed, and if it's different populates expected promotion date column with today's date plus three years.
You may need to tweak this code to handle any other business logic which you haven't included in your question. For instance, in your comment you show some logic relating to the value of STATUS. I've ignored that, as you haven't explained what is supposed to happen. The usefulness of the answer we can give is proportional to the clarity of the question.

Related

I want to create AFTER UPDATE trigger to update DATE OF BIRTH where (AGE < 20)

I want to update the data in table where age is less than 20. So I want to update dob and set it to date where age is greater than 20 (by using after update trigger). I know what I'm saying doesn't make any sense but this is the task given by my proffesor.
and I'm using Oracle Live SQL.
create table SYCS_DBMS(
SID NUMBER(10),
SNAME VARCHAR(50),
DOB DATE,
PRESENT DATE
);
ALTER SESSION SET NLS_DATE_FORMAT = 'DD-MON-YYYY';
INSERT INTO SYCS_DBMS VALUES(1, 'ANKIT', '4-OCTOBER-2002', sysdate);
INSERT INTO SYCS_DBMS VALUES(2, 'AAKASH', '30-OCTOBER-2002', sysdate);
INSERT INTO SYCS_DBMS VALUES(3, 'DHRUV', '5-APRIL-2002', sysdate);
INSERT INTO SYCS_DBMS VALUES(4, 'DEERAJ', '5-AUGUST-2002', sysdate);
INSERT INTO SYCS_DBMS VALUES(5, 'ADIL', '11-MARCH-2003', sysdate);
INSERT INTO SYCS_DBMS VALUES(6, 'VIRAJ', '7-JULY-2002', sysdate);
CREATE OR REPLACE TRIGGER trUPDT
AFTER UPDATE
ON SYCS_DBMS
FOR EACH ROW
DECLARE
STU_AGE NUMBER;
BEGIN
--TO CHECK THE AGE BY DATE OF BIRTH
SELECT MONTHS_BETWEEN(TO_DATE(sysdate,'DD-MON-YYYY'), TO_DATE(:NEW.DOB,'DD-MON-YYYY'))/12
INTO STU_AGE FROM DUAL;
END;
Let's take a walk down "Revision Lane" as this code can make use of some. But first as work about formatting. You need to learn how to do it. In fact I tell my students that if they submit unformatted code they fail that assignment. I don not even bother to read it. Format your code. I should be able to get an idea of the flow just looking at it. It does not really matter how you format, just as long as it consistent through out. Admittedly it is not a big deal for 10 lines of code, but the time to form good habits is before forming bad ones. Now for what you have:
Be careful setting and depending on setting NLS_DATE_FORMAT for here
it is fine. But what happens when you need to call another routine
an that developer also sets NLS_DATE_FORMAT but different from yours.
Once a column/parameter is a date data type never use the to_date function on it, it is already a date. Oracle provides a slew of date processing functions and makes date arithmetic available. In this case both sysdate and the column DOB are already dates so do not do to_date() on them.
Your expression
months_between(to_date(sysdate,'dd-mon-yyyy'),
to_date(:new.dob,'dd-mon-yyyy'))/12
Becomes just simply
months_between(sysdate, :new.dob)/12
Going a step further. Within a plsql block there is no need to Select ... into unless you are actually retrieving from a table (or view, etc). You can just make a direct assignment to a variable.
So with this and the above:
select months_between(to_date(sysdate, :new.dob)/12
into stu_age
from dual;
becomes a simple assignment:
stu_age := months_between(to_date(sysdate, :new.dob)/12;
At this point your trigger has been reduced to:
create or replace trigger trupdt
after update
on sycs_dbms
for each row
declare
stu_age number;
begin
--to check the age by date of birth
stu_age := months_between(sysdate,:new.dob)/12;
end trupdt;
Unfortunately, it is completely useless. You computed the months between the 2 dates as a numeric value (with decimal places), but then the trigger ends and just throws that calculation away doing nothing with it. I do not think this is what you after. You initially stated you wanted to update dob. Well there are 2 issues here.
DOB is defined as a date so your calculation needs to result in a date. This does not.
You have a after update trigger but an after update trigger
cannot change column values, You need a before update trigger.
Now you could the calculated value and a date calculation to get the DOB value, with the appropriate logic to determine if you even should. But the suggestion by #Barmar is extremely good. I'll us it. Thanks Barmer. Instead of the months_between function use the add_months function with sysdate to calculate the date 20 years ago, then use the least function to choose the appropriate value. Also make a direct assignment to :new_dob bypassing the local variable. The final trigger for this becomes simply:
create or replace trigger sycs_dbms_dob_ge20_bur
before update
on sycs_dbms
for each row
begin
:new.dob := least(:new.dob, add_months(sysdate, -240)); -- (20yrs * 12mon/yr)
end sycs_dbms_dob_ge20_bur;
I have put together a fiddle here that walks through each step above. I would not typically provide a complete answer to an obvious homework assignment. But if I had assigned this I would have walked through it in the next class. My hope is you take this to class and discuss it the professor and other students. Do not just submit as your own, any half-way decent professor would catch on real fast. But at least study carefully what it does and how it got there from where you started.

PL/SQL - Simple trigger, take from one column and IF-THEN-ELSE to another column

I am trying to figure out where I am going wrong on this simple trigger. I am quite new to triggers and trying to get used to using them with IFTTT statements.
I want the trigger to watch for a new row entry, and if the value is within a certain range within a column (col_a) it will then enter a certain value in the same row, but different column (col_b), which will be NULL up until this is entered. Please can you help?
CREATE TRIGGER trg_test
BEFORE INSERT
ON test_table
FOR EACH ROW
BEGIN
IF :new.col_a >= 10
THEN :new.col_b := 'High';
ELSE
:new.col_b := 'Low';
END IF;
END;
It just keeps coming back with "success with compilation error".
You can easily do this using a virtual column:
alter table test_table
add col_b generated always as (case when col_a >= 10 then 'High' else 'Low' end);
I wish that exercises in triggers used reasonable use-cases. This is not one -- you need both an insert and an update trigger, for instance.

Using Sysdate in trigger

Hello guys I'm a bit of a noob when it comes to triggers so I'm just looking for some advice on how to do the follow trigger.
I have created a trigger that will throw an error message if someone was to delete a record during office hours however I would like to create another trigger that uses SYSDATE will not delete records from say today and future dates.
I was thinking of maybe using >=SYSDATE but I'm not sure if that is a valid sql statement.
CREATE OR REPLACE TRIGGER records_delete
BEFORE DELETE
ON RECORDS FOR EACH ROW
BEGIN
IF TO_CHAR(SYSDATE, 'HH24MI') NOT >= sysdat
RAISE_APPLICATION_ERROR(-20669, 'You can not delete current or future records');
END IF;
END records_delete;
Thanks, leprejohn
The problem here is that you're not referencing any of the fields of the table that you're deleting from.
If your table had a column called record_date then you could rewrite this as:
CREATE OR REPLACE TRIGGER records_delete
BEFORE DELETE
ON RECORDS FOR EACH ROW
BEGIN
IF (:old.record_date >= SYSDATE) THEN
RAISE_APPLICATION_ERROR(-20669, 'You can not delete current or future records');
END IF;
END records_delete;
The syntax :old. and :new. are how you reference the columns of the current record being acted upon by the trigger, with :old. being the prefix for inspecting the values before the trigger acts on it and :new. being for values after the trigger is done. This syntax allows you to see the values before/after the trigger updates the data, which in your case doesn't matter since you're deleting records.
If you want to disregard the hours, mins, seconds of the date fields, use this in the IF statement
trunc(:old.record_date) >= trunc(SYSDATE)
If record_date is actually stored as a string instead of a date, you would convert it to a date before comparison:
to_date(:old.record_date, 'DDMMYYYY') >= trunc(SYSDATE)
Update the format mask 'DDMMYYYY" to the format your date is actually stored in. Check the Oracle documentation for description of to_date and date format models.
Give this a shot and see if it works.

Compound trigger: collecting mutating rows into nested table

I have two tables in my project: accounts and transactions (one-to-many relationship). In every transaction I store the balance of the associated account (after the transaction is executed). Additionally in every transaction I store a value of the transaction.
So I needed a trigger fired when someone adds new transaction. It should check whether new account balance will be correct (old account balance + transaction value = new account balance stored in transaction).
So I was suggested, I should use a compound trigger which would:
in before each row section: save a row's PK (made of two columns) somewhere,
in after statement section: check whether all inserted transactions where correct.
Now I can't find anywhere how could I implement the first point.
What I already have:
CREATE OR REPLACE TRIGGER check_account_balance_is_valid
FOR INSERT
ON Transactions
COMPOUND TRIGGER
TYPE Modified_transactions_T IS TABLE OF Transactions%ROWTYPE;
Modified_transactions Modified_transactions_T;
BEFORE STATEMENT IS BEGIN
Modified_transactions := Modified_transactions_T();
END BEFORE STATEMENT;
BEFORE EACH ROW IS BEGIN
Modified_transactions.extend;
Modified_transactions(Modified_transactions.last) := :NEW;
END BEFORE EACH ROW;
AFTER STATEMENT IS BEGIN
NULL; -- I will write something here later
END AFTER STATEMENT;
END check_account_balance_is_valid;
/
However, I got that:
Warning: execution completed with warning
11/58 PLS-00049: bad bind variable 'NEW'
Could someone tell me, how to fix it? Or maybe my whole "compound trigger" idea is wrong and you have better suggestions.
Update 1
Here is my ddl script: http://pastebin.com/MW0Eqf9J
Maybe try this one:
TYPE Modified_transactions_T IS TABLE OF ROWID;
Modified_transactions Modified_transactions_T;
BEFORE STATEMENT IS BEGIN
Modified_transactions := Modified_transactions_T();
END BEFORE STATEMENT;
BEFORE EACH ROW IS BEGIN
Modified_transactions.extend;
Modified_transactions(Modified_transactions.last) := :NEW.ROWID;
END BEFORE EACH ROW;
or this
TYPE PrimaryKeyRecType IS RECORD (
Col1 Transactions.PK_COL_1%TYPE, Col2 Transactions.PK_COL_2%TYPE);
TYPE Modified_transactions_T IS TABLE OF PrimaryKeyRecType;
...
Modified_transactions(Modified_transactions.last) := PrimaryKeyRecType(:NEW.PK_COL_1, :NEW.PK_COL_2);
Your immediate problem is that :new is not a real record so it is not of type Transactions%ROWTYPE. If you're really going to go down this path, you would generally want to declare a collection of the primary key of the table
TYPE Modified_transactions_T IS TABLE OF Transactions.Primary_Key%TYPE;
and then put just the primary key in the collection
BEFORE EACH ROW IS BEGIN
Modified_transactions.extend;
Modified_transactions(Modified_transactions.last) := :NEW.Primary_Key;
END BEFORE EACH ROW;
The fact that you are trying to work around a mutating table exception in the first place, however, almost always indicates that you have an underlying data modeling problem that you should really be solving. If you need to query other rows in the table in order to figure out what you want to do with the new rows, that's a pretty good indication that you have improperly normalized your data model and that one row has some dependency on another row in the same table rather than being an autonomous fact. Fixing the data model is almost always preferrable to working around the mutating table exception.

PL/SQL Triggers Library Infotainment System

I am trying to make a Library Infotainment System using PL/SQL. Before any of you speculate, yes it is a homework assignment but I've tried hard and asking a question here only after trying hard enough.
Basically, I have few tables, two of which are:
Issue(Bookid, borrowerid, issuedate, returndate) and
Borrower(borrowerid, name, status).
The status in Borrower table can be either 'student' or 'faculty'. I have to implement a restriction using trigger, that per student, I can issue only 2 books at any point of time and per faculty, 3 books at any point of time.
I am totally new to PL/SQL. It might be easy, and I have an idea of how to do it. This is the best I could do. Please help me in finding design/compiler errors.
CREATE OR REPLACE TRIGGER trg_maxbooks
AFTER INSERT ON ISSUE
FOR EACH ROW
DECLARE
BORROWERCOUNT INTEGER;
SORF VARCHAR2(20);
BEGIN
SELECT COUNT(*) INTO BORROWERCOUNT
FROM ISSUE
WHERE BORROWER_ID = :NEW.BORROWER_ID;
SELECT STATUS INTO SORF
FROM BORROWER
WHERE BORROWER_ID = :NEW.BORROWER_ID;
IF ((BORROWERCOUNT=2 AND SORF='STUDENT')
OR (BORROWERCOUNT=3 AND SORF='FACULTY')) THEN
ROLLBACK TRANSACTION;
END IF;
END;
Try something like this:
CREATE OR REPLACE TRIGGER TRG_MAXBOOKS
BEFORE INSERT
ON ISSUE
FOR EACH ROW
BEGIN
IF ( :NEW.BORROWERCOUNT > 2
AND :NEW.SORF = 'STUDENT' )
OR ( :NEW.BORROWERCOUNT > 3
AND :NEW.SORF = 'FACULTY' )
THEN
RAISE_APPLICATION_ERROR (
-20001,
'Cannot issue beyond the limit, retry as per the limit' );
END IF;
END;
/
There should not be a commit or rollback inside a trigger. The logical exception is equivalent to ROLLBACK
This is so ugly I can't believe you're being asked to do something like this. Triggers are one of the worst ways to implement business logic. They will often fail utterly when confronted with more than one user. They are also hard to debug because they have hard-to-anticipate side effects.
In your example for instance what happens if two people insert at the same time? (hint: they won't see the each other's modification until they both commit, nice way to generate corrupt data :)
Furthermore, as you are probably aware, you can't reference other rows of a table inside a row level trigger (this will raise a mutating error).
That being said, in your case you could use an extra column in Borrower to record the number of books being borrowed. You'll have to make sure that the trigger correctly updates this value. This will also take care of the multi-user problem since as you know only one session can update a single row at the same time. So only one person could update a borrower's count at the same time.
This should help you with the insert trigger (you'll also need a delete trigger and to be on the safe side an update trigger in case someone updates Issue.borrowerid):
CREATE OR REPLACE TRIGGER issue_borrower_trg
AFTER INSERT ON issue
FOR EACH ROW
DECLARE
l_total_borrowed NUMBER;
l_status borrower.status%type;
BEGIN
SELECT nvl(total_borrowed, 0) + 1, status
INTO l_total_borrowed, l_status
FROM borrower
WHERE borrower_id = :new.borrower_id
FOR UPDATE;
-- business rule
IF l_status = 'student' and l_total_borrowed >= 3
/* OR faculty */ THEN
raise_application_error(-20001, 'limit reached!');
END IF;
UPDATE borrower
SET total_borrowed = l_total_borrowed
WHERE borrower_id = :new.borrower_id;
END;
Update: the above approach won't even work in your case because you record the issue date/return date in the issue table so the number of books borrowed is not a constant over time. In that case I would go with a table-level POST-DML trigger. After each DML verify that every row in the table validates your business rules (it won't scale nicely though, for a solution that scales, see this post by Tom Kyte).