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

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.

Related

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

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.

Create Instead of Insert Trigger

I am a beginner at Oracle. I am trying to create an INSTEAD OF trigger to enforce a rule that no tutor should work more than 60 hours in a month
This is what I have so far
CREATE TRIGGER limit_hour
INSTEAD OF INSERT ON SESSIONHOURS
DECLARE
totalHours NUMBER := 60;
monthOnly DATE;
totalSession NUMBER;
FOR EACH ROW
BEGIN
INSERT INTO SESSIONHOURS(SESSIONDATEKEY, TUTORKEY, TOTALSESSION)
SELECT EXTRACT (MONTH FROM DATE S.SESSIONDATEKEY), S.TOTALSESSION
INTO monthOnly, totalSession
FROM SESSIONHOURS S
END;
The error "inappropriate INTO" keeps popping up. Also I need to assign the total sum of each session(30 min each) for the extracted month and then compare it with the "totalHour". How do I assign a time value to a date value? Any suggestions would be appreciated
Rather than use an INSTEAD OF trigger, it seems to me that a BEFORE INSERT trigger would be more appropriate. INSTEAD OF triggers are commonly used to map INSERTs on non-insertable views into INSERTs into the desired tables. A BEFORE INSERT trigger, on the other hand, is fired before each row is inserted into the table, allowing the values in the row to be checked for consistency, etc. Such a trigger might be used as follows:
CREATE TRIGGER SESSIONHOURS_BI
BEFORE INSERT INTO SESSIONHOURS
FOR EACH ROW
DECLARE
nTotal_tutor_hours NUMBER;
BEGIN
SELECT SUM(HOURS)
INTO nTotal_tutor_hours
FROM SESSIONHOURS s
WHERE s.TUTORKEY = :new.TUTORKEY;
IF nTotal_tutor_hours + :new.HOURS > 60 THEN
RAISE_APPLICATION_ERROR(-20001, 'Addition of ' || :new.HOURS ||
' for tutor ' || :new.TUTORKEY ||
' exceeds monthly limit of 60');
END IF;
END SESSIONHOURS_BI;
This trigger will be fired before an INSERT into SESSIONHOURS is processed. It queries the database to determine the total number of hours worked by the tutor whose key is in the INSERT statement (:new.TUTORKEY). If the total hours worked PLUS the hours in the new record exceeds 60 an exception is raised, which causes the INSERT to be aborted. Otherwise the trigger returns normally and the INSERT proceeds.
HOWEVER - even this won't work. The problem is that the trigger is defined on the table SESSIONHOURS, and inside the trigger there is a SELECT on the SESSIONHOURS table. This will cause the database to throw the dreaded ORA-04091 exception, with explanatory text table SESSIONHOURS is mutating, trigger/function may not see it. There are several ways to fix this, the BEST of which is to follow a simple rule:
***NEVER* IMPLEMENT BUSINESS LOGIC IN A TRIGGER!!!!
A rule such as "tutors may not work more than 60 hours" is a business rule. This should be implemented in your application logic, not in a trigger. Create a procedure or function in the database to perform the INSERT INTO SESSIONHOURS and any needed validation logic, and call that procedure every time you need to insert data into SESSIONHOURS. Don't try putting the validation logic into a trigger - you'll find it's rather difficult, and will lead to never-ending debugging sessions, as noted here.
Best of luck.
Your INSERT statement is improperly written. It should be:
INSERT INTO SESSIONHOURS(SESSIONDATEKEY, TOTALSESSION)
SELECT EXTRACT (MONTH FROM DATE S.SESSIONDATEKEY), S.TOTALSESSION
FROM SESSIONHOURS S
This won't solve your "total hours" issues, but it takes care of the error you reported.
Best of luck.

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.

SQL Oracle Trigger to change date

CREATE OR REPLACE TRIGGER shares_to_amount
AFTER INSERT OR UPDATE OF issued ON shares_amount
FOR EACH ROW
BEGIN
INSERT INTO shares_amount(
share_issue_id,
share_id,
issued,
date_start,
date_end
) VALUES (
:OLD.share_issue_id,
:OLD.share_id,
:NEW.issued,
:NEW.date_start,
:((NEW.date_start)-1).date_end
);
END;
I want to change the date_end to the date_new date -1 when a new share value is issued into 'issued'. The start date can be today's date but the end date will have to display the day before.
Fist of all your trigger can't work because of mutating table problem. You can't execute DMLs or queries in row-level triggers agains the table which is being changed by triggering DML (if the trigger is not autonomous but this is dangeros and exceptionac case). If I understand your question right you want to keep the history of changes made for shares. Th best way is to create PL/SQL package, incapsulate logic into procedures and provide this interface to end-users or other programms.

Get seq number used by insert statement called from C# (oracle)

I need to insert a record into a table and set the value of a column (e.g. orderid) to a unique #. And return that number used.
I thought the process would be to do use a sequence and an insert statement with nextval:
insert into ordersTable(orderid) values(ordernums.nextval);
But how to I get the number that was used? My thought is that I have to get it back from the insert call otherwise the nextval (or currval) could have changed if I re-query.
But I'm not sure how to do that. One way around this I thought would be to, on insert, also add my only unique value to a field, then requery for that value.
Is there another way to do this? I figure I probably missing something at the sql level?
CtC
Far as I know, Oracle 9i+ has the RETURNING clause:
DECLARE v_orderid ORDERSTABLE%TYPE;
INSERT INTO ordersTable
(orderid)
VALUES
(ordernums.nextval)
RETURNING orderid INTO v_orderid;
The alternative is to populate the variable before the INSERT statement:
Within a function:
DECLARE v_orderid ORDERSTABLE%TYPE := ORDERNUMS.NEXTVAL;
INSERT INTO ordersTable
(orderid)
VALUES
(v_orderid);
RETURN v_orderid;
Using an OUT parameter:
CREATE OR REPLACE PROCEDURE ORDER_INS(out_orderid OUT ORDERSTABLE%TYPE)
AS
BEGIN
out_orderid := ORDERNUMS.NEXTVAL;
INSERT INTO ordersTable
(orderid)
VALUES
(out_orderid);
END;
In addition, be aware that the CURRVAL of a sequence is session-specific. No matter how long since you called NEXTVAL in the current session and no matter how many other sessions may have called NEXTVAL on the same sequence, ordernums.currval will always return the last value of the sequence that was given to your session. So although you have various options for retrieving the value from the INSERT, it may not be necessary-- you may simply want to reference ordernums.currval in subsequent statements. Of course, since CURRVAL is session-specific, it does not make sense (and would return an error) if you called CURRVAL in a session before you had called NEXTVAL for the same sequence.