SQL Oracle Trigger to change date - sql

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.

Related

APEX - 21.1 - Calculate the ageing of a task

I have columns Status, Updated_Time, Ageing
When status is marked as WIP, I have below triggger to update the "Updated_Time" column with PST systime
create or replace TRIGGER TRIG01
BEFORE INSERT OR UPDATE ON TASKS
REFERENCING FOR EACH ROW
WHEN (NEW.STATUS='WIP')
BEGIN
IF INSERTING OR UPDATING THEN
:NEW.UPDATED_DATE := NEW_TIME(SYSDATE, 'GMT', 'PDT' );
END IF;
END;
I'm planning to insert one more line to the trigger to calculate the ageing. It seems to be working via SQL command but from the interactive grid. Any suggestions on how to calculate the ageing of a task.
:NEW.AGEING := ROUND((CAST(NEW_TIME(SYSDATE, 'GMT', 'PDT') as DATE)- CAST(:NEW.UPDATED_DATE as DATE))*1440,2);
In Oracle or relational database in general, insert/update(DMLs) needed to be completed as part of single transaction and requires you to perform commit or rollback.
My guess here(as you have not shared specific details about how IG is programmed) is that it does not get committed and timed out.
My suggestion is to get either of this to solve the problem :
Use pragma within your trigger Or
Use Apex dynamic action to calculate Date and Aging

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.

Update date using triggers in Postgresql

I have a table that has a contract and columns like starting_date and expiration_date. I need to create a TRIGGER that for a specific category(private) as soon as we are at the expiration date(aka if it expires today), update it by one year.
What I don't really understand is, how could I update the date if I need to do an Update to fire the TRIGGER.
Like if I understand TRIGGERS correctly, I need to execute an Update statement to fire my TRIGGER and let it do its job. But if that's correct then what do I update to fire it?
For the TRIGGER obviously if I do old.expiration_date == current date then I will know if it is going to expire today.
For example let's say that we have a contract with:
starting_date: 1998/02/01
expiration_date: 2021/06/24
And current date: 2021/06/24
So now I need to fire that TRIGGER and update this date but how?
Here is the table:
CREATE TABLE insurance_premiums (
contract_code text NOT NULL,
insurance_team text NOT NULL,
starting_date date NOT NULL,
expiration_date date NOT NULL,
contract_cost float8 NOT NULL,
vehicle_contract text NOT NULL,
customer_contract text NOT NULL,
driver_contract text NOT NULL,
CONSTRAINT insurance_premiums_pkey PRIMARY KEY (contract_code)
);
Here is an INSERT INTO statement for the above example dates:
INSERT INTO insurance_premiums (contract_code, insurance_team, starting_date, expiration_date, contract_cost, vehicle_contract, customer_contract, driver_contract)
VALUES
('FX-30592', 'private', '1998-02-01', '2021-06-24', 3894.68, 'Cavalier', 'Nicoline Vaughn', 'Helen-elizabeth Galiero');
Here is also the TRIGGER:
CREATE TRIGGER update_contract
BEFORE UPDATE ON insurance_premiums
FOR EACH statement
EXECUTE procedure new_date();
And the empty function for the trigger:
CREATE OR REPLACE FUNCTION public.new_date()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
END;
$function$
;
Again the TRIGGER needs to automatically update the date but I don't understand how I "call" it and how it would work
You cannot use a trigger to have an action triggered by time. Triggers can only be fired on data modifications, that is INSERT, UPDATE, DELETE and TRUNCATE statements. That is not what you need here.
You would have to schedule a regular job that expires old entries by running a DELETE statement. Such a job can either be scheduled with operating system tools (like cron on Unix systems) or by a (non-standard) database extension like pg_timetable.
That will work if it is OK to expire data once a day or so. If you need more accuracy, I suggest that you modify your SELECT statements so that they ignore expired data. Then there is no rush expiring them.
If you have a coarser granularity (day/week/month) for data expiry, range partitioning might be the best option. That would allow you to simply drop a partition rather than running a long DELETE.

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.